From 931bfaa9833fb2842532744f45787992a6eecb67 Mon Sep 17 00:00:00 2001 From: Tharun Budidha Date: Wed, 10 Jun 2026 15:48:14 +0530 Subject: [PATCH 1/3] Add course-scoped video picker --- CHANGELOG.md | 17 + README.md | 12 +- amd/build/commands.min.js | 2 +- amd/build/commands.min.js.map | 2 +- amd/build/common.min.js | 13 +- amd/build/common.min.js.map | 2 +- amd/build/configuration.min.js | 2 +- amd/build/configuration.min.js.map | 2 +- amd/build/modal.min.js | 9 +- amd/build/modal.min.js.map | 2 +- amd/build/options.min.js | 4 +- amd/build/options.min.js.map | 2 +- amd/build/plugin.min.js | 4 +- amd/build/plugin.min.js.map | 2 +- amd/build/ui.min.js | 8 +- amd/build/ui.min.js.map | 2 +- amd/src/commands.js | 4 +- amd/src/options.js | 22 +- amd/src/ui.js | 91 ++--- classes/external/get_my_videos.php | 339 +++++------------- classes/plugininfo.php | 5 +- lang/en/tiny_fastpix.php | 4 +- templates/modal.mustache | 8 - templates/videos.mustache | 6 +- tests/behat/insert_shortcode.feature | 4 +- tests/external/get_my_videos_test.php | 209 +++++------ .../behat_tiny_fastpix_generator.php | 11 +- tests/generator/lib.php | 48 ++- tests/plugininfo_test.php | 4 +- version.php | 4 +- 30 files changed, 352 insertions(+), 492 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a73be5..67e0a2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,23 @@ All notable changes to **tiny_fastpix** (FastPix video for TinyMCE) are documented here. This project follows [Semantic Versioning](https://semver.org/). +## 1.1.0 — 2026-06-10 + +### Changed +- **Course-scoped picker.** The picker now lists only the signed-in user's own + videos that belong to the **current course** (referenced by a FastPix activity + in it) — previously it listed the user's videos across all courses. The listing + stays owner-scoped, ready-only and embeddable-only (public, non-DRM). +- **Course-context capability gating.** `mod/fastpix:uploadmedia` is now resolved + at the editor's **course context**, both for showing the toolbar button + (`plugininfo`) and in the `get_my_videos` web service. Students (no capability) + get no button and a rejected service call; they can still **watch** embeds via + `filter_fastpix` (`mod/fastpix:view`). + +### Removed +- **Whole-library search.** With the list scoped to a single course it is no + longer needed; the `query` parameter and the modal search box are gone. + ## 1.0.0 — 2026-06-05 First stable release. diff --git a/README.md b/README.md index 2bac9b0..0f42479 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,7 @@ you already have. 1. A teacher opens any TinyMCE editor — a forum post, a Page, a Book chapter, a label. 2. They click **Insert FastPix video** (or **Insert → FastPix video**). -3. A dialog shows *their* videos. They can scroll, or type to search the whole - library by name. +3. A dialog shows *their* videos for **this course**. 4. They click one. `{fastpix:pb_}` appears at the cursor. When someone later reads that content, `filter_fastpix` swaps the short code for a @@ -42,13 +41,11 @@ actually play: | Rule | Why | | --- | --- | | **Your videos only** | The dialog is scoped to the signed-in user — you never see another teacher's library. | +| **This course only** | Only videos uploaded through a FastPix activity *in the current course* are listed, so a course's editors see just that course's media. | | **Ready only** | Videos still uploading or processing are hidden until FastPix finishes them. | | **Playable only** | DRM-protected videos are left out — `filter_fastpix` can't embed them, so offering them would only insert a dead short code. | | **Named, not numbered** | Each result shows the video's activity name (falling back to *Untitled video*), not a raw UUID. | -Searching looks across your whole library by activity name and title, so a big -collection is still one keystroke away — not just the most recent handful. - ## Setting it up **You need the rest of the FastPix suite first.** This is a picker, not a @@ -82,7 +79,7 @@ capability to manage: | Capability | Effect | Owned by | | --- | --- | --- | -| `mod/fastpix:uploadmedia` | Holders see the toolbar button and can list their own videos; everyone else gets no button at all. | `mod_fastpix` | +| `mod/fastpix:uploadmedia` | Checked at the **course context**: holders see the toolbar button and can list their own videos in that course; everyone else gets no button at all. | `mod_fastpix` | ## Good to know @@ -91,7 +88,8 @@ capability to manage: them, so content written elsewhere (or by hand) still plays. - **No uploading here.** New videos come in through a `mod_fastpix` activity and surface in the picker once they're ready. An upload tab is on the roadmap. -- A video only appears for the teacher recorded as its owner. +- A video only appears for the teacher recorded as its owner, and only in the + course it was uploaded to. ## Privacy diff --git a/amd/build/commands.min.js b/amd/build/commands.min.js index 4a1f8a2..e9fa341 100644 --- a/amd/build/commands.min.js +++ b/amd/build/commands.min.js @@ -1,3 +1,3 @@ -define("tiny_fastpix/commands",["exports","editor_tiny/utils","core/str","./common","./options","./ui"],(function(_exports,_utils,_str,_common,_options,_ui){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.getSetup=void 0;_exports.getSetup=async()=>{const[buttonText,buttonImage]=await Promise.all([(0,_str.getString)("buttontitle",_common.component),(0,_utils.getButtonImage)("icon",_common.component)]);return editor=>{(0,_options.canUpload)(editor)&&(editor.ui.registry.addIcon(_common.icon,buttonImage.html),editor.ui.registry.addButton(_common.buttonName,{icon:_common.icon,tooltip:buttonText,onAction:()=>(0,_ui.handleAction)(editor)}),editor.ui.registry.addMenuItem(_common.buttonName,{icon:_common.icon,text:buttonText,onAction:()=>(0,_ui.handleAction)(editor)}))}}})); +define("tiny_fastpix/commands",["exports","editor_tiny/utils","core/str","./common","./options","./ui"],function(_exports,_utils,_str,_common,_options,_ui){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.getSetup=void 0;_exports.getSetup=async()=>{const[buttonText,buttonImage]=await Promise.all([(0,_str.getString)("buttontitle",_common.component),(0,_utils.getButtonImage)("icon",_common.component)]);return editor=>{(0,_options.canEmbed)(editor)&&(editor.ui.registry.addIcon(_common.icon,buttonImage.html),editor.ui.registry.addButton(_common.buttonName,{icon:_common.icon,tooltip:buttonText,onAction:()=>(0,_ui.handleAction)(editor)}),editor.ui.registry.addMenuItem(_common.buttonName,{icon:_common.icon,text:buttonText,onAction:()=>(0,_ui.handleAction)(editor)}))}}}); //# sourceMappingURL=commands.min.js.map \ No newline at end of file diff --git a/amd/build/commands.min.js.map b/amd/build/commands.min.js.map index e3f0574..d87ec98 100644 --- a/amd/build/commands.min.js.map +++ b/amd/build/commands.min.js.map @@ -1 +1 @@ -{"version":3,"file":"commands.min.js","sources":["../src/commands.js"],"sourcesContent":["// This file is part of Moodle - https://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Tiny FastPix commands — registers the toolbar button and menu item.\n *\n * @module tiny_fastpix/commands\n * @copyright 2026 FastPix Inc. \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {getButtonImage} from 'editor_tiny/utils';\nimport {getString} from 'core/str';\nimport {component, buttonName, icon} from './common';\nimport {canUpload} from './options';\nimport {handleAction} from './ui';\n\nexport const getSetup = async() => {\n const [\n buttonText,\n buttonImage,\n ] = await Promise.all([\n getString('buttontitle', component),\n getButtonImage('icon', component),\n ]);\n\n return (editor) => {\n if (!canUpload(editor)) {\n return;\n }\n\n // Register the FastPix icon.\n editor.ui.registry.addIcon(icon, buttonImage.html);\n\n // Toolbar button.\n editor.ui.registry.addButton(buttonName, {\n icon,\n tooltip: buttonText,\n onAction: () => handleAction(editor),\n });\n\n // Menu item (Insert menu / context menu).\n editor.ui.registry.addMenuItem(buttonName, {\n icon,\n text: buttonText,\n onAction: () => handleAction(editor),\n });\n };\n};\n"],"names":["async","buttonText","buttonImage","Promise","all","component","editor","ui","registry","addIcon","icon","html","addButton","buttonName","tooltip","onAction","addMenuItem","text"],"mappings":"gQA6BwBA,gBAEhBC,WACAC,mBACMC,QAAQC,IAAI,EAClB,kBAAU,cAAeC,oBACzB,yBAAe,OAAQA,4BAGnBC,UACC,sBAAUA,UAKfA,OAAOC,GAAGC,SAASC,QAAQC,aAAMR,YAAYS,MAG7CL,OAAOC,GAAGC,SAASI,UAAUC,mBAAY,CACrCH,KAAAA,aACAI,QAASb,WACTc,SAAU,KAAM,oBAAaT,UAIjCA,OAAOC,GAAGC,SAASQ,YAAYH,mBAAY,CACvCH,KAAAA,aACAO,KAAMhB,WACNc,SAAU,KAAM,oBAAaT"} \ No newline at end of file +{"version":3,"file":"commands.min.js","sources":["../src/commands.js"],"sourcesContent":["// This file is part of Moodle - https://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Tiny FastPix commands — registers the toolbar button and menu item.\n *\n * @module tiny_fastpix/commands\n * @copyright 2026 FastPix Inc. \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {getButtonImage} from 'editor_tiny/utils';\nimport {getString} from 'core/str';\nimport {component, buttonName, icon} from './common';\nimport {canEmbed} from './options';\nimport {handleAction} from './ui';\n\nexport const getSetup = async() => {\n const [\n buttonText,\n buttonImage,\n ] = await Promise.all([\n getString('buttontitle', component),\n getButtonImage('icon', component),\n ]);\n\n return (editor) => {\n if (!canEmbed(editor)) {\n return;\n }\n\n // Register the FastPix icon.\n editor.ui.registry.addIcon(icon, buttonImage.html);\n\n // Toolbar button.\n editor.ui.registry.addButton(buttonName, {\n icon,\n tooltip: buttonText,\n onAction: () => handleAction(editor),\n });\n\n // Menu item (Insert menu / context menu).\n editor.ui.registry.addMenuItem(buttonName, {\n icon,\n text: buttonText,\n onAction: () => handleAction(editor),\n });\n };\n};\n"],"names":["_exports","getSetup","async","buttonText","buttonImage","Promise","all","getString","component","getButtonImage","editor","canEmbed","ui","registry","addIcon","icon","html","addButton","buttonName","tooltip","onAction","handleAction","addMenuItem","text"],"mappings":"6OA4DEA,SAAAC,SA/BsBC,UACpB,MACIC,WACAC,mBACMC,QAAQC,IAAI,EAClB,EAAAC,KAAAA,WAAU,cAAeC,QAAAA,YACzB,EAAAC,OAAAA,gBAAe,OAAQD,QAAAA,aAG3B,OAAQE,UACC,EAAAC,SAAAA,UAASD,UAKdA,OAAOE,GAAGC,SAASC,QAAQC,QAAAA,KAAMX,YAAYY,MAG7CN,OAAOE,GAAGC,SAASI,UAAUC,mBAAY,CACrCH,KAAAA,QAAIA,KACJI,QAAShB,WACTiB,SAAUA,KAAM,EAAAC,IAAAA,cAAaX,UAIjCA,OAAOE,GAAGC,SAASS,YAAYJ,mBAAY,CACvCH,KAAAA,QAAIA,KACJQ,KAAMpB,WACNiB,SAAUA,KAAM,EAAAC,IAAAA,cAAaX,YAGvC"} \ No newline at end of file diff --git a/amd/build/common.min.js b/amd/build/common.min.js index 83569ea..7b2b3fe 100644 --- a/amd/build/common.min.js +++ b/amd/build/common.min.js @@ -1,3 +1,14 @@ -define("tiny_fastpix/common",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.pluginName=_exports.icon=_exports.component=_exports.buttonName=void 0;_exports.component="tiny_fastpix";const pluginName="".concat("tiny_fastpix","/plugin");_exports.pluginName=pluginName;const buttonName="".concat("tiny_fastpix","/insertvideo");_exports.buttonName=buttonName;_exports.icon="tiny_fastpix"})); +define("tiny_fastpix/common",["exports"],function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.pluginName=_exports.icon=_exports.component=_exports.buttonName=void 0; +/** + * Tiny FastPix common values. + * + * Named exports (not a default object) so src and the hand-written build agree + * on the module's named bindings. + * + * @module tiny_fastpix/common + * @copyright 2026 FastPix Inc. + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +const component=_exports.component="tiny_fastpix";_exports.pluginName=`${component}/plugin`,_exports.buttonName=`${component}/insertvideo`,_exports.icon="tiny_fastpix"}); //# sourceMappingURL=common.min.js.map \ No newline at end of file diff --git a/amd/build/common.min.js.map b/amd/build/common.min.js.map index 59ae245..a0a0ea5 100644 --- a/amd/build/common.min.js.map +++ b/amd/build/common.min.js.map @@ -1 +1 @@ -{"version":3,"file":"common.min.js","sources":["../src/common.js"],"sourcesContent":["// This file is part of Moodle - https://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Tiny FastPix common values.\n *\n * Named exports (not a default object) so src and the hand-written build agree\n * on the module's named bindings.\n *\n * @module tiny_fastpix/common\n * @copyright 2026 FastPix Inc. \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nexport const component = 'tiny_fastpix';\nexport const pluginName = `${component}/plugin`;\nexport const buttonName = `${component}/insertvideo`;\nexport const icon = 'tiny_fastpix';\n"],"names":["pluginName","buttonName"],"mappings":"wNA0ByB,qBACZA,qBADY,+DAEZC,qBAFY,4EAGL"} \ No newline at end of file +{"version":3,"file":"common.min.js","sources":["../src/common.js"],"sourcesContent":["// This file is part of Moodle - https://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Tiny FastPix common values.\n *\n * Named exports (not a default object) so src and the hand-written build agree\n * on the module's named bindings.\n *\n * @module tiny_fastpix/common\n * @copyright 2026 FastPix Inc. \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nexport const component = 'tiny_fastpix';\nexport const pluginName = `${component}/plugin`;\nexport const buttonName = `${component}/insertvideo`;\nexport const icon = 'tiny_fastpix';\n"],"names":["component","_exports","pluginName","buttonName","icon"],"mappings":";;;;;;;;;;;AA0BO,MAAMA,UAASC,SAAAD,UAAG,eACFC,SAAAC,WAAG,GAAGF,mBACNC,SAAAE,WAAG,GAAGH,wBACZC,SAAAG,KAAG,cAAe"} \ No newline at end of file diff --git a/amd/build/configuration.min.js b/amd/build/configuration.min.js index ac4e776..5ccf7c8 100644 --- a/amd/build/configuration.min.js +++ b/amd/build/configuration.min.js @@ -1,3 +1,3 @@ -define("tiny_fastpix/configuration",["exports","./common","editor_tiny/utils"],(function(_exports,_common,_utils){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.configure=void 0;_exports.configure=instanceConfig=>({toolbar:(0,_utils.addToolbarButton)(instanceConfig.toolbar,"content",_common.buttonName),menu:(0,_utils.addMenubarItem)(instanceConfig.menu,"insert",_common.buttonName)})})); +define("tiny_fastpix/configuration",["exports","./common","editor_tiny/utils"],function(_exports,_common,_utils){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.configure=void 0;_exports.configure=instanceConfig=>({toolbar:(0,_utils.addToolbarButton)(instanceConfig.toolbar,"content",_common.buttonName),menu:(0,_utils.addMenubarItem)(instanceConfig.menu,"insert",_common.buttonName)})}); //# sourceMappingURL=configuration.min.js.map \ No newline at end of file diff --git a/amd/build/configuration.min.js.map b/amd/build/configuration.min.js.map index 2936f22..d2a13df 100644 --- a/amd/build/configuration.min.js.map +++ b/amd/build/configuration.min.js.map @@ -1 +1 @@ -{"version":3,"file":"configuration.min.js","sources":["../src/configuration.js"],"sourcesContent":["// This file is part of Moodle - https://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Tiny FastPix configuration — adds the button to the toolbar and menu.\n *\n * @module tiny_fastpix/configuration\n * @copyright 2026 FastPix Inc. \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {buttonName} from './common';\nimport {\n addMenubarItem,\n addToolbarButton,\n} from 'editor_tiny/utils';\n\nexport const configure = (instanceConfig) => {\n return {\n toolbar: addToolbarButton(instanceConfig.toolbar, 'content', buttonName),\n menu: addMenubarItem(instanceConfig.menu, 'insert', buttonName),\n };\n};\n"],"names":["instanceConfig","toolbar","buttonName","menu"],"mappings":"uNA6B0BA,iBACf,CACHC,SAAS,2BAAiBD,eAAeC,QAAS,UAAWC,oBAC7DC,MAAM,yBAAeH,eAAeG,KAAM,SAAUD"} \ No newline at end of file +{"version":3,"file":"configuration.min.js","sources":["../src/configuration.js"],"sourcesContent":["// This file is part of Moodle - https://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Tiny FastPix configuration — adds the button to the toolbar and menu.\n *\n * @module tiny_fastpix/configuration\n * @copyright 2026 FastPix Inc. \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {buttonName} from './common';\nimport {\n addMenubarItem,\n addToolbarButton,\n} from 'editor_tiny/utils';\n\nexport const configure = (instanceConfig) => {\n return {\n toolbar: addToolbarButton(instanceConfig.toolbar, 'content', buttonName),\n menu: addMenubarItem(instanceConfig.menu, 'insert', buttonName),\n };\n};\n"],"names":["_exports","configure","instanceConfig","toolbar","addToolbarButton","buttonName","menu","addMenubarItem"],"mappings":"mMAkCEA,SAAAC,UALwBC,iBACf,CACHC,SAAS,EAAAC,OAAAA,kBAAiBF,eAAeC,QAAS,UAAWE,QAAAA,YAC7DC,MAAM,EAAAC,OAAAA,gBAAeL,eAAeI,KAAM,SAAUD,QAAUA,aAEpE"} \ No newline at end of file diff --git a/amd/build/modal.min.js b/amd/build/modal.min.js index 256cf21..7617ec4 100644 --- a/amd/build/modal.min.js +++ b/amd/build/modal.min.js @@ -1,3 +1,10 @@ -define("tiny_fastpix/modal",["exports","core/modal"],(function(_exports,_modal){var obj;function _defineProperty(obj,key,value){return key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,obj}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_modal=(obj=_modal)&&obj.__esModule?obj:{default:obj};class FastpixModal extends _modal.default{registerEventListeners(){super.registerEventListeners(),this.registerCloseOnCancel()}configure(modalConfig){modalConfig.large=!0,modalConfig.show=!0,modalConfig.removeOnClose=!0,super.configure(modalConfig)}}return _exports.default=FastpixModal,_defineProperty(FastpixModal,"TYPE","tiny_fastpix/modal"),_defineProperty(FastpixModal,"TEMPLATE","tiny_fastpix/modal"),FastpixModal.registerModalType(),_exports.default})); +define("tiny_fastpix/modal",["exports","core/modal"],function(_exports,_modal){var e; +/** + * Video-picker modal for Tiny FastPix. + * + * @module tiny_fastpix/modal + * @copyright 2026 FastPix Inc. + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_modal=(e=_modal)&&e.__esModule?e:{default:e};class FastpixModal extends _modal.default{static TYPE="tiny_fastpix/modal";static TEMPLATE="tiny_fastpix/modal";registerEventListeners(){super.registerEventListeners(),this.registerCloseOnCancel()}configure(modalConfig){modalConfig.large=!0,modalConfig.show=!0,modalConfig.removeOnClose=!0,super.configure(modalConfig)}}return _exports.default=FastpixModal,FastpixModal.registerModalType(),_exports.default}); //# sourceMappingURL=modal.min.js.map \ No newline at end of file diff --git a/amd/build/modal.min.js.map b/amd/build/modal.min.js.map index d456ca5..d89af8e 100644 --- a/amd/build/modal.min.js.map +++ b/amd/build/modal.min.js.map @@ -1 +1 @@ -{"version":3,"file":"modal.min.js","sources":["../src/modal.js"],"sourcesContent":["// This file is part of Moodle - https://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Video-picker modal for Tiny FastPix.\n *\n * @module tiny_fastpix/modal\n * @copyright 2026 FastPix Inc. \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Modal from 'core/modal';\n\nexport default class FastpixModal extends Modal {\n static TYPE = 'tiny_fastpix/modal';\n static TEMPLATE = 'tiny_fastpix/modal';\n\n registerEventListeners() {\n super.registerEventListeners();\n this.registerCloseOnCancel();\n }\n\n configure(modalConfig) {\n modalConfig.large = true;\n modalConfig.show = true;\n modalConfig.removeOnClose = true;\n\n super.configure(modalConfig);\n }\n}\n\nFastpixModal.registerModalType();\n"],"names":["FastpixModal","Modal","registerEventListeners","registerCloseOnCancel","configure","modalConfig","large","show","removeOnClose","registerModalType"],"mappings":"wYAyBqBA,qBAAqBC,eAItCC,+BACUA,8BACDC,wBAGTC,UAAUC,aACNA,YAAYC,OAAQ,EACpBD,YAAYE,MAAO,EACnBF,YAAYG,eAAgB,QAEtBJ,UAAUC,mEAdHL,oBACH,sCADGA,wBAEC,sBAgBtBA,aAAaS"} \ No newline at end of file +{"version":3,"file":"modal.min.js","sources":["../src/modal.js"],"sourcesContent":["// This file is part of Moodle - https://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Video-picker modal for Tiny FastPix.\n *\n * @module tiny_fastpix/modal\n * @copyright 2026 FastPix Inc. \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Modal from 'core/modal';\n\nexport default class FastpixModal extends Modal {\n static TYPE = 'tiny_fastpix/modal';\n static TEMPLATE = 'tiny_fastpix/modal';\n\n registerEventListeners() {\n super.registerEventListeners();\n this.registerCloseOnCancel();\n }\n\n configure(modalConfig) {\n modalConfig.large = true;\n modalConfig.show = true;\n modalConfig.removeOnClose = true;\n\n super.configure(modalConfig);\n }\n}\n\nFastpixModal.registerModalType();\n"],"names":["e","_modal","__esModule","default","FastpixModal","Modal","static","registerEventListeners","super","this","registerCloseOnCancel","configure","modalConfig","large","show","removeOnClose","_exports","registerModalType"],"mappings":"+EAuB+B,IAAAA;;;;;;;qFAA/BC,QAA+BD,EAA/BC,SAA+BD,EAAAE,WAAAF,EAAAG,CAAAA,QAAAH,GAEhB,MAAMI,qBAAqBC,OAAAA,QACtCC,YAAc,qBACdA,gBAAkB,qBAElBC,sBAAAA,GACIC,MAAMD,yBACNE,KAAKC,uBACT,CAEAC,SAAAA,CAAUC,aACNA,YAAYC,OAAQ,EACpBD,YAAYE,MAAO,EACnBF,YAAYG,eAAgB,EAE5BP,MAAMG,UAAUC,YACpB,EAG6B,OAFhCI,SAAAb,QAAAC,aAEDA,aAAaa,oBAAoBD,SAAAb,OAAA"} \ No newline at end of file diff --git a/amd/build/options.min.js b/amd/build/options.min.js index 80ce60d..32f5ba9 100644 --- a/amd/build/options.min.js +++ b/amd/build/options.min.js @@ -1,4 +1,4 @@ -define("tiny_fastpix/options",["exports","editor_tiny/options","./common"],(function(_exports,_options,_common){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.register=_exports.getContextId=_exports.canUpload=void 0; +define("tiny_fastpix/options",["exports","editor_tiny/options","./common"],function(_exports,_options,_common){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.register=_exports.getCourseId=_exports.getContextId=_exports.canEmbed=void 0; /** * Options helper for the Tiny FastPix plugin. * @@ -6,6 +6,6 @@ define("tiny_fastpix/options",["exports","editor_tiny/options","./common"],(func * @copyright 2026 FastPix Inc. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -const contextIdName=(0,_options.getPluginOptionName)(_common.pluginName,"contextid"),canUploadName=(0,_options.getPluginOptionName)(_common.pluginName,"canupload");_exports.register=editor=>{const registerOption=editor.options.register;registerOption(contextIdName,{processor:"number",default:0}),registerOption(canUploadName,{processor:"boolean",default:!1})};_exports.getContextId=editor=>editor.options.get(contextIdName);_exports.canUpload=editor=>editor.options.get(canUploadName)})); +const contextIdName=(0,_options.getPluginOptionName)(_common.pluginName,"contextid"),courseIdName=(0,_options.getPluginOptionName)(_common.pluginName,"courseid"),canEmbedName=(0,_options.getPluginOptionName)(_common.pluginName,"canembed");_exports.register=editor=>{const registerOption=editor.options.register;registerOption(contextIdName,{processor:"number",default:0}),registerOption(courseIdName,{processor:"number",default:0}),registerOption(canEmbedName,{processor:"boolean",default:!1})};_exports.getContextId=editor=>editor.options.get(contextIdName);_exports.getCourseId=editor=>editor.options.get(courseIdName);_exports.canEmbed=editor=>editor.options.get(canEmbedName)}); //# sourceMappingURL=options.min.js.map \ No newline at end of file diff --git a/amd/build/options.min.js.map b/amd/build/options.min.js.map index 695ec13..c27087e 100644 --- a/amd/build/options.min.js.map +++ b/amd/build/options.min.js.map @@ -1 +1 @@ -{"version":3,"file":"options.min.js","sources":["../src/options.js"],"sourcesContent":["// This file is part of Moodle - https://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Options helper for the Tiny FastPix plugin.\n *\n * @module tiny_fastpix/options\n * @copyright 2026 FastPix Inc. \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {getPluginOptionName} from 'editor_tiny/options';\nimport {pluginName} from './common';\n\nconst contextIdName = getPluginOptionName(pluginName, 'contextid');\nconst canUploadName = getPluginOptionName(pluginName, 'canupload');\n\n/**\n * Register the plugin options (populated from plugininfo).\n *\n * @param {TinyMCE} editor\n */\nexport const register = (editor) => {\n const registerOption = editor.options.register;\n\n registerOption(contextIdName, {\n processor: 'number',\n \"default\": 0,\n });\n\n registerOption(canUploadName, {\n processor: 'boolean',\n \"default\": false,\n });\n};\n\n/**\n * The context id the editor is used in.\n *\n * @param {TinyMCE} editor\n * @returns {number}\n */\nexport const getContextId = (editor) => editor.options.get(contextIdName);\n\n/**\n * Whether the current user may use the picker.\n *\n * @param {TinyMCE} editor\n * @returns {boolean}\n */\nexport const canUpload = (editor) => editor.options.get(canUploadName);\n"],"names":["contextIdName","pluginName","canUploadName","editor","registerOption","options","register","processor","get"],"mappings":";;;;;;;;MA0BMA,eAAgB,gCAAoBC,mBAAY,aAChDC,eAAgB,gCAAoBD,mBAAY,+BAO7BE,eACfC,eAAiBD,OAAOE,QAAQC,SAEtCF,eAAeJ,cAAe,CAC1BO,UAAW,iBACA,IAGfH,eAAeF,cAAe,CAC1BK,UAAW,mBACA,2BAUUJ,QAAWA,OAAOE,QAAQG,IAAIR,kCAQjCG,QAAWA,OAAOE,QAAQG,IAAIN"} \ No newline at end of file +{"version":3,"file":"options.min.js","sources":["../src/options.js"],"sourcesContent":["// This file is part of Moodle - https://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Options helper for the Tiny FastPix plugin.\n *\n * @module tiny_fastpix/options\n * @copyright 2026 FastPix Inc. \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {getPluginOptionName} from 'editor_tiny/options';\nimport {pluginName} from './common';\n\nconst contextIdName = getPluginOptionName(pluginName, 'contextid');\nconst courseIdName = getPluginOptionName(pluginName, 'courseid');\nconst canEmbedName = getPluginOptionName(pluginName, 'canembed');\n\n/**\n * Register the plugin options (populated from plugininfo).\n *\n * @param {TinyMCE} editor\n */\nexport const register = (editor) => {\n const registerOption = editor.options.register;\n\n registerOption(contextIdName, {\n processor: 'number',\n \"default\": 0,\n });\n\n registerOption(courseIdName, {\n processor: 'number',\n \"default\": 0,\n });\n\n registerOption(canEmbedName, {\n processor: 'boolean',\n \"default\": false,\n });\n};\n\n/**\n * The context id the editor is used in.\n *\n * @param {TinyMCE} editor\n * @returns {number}\n */\nexport const getContextId = (editor) => editor.options.get(contextIdName);\n\n/**\n * The course id the editor is used in (0 outside a course).\n *\n * @param {TinyMCE} editor\n * @returns {number}\n */\nexport const getCourseId = (editor) => editor.options.get(courseIdName);\n\n/**\n * Whether the current user may embed FastPix videos (gates the toolbar button).\n *\n * @param {TinyMCE} editor\n * @returns {boolean}\n */\nexport const canEmbed = (editor) => editor.options.get(canEmbedName);\n"],"names":["contextIdName","getPluginOptionName","pluginName","courseIdName","canEmbedName","_exports","register","editor","registerOption","options","processor","default","getContextId","get","getCourseId","canEmbed"],"mappings":";;;;;;;;AA0BA,MAAMA,eAAgB,EAAAC,SAAAA,qBAAoBC,QAAUA,WAAE,aAChDC,cAAe,EAAAF,SAAAA,qBAAoBC,QAAUA,WAAE,YAC/CE,cAAe,EAAAH,SAAAA,qBAAoBC,QAAUA,WAAE,YAwBnDG,SAAAC,SAjBuBC,SACrB,MAAMC,eAAiBD,OAAOE,QAAQH,SAEtCE,eAAeR,cAAe,CAC1BU,UAAW,SACXC,QAAW,IAGfH,eAAeL,aAAc,CACzBO,UAAW,SACXC,QAAW,IAGfH,eAAeJ,aAAc,CACzBM,UAAW,UACXC,SAAW,KAUuDN,SAAAO,aAA7CL,QAAWA,OAAOE,QAAQI,IAAIb,eAQaK,SAAAS,YAA5CP,QAAWA,OAAOE,QAAQI,IAAIV,cAQWE,SAAAU,SAA5CR,QAAWA,OAAOE,QAAQI,IAAIT,aAAc"} \ No newline at end of file diff --git a/amd/build/plugin.min.js b/amd/build/plugin.min.js index cef03b1..53123d7 100644 --- a/amd/build/plugin.min.js +++ b/amd/build/plugin.min.js @@ -1,10 +1,10 @@ -define("tiny_fastpix/plugin",["exports","editor_tiny/loader","editor_tiny/utils","./common","./commands","./configuration","./options"],(function(_exports,_loader,_utils,_common,Commands,Configuration,Options){function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireWildcard(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}return newObj.default=obj,cache&&cache.set(obj,newObj),newObj} +define("tiny_fastpix/plugin",["exports","editor_tiny/loader","editor_tiny/utils","./common","./commands","./configuration","./options"],function(_exports,_loader,_utils,_common,Commands,Configuration,Options){function _interopRequireWildcard(e,t){if("function"==typeof WeakMap)var r=new WeakMap,n=new WeakMap;return(_interopRequireWildcard=function(e,t){if(!t&&e&&e.__esModule)return e;var o,i,f={__proto__:null,default:e};if(null===e||"object"!=typeof e&&"function"!=typeof e)return f;if(o=t?n:r){if(o.has(e))return o.get(e);o.set(e,f)}for(const t in e)"default"!==t&&{}.hasOwnProperty.call(e,t)&&((i=(o=Object.defineProperty)&&Object.getOwnPropertyDescriptor(e,t))&&(i.get||i.set)?o(f,t,i):f[t]=e[t]);return f})(e,t)} /** * Tiny FastPix plugin for Moodle. * * @module tiny_fastpix/plugin * @copyright 2026 FastPix Inc. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,Commands=_interopRequireWildcard(Commands),Configuration=_interopRequireWildcard(Configuration),Options=_interopRequireWildcard(Options);var _default=new Promise((async resolve=>{const[tinyMCE,setupCommands,pluginMetadata]=await Promise.all([(0,_loader.getTinyMCE)(),Commands.getSetup(),(0,_utils.getPluginMetadata)(_common.component,_common.pluginName)]);tinyMCE.PluginManager.add(_common.pluginName,(editor=>(Options.register(editor),setupCommands(editor),pluginMetadata))),resolve([_common.pluginName,Configuration])}));return _exports.default=_default,_exports.default})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,Commands=_interopRequireWildcard(Commands),Configuration=_interopRequireWildcard(Configuration),Options=_interopRequireWildcard(Options);_exports.default=new Promise(async resolve=>{const[tinyMCE,setupCommands,pluginMetadata]=await Promise.all([(0,_loader.getTinyMCE)(),Commands.getSetup(),(0,_utils.getPluginMetadata)(_common.component,_common.pluginName)]);tinyMCE.PluginManager.add(_common.pluginName,editor=>(Options.register(editor),setupCommands(editor),pluginMetadata)),resolve([_common.pluginName,Configuration])});return _exports.default}); //# sourceMappingURL=plugin.min.js.map \ No newline at end of file diff --git a/amd/build/plugin.min.js.map b/amd/build/plugin.min.js.map index 28e55d8..da34891 100644 --- a/amd/build/plugin.min.js.map +++ b/amd/build/plugin.min.js.map @@ -1 +1 @@ -{"version":3,"file":"plugin.min.js","sources":["../src/plugin.js"],"sourcesContent":["// This file is part of Moodle - https://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Tiny FastPix plugin for Moodle.\n *\n * @module tiny_fastpix/plugin\n * @copyright 2026 FastPix Inc. \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {getTinyMCE} from 'editor_tiny/loader';\nimport {getPluginMetadata} from 'editor_tiny/utils';\n\nimport {component, pluginName} from './common';\nimport * as Commands from './commands';\nimport * as Configuration from './configuration';\nimport * as Options from './options';\n\n// eslint-disable-next-line no-async-promise-executor\nexport default new Promise(async(resolve) => {\n const [\n tinyMCE,\n setupCommands,\n pluginMetadata,\n ] = await Promise.all([\n getTinyMCE(),\n Commands.getSetup(),\n getPluginMetadata(component, pluginName),\n ]);\n\n // PluginManager.add does not accept a Promise — all async work is done above.\n tinyMCE.PluginManager.add(pluginName, (editor) => {\n Options.register(editor);\n setupCommands(editor);\n\n return pluginMetadata;\n });\n\n resolve([pluginName, Configuration]);\n});\n"],"names":["Promise","async","tinyMCE","setupCommands","pluginMetadata","all","Commands","getSetup","component","pluginName","PluginManager","add","editor","Options","register","resolve","Configuration"],"mappings":";;;;;;;2OAgCe,IAAIA,SAAQC,MAAAA,gBAEnBC,QACAC,cACAC,sBACMJ,QAAQK,IAAI,EAClB,wBACAC,SAASC,YACT,4BAAkBC,kBAAWC,sBAIjCP,QAAQQ,cAAcC,IAAIF,oBAAaG,SACnCC,QAAQC,SAASF,QACjBT,cAAcS,QAEPR,kBAGXW,QAAQ,CAACN,mBAAYO"} \ No newline at end of file +{"version":3,"file":"plugin.min.js","sources":["../src/plugin.js"],"sourcesContent":["// This file is part of Moodle - https://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Tiny FastPix plugin for Moodle.\n *\n * @module tiny_fastpix/plugin\n * @copyright 2026 FastPix Inc. \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {getTinyMCE} from 'editor_tiny/loader';\nimport {getPluginMetadata} from 'editor_tiny/utils';\n\nimport {component, pluginName} from './common';\nimport * as Commands from './commands';\nimport * as Configuration from './configuration';\nimport * as Options from './options';\n\n// eslint-disable-next-line no-async-promise-executor\nexport default new Promise(async(resolve) => {\n const [\n tinyMCE,\n setupCommands,\n pluginMetadata,\n ] = await Promise.all([\n getTinyMCE(),\n Commands.getSetup(),\n getPluginMetadata(component, pluginName),\n ]);\n\n // PluginManager.add does not accept a Promise — all async work is done above.\n tinyMCE.PluginManager.add(pluginName, (editor) => {\n Options.register(editor);\n setupCommands(editor);\n\n return pluginMetadata;\n });\n\n resolve([pluginName, Configuration]);\n});\n"],"names":["_interopRequireWildcard","e","t","WeakMap","r","n","__esModule","o","i","f","__proto__","default","has","get","set","hasOwnProperty","call","Object","defineProperty","getOwnPropertyDescriptor","Commands","Configuration","Options","_exports","Promise","async","tinyMCE","setupCommands","pluginMetadata","all","getTinyMCE","getSetup","getPluginMetadata","component","pluginName","PluginManager","add","editor","register","resolve"],"mappings":"iNA6BqC,SAAAA,wBAAAC,EAAAC,GAAAC,GAAAA,mBAAAA,QAAAC,IAAAA,EAAAD,IAAAA,QAAAE,MAAAF,QAAA,OAAAH,wBAAA,SAAAC,EAAAC,OAAAA,GAAAD,GAAAA,EAAAK,WAAA,OAAAL,EAAAM,IAAAA,EAAAC,EAAAC,EAAAC,CAAAA,eAAAC,QAAAV,GAAA,GAAA,OAAAA,GAAA,iBAAAA,GAAA,mBAAAA,EAAAQ,OAAAA,EAAAF,GAAAA,EAAAL,EAAAG,EAAAD,EAAA,CAAA,GAAAG,EAAAK,IAAAX,GAAA,OAAAM,EAAAM,IAAAZ,GAAAM,EAAAO,IAAAb,EAAAQ,EAAAP,CAAAA,IAAAA,MAAAA,KAAAD,cAAAC,GAAA,CAAA,EAAAa,eAAAC,KAAAf,EAAAC,MAAAM,GAAAD,EAAAU,OAAAC,iBAAAD,OAAAE,yBAAAlB,EAAAC,MAAAM,EAAAK,KAAAL,EAAAM,KAAAP,EAAAE,EAAAP,EAAAM,GAAAC,EAAAP,GAAAD,EAAAC,IAAAO,OAAAA,CAAAR,GAAAA,EAAAC,EAAA;;;;;;;qFAFrCkB,SAAApB,wBAAAoB,UACAC,cAAArB,wBAAAqB,eACAC,QAAAtB,wBAAAsB,SAdAC,SAAAZ,QAiBe,IAAIa,QAAQC,gBACvB,MACIC,QACAC,cACAC,sBACMJ,QAAQK,IAAI,EAClB,EAAAC,QAAAA,cACAV,SAASW,YACT,EAAAC,OAAAA,mBAAkBC,QAASA,UAAEC,QAAUA,cAI3CR,QAAQS,cAAcC,IAAIF,QAAUA,WAAGG,SACnCf,QAAQgB,SAASD,QACjBV,cAAcU,QAEPT,iBAGXW,QAAQ,CAACL,QAAAA,WAAYb,kBACvB,OAAAE,SAAAZ,OAAA"} \ No newline at end of file diff --git a/amd/build/ui.min.js b/amd/build/ui.min.js index cefb4b0..ad156c5 100644 --- a/amd/build/ui.min.js +++ b/amd/build/ui.min.js @@ -1,11 +1,11 @@ -define("tiny_fastpix/ui",["exports","core/ajax","core/templates","core/str","./common","./options","./modal"],(function(_exports,_ajax,_templates,_str,_common,_options,_modal){var obj; +define("tiny_fastpix/ui",["exports","core/ajax","core/templates","core/str","./common","./options","./modal"],function(_exports,_ajax,_templates,_str,_common,_options,_modal){var e; /** - * Tiny FastPix UI — opens the picker, lists the author's videos and inserts the - * {fastpix:pb_} shortcode for the chosen one. + * Tiny FastPix UI — opens the picker, lists this course's videos the author + * uploaded and inserts the {fastpix:pb_} shortcode for the chosen one. * * @module tiny_fastpix/ui * @copyright 2026 FastPix Inc. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.handleAction=void 0,_modal=(obj=_modal)&&obj.__esModule?obj:{default:obj};_exports.handleAction=editor=>{displayDialogue(editor)};const displayDialogue=async editor=>{const bookmark=editor.selection.getBookmark(),contextid=(0,_options.getContextId)(editor),modal=await _modal.default.create({templateContext:{elementid:editor.id}}),root=modal.getRoot()[0],region=root.querySelector('[data-region="fastpix-video-list"]'),search=root.querySelector('[data-region="fastpix-search"]');let currentQuery="";const renderList=async query=>{let videos;currentQuery=query;try{videos=await function(contextid){let query=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";return(0,_ajax.call)([{methodname:"tiny_fastpix_get_my_videos",args:{contextid:contextid,query:query}}])[0].then((result=>result.videos))}(contextid,query)}catch(error){if(currentQuery!==query)return;const message=await(0,_str.getString)("loaderror",_common.component);return void(region.textContent=message)}if(currentQuery!==query)return;const{html:html,js:js}=await(0,_templates.renderForPromise)("".concat(_common.component,"/videos"),{videos:videos,hasvideos:videos.length>0,issearch:""!==query});currentQuery===query&&(0,_templates.replaceNodeContents)(region,html,js)};region.addEventListener("click",(e=>{const choice=e.target.closest("[data-playbackid]");choice&&(e.preventDefault(),((editor,modal,bookmark,playbackid)=>{editor.selection.moveToBookmark(bookmark),editor.execCommand("mceInsertContent",!1,"{fastpix:pb_".concat(playbackid,"}")),editor.selection.moveToBookmark(bookmark),modal.destroy()})(editor,modal,bookmark,choice.dataset.playbackid))})),search&&search.addEventListener("input",((fn,delay)=>{let timer;return function(){for(var _len=arguments.length,args=new Array(_len),_key=0;_key<_len;_key++)args[_key]=arguments[_key];clearTimeout(timer),timer=setTimeout((()=>fn(...args)),delay)}})((()=>{const term=search.value.trim();renderList(term.length>=2?term:"")}),300)),await renderList("")}})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.handleAction=void 0,_modal=(e=_modal)&&e.__esModule?e:{default:e};_exports.handleAction=editor=>{displayDialogue(editor)};const displayDialogue=async editor=>{const bookmark=editor.selection.getBookmark(),contextid=(0,_options.getContextId)(editor),modal=await _modal.default.create({templateContext:{elementid:editor.id}}),region=modal.getRoot()[0].querySelector('[data-region="fastpix-video-list"]');let videos;region.addEventListener("click",e=>{const choice=e.target.closest("[data-playbackid]");choice&&(e.preventDefault(),((editor,modal,bookmark,playbackid)=>{editor.selection.moveToBookmark(bookmark),editor.execCommand("mceInsertContent",!1,`{fastpix:pb_${playbackid}}`),editor.selection.moveToBookmark(bookmark),modal.destroy()})(editor,modal,bookmark,choice.dataset.playbackid))});try{videos=await(contextid=>(0,_ajax.call)([{methodname:"tiny_fastpix_get_my_videos",args:{contextid:contextid}}])[0].then(result=>result.videos))(contextid)}catch(error){const message=await(0,_str.getString)("loaderror",_common.component);return void(region.textContent=message)}const{html:html,js:js}=await(0,_templates.renderForPromise)(`${_common.component}/videos`,{videos:videos,hasvideos:videos.length>0});(0,_templates.replaceNodeContents)(region,html,js)}}); //# sourceMappingURL=ui.min.js.map \ No newline at end of file diff --git a/amd/build/ui.min.js.map b/amd/build/ui.min.js.map index bb4beaa..33a5449 100644 --- a/amd/build/ui.min.js.map +++ b/amd/build/ui.min.js.map @@ -1 +1 @@ -{"version":3,"file":"ui.min.js","sources":["../src/ui.js"],"sourcesContent":["// This file is part of Moodle - https://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Tiny FastPix UI — opens the picker, lists the author's videos and inserts the\n * {fastpix:pb_} shortcode for the chosen one.\n *\n * @module tiny_fastpix/ui\n * @copyright 2026 FastPix Inc. \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {call as fetchMany} from 'core/ajax';\nimport {renderForPromise, replaceNodeContents} from 'core/templates';\nimport {getString} from 'core/str';\nimport {component} from './common';\nimport {getContextId} from './options';\nimport FastpixModal from './modal';\n\nconst listRegionSelector = '[data-region=\"fastpix-video-list\"]';\nconst searchSelector = '[data-region=\"fastpix-search\"]';\n\n// Wait for a pause in typing before hitting the web service, and ignore very\n// short terms (a single letter would match almost everything).\nconst searchDebounceMs = 300;\nconst searchMinChars = 2;\n\n/**\n * Open the picker for the current editor.\n *\n * @param {TinyMCE} editor\n */\nexport const handleAction = (editor) => {\n displayDialogue(editor);\n};\n\n/**\n * Fetch the current user's embeddable videos via the web service.\n *\n * @param {number} contextid\n * @param {string} query Optional search term; empty returns the recent list.\n * @returns {Promise>}\n */\nconst fetchVideos = (contextid, query = '') => fetchMany([{\n methodname: 'tiny_fastpix_get_my_videos',\n args: {contextid, query},\n}])[0].then((result) => result.videos);\n\n/**\n * Debounce a function: only run it after `delay` ms pass without a new call.\n *\n * @param {Function} fn\n * @param {number} delay\n * @returns {Function}\n */\nconst debounce = (fn, delay) => {\n let timer;\n return (...args) => {\n clearTimeout(timer);\n timer = setTimeout(() => fn(...args), delay);\n };\n};\n\n/**\n * Insert the shortcode for the chosen video and close the modal.\n *\n * @param {TinyMCE} editor\n * @param {object} modal\n * @param {string} bookmark\n * @param {string} playbackid\n */\nconst insertShortcode = (editor, modal, bookmark, playbackid) => {\n editor.selection.moveToBookmark(bookmark);\n editor.execCommand('mceInsertContent', false, `{fastpix:pb_${playbackid}}`);\n editor.selection.moveToBookmark(bookmark);\n modal.destroy();\n};\n\n/**\n * Build and show the picker dialogue.\n *\n * @param {TinyMCE} editor\n */\nconst displayDialogue = async(editor) => {\n // Remember where the cursor was so the shortcode lands there.\n const bookmark = editor.selection.getBookmark();\n const contextid = getContextId(editor);\n\n const modal = await FastpixModal.create({\n templateContext: {\n elementid: editor.id,\n },\n });\n\n const root = modal.getRoot()[0];\n const region = root.querySelector(listRegionSelector);\n const search = root.querySelector(searchSelector);\n\n // Tracks the most recent query so an out-of-order web service response (a\n // slow early request landing after a later one) can't overwrite the list.\n let currentQuery = '';\n\n const renderList = async(query) => {\n currentQuery = query;\n\n let videos;\n try {\n videos = await fetchVideos(contextid, query);\n } catch (error) {\n if (currentQuery !== query) {\n return;\n }\n const message = await getString('loaderror', component);\n region.textContent = message;\n return;\n }\n if (currentQuery !== query) {\n return;\n }\n\n const {html, js} = await renderForPromise(`${component}/videos`, {\n videos,\n hasvideos: videos.length > 0,\n issearch: query !== '',\n });\n if (currentQuery !== query) {\n return;\n }\n replaceNodeContents(region, html, js);\n };\n\n // The list region persists across re-renders, so one delegated click\n // listener covers every (re)rendered card.\n region.addEventListener('click', (e) => {\n const choice = e.target.closest('[data-playbackid]');\n if (!choice) {\n return;\n }\n e.preventDefault();\n insertShortcode(editor, modal, bookmark, choice.dataset.playbackid);\n });\n\n if (search) {\n search.addEventListener('input', debounce(() => {\n const term = search.value.trim();\n // Below the minimum length, fall back to the recent list.\n renderList(term.length >= searchMinChars ? term : '');\n }, searchDebounceMs));\n }\n\n // Initial load: the most recent videos.\n await renderList('');\n};\n"],"names":["editor","displayDialogue","async","bookmark","selection","getBookmark","contextid","modal","FastpixModal","create","templateContext","elementid","id","root","getRoot","region","querySelector","search","currentQuery","renderList","videos","query","methodname","args","then","result","fetchVideos","error","message","component","textContent","html","js","hasvideos","length","issearch","addEventListener","e","choice","target","closest","preventDefault","playbackid","moveToBookmark","execCommand","destroy","insertShortcode","dataset","fn","delay","timer","clearTimeout","setTimeout","debounce","term","value","trim"],"mappings":";;;;;;;;sKA4C6BA,SACzBC,gBAAgBD,eAkDdC,gBAAkBC,MAAAA,eAEdC,SAAWH,OAAOI,UAAUC,cAC5BC,WAAY,yBAAaN,QAEzBO,YAAcC,eAAaC,OAAO,CACpCC,gBAAiB,CACbC,UAAWX,OAAOY,MAIpBC,KAAON,MAAMO,UAAU,GACvBC,OAASF,KAAKG,cA5EG,sCA6EjBC,OAASJ,KAAKG,cA5ED,sCAgFfE,aAAe,SAEbC,WAAajB,MAAAA,YAGXkB,OAFJF,aAAeG,UAIXD,aAhEQ,SAACd,eAAWe,6DAAQ,UAAO,cAAU,CAAC,CACtDC,WAAY,6BACZC,KAAM,CAACjB,UAAAA,UAAWe,MAAAA,UAClB,GAAGG,MAAMC,QAAWA,OAAOL,SA6DJM,CAAYpB,UAAWe,OACxC,MAAOM,UACDT,eAAiBG,mBAGfO,cAAgB,kBAAU,YAAaC,+BAC7Cd,OAAOe,YAAcF,YAGrBV,eAAiBG,mBAIfU,KAACA,KAADC,GAAOA,UAAY,yCAAoBH,6BAAoB,CAC7DT,OAAAA,OACAa,UAAWb,OAAOc,OAAS,EAC3BC,SAAoB,KAAVd,QAEVH,eAAiBG,0CAGDN,OAAQgB,KAAMC,KAKtCjB,OAAOqB,iBAAiB,SAAUC,UACxBC,OAASD,EAAEE,OAAOC,QAAQ,qBAC3BF,SAGLD,EAAEI,iBAnEc,EAACzC,OAAQO,MAAOJ,SAAUuC,cAC9C1C,OAAOI,UAAUuC,eAAexC,UAChCH,OAAO4C,YAAY,oBAAoB,wBAAsBF,iBAC7D1C,OAAOI,UAAUuC,eAAexC,UAChCI,MAAMsC,WAgEFC,CAAgB9C,OAAQO,MAAOJ,SAAUmC,OAAOS,QAAQL,gBAGxDzB,QACAA,OAAOmB,iBAAiB,QAxFf,EAACY,GAAIC,aACdC,aACG,yCAAI3B,6CAAAA,2BACP4B,aAAaD,OACbA,MAAQE,YAAW,IAAMJ,MAAMzB,OAAO0B,SAoFLI,EAAS,WAChCC,KAAOrC,OAAOsC,MAAMC,OAE1BrC,WAAWmC,KAAKpB,QAzHL,EAyHgCoB,KAAO,MA1HrC,YA+HfnC,WAAW"} \ No newline at end of file +{"version":3,"file":"ui.min.js","sources":["../src/ui.js"],"sourcesContent":["// This file is part of Moodle - https://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Tiny FastPix UI — opens the picker, lists this course's videos the author\n * uploaded and inserts the {fastpix:pb_} shortcode for the chosen one.\n *\n * @module tiny_fastpix/ui\n * @copyright 2026 FastPix Inc. \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {call as fetchMany} from 'core/ajax';\nimport {renderForPromise, replaceNodeContents} from 'core/templates';\nimport {getString} from 'core/str';\nimport {component} from './common';\nimport {getContextId} from './options';\nimport FastpixModal from './modal';\n\nconst listRegionSelector = '[data-region=\"fastpix-video-list\"]';\n\n/**\n * Open the picker for the current editor.\n *\n * @param {TinyMCE} editor\n */\nexport const handleAction = (editor) => {\n displayDialogue(editor);\n};\n\n/**\n * Fetch the videos this course holds for the current user via the web service.\n * The server derives the course from the context, so only the context is sent.\n *\n * @param {number} contextid\n * @returns {Promise>}\n */\nconst fetchVideos = (contextid) => fetchMany([{\n methodname: 'tiny_fastpix_get_my_videos',\n args: {contextid},\n}])[0].then((result) => result.videos);\n\n/**\n * Insert the shortcode for the chosen video and close the modal.\n *\n * @param {TinyMCE} editor\n * @param {object} modal\n * @param {string} bookmark\n * @param {string} playbackid\n */\nconst insertShortcode = (editor, modal, bookmark, playbackid) => {\n editor.selection.moveToBookmark(bookmark);\n editor.execCommand('mceInsertContent', false, `{fastpix:pb_${playbackid}}`);\n editor.selection.moveToBookmark(bookmark);\n modal.destroy();\n};\n\n/**\n * Build and show the picker dialogue.\n *\n * @param {TinyMCE} editor\n */\nconst displayDialogue = async(editor) => {\n // Remember where the cursor was so the shortcode lands there.\n const bookmark = editor.selection.getBookmark();\n const contextid = getContextId(editor);\n\n const modal = await FastpixModal.create({\n templateContext: {\n elementid: editor.id,\n },\n });\n\n const root = modal.getRoot()[0];\n const region = root.querySelector(listRegionSelector);\n\n // The list region persists, so one delegated click listener covers every\n // rendered card.\n region.addEventListener('click', (e) => {\n const choice = e.target.closest('[data-playbackid]');\n if (!choice) {\n return;\n }\n e.preventDefault();\n insertShortcode(editor, modal, bookmark, choice.dataset.playbackid);\n });\n\n let videos;\n try {\n videos = await fetchVideos(contextid);\n } catch (error) {\n const message = await getString('loaderror', component);\n region.textContent = message;\n return;\n }\n\n const {html, js} = await renderForPromise(`${component}/videos`, {\n videos,\n hasvideos: videos.length > 0,\n });\n replaceNodeContents(region, html, js);\n};\n"],"names":["e","_modal","__esModule","default","_exports","handleAction","editor","displayDialogue","async","bookmark","selection","getBookmark","contextid","getContextId","modal","FastpixModal","create","templateContext","elementid","id","region","getRoot","querySelector","videos","addEventListener","choice","target","closest","preventDefault","insertShortcode","playbackid","moveToBookmark","execCommand","destroy","dataset","fetchMany","methodname","args","then","result","fetchVideos","error","message","getString","component","textContent","html","js","renderForPromise","hasvideos","length","replaceNodeContents"],"mappings":"+KA6BmC,IAAAA;;;;;;;;0FAAnCC,QAAmCD,EAAnCC,SAAmCD,EAAAE,WAAAF,EAAAG,CAAAA,QAAAH,GAWjCI,SAAAC,aAF2BC,SACzBC,gBAAgBD,SAUpB,MAyBMC,gBAAkBC,eAEpB,MAAMC,SAAWH,OAAOI,UAAUC,cAC5BC,WAAY,EAAAC,SAAYA,cAACP,QAEzBQ,YAAcC,OAAYZ,QAACa,OAAO,CACpCC,gBAAiB,CACbC,UAAWZ,OAAOa,MAKpBC,OADON,MAAMO,UAAU,GACTC,cAvDG,sCAoEvB,IAAIC,OATJH,OAAOI,iBAAiB,QAAUxB,IAC9B,MAAMyB,OAASzB,EAAE0B,OAAOC,QAAQ,qBAC3BF,SAGLzB,EAAE4B,iBAjCcC,EAACvB,OAAQQ,MAAOL,SAAUqB,cAC9CxB,OAAOI,UAAUqB,eAAetB,UAChCH,OAAO0B,YAAY,oBAAoB,EAAO,eAAeF,eAC7DxB,OAAOI,UAAUqB,eAAetB,UAChCK,MAAMmB,WA8BFJ,CAAgBvB,OAAQQ,MAAOL,SAAUgB,OAAOS,QAAQJ,eAI5D,IACIP,YApDaX,aAAc,EAAAuB,MAAAA,MAAU,CAAC,CAC1CC,WAAY,6BACZC,KAAM,CAACzB,wBACP,GAAG0B,KAAMC,QAAWA,OAAOhB,QAiDRiB,CAAY5B,UAC9B,CAAC,MAAO6B,OACL,MAAMC,cAAgB,EAAAC,gBAAU,YAAaC,QAAAA,WAE7C,YADAxB,OAAOyB,YAAcH,QAEzB,CAEA,MAAMI,KAACA,KAAIC,GAAEA,UAAY,EAAAC,WAAAA,kBAAiB,GAAGJ,QAAAA,mBAAoB,CAC7DrB,cACA0B,UAAW1B,OAAO2B,OAAS,KAE/B,EAAAC,gCAAoB/B,OAAQ0B,KAAMC,IACpC"} \ No newline at end of file diff --git a/amd/src/commands.js b/amd/src/commands.js index ca3fb7b..cc8842e 100644 --- a/amd/src/commands.js +++ b/amd/src/commands.js @@ -24,7 +24,7 @@ import {getButtonImage} from 'editor_tiny/utils'; import {getString} from 'core/str'; import {component, buttonName, icon} from './common'; -import {canUpload} from './options'; +import {canEmbed} from './options'; import {handleAction} from './ui'; export const getSetup = async() => { @@ -37,7 +37,7 @@ export const getSetup = async() => { ]); return (editor) => { - if (!canUpload(editor)) { + if (!canEmbed(editor)) { return; } diff --git a/amd/src/options.js b/amd/src/options.js index 35a4239..23ae68f 100644 --- a/amd/src/options.js +++ b/amd/src/options.js @@ -25,7 +25,8 @@ import {getPluginOptionName} from 'editor_tiny/options'; import {pluginName} from './common'; const contextIdName = getPluginOptionName(pluginName, 'contextid'); -const canUploadName = getPluginOptionName(pluginName, 'canupload'); +const courseIdName = getPluginOptionName(pluginName, 'courseid'); +const canEmbedName = getPluginOptionName(pluginName, 'canembed'); /** * Register the plugin options (populated from plugininfo). @@ -40,7 +41,12 @@ export const register = (editor) => { "default": 0, }); - registerOption(canUploadName, { + registerOption(courseIdName, { + processor: 'number', + "default": 0, + }); + + registerOption(canEmbedName, { processor: 'boolean', "default": false, }); @@ -55,9 +61,17 @@ export const register = (editor) => { export const getContextId = (editor) => editor.options.get(contextIdName); /** - * Whether the current user may use the picker. + * The course id the editor is used in (0 outside a course). + * + * @param {TinyMCE} editor + * @returns {number} + */ +export const getCourseId = (editor) => editor.options.get(courseIdName); + +/** + * Whether the current user may embed FastPix videos (gates the toolbar button). * * @param {TinyMCE} editor * @returns {boolean} */ -export const canUpload = (editor) => editor.options.get(canUploadName); +export const canEmbed = (editor) => editor.options.get(canEmbedName); diff --git a/amd/src/ui.js b/amd/src/ui.js index f9a462c..6ca0b01 100644 --- a/amd/src/ui.js +++ b/amd/src/ui.js @@ -14,8 +14,8 @@ // along with Moodle. If not, see . /** - * Tiny FastPix UI — opens the picker, lists the author's videos and inserts the - * {fastpix:pb_} shortcode for the chosen one. + * Tiny FastPix UI — opens the picker, lists this course's videos the author + * uploaded and inserts the {fastpix:pb_} shortcode for the chosen one. * * @module tiny_fastpix/ui * @copyright 2026 FastPix Inc. @@ -30,12 +30,6 @@ import {getContextId} from './options'; import FastpixModal from './modal'; const listRegionSelector = '[data-region="fastpix-video-list"]'; -const searchSelector = '[data-region="fastpix-search"]'; - -// Wait for a pause in typing before hitting the web service, and ignore very -// short terms (a single letter would match almost everything). -const searchDebounceMs = 300; -const searchMinChars = 2; /** * Open the picker for the current editor. @@ -47,32 +41,17 @@ export const handleAction = (editor) => { }; /** - * Fetch the current user's embeddable videos via the web service. + * Fetch the videos this course holds for the current user via the web service. + * The server derives the course from the context, so only the context is sent. * * @param {number} contextid - * @param {string} query Optional search term; empty returns the recent list. * @returns {Promise>} */ -const fetchVideos = (contextid, query = '') => fetchMany([{ +const fetchVideos = (contextid) => fetchMany([{ methodname: 'tiny_fastpix_get_my_videos', - args: {contextid, query}, + args: {contextid}, }])[0].then((result) => result.videos); -/** - * Debounce a function: only run it after `delay` ms pass without a new call. - * - * @param {Function} fn - * @param {number} delay - * @returns {Function} - */ -const debounce = (fn, delay) => { - let timer; - return (...args) => { - clearTimeout(timer); - timer = setTimeout(() => fn(...args), delay); - }; -}; - /** * Insert the shortcode for the chosen video and close the modal. * @@ -106,43 +85,9 @@ const displayDialogue = async(editor) => { const root = modal.getRoot()[0]; const region = root.querySelector(listRegionSelector); - const search = root.querySelector(searchSelector); - - // Tracks the most recent query so an out-of-order web service response (a - // slow early request landing after a later one) can't overwrite the list. - let currentQuery = ''; - const renderList = async(query) => { - currentQuery = query; - - let videos; - try { - videos = await fetchVideos(contextid, query); - } catch (error) { - if (currentQuery !== query) { - return; - } - const message = await getString('loaderror', component); - region.textContent = message; - return; - } - if (currentQuery !== query) { - return; - } - - const {html, js} = await renderForPromise(`${component}/videos`, { - videos, - hasvideos: videos.length > 0, - issearch: query !== '', - }); - if (currentQuery !== query) { - return; - } - replaceNodeContents(region, html, js); - }; - - // The list region persists across re-renders, so one delegated click - // listener covers every (re)rendered card. + // The list region persists, so one delegated click listener covers every + // rendered card. region.addEventListener('click', (e) => { const choice = e.target.closest('[data-playbackid]'); if (!choice) { @@ -152,14 +97,18 @@ const displayDialogue = async(editor) => { insertShortcode(editor, modal, bookmark, choice.dataset.playbackid); }); - if (search) { - search.addEventListener('input', debounce(() => { - const term = search.value.trim(); - // Below the minimum length, fall back to the recent list. - renderList(term.length >= searchMinChars ? term : ''); - }, searchDebounceMs)); + let videos; + try { + videos = await fetchVideos(contextid); + } catch (error) { + const message = await getString('loaderror', component); + region.textContent = message; + return; } - // Initial load: the most recent videos. - await renderList(''); + const {html, js} = await renderForPromise(`${component}/videos`, { + videos, + hasvideos: videos.length > 0, + }); + replaceNodeContents(region, html, js); }; diff --git a/classes/external/get_my_videos.php b/classes/external/get_my_videos.php index 721b0d9..7d5c1bb 100644 --- a/classes/external/get_my_videos.php +++ b/classes/external/get_my_videos.php @@ -15,7 +15,7 @@ // along with Moodle. If not, see . /** - * External function: list the author's embeddable FastPix videos. + * External function: list the current course's videos the caller uploaded. * * @package tiny_fastpix * @copyright 2026 FastPix Inc. @@ -31,19 +31,30 @@ use core_external\external_value; /** - * Returns the current user's ready, public FastPix videos for the picker. + * Returns the signed-in user's ready, embeddable FastPix videos that belong to + * the course the editor is being used in. * - * The list is sourced from local_fastpix (asset_service::list_for_owner). Only - * PUBLIC videos are offered: filter_fastpix plays public videos in casual - * embeds, but renders the "unavailable" placeholder for private and DRM ones, - * so offering anything non-public would only let an author insert a shortcode - * that never plays. + * Scope (all must hold): + * - owner == the current user (asset_service::list_for_owner), + * - course == the editor's course (the asset is referenced by a mod_fastpix + * activity in that course), + * - state == ready, + * - embeddable == public with a playback id (filter_fastpix plays public + * videos in casual embeds but renders "unavailable" for private/DRM, so + * offering those would only insert a dead embed). + * + * Asset data is read exclusively through local_fastpix's service methods; the + * only direct DB read is against mod_fastpix's own {fastpix} table, because the + * course association lives there (local_fastpix assets have no course). * * @package tiny_fastpix * @copyright 2026 FastPix Inc. * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class get_my_videos extends external_api { + /** @var int Upper bound on the owner list scanned; the course scope keeps the real count small. */ + private const MAX_VIDEOS = 500; + /** * Parameter definition. * @@ -52,13 +63,6 @@ class get_my_videos extends external_api { public static function execute_parameters(): external_function_parameters { return new external_function_parameters([ 'contextid' => new external_value(PARAM_INT, 'The context the editor is used in.'), - 'query' => new external_value( - PARAM_TEXT, - 'Optional search term. Empty returns the most recent videos; otherwise the ' - . 'whole owned library is searched by activity name and asset title.', - VALUE_DEFAULT, - '' - ), ]); } @@ -79,52 +83,70 @@ public static function execute_returns(): external_single_structure { } /** - * List the current user's ready, embeddable videos. + * List the caller's ready, embeddable videos that belong to this course. * * @param int $contextid The context the editor is used in. - * @param string $query Optional search term; empty returns the recent list. * @return array The list of videos. */ - public static function execute(int $contextid, string $query = ''): array { - global $USER; + public static function execute(int $contextid): array { + global $USER, $DB; $params = self::validate_parameters( self::execute_parameters(), - ['contextid' => $contextid, 'query' => $query] + ['contextid' => $contextid] ); $context = \context::instance_by_id($params['contextid']); self::validate_context($context); - require_capability('mod/fastpix:uploadmedia', $context); - $search = trim($params['query']); - if ($search === '') { - // No search: the most-recent owned, ready assets. - $assets = \local_fastpix\service\asset_service::list_for_owner((int)$USER->id, 'ready'); - } else { - // Search the whole owned library by activity name and asset title. - $assets = self::search_assets((int)$USER->id, $search); + // Course scope: the picker only lists videos belonging to the course the + // editor lives in. Outside a course (user/system context) nothing is in + // scope, so return empty rather than leak anything cross-course. + $coursecontext = $context->get_course_context(false); + if (!$coursecontext) { + return ['videos' => []]; + } + require_capability('mod/fastpix:uploadmedia', $coursecontext); + $courseid = (int)$coursecontext->instanceid; + + // Owner + ready scope from local_fastpix's own list method (no direct + // local_fastpix table queries). + $ownedbyid = []; + foreach (\local_fastpix\service\asset_service::list_for_owner((int)$USER->id, 'ready', self::MAX_VIDEOS) as $asset) { + $ownedbyid[(int)$asset->id] = $asset; + } + if (empty($ownedbyid)) { + return ['videos' => []]; } - // The local_fastpix asset title is frequently the "Asset " - // fallback, which tells an author nothing in the picker. The name they - // actually typed lives on the mod_fastpix activity that uploaded the - // asset, so resolve a display name from there. Read-only lookup — this - // plugin writes nothing to mod_fastpix or local_fastpix. - $names = self::resolve_display_names($assets); + // Walk this course's FastPix activities (mod_fastpix's own {fastpix} + // table — the course lives there, not in local_fastpix). id DESC so the + // most recent activity name wins; $seen dedupes a reused asset. + $activities = $DB->get_records( + 'fastpix', + ['course' => $courseid], + 'id DESC', + 'id, name, fastpix_asset_id, upload_session_id' + ); $videos = []; - foreach ($assets as $asset) { - // Embeddable = has a playback id AND is public. Private and DRM - // videos are excluded: filter_fastpix only plays public videos in - // casual embeds (a private/DRM shortcode renders "unavailable"), so - // offering them here would only let an author insert a dead embed. + $seen = []; + foreach ($activities as $activity) { + $assetid = self::resolve_asset_id($activity); + if ($assetid === 0 || isset($seen[$assetid]) || !isset($ownedbyid[$assetid])) { + continue; + } + $asset = $ownedbyid[$assetid]; + // Embeddable = public + has a playback id. Private/DRM render + // "unavailable" in filter_fastpix, so offering them would only let an + // author insert a dead embed. if (empty($asset->playback_id) || ($asset->access_policy ?? '') !== 'public') { continue; } + $seen[$assetid] = true; $videos[] = [ 'playbackid' => (string)$asset->playback_id, - 'title' => self::display_title($asset, $names[(int)$asset->id] ?? ''), + 'title' => self::display_title($asset, trim((string)$activity->name)), ]; } @@ -132,229 +154,52 @@ public static function execute(int $contextid, string $query = ''): array { } /** - * Pick the label shown in the picker for one asset. - * - * Preference order: the resolved activity name; then the asset's own title - * if it is a real one; then a localised "Untitled video" placeholder. The - * local_fastpix projector writes the literal "Asset " when an - * upload arrives without a title, so we treat exactly that value as "no - * title" rather than show a raw UUID to authors. - * - * @param \stdClass $asset The asset record (needs ->title and ->fastpix_id). - * @param string $resolvedname The activity name resolved upstream, or ''. - * @return string The label to display. - */ - private static function display_title(\stdClass $asset, string $resolvedname): string { - if ($resolvedname !== '') { - return $resolvedname; - } - - $title = (string)$asset->title; - $placeholder = 'Asset ' . (string)$asset->fastpix_id; - if ($title !== '' && $title !== $placeholder) { - return $title; - } - - return get_string('untitledvideo', 'tiny_fastpix'); - } - - /** - * Resolve a human display name for each asset, asset id => name. + * Resolve a mod_fastpix activity row to its local_fastpix asset id. * - * The author-facing name lives on mod_fastpix.name; the local_fastpix asset - * title is often the "Asset " fallback. We read the name here so the - * picker shows something a human recognises. Two read-only lookups, in - * order of reliability — no writes to mod_fastpix or local_fastpix: + * Prefers the direct link (fastpix_asset_id); falls back to the upload + * session that produced the asset. The session lookup goes through + * local_fastpix's sanctioned service method, never a direct table query. * - * 1. Direct link: the activity points at the asset - * (mod_fastpix.fastpix_asset_id = asset.id). - * 2. Upload-session fallback: used when the direct link was never set - * (fastpix_asset_id NULL). Recover the name through the upload session - * that produced the asset - * (asset.fastpix_id -> upload_session.id -> activity.upload_session_id). - * - * When several activities resolve to one asset, the most recent name wins. - * Assets with no activity on either path keep the "Asset " fallback. - * - * @param array $assets Asset records from asset_service::list_for_owner. - * @return array Asset id => display name (only non-empty names). + * @param \stdClass $activity A {fastpix} row (needs ->fastpix_asset_id, ->upload_session_id). + * @return int The local_fastpix_asset id, or 0 when the activity links to no asset. */ - private static function resolve_display_names(array $assets): array { - global $DB; - - if (empty($assets)) { - return []; + private static function resolve_asset_id(\stdClass $activity): int { + if (!empty($activity->fastpix_asset_id)) { + return (int)$activity->fastpix_asset_id; } - - // Seed every asset id as unresolved, and index fastpix_id -> asset id - // for the second-pass session lookup. - $names = []; - $assetidbyfpid = []; - foreach ($assets as $asset) { - $names[(int)$asset->id] = ''; - if (!empty($asset->fastpix_id)) { - $assetidbyfpid[(string)$asset->fastpix_id] = (int)$asset->id; - } - } - - // Pass 1 — direct activity link. Ascending id order means a later (more - // recent) activity overwrites an earlier one for a reused asset. - [$insql, $params] = $DB->get_in_or_equal(array_keys($names), SQL_PARAMS_NAMED); - $rows = $DB->get_records_select( - 'fastpix', - "fastpix_asset_id {$insql}", - $params, - 'id ASC', - 'id, fastpix_asset_id, name' - ); - foreach ($rows as $row) { - $name = trim((string)$row->name); - if ($name !== '') { - $names[(int)$row->fastpix_asset_id] = $name; + if (!empty($activity->upload_session_id)) { + $asset = \local_fastpix\service\asset_service::get_by_upload_session_id((int)$activity->upload_session_id); + if ($asset) { + return (int)$asset->id; } } - - // Pass 2 — upload-session fallback for assets the direct link missed. - $unresolved = array_filter($assetidbyfpid, static fn($aid) => $names[$aid] === ''); - if (!empty($unresolved)) { - [$insql2, $params2] = $DB->get_in_or_equal(array_keys($unresolved), SQL_PARAMS_NAMED); - $sessrows = $DB->get_records_sql( - "SELECT m.id, us.fastpix_id, m.name - FROM {local_fastpix_upload_session} us - JOIN {fastpix} m ON m.upload_session_id = us.id - WHERE us.fastpix_id {$insql2} - ORDER BY m.id ASC", - $params2 - ); - foreach ($sessrows as $row) { - $name = trim((string)$row->name); - $aid = $unresolved[(string)$row->fastpix_id] ?? null; - if ($name !== '' && $aid !== null) { - $names[$aid] = $name; - } - } - } - - return array_filter($names, static fn($name) => $name !== ''); + return 0; } /** - * Search the user's whole library for ready assets matching a term. - * - * Real search (not just the loaded recent list). Three owner-safe sources - * are merged, deduplicated by asset id and capped, most-recent first: - * 1. Asset title — via local_fastpix's own search - * (asset_service::list_for_owner_paginated), already owner-scoped. - * 2. Activity name, direct link — mod_fastpix.name LIKE term, mapped to - * the asset through mod_fastpix.fastpix_asset_id. - * 3. Activity name, upload-session fallback — mod_fastpix.name LIKE term, - * mapped through mod_fastpix.upload_session_id -> upload_session.fastpix_id. - * - * The author-typed name lives on mod_fastpix and may have been typed by any - * teacher, but the matched assets are loaded with owner_userid = $userid in - * load_owned_ready_assets(), so search can never surface another user's - * videos. The embeddable filter (playback id, non-DRM) is applied by the - * caller's loop. Read-only — no writes to mod_fastpix or local_fastpix. - * - * @param int $userid The owner whose library is searched. - * @param string $query The (already trimmed, non-empty) search term. - * @param int $limit Maximum assets to return. - * @return array Asset records, most-recent first. - */ - private static function search_assets(int $userid, string $query, int $limit = 50): array { - global $DB; - - // 1. Title matches, straight from local_fastpix (owner-scoped, ready). - $assets = []; - $bytitle = \local_fastpix\service\asset_service::list_for_owner_paginated( - $userid, - 'ready', - 0, - $limit, - $query - ); - foreach ($bytitle as $asset) { - $assets[(int)$asset->id] = $asset; - } - - // 2 + 3. Activity-name matches -> candidate asset identifiers. Owner - // scoping is enforced when these are loaded, not here. - $like = '%' . $DB->sql_like_escape($query) . '%'; - $namelike = $DB->sql_like('m.name', ':q', false); - - $directids = $DB->get_fieldset_sql( - "SELECT DISTINCT m.fastpix_asset_id - FROM {fastpix} m - WHERE {$namelike} AND m.fastpix_asset_id IS NOT NULL AND m.fastpix_asset_id <> 0", - ['q' => $like] - ); - - $sessfpids = $DB->get_fieldset_sql( - "SELECT DISTINCT us.fastpix_id - FROM {fastpix} m - JOIN {local_fastpix_upload_session} us ON us.id = m.upload_session_id - WHERE {$namelike} AND us.fastpix_id IS NOT NULL", - ['q' => $like] - ); - - foreach (self::load_owned_ready_assets($userid, $directids, $sessfpids) as $asset) { - $assets[(int)$asset->id] = $asset; - } - - // Most-recent first, then cap. - $assets = array_values($assets); - usort($assets, static fn($a, $b) => ($b->timecreated <=> $a->timecreated)); - - return array_slice($assets, 0, $limit); - } - - /** - * Load owned, ready, live asset records by id and by fastpix_id. + * Pick the label shown in the picker for one asset. * - * This is the owner-scope gate for name search: candidate identifiers come - * from mod_fastpix (any author), but only assets owned by $userid are - * returned. Soft-deleted assets are excluded. + * Preference order: the activity name; then the asset's own title if it is a + * real one; then a localised "Untitled video" placeholder. The local_fastpix + * projector writes the literal "Asset " when an upload arrives + * without a title, so we treat exactly that value as "no title" rather than + * show a raw UUID to authors. * - * @param int $userid The owner. - * @param array $ids Candidate local_fastpix_asset ids (direct link). - * @param array $fpids Candidate fastpix_id values (upload-session fallback). - * @return array Asset id => asset record. + * @param \stdClass $asset The asset record (needs ->title and ->fastpix_id). + * @param string $activityname The activity name, or '' when unavailable. + * @return string The label to display. */ - private static function load_owned_ready_assets(int $userid, array $ids, array $fpids): array { - global $DB; - - $out = []; - - if (!empty($ids)) { - [$insql, $params] = $DB->get_in_or_equal($ids, SQL_PARAMS_NAMED, 'aid'); - $params['userid'] = $userid; - $params['st'] = 'ready'; - $rows = $DB->get_records_select( - 'local_fastpix_asset', - "id {$insql} AND owner_userid = :userid AND status = :st " - . "AND (deleted_at IS NULL OR deleted_at = 0)", - $params - ); - foreach ($rows as $row) { - $out[(int)$row->id] = $row; - } + private static function display_title(\stdClass $asset, string $activityname): string { + if ($activityname !== '') { + return $activityname; } - if (!empty($fpids)) { - [$insql, $params] = $DB->get_in_or_equal($fpids, SQL_PARAMS_NAMED, 'fp'); - $params['userid'] = $userid; - $params['st'] = 'ready'; - $rows = $DB->get_records_select( - 'local_fastpix_asset', - "fastpix_id {$insql} AND owner_userid = :userid AND status = :st " - . "AND (deleted_at IS NULL OR deleted_at = 0)", - $params - ); - foreach ($rows as $row) { - $out[(int)$row->id] = $row; - } + $title = (string)$asset->title; + $placeholder = 'Asset ' . (string)$asset->fastpix_id; + if ($title !== '' && $title !== $placeholder) { + return $title; } - return $out; + return get_string('untitledvideo', 'tiny_fastpix'); } } diff --git a/classes/plugininfo.php b/classes/plugininfo.php index 503ffd9..b72c89d 100644 --- a/classes/plugininfo.php +++ b/classes/plugininfo.php @@ -100,9 +100,12 @@ public static function get_plugin_configuration_for_context( array $fpoptions, ?\editor_tiny\editor $editor = null ): array { + $coursecontext = $context->get_course_context(false); + return [ 'contextid' => $context->id, - 'canupload' => has_capability('mod/fastpix:uploadmedia', $context), + 'courseid' => $coursecontext ? $coursecontext->instanceid : 0, + 'canembed' => has_capability('mod/fastpix:uploadmedia', $context), ]; } } diff --git a/lang/en/tiny_fastpix.php b/lang/en/tiny_fastpix.php index 39a13c7..d044bf1 100644 --- a/lang/en/tiny_fastpix.php +++ b/lang/en/tiny_fastpix.php @@ -28,9 +28,7 @@ $string['loaderror'] = 'Could not load your videos. Please try again.'; $string['loading'] = 'Loading your videos…'; $string['modaltitle'] = 'Insert FastPix video'; -$string['noresults'] = 'No videos match your search.'; -$string['novideos'] = 'You have no ready videos to embed yet. Upload a video in a FastPix activity first.'; +$string['novideos'] = 'This course has no ready videos you can embed yet. Upload a video in a FastPix activity in this course first.'; $string['pluginname'] = 'FastPix video'; $string['privacy:metadata'] = 'The FastPix video plugin for TinyMCE does not store any personal data. It lists the videos you already own and inserts a shortcode.'; -$string['searchplaceholder'] = 'Search your videos…'; $string['untitledvideo'] = 'Untitled video'; diff --git a/templates/modal.mustache b/templates/modal.mustache index 00d3f1c..bcb6017 100644 --- a/templates/modal.mustache +++ b/templates/modal.mustache @@ -35,14 +35,6 @@ {{/title}} {{$body}} -
- -
{{#str}} loading, tiny_fastpix {{/str}}
diff --git a/templates/videos.mustache b/templates/videos.mustache index 9097f00..7944c26 100644 --- a/templates/videos.mustache +++ b/templates/videos.mustache @@ -24,13 +24,10 @@ Context variables required for this template: * hasvideos - bool, whether the videos array is non-empty. * videos - array of { playbackid, title }. - * issearch - bool, whether this list is the result of a search (changes the - empty-state message from "no videos" to "no results"). Example context (json): { "hasvideos": true, - "issearch": false, "videos": [ { "playbackid": "abc123", "title": "Welcome video" } ] @@ -51,7 +48,6 @@ {{/hasvideos}} {{^hasvideos}}
- {{#issearch}}{{#str}} noresults, tiny_fastpix {{/str}}{{/issearch}} - {{^issearch}}{{#str}} novideos, tiny_fastpix {{/str}}{{/issearch}} + {{#str}} novideos, tiny_fastpix {{/str}}
{{/hasvideos}} diff --git a/tests/behat/insert_shortcode.feature b/tests/behat/insert_shortcode.feature index e4f4305..0754a29 100644 --- a/tests/behat/insert_shortcode.feature +++ b/tests/behat/insert_shortcode.feature @@ -58,8 +58,8 @@ Feature: Insert a FastPix shortcode via the TinyMCE picker | activity | name | intro | introformat | course | content | contentformat | idnumber | | page | PageName1 | PageDesc1 | 1 | C1 | PageContent | 1 | 1 | And the following "tiny_fastpix > assets" exist: - | user | playback_id | title | - | teacher1 | keepme01 | My lecture | + | user | course | playback_id | title | + | teacher1 | C1 | keepme01 | My lecture | # --------------------------------------------------------------------------- # Capability gate — an uploading teacher sees the button; a teacher whose diff --git a/tests/external/get_my_videos_test.php b/tests/external/get_my_videos_test.php index 260ccf2..be94f21 100644 --- a/tests/external/get_my_videos_test.php +++ b/tests/external/get_my_videos_test.php @@ -24,45 +24,25 @@ /** * Tests for the tiny_fastpix_get_my_videos web service. * + * The picker lists ONLY videos that (a) the caller owns, (b) belong to the + * editor's course (referenced by a mod_fastpix activity in it), (c) are ready + * and (d) are embeddable (public + has a playback id). These tests lock each + * facet, with the owner-scope and course-scope cases as the P0 security floor. + * * @package tiny_fastpix * @copyright 2026 FastPix Inc. * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @covers \tiny_fastpix\external\get_my_videos */ final class get_my_videos_test extends \externallib_advanced_testcase { - /** - * Insert an asset row into local_fastpix_asset. - * - * @param array $overrides Column overrides; owner_userid + playback_id matter. - * @return void - */ - private function make_asset(array $overrides): void { - global $DB; - $now = time(); - $DB->insert_record('local_fastpix_asset', (object)array_merge([ - 'fastpix_id' => 'fp' . random_string(10), - 'playback_id' => 'play' . random_string(8), - 'owner_userid' => 0, - 'title' => 'Untitled', - 'status' => 'ready', - 'access_policy' => 'public', - 'drm_required' => 0, - 'no_skip_required' => 0, - 'has_captions' => 0, - 'gdpr_delete_attempts' => 0, - 'timecreated' => $now, - 'timemodified' => $now, - ], $overrides)); - } - /** * Insert an asset and return it, including the generated id and fastpix_id - * (needed to link mod_fastpix activities for the name-search tests). + * (needed to link mod_fastpix activities). * - * @param array $overrides Column overrides. + * @param array $overrides Column overrides; owner_userid + playback_id matter. * @return \stdClass The inserted asset (with ->id and ->fastpix_id set). */ - private function make_asset_linked(array $overrides): \stdClass { + private function make_asset(array $overrides): \stdClass { global $DB; $now = time(); $record = (object)array_merge([ @@ -84,8 +64,8 @@ private function make_asset_linked(array $overrides): \stdClass { } /** - * Insert a mod_fastpix activity row carrying the author-typed name. Only the - * fields the name search reads are populated. + * Insert a mod_fastpix activity row carrying the author-typed name and the + * link columns the resolver reads. * * @param int $courseid The course the activity lives in. * @param string $name The author-typed activity name. @@ -146,156 +126,159 @@ private function teacher_in_course(): array { } /** - * Run the web service with a search term and clean the return value. + * Run the web service and clean the return value. * * @param \context $context The editor context. - * @param string $query The search term. * @return array The cleaned result. */ - private function run_search(\context $context, string $query): array { - $result = get_my_videos::execute($context->id, $query); + private function list_videos(\context $context): array { + $result = get_my_videos::execute($context->id); return \core_external\external_api::clean_returnvalue(get_my_videos::execute_returns(), $result); } /** - * The service returns only the caller's own ready, non-DRM, embeddable videos. + * The happy path: an owned, ready, public video linked to an activity in the + * editor's course is listed, labelled with the activity name. */ - public function test_returns_only_own_embeddable_videos(): void { + public function test_lists_owned_ready_public_video_in_course(): void { $this->resetAfterTest(); - $course = $this->getDataGenerator()->create_course(); - $context = \context_course::instance($course->id); - $teacher = $this->getDataGenerator()->create_user(); - $other = $this->getDataGenerator()->create_user(); - $this->getDataGenerator()->enrol_user($teacher->id, $course->id, 'editingteacher'); + [$context, $teacher, $course] = $this->teacher_in_course(); $this->setUser($teacher); - // Should appear: own, ready, public, has playback id. - $this->make_asset(['owner_userid' => $teacher->id, 'playback_id' => 'keepme01', 'title' => 'Keep me']); - // Excluded: DRM-required (filter can't play it). - $this->make_asset([ - 'owner_userid' => $teacher->id, 'playback_id' => 'drmone01', - 'drm_required' => 1, 'access_policy' => 'drm', - ]); - // Excluded: not ready yet. - $this->make_asset(['owner_userid' => $teacher->id, 'playback_id' => 'prep0001', 'status' => 'preparing']); - // Excluded: no playback id. - $this->make_asset(['owner_userid' => $teacher->id, 'playback_id' => null]); - // Excluded: belongs to another user. - $this->make_asset(['owner_userid' => $other->id, 'playback_id' => 'other001']); + $asset = $this->make_asset(['owner_userid' => $teacher->id, 'playback_id' => 'keepme01', 'title' => 'zzz']); + $this->make_activity($course->id, 'Photosynthesis lecture', ['fastpix_asset_id' => $asset->id]); - $result = get_my_videos::execute($context->id); - $result = \core_external\external_api::clean_returnvalue(get_my_videos::execute_returns(), $result); + $result = $this->list_videos($context); $this->assertCount(1, $result['videos']); $this->assertSame('keepme01', $result['videos'][0]['playbackid']); - $this->assertSame('Keep me', $result['videos'][0]['title']); + $this->assertSame('Photosynthesis lecture', $result['videos'][0]['title']); } /** - * A user without mod/fastpix:uploadmedia is refused. + * The activity may link to the asset only through its upload session (the + * direct fastpix_asset_id was never set); the video must still be listed. */ - public function test_requires_uploadmedia_capability(): void { + public function test_resolves_asset_via_upload_session(): void { $this->resetAfterTest(); - $course = $this->getDataGenerator()->create_course(); - $context = \context_course::instance($course->id); - $student = $this->getDataGenerator()->create_user(); - $this->getDataGenerator()->enrol_user($student->id, $course->id, 'student'); - $this->setUser($student); + [$context, $teacher, $course] = $this->teacher_in_course(); + $this->setUser($teacher); - $this->expectException(\required_capability_exception::class); - get_my_videos::execute($context->id); + $asset = $this->make_asset(['owner_userid' => $teacher->id, 'playback_id' => 'sess0001', 'title' => 'zzz']); + $sessionid = $this->make_upload_session($asset->fastpix_id, $teacher->id); + $this->make_activity($course->id, 'Mitosis recap', ['upload_session_id' => $sessionid]); + + $result = $this->list_videos($context); + + $this->assertCount(1, $result['videos']); + $this->assertSame('sess0001', $result['videos'][0]['playbackid']); + $this->assertSame('Mitosis recap', $result['videos'][0]['title']); } /** - * Search matches the asset title. + * Owner scope (P0): a video owned by another user, even when its activity is + * in this course, must never appear. */ - public function test_search_matches_asset_title(): void { + public function test_excludes_video_owned_by_other_user(): void { $this->resetAfterTest(); - [$context, $teacher] = $this->teacher_in_course(); + [$context, $teacher, $course] = $this->teacher_in_course(); + $other = $this->getDataGenerator()->create_user(); $this->setUser($teacher); - $this->make_asset_linked(['owner_userid' => $teacher->id, 'playback_id' => 'titlem01', 'title' => 'Annual report 2026']); - $this->make_asset_linked(['owner_userid' => $teacher->id, 'playback_id' => 'titlem02', 'title' => 'Holiday party']); + $otherasset = $this->make_asset(['owner_userid' => $other->id, 'playback_id' => 'leak0001', 'title' => 'zzz']); + $this->make_activity($course->id, 'Confidential briefing', ['fastpix_asset_id' => $otherasset->id]); - $result = $this->run_search($context, 'annual'); + $result = $this->list_videos($context); - $this->assertCount(1, $result['videos']); - $this->assertSame('titlem01', $result['videos'][0]['playbackid']); + $this->assertCount(0, $result['videos']); } /** - * Search matches the mod_fastpix activity name via the direct asset link, - * even when the asset title itself does not contain the term. + * Course scope (P0): the caller's own video linked to an activity in a + * different course must not appear in this course's picker. */ - public function test_search_matches_activity_name_direct(): void { + public function test_excludes_video_in_other_course(): void { $this->resetAfterTest(); - [$context, $teacher, $course] = $this->teacher_in_course(); + [$context, $teacher] = $this->teacher_in_course(); + $othercourse = $this->getDataGenerator()->create_course(); $this->setUser($teacher); - $asset = $this->make_asset_linked([ - 'owner_userid' => $teacher->id, 'playback_id' => 'namedir1', 'title' => 'zzz', - ]); - $this->make_activity($course->id, 'Photosynthesis lecture', ['fastpix_asset_id' => $asset->id]); + $asset = $this->make_asset(['owner_userid' => $teacher->id, 'playback_id' => 'elsew001', 'title' => 'zzz']); + $this->make_activity($othercourse->id, 'Lecture in another course', ['fastpix_asset_id' => $asset->id]); - $result = $this->run_search($context, 'photosynthesis'); + $result = $this->list_videos($context); - $this->assertCount(1, $result['videos']); - $this->assertSame('namedir1', $result['videos'][0]['playbackid']); - $this->assertSame('Photosynthesis lecture', $result['videos'][0]['title']); + $this->assertCount(0, $result['videos']); } /** - * Search matches the activity name reached only through the upload session - * (the direct fastpix_asset_id link was never set). + * An owned, ready, public video that is not referenced by any activity in + * the course is out of scope (course membership comes from the activity). */ - public function test_search_matches_activity_name_via_upload_session(): void { + public function test_excludes_video_with_no_activity(): void { $this->resetAfterTest(); - [$context, $teacher, $course] = $this->teacher_in_course(); + [$context, $teacher] = $this->teacher_in_course(); $this->setUser($teacher); - $asset = $this->make_asset_linked([ - 'owner_userid' => $teacher->id, 'playback_id' => 'namesess', 'title' => 'zzz', - ]); - $sessionid = $this->make_upload_session($asset->fastpix_id, $teacher->id); - $this->make_activity($course->id, 'Mitosis recap', ['upload_session_id' => $sessionid]); + $this->make_asset(['owner_userid' => $teacher->id, 'playback_id' => 'noact001', 'title' => 'Orphan']); - $result = $this->run_search($context, 'mitosis'); + $result = $this->list_videos($context); - $this->assertCount(1, $result['videos']); - $this->assertSame('namesess', $result['videos'][0]['playbackid']); - $this->assertSame('Mitosis recap', $result['videos'][0]['title']); + $this->assertCount(0, $result['videos']); } /** - * Search is owner-scoped: another user's asset must never surface, even when - * its activity name matches the search term. (P0 security invariant.) + * Non-ready, DRM/non-public, and playback-less assets are all excluded even + * when correctly owned and linked in the course. */ - public function test_search_is_owner_scoped(): void { + public function test_excludes_unembeddable_states(): void { $this->resetAfterTest(); [$context, $teacher, $course] = $this->teacher_in_course(); - $other = $this->getDataGenerator()->create_user(); $this->setUser($teacher); - $otherasset = $this->make_asset_linked([ - 'owner_userid' => $other->id, 'playback_id' => 'leak0001', 'title' => 'zzz', + $notready = $this->make_asset([ + 'owner_userid' => $teacher->id, 'playback_id' => 'prep0001', 'status' => 'preparing', ]); - $this->make_activity($course->id, 'Confidential briefing', ['fastpix_asset_id' => $otherasset->id]); + $drm = $this->make_asset([ + 'owner_userid' => $teacher->id, 'playback_id' => 'drmone01', + 'drm_required' => 1, 'access_policy' => 'drm', + ]); + $noplayback = $this->make_asset(['owner_userid' => $teacher->id, 'playback_id' => null]); + + $this->make_activity($course->id, 'Not ready', ['fastpix_asset_id' => $notready->id]); + $this->make_activity($course->id, 'DRM', ['fastpix_asset_id' => $drm->id]); + $this->make_activity($course->id, 'No playback', ['fastpix_asset_id' => $noplayback->id]); - $result = $this->run_search($context, 'confidential'); + $result = $this->list_videos($context); $this->assertCount(0, $result['videos']); } /** - * A search term that matches nothing returns an empty list. + * A user without mod/fastpix:uploadmedia in the course is refused. */ - public function test_search_no_match_returns_empty(): void { + public function test_requires_uploadmedia_capability(): void { $this->resetAfterTest(); - [$context, $teacher] = $this->teacher_in_course(); - $this->setUser($teacher); - $this->make_asset_linked(['owner_userid' => $teacher->id, 'playback_id' => 'present1', 'title' => 'Welcome video']); + $course = $this->getDataGenerator()->create_course(); + $context = \context_course::instance($course->id); + $student = $this->getDataGenerator()->create_user(); + $this->getDataGenerator()->enrol_user($student->id, $course->id, 'student'); + $this->setUser($student); + + $this->expectException(\required_capability_exception::class); + get_my_videos::execute($context->id); + } + + /** + * Outside a course (e.g. a user context) there is nothing in scope, so the + * service returns an empty list rather than leaking anything. + */ + public function test_non_course_context_returns_empty(): void { + $this->resetAfterTest(); + $user = $this->getDataGenerator()->create_user(); + $this->setUser($user); - $result = $this->run_search($context, 'nonexistentterm'); + $result = $this->list_videos(\context_user::instance($user->id)); $this->assertCount(0, $result['videos']); } diff --git a/tests/generator/behat_tiny_fastpix_generator.php b/tests/generator/behat_tiny_fastpix_generator.php index ce1be59..386588a 100644 --- a/tests/generator/behat_tiny_fastpix_generator.php +++ b/tests/generator/behat_tiny_fastpix_generator.php @@ -74,11 +74,12 @@ protected function get_creatable_entities(): array { 'singular' => 'asset', 'datagenerator' => 'asset', 'required' => ['playback_id', 'user'], - // The 'user' column (a username string) is resolved to - // 'owner_userid' (an integer) by behat_generator_base::get_user_id(). - // See lib/behat/classes/behat_generator_base.php, the switchids - // processing loop and the get_user_id() method. - 'switchids' => ['user' => 'owner_userid'], + // 'user' (a username) resolves to 'owner_userid'; the optional + // 'course' (a shortname) resolves to 'courseid', which makes the + // generator link the asset to a mod_fastpix activity in that + // course so the course-scoped picker lists it. Resolution is the + // switchids mechanism in behat_generator_base. + 'switchids' => ['user' => 'owner_userid', 'course' => 'courseid'], ], ]; } diff --git a/tests/generator/lib.php b/tests/generator/lib.php index 218bb6e..684b125 100644 --- a/tests/generator/lib.php +++ b/tests/generator/lib.php @@ -85,6 +85,14 @@ public function create_asset($record = null): int { ); } + // The picker is course-scoped: an asset only appears when a mod_fastpix + // activity in that course references it. The optional 'course' column + // (resolved to 'courseid' via switchids) creates that link. Pulled out + // before the asset insert — it is not a local_fastpix_asset column. + $courseid = (int)($record['courseid'] ?? 0); + $title = $record['title'] ?? null; + unset($record['courseid']); + $now = time(); // Apply defaults for every NOT-NULL column that has no schema default @@ -108,6 +116,44 @@ public function create_asset($record = null): int { } } - return $DB->insert_record('local_fastpix_asset', (object) $record); + $assetid = $DB->insert_record('local_fastpix_asset', (object) $record); + + if ($courseid > 0) { + $this->link_course_activity($courseid, $assetid, (string)($title ?? $record['title'])); + } + + return $assetid; + } + + /** + * Insert a minimal mod_fastpix activity row in a course that references the + * asset, so the course-scoped picker (get_my_videos) finds it. + * + * Only the columns the picker reads ({fastpix}.course, .name, + * .fastpix_asset_id) plus the table's NOT-NULL fields are populated — the + * web service queries this table directly and does not need a course module. + * + * @param int $courseid The course the activity (and so the video) belongs to. + * @param int $assetid The local_fastpix_asset id to reference. + * @param string $name The activity name used as the picker label. + * @return void + */ + private function link_course_activity(int $courseid, int $assetid, string $name): void { + global $DB; + $now = time(); + $DB->insert_record('fastpix', (object)[ + 'course' => $courseid, + 'name' => $name, + 'intro' => '', + 'introformat' => FORMAT_HTML, + 'fastpix_asset_id' => $assetid, + 'upload_session_id' => null, + 'completion_watch_percent' => 0, + 'no_skip_required' => 0, + 'default_show_captions' => 0, + 'grademax' => 0, + 'timecreated' => $now, + 'timemodified' => $now, + ]); } } diff --git a/tests/plugininfo_test.php b/tests/plugininfo_test.php index a25e4d3..85164c4 100644 --- a/tests/plugininfo_test.php +++ b/tests/plugininfo_test.php @@ -43,7 +43,7 @@ public function test_button_hidden_without_capability(): void { $this->assertFalse(plugininfo::is_enabled($context, [], [])); $this->assertFalse( - plugininfo::get_plugin_configuration_for_context($context, [], [])['canupload'] + plugininfo::get_plugin_configuration_for_context($context, [], [])['canembed'] ); } @@ -60,7 +60,7 @@ public function test_button_visible_with_capability(): void { $this->assertTrue(plugininfo::is_enabled($context, [], [])); $this->assertTrue( - plugininfo::get_plugin_configuration_for_context($context, [], [])['canupload'] + plugininfo::get_plugin_configuration_for_context($context, [], [])['canembed'] ); } } diff --git a/version.php b/version.php index 5c19111..4da2423 100644 --- a/version.php +++ b/version.php @@ -25,10 +25,10 @@ defined('MOODLE_INTERNAL') || die(); $plugin->component = 'tiny_fastpix'; -$plugin->version = 2026060500; +$plugin->version = 2026061010; $plugin->requires = 2024100100; // Moodle 4.5 LTS. $plugin->maturity = MATURITY_STABLE; -$plugin->release = '1.0.0'; +$plugin->release = '1.1.0'; $plugin->dependencies = [ // Reuses mod/fastpix:uploadmedia (capability) and lists the author's assets // through local_fastpix's asset_service. Inserts the {fastpix:pb_} From af846e2a3b5c9b310d9dd9118935cf62bcfc6beb Mon Sep 17 00:00:00 2001 From: Tharun Budidha Date: Wed, 10 Jun 2026 16:41:07 +0530 Subject: [PATCH 2/3] Fix course-video-picker CI: capitalize inline comment, rebuild AMD --- tests/generator/behat_tiny_fastpix_generator.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/generator/behat_tiny_fastpix_generator.php b/tests/generator/behat_tiny_fastpix_generator.php index 386588a..0eb2bd7 100644 --- a/tests/generator/behat_tiny_fastpix_generator.php +++ b/tests/generator/behat_tiny_fastpix_generator.php @@ -74,11 +74,11 @@ protected function get_creatable_entities(): array { 'singular' => 'asset', 'datagenerator' => 'asset', 'required' => ['playback_id', 'user'], - // 'user' (a username) resolves to 'owner_userid'; the optional - // 'course' (a shortname) resolves to 'courseid', which makes the - // generator link the asset to a mod_fastpix activity in that - // course so the course-scoped picker lists it. Resolution is the - // switchids mechanism in behat_generator_base. + // The 'user' column (a username) resolves to 'owner_userid'; the + // optional 'course' column (a shortname) resolves to 'courseid', + // which makes the generator link the asset to a mod_fastpix + // activity in that course so the course-scoped picker lists it. + // Resolution is the switchids mechanism in behat_generator_base. 'switchids' => ['user' => 'owner_userid', 'course' => 'courseid'], ], ]; From 801afc4ad227c550e03bbf04158e19fec8cf0990 Mon Sep 17 00:00:00 2001 From: Tharun Budidha Date: Wed, 10 Jun 2026 17:25:11 +0530 Subject: [PATCH 3/3] Rebuild AMD with pinned toolchain; cover get_my_videos branches --- amd/build/commands.min.js | 2 +- amd/build/commands.min.js.map | 2 +- amd/build/common.min.js | 13 +---- amd/build/common.min.js.map | 2 +- amd/build/configuration.min.js | 2 +- amd/build/configuration.min.js.map | 2 +- amd/build/modal.min.js | 9 +-- amd/build/modal.min.js.map | 2 +- amd/build/options.min.js | 4 +- amd/build/options.min.js.map | 2 +- amd/build/plugin.min.js | 4 +- amd/build/plugin.min.js.map | 2 +- amd/build/ui.min.js | 4 +- amd/build/ui.min.js.map | 2 +- tests/external/get_my_videos_test.php | 79 +++++++++++++++++++++++++++ 15 files changed, 96 insertions(+), 35 deletions(-) diff --git a/amd/build/commands.min.js b/amd/build/commands.min.js index e9fa341..2478322 100644 --- a/amd/build/commands.min.js +++ b/amd/build/commands.min.js @@ -1,3 +1,3 @@ -define("tiny_fastpix/commands",["exports","editor_tiny/utils","core/str","./common","./options","./ui"],function(_exports,_utils,_str,_common,_options,_ui){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.getSetup=void 0;_exports.getSetup=async()=>{const[buttonText,buttonImage]=await Promise.all([(0,_str.getString)("buttontitle",_common.component),(0,_utils.getButtonImage)("icon",_common.component)]);return editor=>{(0,_options.canEmbed)(editor)&&(editor.ui.registry.addIcon(_common.icon,buttonImage.html),editor.ui.registry.addButton(_common.buttonName,{icon:_common.icon,tooltip:buttonText,onAction:()=>(0,_ui.handleAction)(editor)}),editor.ui.registry.addMenuItem(_common.buttonName,{icon:_common.icon,text:buttonText,onAction:()=>(0,_ui.handleAction)(editor)}))}}}); +define("tiny_fastpix/commands",["exports","editor_tiny/utils","core/str","./common","./options","./ui"],(function(_exports,_utils,_str,_common,_options,_ui){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.getSetup=void 0;_exports.getSetup=async()=>{const[buttonText,buttonImage]=await Promise.all([(0,_str.getString)("buttontitle",_common.component),(0,_utils.getButtonImage)("icon",_common.component)]);return editor=>{(0,_options.canEmbed)(editor)&&(editor.ui.registry.addIcon(_common.icon,buttonImage.html),editor.ui.registry.addButton(_common.buttonName,{icon:_common.icon,tooltip:buttonText,onAction:()=>(0,_ui.handleAction)(editor)}),editor.ui.registry.addMenuItem(_common.buttonName,{icon:_common.icon,text:buttonText,onAction:()=>(0,_ui.handleAction)(editor)}))}}})); //# sourceMappingURL=commands.min.js.map \ No newline at end of file diff --git a/amd/build/commands.min.js.map b/amd/build/commands.min.js.map index d87ec98..fdcc600 100644 --- a/amd/build/commands.min.js.map +++ b/amd/build/commands.min.js.map @@ -1 +1 @@ -{"version":3,"file":"commands.min.js","sources":["../src/commands.js"],"sourcesContent":["// This file is part of Moodle - https://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Tiny FastPix commands — registers the toolbar button and menu item.\n *\n * @module tiny_fastpix/commands\n * @copyright 2026 FastPix Inc. \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {getButtonImage} from 'editor_tiny/utils';\nimport {getString} from 'core/str';\nimport {component, buttonName, icon} from './common';\nimport {canEmbed} from './options';\nimport {handleAction} from './ui';\n\nexport const getSetup = async() => {\n const [\n buttonText,\n buttonImage,\n ] = await Promise.all([\n getString('buttontitle', component),\n getButtonImage('icon', component),\n ]);\n\n return (editor) => {\n if (!canEmbed(editor)) {\n return;\n }\n\n // Register the FastPix icon.\n editor.ui.registry.addIcon(icon, buttonImage.html);\n\n // Toolbar button.\n editor.ui.registry.addButton(buttonName, {\n icon,\n tooltip: buttonText,\n onAction: () => handleAction(editor),\n });\n\n // Menu item (Insert menu / context menu).\n editor.ui.registry.addMenuItem(buttonName, {\n icon,\n text: buttonText,\n onAction: () => handleAction(editor),\n });\n };\n};\n"],"names":["_exports","getSetup","async","buttonText","buttonImage","Promise","all","getString","component","getButtonImage","editor","canEmbed","ui","registry","addIcon","icon","html","addButton","buttonName","tooltip","onAction","handleAction","addMenuItem","text"],"mappings":"6OA4DEA,SAAAC,SA/BsBC,UACpB,MACIC,WACAC,mBACMC,QAAQC,IAAI,EAClB,EAAAC,KAAAA,WAAU,cAAeC,QAAAA,YACzB,EAAAC,OAAAA,gBAAe,OAAQD,QAAAA,aAG3B,OAAQE,UACC,EAAAC,SAAAA,UAASD,UAKdA,OAAOE,GAAGC,SAASC,QAAQC,QAAAA,KAAMX,YAAYY,MAG7CN,OAAOE,GAAGC,SAASI,UAAUC,mBAAY,CACrCH,KAAAA,QAAIA,KACJI,QAAShB,WACTiB,SAAUA,KAAM,EAAAC,IAAAA,cAAaX,UAIjCA,OAAOE,GAAGC,SAASS,YAAYJ,mBAAY,CACvCH,KAAAA,QAAIA,KACJQ,KAAMpB,WACNiB,SAAUA,KAAM,EAAAC,IAAAA,cAAaX,YAGvC"} \ No newline at end of file +{"version":3,"file":"commands.min.js","sources":["../src/commands.js"],"sourcesContent":["// This file is part of Moodle - https://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Tiny FastPix commands — registers the toolbar button and menu item.\n *\n * @module tiny_fastpix/commands\n * @copyright 2026 FastPix Inc. \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {getButtonImage} from 'editor_tiny/utils';\nimport {getString} from 'core/str';\nimport {component, buttonName, icon} from './common';\nimport {canEmbed} from './options';\nimport {handleAction} from './ui';\n\nexport const getSetup = async() => {\n const [\n buttonText,\n buttonImage,\n ] = await Promise.all([\n getString('buttontitle', component),\n getButtonImage('icon', component),\n ]);\n\n return (editor) => {\n if (!canEmbed(editor)) {\n return;\n }\n\n // Register the FastPix icon.\n editor.ui.registry.addIcon(icon, buttonImage.html);\n\n // Toolbar button.\n editor.ui.registry.addButton(buttonName, {\n icon,\n tooltip: buttonText,\n onAction: () => handleAction(editor),\n });\n\n // Menu item (Insert menu / context menu).\n editor.ui.registry.addMenuItem(buttonName, {\n icon,\n text: buttonText,\n onAction: () => handleAction(editor),\n });\n };\n};\n"],"names":["async","buttonText","buttonImage","Promise","all","component","editor","ui","registry","addIcon","icon","html","addButton","buttonName","tooltip","onAction","addMenuItem","text"],"mappings":"gQA6BwBA,gBAEhBC,WACAC,mBACMC,QAAQC,IAAI,EAClB,kBAAU,cAAeC,oBACzB,yBAAe,OAAQA,4BAGnBC,UACC,qBAASA,UAKdA,OAAOC,GAAGC,SAASC,QAAQC,aAAMR,YAAYS,MAG7CL,OAAOC,GAAGC,SAASI,UAAUC,mBAAY,CACrCH,KAAAA,aACAI,QAASb,WACTc,SAAU,KAAM,oBAAaT,UAIjCA,OAAOC,GAAGC,SAASQ,YAAYH,mBAAY,CACvCH,KAAAA,aACAO,KAAMhB,WACNc,SAAU,KAAM,oBAAaT"} \ No newline at end of file diff --git a/amd/build/common.min.js b/amd/build/common.min.js index 7b2b3fe..83569ea 100644 --- a/amd/build/common.min.js +++ b/amd/build/common.min.js @@ -1,14 +1,3 @@ -define("tiny_fastpix/common",["exports"],function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.pluginName=_exports.icon=_exports.component=_exports.buttonName=void 0; -/** - * Tiny FastPix common values. - * - * Named exports (not a default object) so src and the hand-written build agree - * on the module's named bindings. - * - * @module tiny_fastpix/common - * @copyright 2026 FastPix Inc. - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -const component=_exports.component="tiny_fastpix";_exports.pluginName=`${component}/plugin`,_exports.buttonName=`${component}/insertvideo`,_exports.icon="tiny_fastpix"}); +define("tiny_fastpix/common",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.pluginName=_exports.icon=_exports.component=_exports.buttonName=void 0;_exports.component="tiny_fastpix";const pluginName="".concat("tiny_fastpix","/plugin");_exports.pluginName=pluginName;const buttonName="".concat("tiny_fastpix","/insertvideo");_exports.buttonName=buttonName;_exports.icon="tiny_fastpix"})); //# sourceMappingURL=common.min.js.map \ No newline at end of file diff --git a/amd/build/common.min.js.map b/amd/build/common.min.js.map index a0a0ea5..59ae245 100644 --- a/amd/build/common.min.js.map +++ b/amd/build/common.min.js.map @@ -1 +1 @@ -{"version":3,"file":"common.min.js","sources":["../src/common.js"],"sourcesContent":["// This file is part of Moodle - https://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Tiny FastPix common values.\n *\n * Named exports (not a default object) so src and the hand-written build agree\n * on the module's named bindings.\n *\n * @module tiny_fastpix/common\n * @copyright 2026 FastPix Inc. \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nexport const component = 'tiny_fastpix';\nexport const pluginName = `${component}/plugin`;\nexport const buttonName = `${component}/insertvideo`;\nexport const icon = 'tiny_fastpix';\n"],"names":["component","_exports","pluginName","buttonName","icon"],"mappings":";;;;;;;;;;;AA0BO,MAAMA,UAASC,SAAAD,UAAG,eACFC,SAAAC,WAAG,GAAGF,mBACNC,SAAAE,WAAG,GAAGH,wBACZC,SAAAG,KAAG,cAAe"} \ No newline at end of file +{"version":3,"file":"common.min.js","sources":["../src/common.js"],"sourcesContent":["// This file is part of Moodle - https://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Tiny FastPix common values.\n *\n * Named exports (not a default object) so src and the hand-written build agree\n * on the module's named bindings.\n *\n * @module tiny_fastpix/common\n * @copyright 2026 FastPix Inc. \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nexport const component = 'tiny_fastpix';\nexport const pluginName = `${component}/plugin`;\nexport const buttonName = `${component}/insertvideo`;\nexport const icon = 'tiny_fastpix';\n"],"names":["pluginName","buttonName"],"mappings":"wNA0ByB,qBACZA,qBADY,+DAEZC,qBAFY,4EAGL"} \ No newline at end of file diff --git a/amd/build/configuration.min.js b/amd/build/configuration.min.js index 5ccf7c8..ac4e776 100644 --- a/amd/build/configuration.min.js +++ b/amd/build/configuration.min.js @@ -1,3 +1,3 @@ -define("tiny_fastpix/configuration",["exports","./common","editor_tiny/utils"],function(_exports,_common,_utils){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.configure=void 0;_exports.configure=instanceConfig=>({toolbar:(0,_utils.addToolbarButton)(instanceConfig.toolbar,"content",_common.buttonName),menu:(0,_utils.addMenubarItem)(instanceConfig.menu,"insert",_common.buttonName)})}); +define("tiny_fastpix/configuration",["exports","./common","editor_tiny/utils"],(function(_exports,_common,_utils){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.configure=void 0;_exports.configure=instanceConfig=>({toolbar:(0,_utils.addToolbarButton)(instanceConfig.toolbar,"content",_common.buttonName),menu:(0,_utils.addMenubarItem)(instanceConfig.menu,"insert",_common.buttonName)})})); //# sourceMappingURL=configuration.min.js.map \ No newline at end of file diff --git a/amd/build/configuration.min.js.map b/amd/build/configuration.min.js.map index d2a13df..2936f22 100644 --- a/amd/build/configuration.min.js.map +++ b/amd/build/configuration.min.js.map @@ -1 +1 @@ -{"version":3,"file":"configuration.min.js","sources":["../src/configuration.js"],"sourcesContent":["// This file is part of Moodle - https://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Tiny FastPix configuration — adds the button to the toolbar and menu.\n *\n * @module tiny_fastpix/configuration\n * @copyright 2026 FastPix Inc. \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {buttonName} from './common';\nimport {\n addMenubarItem,\n addToolbarButton,\n} from 'editor_tiny/utils';\n\nexport const configure = (instanceConfig) => {\n return {\n toolbar: addToolbarButton(instanceConfig.toolbar, 'content', buttonName),\n menu: addMenubarItem(instanceConfig.menu, 'insert', buttonName),\n };\n};\n"],"names":["_exports","configure","instanceConfig","toolbar","addToolbarButton","buttonName","menu","addMenubarItem"],"mappings":"mMAkCEA,SAAAC,UALwBC,iBACf,CACHC,SAAS,EAAAC,OAAAA,kBAAiBF,eAAeC,QAAS,UAAWE,QAAAA,YAC7DC,MAAM,EAAAC,OAAAA,gBAAeL,eAAeI,KAAM,SAAUD,QAAUA,aAEpE"} \ No newline at end of file +{"version":3,"file":"configuration.min.js","sources":["../src/configuration.js"],"sourcesContent":["// This file is part of Moodle - https://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Tiny FastPix configuration — adds the button to the toolbar and menu.\n *\n * @module tiny_fastpix/configuration\n * @copyright 2026 FastPix Inc. \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {buttonName} from './common';\nimport {\n addMenubarItem,\n addToolbarButton,\n} from 'editor_tiny/utils';\n\nexport const configure = (instanceConfig) => {\n return {\n toolbar: addToolbarButton(instanceConfig.toolbar, 'content', buttonName),\n menu: addMenubarItem(instanceConfig.menu, 'insert', buttonName),\n };\n};\n"],"names":["instanceConfig","toolbar","buttonName","menu"],"mappings":"uNA6B0BA,iBACf,CACHC,SAAS,2BAAiBD,eAAeC,QAAS,UAAWC,oBAC7DC,MAAM,yBAAeH,eAAeG,KAAM,SAAUD"} \ No newline at end of file diff --git a/amd/build/modal.min.js b/amd/build/modal.min.js index 7617ec4..256cf21 100644 --- a/amd/build/modal.min.js +++ b/amd/build/modal.min.js @@ -1,10 +1,3 @@ -define("tiny_fastpix/modal",["exports","core/modal"],function(_exports,_modal){var e; -/** - * Video-picker modal for Tiny FastPix. - * - * @module tiny_fastpix/modal - * @copyright 2026 FastPix Inc. - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_modal=(e=_modal)&&e.__esModule?e:{default:e};class FastpixModal extends _modal.default{static TYPE="tiny_fastpix/modal";static TEMPLATE="tiny_fastpix/modal";registerEventListeners(){super.registerEventListeners(),this.registerCloseOnCancel()}configure(modalConfig){modalConfig.large=!0,modalConfig.show=!0,modalConfig.removeOnClose=!0,super.configure(modalConfig)}}return _exports.default=FastpixModal,FastpixModal.registerModalType(),_exports.default}); +define("tiny_fastpix/modal",["exports","core/modal"],(function(_exports,_modal){var obj;function _defineProperty(obj,key,value){return key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,obj}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_modal=(obj=_modal)&&obj.__esModule?obj:{default:obj};class FastpixModal extends _modal.default{registerEventListeners(){super.registerEventListeners(),this.registerCloseOnCancel()}configure(modalConfig){modalConfig.large=!0,modalConfig.show=!0,modalConfig.removeOnClose=!0,super.configure(modalConfig)}}return _exports.default=FastpixModal,_defineProperty(FastpixModal,"TYPE","tiny_fastpix/modal"),_defineProperty(FastpixModal,"TEMPLATE","tiny_fastpix/modal"),FastpixModal.registerModalType(),_exports.default})); //# sourceMappingURL=modal.min.js.map \ No newline at end of file diff --git a/amd/build/modal.min.js.map b/amd/build/modal.min.js.map index d89af8e..d456ca5 100644 --- a/amd/build/modal.min.js.map +++ b/amd/build/modal.min.js.map @@ -1 +1 @@ -{"version":3,"file":"modal.min.js","sources":["../src/modal.js"],"sourcesContent":["// This file is part of Moodle - https://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Video-picker modal for Tiny FastPix.\n *\n * @module tiny_fastpix/modal\n * @copyright 2026 FastPix Inc. \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Modal from 'core/modal';\n\nexport default class FastpixModal extends Modal {\n static TYPE = 'tiny_fastpix/modal';\n static TEMPLATE = 'tiny_fastpix/modal';\n\n registerEventListeners() {\n super.registerEventListeners();\n this.registerCloseOnCancel();\n }\n\n configure(modalConfig) {\n modalConfig.large = true;\n modalConfig.show = true;\n modalConfig.removeOnClose = true;\n\n super.configure(modalConfig);\n }\n}\n\nFastpixModal.registerModalType();\n"],"names":["e","_modal","__esModule","default","FastpixModal","Modal","static","registerEventListeners","super","this","registerCloseOnCancel","configure","modalConfig","large","show","removeOnClose","_exports","registerModalType"],"mappings":"+EAuB+B,IAAAA;;;;;;;qFAA/BC,QAA+BD,EAA/BC,SAA+BD,EAAAE,WAAAF,EAAAG,CAAAA,QAAAH,GAEhB,MAAMI,qBAAqBC,OAAAA,QACtCC,YAAc,qBACdA,gBAAkB,qBAElBC,sBAAAA,GACIC,MAAMD,yBACNE,KAAKC,uBACT,CAEAC,SAAAA,CAAUC,aACNA,YAAYC,OAAQ,EACpBD,YAAYE,MAAO,EACnBF,YAAYG,eAAgB,EAE5BP,MAAMG,UAAUC,YACpB,EAG6B,OAFhCI,SAAAb,QAAAC,aAEDA,aAAaa,oBAAoBD,SAAAb,OAAA"} \ No newline at end of file +{"version":3,"file":"modal.min.js","sources":["../src/modal.js"],"sourcesContent":["// This file is part of Moodle - https://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Video-picker modal for Tiny FastPix.\n *\n * @module tiny_fastpix/modal\n * @copyright 2026 FastPix Inc. \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Modal from 'core/modal';\n\nexport default class FastpixModal extends Modal {\n static TYPE = 'tiny_fastpix/modal';\n static TEMPLATE = 'tiny_fastpix/modal';\n\n registerEventListeners() {\n super.registerEventListeners();\n this.registerCloseOnCancel();\n }\n\n configure(modalConfig) {\n modalConfig.large = true;\n modalConfig.show = true;\n modalConfig.removeOnClose = true;\n\n super.configure(modalConfig);\n }\n}\n\nFastpixModal.registerModalType();\n"],"names":["FastpixModal","Modal","registerEventListeners","registerCloseOnCancel","configure","modalConfig","large","show","removeOnClose","registerModalType"],"mappings":"wYAyBqBA,qBAAqBC,eAItCC,+BACUA,8BACDC,wBAGTC,UAAUC,aACNA,YAAYC,OAAQ,EACpBD,YAAYE,MAAO,EACnBF,YAAYG,eAAgB,QAEtBJ,UAAUC,mEAdHL,oBACH,sCADGA,wBAEC,sBAgBtBA,aAAaS"} \ No newline at end of file diff --git a/amd/build/options.min.js b/amd/build/options.min.js index 32f5ba9..87e5e8d 100644 --- a/amd/build/options.min.js +++ b/amd/build/options.min.js @@ -1,4 +1,4 @@ -define("tiny_fastpix/options",["exports","editor_tiny/options","./common"],function(_exports,_options,_common){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.register=_exports.getCourseId=_exports.getContextId=_exports.canEmbed=void 0; +define("tiny_fastpix/options",["exports","editor_tiny/options","./common"],(function(_exports,_options,_common){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.register=_exports.getCourseId=_exports.getContextId=_exports.canEmbed=void 0; /** * Options helper for the Tiny FastPix plugin. * @@ -6,6 +6,6 @@ define("tiny_fastpix/options",["exports","editor_tiny/options","./common"],funct * @copyright 2026 FastPix Inc. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -const contextIdName=(0,_options.getPluginOptionName)(_common.pluginName,"contextid"),courseIdName=(0,_options.getPluginOptionName)(_common.pluginName,"courseid"),canEmbedName=(0,_options.getPluginOptionName)(_common.pluginName,"canembed");_exports.register=editor=>{const registerOption=editor.options.register;registerOption(contextIdName,{processor:"number",default:0}),registerOption(courseIdName,{processor:"number",default:0}),registerOption(canEmbedName,{processor:"boolean",default:!1})};_exports.getContextId=editor=>editor.options.get(contextIdName);_exports.getCourseId=editor=>editor.options.get(courseIdName);_exports.canEmbed=editor=>editor.options.get(canEmbedName)}); +const contextIdName=(0,_options.getPluginOptionName)(_common.pluginName,"contextid"),courseIdName=(0,_options.getPluginOptionName)(_common.pluginName,"courseid"),canEmbedName=(0,_options.getPluginOptionName)(_common.pluginName,"canembed");_exports.register=editor=>{const registerOption=editor.options.register;registerOption(contextIdName,{processor:"number",default:0}),registerOption(courseIdName,{processor:"number",default:0}),registerOption(canEmbedName,{processor:"boolean",default:!1})};_exports.getContextId=editor=>editor.options.get(contextIdName);_exports.getCourseId=editor=>editor.options.get(courseIdName);_exports.canEmbed=editor=>editor.options.get(canEmbedName)})); //# sourceMappingURL=options.min.js.map \ No newline at end of file diff --git a/amd/build/options.min.js.map b/amd/build/options.min.js.map index c27087e..8ae47ec 100644 --- a/amd/build/options.min.js.map +++ b/amd/build/options.min.js.map @@ -1 +1 @@ -{"version":3,"file":"options.min.js","sources":["../src/options.js"],"sourcesContent":["// This file is part of Moodle - https://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Options helper for the Tiny FastPix plugin.\n *\n * @module tiny_fastpix/options\n * @copyright 2026 FastPix Inc. \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {getPluginOptionName} from 'editor_tiny/options';\nimport {pluginName} from './common';\n\nconst contextIdName = getPluginOptionName(pluginName, 'contextid');\nconst courseIdName = getPluginOptionName(pluginName, 'courseid');\nconst canEmbedName = getPluginOptionName(pluginName, 'canembed');\n\n/**\n * Register the plugin options (populated from plugininfo).\n *\n * @param {TinyMCE} editor\n */\nexport const register = (editor) => {\n const registerOption = editor.options.register;\n\n registerOption(contextIdName, {\n processor: 'number',\n \"default\": 0,\n });\n\n registerOption(courseIdName, {\n processor: 'number',\n \"default\": 0,\n });\n\n registerOption(canEmbedName, {\n processor: 'boolean',\n \"default\": false,\n });\n};\n\n/**\n * The context id the editor is used in.\n *\n * @param {TinyMCE} editor\n * @returns {number}\n */\nexport const getContextId = (editor) => editor.options.get(contextIdName);\n\n/**\n * The course id the editor is used in (0 outside a course).\n *\n * @param {TinyMCE} editor\n * @returns {number}\n */\nexport const getCourseId = (editor) => editor.options.get(courseIdName);\n\n/**\n * Whether the current user may embed FastPix videos (gates the toolbar button).\n *\n * @param {TinyMCE} editor\n * @returns {boolean}\n */\nexport const canEmbed = (editor) => editor.options.get(canEmbedName);\n"],"names":["contextIdName","getPluginOptionName","pluginName","courseIdName","canEmbedName","_exports","register","editor","registerOption","options","processor","default","getContextId","get","getCourseId","canEmbed"],"mappings":";;;;;;;;AA0BA,MAAMA,eAAgB,EAAAC,SAAAA,qBAAoBC,QAAUA,WAAE,aAChDC,cAAe,EAAAF,SAAAA,qBAAoBC,QAAUA,WAAE,YAC/CE,cAAe,EAAAH,SAAAA,qBAAoBC,QAAUA,WAAE,YAwBnDG,SAAAC,SAjBuBC,SACrB,MAAMC,eAAiBD,OAAOE,QAAQH,SAEtCE,eAAeR,cAAe,CAC1BU,UAAW,SACXC,QAAW,IAGfH,eAAeL,aAAc,CACzBO,UAAW,SACXC,QAAW,IAGfH,eAAeJ,aAAc,CACzBM,UAAW,UACXC,SAAW,KAUuDN,SAAAO,aAA7CL,QAAWA,OAAOE,QAAQI,IAAIb,eAQaK,SAAAS,YAA5CP,QAAWA,OAAOE,QAAQI,IAAIV,cAQWE,SAAAU,SAA5CR,QAAWA,OAAOE,QAAQI,IAAIT,aAAc"} \ No newline at end of file +{"version":3,"file":"options.min.js","sources":["../src/options.js"],"sourcesContent":["// This file is part of Moodle - https://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Options helper for the Tiny FastPix plugin.\n *\n * @module tiny_fastpix/options\n * @copyright 2026 FastPix Inc. \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {getPluginOptionName} from 'editor_tiny/options';\nimport {pluginName} from './common';\n\nconst contextIdName = getPluginOptionName(pluginName, 'contextid');\nconst courseIdName = getPluginOptionName(pluginName, 'courseid');\nconst canEmbedName = getPluginOptionName(pluginName, 'canembed');\n\n/**\n * Register the plugin options (populated from plugininfo).\n *\n * @param {TinyMCE} editor\n */\nexport const register = (editor) => {\n const registerOption = editor.options.register;\n\n registerOption(contextIdName, {\n processor: 'number',\n \"default\": 0,\n });\n\n registerOption(courseIdName, {\n processor: 'number',\n \"default\": 0,\n });\n\n registerOption(canEmbedName, {\n processor: 'boolean',\n \"default\": false,\n });\n};\n\n/**\n * The context id the editor is used in.\n *\n * @param {TinyMCE} editor\n * @returns {number}\n */\nexport const getContextId = (editor) => editor.options.get(contextIdName);\n\n/**\n * The course id the editor is used in (0 outside a course).\n *\n * @param {TinyMCE} editor\n * @returns {number}\n */\nexport const getCourseId = (editor) => editor.options.get(courseIdName);\n\n/**\n * Whether the current user may embed FastPix videos (gates the toolbar button).\n *\n * @param {TinyMCE} editor\n * @returns {boolean}\n */\nexport const canEmbed = (editor) => editor.options.get(canEmbedName);\n"],"names":["contextIdName","pluginName","courseIdName","canEmbedName","editor","registerOption","options","register","processor","get"],"mappings":";;;;;;;;MA0BMA,eAAgB,gCAAoBC,mBAAY,aAChDC,cAAe,gCAAoBD,mBAAY,YAC/CE,cAAe,gCAAoBF,mBAAY,8BAO5BG,eACfC,eAAiBD,OAAOE,QAAQC,SAEtCF,eAAeL,cAAe,CAC1BQ,UAAW,iBACA,IAGfH,eAAeH,aAAc,CACzBM,UAAW,iBACA,IAGfH,eAAeF,aAAc,CACzBK,UAAW,mBACA,2BAUUJ,QAAWA,OAAOE,QAAQG,IAAIT,oCAQ/BI,QAAWA,OAAOE,QAAQG,IAAIP,gCAQjCE,QAAWA,OAAOE,QAAQG,IAAIN"} \ No newline at end of file diff --git a/amd/build/plugin.min.js b/amd/build/plugin.min.js index 53123d7..cef03b1 100644 --- a/amd/build/plugin.min.js +++ b/amd/build/plugin.min.js @@ -1,10 +1,10 @@ -define("tiny_fastpix/plugin",["exports","editor_tiny/loader","editor_tiny/utils","./common","./commands","./configuration","./options"],function(_exports,_loader,_utils,_common,Commands,Configuration,Options){function _interopRequireWildcard(e,t){if("function"==typeof WeakMap)var r=new WeakMap,n=new WeakMap;return(_interopRequireWildcard=function(e,t){if(!t&&e&&e.__esModule)return e;var o,i,f={__proto__:null,default:e};if(null===e||"object"!=typeof e&&"function"!=typeof e)return f;if(o=t?n:r){if(o.has(e))return o.get(e);o.set(e,f)}for(const t in e)"default"!==t&&{}.hasOwnProperty.call(e,t)&&((i=(o=Object.defineProperty)&&Object.getOwnPropertyDescriptor(e,t))&&(i.get||i.set)?o(f,t,i):f[t]=e[t]);return f})(e,t)} +define("tiny_fastpix/plugin",["exports","editor_tiny/loader","editor_tiny/utils","./common","./commands","./configuration","./options"],(function(_exports,_loader,_utils,_common,Commands,Configuration,Options){function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireWildcard(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}return newObj.default=obj,cache&&cache.set(obj,newObj),newObj} /** * Tiny FastPix plugin for Moodle. * * @module tiny_fastpix/plugin * @copyright 2026 FastPix Inc. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,Commands=_interopRequireWildcard(Commands),Configuration=_interopRequireWildcard(Configuration),Options=_interopRequireWildcard(Options);_exports.default=new Promise(async resolve=>{const[tinyMCE,setupCommands,pluginMetadata]=await Promise.all([(0,_loader.getTinyMCE)(),Commands.getSetup(),(0,_utils.getPluginMetadata)(_common.component,_common.pluginName)]);tinyMCE.PluginManager.add(_common.pluginName,editor=>(Options.register(editor),setupCommands(editor),pluginMetadata)),resolve([_common.pluginName,Configuration])});return _exports.default}); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,Commands=_interopRequireWildcard(Commands),Configuration=_interopRequireWildcard(Configuration),Options=_interopRequireWildcard(Options);var _default=new Promise((async resolve=>{const[tinyMCE,setupCommands,pluginMetadata]=await Promise.all([(0,_loader.getTinyMCE)(),Commands.getSetup(),(0,_utils.getPluginMetadata)(_common.component,_common.pluginName)]);tinyMCE.PluginManager.add(_common.pluginName,(editor=>(Options.register(editor),setupCommands(editor),pluginMetadata))),resolve([_common.pluginName,Configuration])}));return _exports.default=_default,_exports.default})); //# sourceMappingURL=plugin.min.js.map \ No newline at end of file diff --git a/amd/build/plugin.min.js.map b/amd/build/plugin.min.js.map index da34891..28e55d8 100644 --- a/amd/build/plugin.min.js.map +++ b/amd/build/plugin.min.js.map @@ -1 +1 @@ -{"version":3,"file":"plugin.min.js","sources":["../src/plugin.js"],"sourcesContent":["// This file is part of Moodle - https://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Tiny FastPix plugin for Moodle.\n *\n * @module tiny_fastpix/plugin\n * @copyright 2026 FastPix Inc. \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {getTinyMCE} from 'editor_tiny/loader';\nimport {getPluginMetadata} from 'editor_tiny/utils';\n\nimport {component, pluginName} from './common';\nimport * as Commands from './commands';\nimport * as Configuration from './configuration';\nimport * as Options from './options';\n\n// eslint-disable-next-line no-async-promise-executor\nexport default new Promise(async(resolve) => {\n const [\n tinyMCE,\n setupCommands,\n pluginMetadata,\n ] = await Promise.all([\n getTinyMCE(),\n Commands.getSetup(),\n getPluginMetadata(component, pluginName),\n ]);\n\n // PluginManager.add does not accept a Promise — all async work is done above.\n tinyMCE.PluginManager.add(pluginName, (editor) => {\n Options.register(editor);\n setupCommands(editor);\n\n return pluginMetadata;\n });\n\n resolve([pluginName, Configuration]);\n});\n"],"names":["_interopRequireWildcard","e","t","WeakMap","r","n","__esModule","o","i","f","__proto__","default","has","get","set","hasOwnProperty","call","Object","defineProperty","getOwnPropertyDescriptor","Commands","Configuration","Options","_exports","Promise","async","tinyMCE","setupCommands","pluginMetadata","all","getTinyMCE","getSetup","getPluginMetadata","component","pluginName","PluginManager","add","editor","register","resolve"],"mappings":"iNA6BqC,SAAAA,wBAAAC,EAAAC,GAAAC,GAAAA,mBAAAA,QAAAC,IAAAA,EAAAD,IAAAA,QAAAE,MAAAF,QAAA,OAAAH,wBAAA,SAAAC,EAAAC,OAAAA,GAAAD,GAAAA,EAAAK,WAAA,OAAAL,EAAAM,IAAAA,EAAAC,EAAAC,EAAAC,CAAAA,eAAAC,QAAAV,GAAA,GAAA,OAAAA,GAAA,iBAAAA,GAAA,mBAAAA,EAAAQ,OAAAA,EAAAF,GAAAA,EAAAL,EAAAG,EAAAD,EAAA,CAAA,GAAAG,EAAAK,IAAAX,GAAA,OAAAM,EAAAM,IAAAZ,GAAAM,EAAAO,IAAAb,EAAAQ,EAAAP,CAAAA,IAAAA,MAAAA,KAAAD,cAAAC,GAAA,CAAA,EAAAa,eAAAC,KAAAf,EAAAC,MAAAM,GAAAD,EAAAU,OAAAC,iBAAAD,OAAAE,yBAAAlB,EAAAC,MAAAM,EAAAK,KAAAL,EAAAM,KAAAP,EAAAE,EAAAP,EAAAM,GAAAC,EAAAP,GAAAD,EAAAC,IAAAO,OAAAA,CAAAR,GAAAA,EAAAC,EAAA;;;;;;;qFAFrCkB,SAAApB,wBAAAoB,UACAC,cAAArB,wBAAAqB,eACAC,QAAAtB,wBAAAsB,SAdAC,SAAAZ,QAiBe,IAAIa,QAAQC,gBACvB,MACIC,QACAC,cACAC,sBACMJ,QAAQK,IAAI,EAClB,EAAAC,QAAAA,cACAV,SAASW,YACT,EAAAC,OAAAA,mBAAkBC,QAASA,UAAEC,QAAUA,cAI3CR,QAAQS,cAAcC,IAAIF,QAAUA,WAAGG,SACnCf,QAAQgB,SAASD,QACjBV,cAAcU,QAEPT,iBAGXW,QAAQ,CAACL,QAAAA,WAAYb,kBACvB,OAAAE,SAAAZ,OAAA"} \ No newline at end of file +{"version":3,"file":"plugin.min.js","sources":["../src/plugin.js"],"sourcesContent":["// This file is part of Moodle - https://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Tiny FastPix plugin for Moodle.\n *\n * @module tiny_fastpix/plugin\n * @copyright 2026 FastPix Inc. \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {getTinyMCE} from 'editor_tiny/loader';\nimport {getPluginMetadata} from 'editor_tiny/utils';\n\nimport {component, pluginName} from './common';\nimport * as Commands from './commands';\nimport * as Configuration from './configuration';\nimport * as Options from './options';\n\n// eslint-disable-next-line no-async-promise-executor\nexport default new Promise(async(resolve) => {\n const [\n tinyMCE,\n setupCommands,\n pluginMetadata,\n ] = await Promise.all([\n getTinyMCE(),\n Commands.getSetup(),\n getPluginMetadata(component, pluginName),\n ]);\n\n // PluginManager.add does not accept a Promise — all async work is done above.\n tinyMCE.PluginManager.add(pluginName, (editor) => {\n Options.register(editor);\n setupCommands(editor);\n\n return pluginMetadata;\n });\n\n resolve([pluginName, Configuration]);\n});\n"],"names":["Promise","async","tinyMCE","setupCommands","pluginMetadata","all","Commands","getSetup","component","pluginName","PluginManager","add","editor","Options","register","resolve","Configuration"],"mappings":";;;;;;;2OAgCe,IAAIA,SAAQC,MAAAA,gBAEnBC,QACAC,cACAC,sBACMJ,QAAQK,IAAI,EAClB,wBACAC,SAASC,YACT,4BAAkBC,kBAAWC,sBAIjCP,QAAQQ,cAAcC,IAAIF,oBAAaG,SACnCC,QAAQC,SAASF,QACjBT,cAAcS,QAEPR,kBAGXW,QAAQ,CAACN,mBAAYO"} \ No newline at end of file diff --git a/amd/build/ui.min.js b/amd/build/ui.min.js index ad156c5..0d48edd 100644 --- a/amd/build/ui.min.js +++ b/amd/build/ui.min.js @@ -1,4 +1,4 @@ -define("tiny_fastpix/ui",["exports","core/ajax","core/templates","core/str","./common","./options","./modal"],function(_exports,_ajax,_templates,_str,_common,_options,_modal){var e; +define("tiny_fastpix/ui",["exports","core/ajax","core/templates","core/str","./common","./options","./modal"],(function(_exports,_ajax,_templates,_str,_common,_options,_modal){var obj; /** * Tiny FastPix UI — opens the picker, lists this course's videos the author * uploaded and inserts the {fastpix:pb_} shortcode for the chosen one. @@ -6,6 +6,6 @@ define("tiny_fastpix/ui",["exports","core/ajax","core/templates","core/str","./c * @module tiny_fastpix/ui * @copyright 2026 FastPix Inc. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.handleAction=void 0,_modal=(e=_modal)&&e.__esModule?e:{default:e};_exports.handleAction=editor=>{displayDialogue(editor)};const displayDialogue=async editor=>{const bookmark=editor.selection.getBookmark(),contextid=(0,_options.getContextId)(editor),modal=await _modal.default.create({templateContext:{elementid:editor.id}}),region=modal.getRoot()[0].querySelector('[data-region="fastpix-video-list"]');let videos;region.addEventListener("click",e=>{const choice=e.target.closest("[data-playbackid]");choice&&(e.preventDefault(),((editor,modal,bookmark,playbackid)=>{editor.selection.moveToBookmark(bookmark),editor.execCommand("mceInsertContent",!1,`{fastpix:pb_${playbackid}}`),editor.selection.moveToBookmark(bookmark),modal.destroy()})(editor,modal,bookmark,choice.dataset.playbackid))});try{videos=await(contextid=>(0,_ajax.call)([{methodname:"tiny_fastpix_get_my_videos",args:{contextid:contextid}}])[0].then(result=>result.videos))(contextid)}catch(error){const message=await(0,_str.getString)("loaderror",_common.component);return void(region.textContent=message)}const{html:html,js:js}=await(0,_templates.renderForPromise)(`${_common.component}/videos`,{videos:videos,hasvideos:videos.length>0});(0,_templates.replaceNodeContents)(region,html,js)}}); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.handleAction=void 0,_modal=(obj=_modal)&&obj.__esModule?obj:{default:obj};_exports.handleAction=editor=>{displayDialogue(editor)};const displayDialogue=async editor=>{const bookmark=editor.selection.getBookmark(),contextid=(0,_options.getContextId)(editor),modal=await _modal.default.create({templateContext:{elementid:editor.id}}),region=modal.getRoot()[0].querySelector('[data-region="fastpix-video-list"]');let videos;region.addEventListener("click",(e=>{const choice=e.target.closest("[data-playbackid]");choice&&(e.preventDefault(),((editor,modal,bookmark,playbackid)=>{editor.selection.moveToBookmark(bookmark),editor.execCommand("mceInsertContent",!1,"{fastpix:pb_".concat(playbackid,"}")),editor.selection.moveToBookmark(bookmark),modal.destroy()})(editor,modal,bookmark,choice.dataset.playbackid))}));try{videos=await(contextid=>(0,_ajax.call)([{methodname:"tiny_fastpix_get_my_videos",args:{contextid:contextid}}])[0].then((result=>result.videos)))(contextid)}catch(error){const message=await(0,_str.getString)("loaderror",_common.component);return void(region.textContent=message)}const{html:html,js:js}=await(0,_templates.renderForPromise)("".concat(_common.component,"/videos"),{videos:videos,hasvideos:videos.length>0});(0,_templates.replaceNodeContents)(region,html,js)}})); //# sourceMappingURL=ui.min.js.map \ No newline at end of file diff --git a/amd/build/ui.min.js.map b/amd/build/ui.min.js.map index 33a5449..d8f7e76 100644 --- a/amd/build/ui.min.js.map +++ b/amd/build/ui.min.js.map @@ -1 +1 @@ -{"version":3,"file":"ui.min.js","sources":["../src/ui.js"],"sourcesContent":["// This file is part of Moodle - https://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Tiny FastPix UI — opens the picker, lists this course's videos the author\n * uploaded and inserts the {fastpix:pb_} shortcode for the chosen one.\n *\n * @module tiny_fastpix/ui\n * @copyright 2026 FastPix Inc. \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {call as fetchMany} from 'core/ajax';\nimport {renderForPromise, replaceNodeContents} from 'core/templates';\nimport {getString} from 'core/str';\nimport {component} from './common';\nimport {getContextId} from './options';\nimport FastpixModal from './modal';\n\nconst listRegionSelector = '[data-region=\"fastpix-video-list\"]';\n\n/**\n * Open the picker for the current editor.\n *\n * @param {TinyMCE} editor\n */\nexport const handleAction = (editor) => {\n displayDialogue(editor);\n};\n\n/**\n * Fetch the videos this course holds for the current user via the web service.\n * The server derives the course from the context, so only the context is sent.\n *\n * @param {number} contextid\n * @returns {Promise>}\n */\nconst fetchVideos = (contextid) => fetchMany([{\n methodname: 'tiny_fastpix_get_my_videos',\n args: {contextid},\n}])[0].then((result) => result.videos);\n\n/**\n * Insert the shortcode for the chosen video and close the modal.\n *\n * @param {TinyMCE} editor\n * @param {object} modal\n * @param {string} bookmark\n * @param {string} playbackid\n */\nconst insertShortcode = (editor, modal, bookmark, playbackid) => {\n editor.selection.moveToBookmark(bookmark);\n editor.execCommand('mceInsertContent', false, `{fastpix:pb_${playbackid}}`);\n editor.selection.moveToBookmark(bookmark);\n modal.destroy();\n};\n\n/**\n * Build and show the picker dialogue.\n *\n * @param {TinyMCE} editor\n */\nconst displayDialogue = async(editor) => {\n // Remember where the cursor was so the shortcode lands there.\n const bookmark = editor.selection.getBookmark();\n const contextid = getContextId(editor);\n\n const modal = await FastpixModal.create({\n templateContext: {\n elementid: editor.id,\n },\n });\n\n const root = modal.getRoot()[0];\n const region = root.querySelector(listRegionSelector);\n\n // The list region persists, so one delegated click listener covers every\n // rendered card.\n region.addEventListener('click', (e) => {\n const choice = e.target.closest('[data-playbackid]');\n if (!choice) {\n return;\n }\n e.preventDefault();\n insertShortcode(editor, modal, bookmark, choice.dataset.playbackid);\n });\n\n let videos;\n try {\n videos = await fetchVideos(contextid);\n } catch (error) {\n const message = await getString('loaderror', component);\n region.textContent = message;\n return;\n }\n\n const {html, js} = await renderForPromise(`${component}/videos`, {\n videos,\n hasvideos: videos.length > 0,\n });\n replaceNodeContents(region, html, js);\n};\n"],"names":["e","_modal","__esModule","default","_exports","handleAction","editor","displayDialogue","async","bookmark","selection","getBookmark","contextid","getContextId","modal","FastpixModal","create","templateContext","elementid","id","region","getRoot","querySelector","videos","addEventListener","choice","target","closest","preventDefault","insertShortcode","playbackid","moveToBookmark","execCommand","destroy","dataset","fetchMany","methodname","args","then","result","fetchVideos","error","message","getString","component","textContent","html","js","renderForPromise","hasvideos","length","replaceNodeContents"],"mappings":"+KA6BmC,IAAAA;;;;;;;;0FAAnCC,QAAmCD,EAAnCC,SAAmCD,EAAAE,WAAAF,EAAAG,CAAAA,QAAAH,GAWjCI,SAAAC,aAF2BC,SACzBC,gBAAgBD,SAUpB,MAyBMC,gBAAkBC,eAEpB,MAAMC,SAAWH,OAAOI,UAAUC,cAC5BC,WAAY,EAAAC,SAAYA,cAACP,QAEzBQ,YAAcC,OAAYZ,QAACa,OAAO,CACpCC,gBAAiB,CACbC,UAAWZ,OAAOa,MAKpBC,OADON,MAAMO,UAAU,GACTC,cAvDG,sCAoEvB,IAAIC,OATJH,OAAOI,iBAAiB,QAAUxB,IAC9B,MAAMyB,OAASzB,EAAE0B,OAAOC,QAAQ,qBAC3BF,SAGLzB,EAAE4B,iBAjCcC,EAACvB,OAAQQ,MAAOL,SAAUqB,cAC9CxB,OAAOI,UAAUqB,eAAetB,UAChCH,OAAO0B,YAAY,oBAAoB,EAAO,eAAeF,eAC7DxB,OAAOI,UAAUqB,eAAetB,UAChCK,MAAMmB,WA8BFJ,CAAgBvB,OAAQQ,MAAOL,SAAUgB,OAAOS,QAAQJ,eAI5D,IACIP,YApDaX,aAAc,EAAAuB,MAAAA,MAAU,CAAC,CAC1CC,WAAY,6BACZC,KAAM,CAACzB,wBACP,GAAG0B,KAAMC,QAAWA,OAAOhB,QAiDRiB,CAAY5B,UAC9B,CAAC,MAAO6B,OACL,MAAMC,cAAgB,EAAAC,gBAAU,YAAaC,QAAAA,WAE7C,YADAxB,OAAOyB,YAAcH,QAEzB,CAEA,MAAMI,KAACA,KAAIC,GAAEA,UAAY,EAAAC,WAAAA,kBAAiB,GAAGJ,QAAAA,mBAAoB,CAC7DrB,cACA0B,UAAW1B,OAAO2B,OAAS,KAE/B,EAAAC,gCAAoB/B,OAAQ0B,KAAMC,IACpC"} \ No newline at end of file +{"version":3,"file":"ui.min.js","sources":["../src/ui.js"],"sourcesContent":["// This file is part of Moodle - https://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Tiny FastPix UI — opens the picker, lists this course's videos the author\n * uploaded and inserts the {fastpix:pb_} shortcode for the chosen one.\n *\n * @module tiny_fastpix/ui\n * @copyright 2026 FastPix Inc. \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {call as fetchMany} from 'core/ajax';\nimport {renderForPromise, replaceNodeContents} from 'core/templates';\nimport {getString} from 'core/str';\nimport {component} from './common';\nimport {getContextId} from './options';\nimport FastpixModal from './modal';\n\nconst listRegionSelector = '[data-region=\"fastpix-video-list\"]';\n\n/**\n * Open the picker for the current editor.\n *\n * @param {TinyMCE} editor\n */\nexport const handleAction = (editor) => {\n displayDialogue(editor);\n};\n\n/**\n * Fetch the videos this course holds for the current user via the web service.\n * The server derives the course from the context, so only the context is sent.\n *\n * @param {number} contextid\n * @returns {Promise>}\n */\nconst fetchVideos = (contextid) => fetchMany([{\n methodname: 'tiny_fastpix_get_my_videos',\n args: {contextid},\n}])[0].then((result) => result.videos);\n\n/**\n * Insert the shortcode for the chosen video and close the modal.\n *\n * @param {TinyMCE} editor\n * @param {object} modal\n * @param {string} bookmark\n * @param {string} playbackid\n */\nconst insertShortcode = (editor, modal, bookmark, playbackid) => {\n editor.selection.moveToBookmark(bookmark);\n editor.execCommand('mceInsertContent', false, `{fastpix:pb_${playbackid}}`);\n editor.selection.moveToBookmark(bookmark);\n modal.destroy();\n};\n\n/**\n * Build and show the picker dialogue.\n *\n * @param {TinyMCE} editor\n */\nconst displayDialogue = async(editor) => {\n // Remember where the cursor was so the shortcode lands there.\n const bookmark = editor.selection.getBookmark();\n const contextid = getContextId(editor);\n\n const modal = await FastpixModal.create({\n templateContext: {\n elementid: editor.id,\n },\n });\n\n const root = modal.getRoot()[0];\n const region = root.querySelector(listRegionSelector);\n\n // The list region persists, so one delegated click listener covers every\n // rendered card.\n region.addEventListener('click', (e) => {\n const choice = e.target.closest('[data-playbackid]');\n if (!choice) {\n return;\n }\n e.preventDefault();\n insertShortcode(editor, modal, bookmark, choice.dataset.playbackid);\n });\n\n let videos;\n try {\n videos = await fetchVideos(contextid);\n } catch (error) {\n const message = await getString('loaderror', component);\n region.textContent = message;\n return;\n }\n\n const {html, js} = await renderForPromise(`${component}/videos`, {\n videos,\n hasvideos: videos.length > 0,\n });\n replaceNodeContents(region, html, js);\n};\n"],"names":["editor","displayDialogue","async","bookmark","selection","getBookmark","contextid","modal","FastpixModal","create","templateContext","elementid","id","region","getRoot","querySelector","videos","addEventListener","e","choice","target","closest","preventDefault","playbackid","moveToBookmark","execCommand","destroy","insertShortcode","dataset","methodname","args","then","result","fetchVideos","error","message","component","textContent","html","js","hasvideos","length"],"mappings":";;;;;;;;sKAsC6BA,SACzBC,gBAAgBD,eAmCdC,gBAAkBC,MAAAA,eAEdC,SAAWH,OAAOI,UAAUC,cAC5BC,WAAY,yBAAaN,QAEzBO,YAAcC,eAAaC,OAAO,CACpCC,gBAAiB,CACbC,UAAWX,OAAOY,MAKpBC,OADON,MAAMO,UAAU,GACTC,cAvDG,0CAoEnBC,OATJH,OAAOI,iBAAiB,SAAUC,UACxBC,OAASD,EAAEE,OAAOC,QAAQ,qBAC3BF,SAGLD,EAAEI,iBAjCc,EAACtB,OAAQO,MAAOJ,SAAUoB,cAC9CvB,OAAOI,UAAUoB,eAAerB,UAChCH,OAAOyB,YAAY,oBAAoB,wBAAsBF,iBAC7DvB,OAAOI,UAAUoB,eAAerB,UAChCI,MAAMmB,WA8BFC,CAAgB3B,OAAQO,MAAOJ,SAAUgB,OAAOS,QAAQL,oBAKxDP,YApDaV,CAAAA,YAAc,cAAU,CAAC,CAC1CuB,WAAY,6BACZC,KAAM,CAACxB,UAAAA,cACP,GAAGyB,MAAMC,QAAWA,OAAOhB,SAiDRiB,CAAY3B,WAC7B,MAAO4B,aACCC,cAAgB,kBAAU,YAAaC,+BAC7CvB,OAAOwB,YAAcF,eAInBG,KAACA,KAADC,GAAOA,UAAY,yCAAoBH,6BAAoB,CAC7DpB,OAAAA,OACAwB,UAAWxB,OAAOyB,OAAS,uCAEX5B,OAAQyB,KAAMC"} \ No newline at end of file diff --git a/tests/external/get_my_videos_test.php b/tests/external/get_my_videos_test.php index be94f21..7030e55 100644 --- a/tests/external/get_my_videos_test.php +++ b/tests/external/get_my_videos_test.php @@ -282,4 +282,83 @@ public function test_non_course_context_returns_empty(): void { $this->assertCount(0, $result['videos']); } + + /** + * A capability holder in a course who owns no ready videos gets an empty + * list (the owner-list short-circuit). + */ + public function test_returns_empty_when_user_owns_no_videos(): void { + $this->resetAfterTest(); + [$context, $teacher, $course] = $this->teacher_in_course(); + $this->setUser($teacher); + + // An activity exists in the course, but it references no owned asset. + $this->make_activity($course->id, 'Empty activity'); + + $result = $this->list_videos($context); + + $this->assertCount(0, $result['videos']); + } + + /** + * An activity in the course that links to no asset (neither a direct asset + * id nor an upload session) is skipped without affecting valid results. + */ + public function test_ignores_activity_with_no_asset_link(): void { + $this->resetAfterTest(); + [$context, $teacher, $course] = $this->teacher_in_course(); + $this->setUser($teacher); + + $asset = $this->make_asset(['owner_userid' => $teacher->id, 'playback_id' => 'valid001']); + $this->make_activity($course->id, 'Good video', ['fastpix_asset_id' => $asset->id]); + // No fastpix_asset_id and no upload_session_id -> resolves to nothing. + $this->make_activity($course->id, 'Dangling activity'); + + $result = $this->list_videos($context); + + $this->assertCount(1, $result['videos']); + $this->assertSame('valid001', $result['videos'][0]['playbackid']); + } + + /** + * When the activity name is blank, the label falls back to the asset's own + * title (provided it is a real title, not the "Asset " placeholder). + */ + public function test_label_falls_back_to_asset_title(): void { + $this->resetAfterTest(); + [$context, $teacher, $course] = $this->teacher_in_course(); + $this->setUser($teacher); + + $asset = $this->make_asset([ + 'owner_userid' => $teacher->id, 'playback_id' => 'fallbk01', 'title' => 'Real asset title', + ]); + $this->make_activity($course->id, ' ', ['fastpix_asset_id' => $asset->id]); + + $result = $this->list_videos($context); + + $this->assertCount(1, $result['videos']); + $this->assertSame('Real asset title', $result['videos'][0]['title']); + } + + /** + * When the activity name is blank and the asset only carries the projector's + * "Asset " placeholder title, the label is the localised + * "Untitled video" rather than a raw id. + */ + public function test_label_falls_back_to_untitled(): void { + $this->resetAfterTest(); + [$context, $teacher, $course] = $this->teacher_in_course(); + $this->setUser($teacher); + + $asset = $this->make_asset([ + 'owner_userid' => $teacher->id, 'playback_id' => 'untitl01', + 'fastpix_id' => 'fpplaceholder', 'title' => 'Asset fpplaceholder', + ]); + $this->make_activity($course->id, '', ['fastpix_asset_id' => $asset->id]); + + $result = $this->list_videos($context); + + $this->assertCount(1, $result['videos']); + $this->assertSame(get_string('untitledvideo', 'tiny_fastpix'), $result['videos'][0]['title']); + } }