From fa8da9f6bf8f9b48977148bb7d1423d7a53cc660 Mon Sep 17 00:00:00 2001 From: Tharun Budidha Date: Tue, 16 Jun 2026 07:54:37 +0530 Subject: [PATCH 1/3] Add a searchable video picker with usage logging, plus CI and security fixes --- .gitattributes | 12 ++ .github/workflows/moodle-plugin-ci.yml | 28 +++- CHANGELOG.md | 12 ++ README.md | 9 ++ 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 +- amd/src/ui.js | 68 ++++++++- classes/event/picker_opened.php | 76 +++++++++ classes/event/shortcode_inserted.php | 77 ++++++++++ classes/external/get_my_videos.php | 8 + classes/external/log_shortcode_inserted.php | 102 +++++++++++++ db/services.php | 8 + lang/en/tiny_fastpix.php | 5 + templates/modal.mustache | 14 ++ tests/behat/insert_shortcode.feature | 17 +++ tests/external/get_my_videos_test.php | 61 ++++++++ .../external/log_shortcode_inserted_test.php | 144 ++++++++++++++++++ version.php | 2 +- 30 files changed, 673 insertions(+), 22 deletions(-) create mode 100644 .gitattributes create mode 100644 classes/event/picker_opened.php create mode 100644 classes/event/shortcode_inserted.php create mode 100644 classes/external/log_shortcode_inserted.php create mode 100644 tests/external/log_shortcode_inserted_test.php diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..08a4490 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,12 @@ +# Development-only paths are stripped from the released package produced by +# `git archive` (the zip the Moodle Plugins Directory builds). They are kept in +# the repository for CI and contributors, but are not part of the shipped plugin. +# +# tests/ is deliberately NOT excluded: Moodle plugins ship their tests so a site +# can run them under its own PHPUnit/Behat, matching core convention. +/.github/ export-ignore +/.claude/ export-ignore +/docs/ export-ignore +/tools/ export-ignore +/.gitignore export-ignore +/.gitattributes export-ignore diff --git a/.github/workflows/moodle-plugin-ci.yml b/.github/workflows/moodle-plugin-ci.yml index 5937614..42b1a1a 100644 --- a/.github/workflows/moodle-plugin-ci.yml +++ b/.github/workflows/moodle-plugin-ci.yml @@ -25,12 +25,27 @@ jobs: --health-timeout=5s --health-retries=3 + postgres: + image: postgres:14 + env: + POSTGRES_USER: 'postgres' + POSTGRES_HOST_AUTH_METHOD: 'trust' + ports: + - 5432:5432 + options: >- + --health-cmd=pg_isready + --health-interval=10s + --health-timeout=5s + --health-retries=3 + strategy: fail-fast: false matrix: php: ['8.1', '8.2', '8.3'] moodle-branch: ['MOODLE_405_STABLE'] - database: ['mariadb'] + # Both engines are exercised: a PostgreSQL failure is a Plugins-directory + # approval blocker, so it must run in CI, not just MariaDB. + database: ['mariadb', 'pgsql'] steps: - name: Check out plugin @@ -46,6 +61,13 @@ jobs: ini-values: max_input_vars=5000 coverage: pcov + # Moodle 4.5's Grunt toolchain requires Node 22; needed for the grunt step + # (rebuilds amd/src -> amd/build and fails if the committed artifacts differ). + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '22' + - name: Initialise moodle-plugin-ci run: | composer create-project -n --no-dev --prefer-dist \ @@ -130,6 +152,8 @@ jobs: run: moodle-plugin-ci behat --profile chrome - name: Coverage gate (overall 85% / get_my_videos 90%) - if: ${{ !cancelled() && matrix.php == '8.3' }} + # One representative job only — coverage is DB-independent, so it need + # not run on every (php, database) combination. + if: ${{ !cancelled() && matrix.php == '8.3' && matrix.database == 'mariadb' }} working-directory: plugin run: bash tools/coverage.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 67e0a2d..f4730b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,18 @@ All notable changes to **tiny_fastpix** (FastPix video for TinyMCE) are documented here. This project follows [Semantic Versioning](https://semver.org/). +## Unreleased + +### Added +- **Search box in the picker.** The modal now has a search field that filters the + listed videos by title as you type. It is a client-side filter over the + already-loaded, course-scoped list — no extra web service call. +- **Usage events.** The picker now records two Moodle events for the site log: + `\tiny_fastpix\event\picker_opened` (fired when the picker loads its list) and + `\tiny_fastpix\event\shortcode_inserted` (fired when an author inserts a + shortcode, via the new `tiny_fastpix_log_shortcode_inserted` web service). +- **`.gitattributes`** to keep CI/dev scaffolding out of the released package. + ## 1.1.0 — 2026-06-10 ### Changed diff --git a/README.md b/README.md index 0f42479..a8948af 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,15 @@ Also: Moodle 4.5 LTS or newer with the TinyMCE editor, and PHP 8.1+ (tested to 8.3). The picker itself adds no tables, holds no FastPix credentials, and pulls in no Composer packages. +**FastPix account & credentials.** Using FastPix requires a FastPix account and a +FastPix **API Key** (created in the FastPix Dashboard under *Settings → API Keys*; +see [Activate your account](https://fastpix.com/docs/getting-started/activate-your-account)). +This picker never asks for or stores the key — it is configured **once** in +`local_fastpix` under *Site administration → Plugins → Local plugins → FastPix*, +and every plugin in the suite (including this one) reads through `local_fastpix` +from there. If listings aren't working, check the credentials in `local_fastpix` +first. + **Install it** the usual Moodle way — upload the ZIP under *Site administration → Plugins → Install plugins*, or drop the folder at `lib/editor/tiny/plugins/fastpix/` and finish the upgrade at *Notifications*. diff --git a/amd/build/commands.min.js b/amd/build/commands.min.js index 2478322..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.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 fdcc600..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 {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 +{"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 87e5e8d..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.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"],(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"),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 8ae47ec..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 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 +{"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 0d48edd..21d1bc4 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 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 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=(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)}})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.handleAction=void 0,_modal=(e=_modal)&&e.__esModule?e:{default:e};_exports.handleAction=editor=>{displayDialogue(editor)};const insertShortcode=(editor,modal,bookmark,contextid,playbackid)=>{editor.selection.moveToBookmark(bookmark),editor.execCommand("mceInsertContent",!1,`{fastpix:pb_${playbackid}}`),editor.selection.moveToBookmark(bookmark),((contextid,playbackid)=>{(0,_ajax.call)([{methodname:"tiny_fastpix_log_shortcode_inserted",args:{contextid:contextid,playbackid:playbackid}}])[0].catch(()=>null)})(contextid,playbackid),modal.destroy()},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"]');let videos;region.addEventListener("click",e=>{const choice=e.target.closest("[data-playbackid]");choice&&(e.preventDefault(),insertShortcode(editor,modal,bookmark,contextid,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),((root,region)=>{const search=root.querySelector('[data-region="fastpix-video-search"]'),wrap=root.querySelector('[data-region="fastpix-video-search-wrap"]'),nomatches=root.querySelector('[data-region="fastpix-video-nomatches"]'),cards=region.querySelectorAll("[data-playbackid]");search&&cards.length&&(wrap&&wrap.classList.remove("d-none"),search.addEventListener("input",()=>{const needle=search.value.trim().toLowerCase();let visible=0;cards.forEach(card=>{const match=""===needle||card.textContent.trim().toLowerCase().includes(needle);card.closest("li").classList.toggle("d-none",!match),match&&(visible+=1)}),nomatches&&nomatches.classList.toggle("d-none",!(""!==needle&&0===visible))}))})(root,region)}}); //# 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 d8f7e76..ca74907 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":["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 +{"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\"]';\nconst searchSelector = '[data-region=\"fastpix-video-search\"]';\nconst searchWrapSelector = '[data-region=\"fastpix-video-search-wrap\"]';\nconst noMatchesSelector = '[data-region=\"fastpix-video-nomatches\"]';\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 * Record that the author inserted a shortcode. Best-effort telemetry: a failure\n * here must never block the insertion, so the rejection is swallowed.\n *\n * @param {number} contextid\n * @param {string} playbackid\n */\nconst logInsertion = (contextid, playbackid) => {\n fetchMany([{\n methodname: 'tiny_fastpix_log_shortcode_inserted',\n args: {contextid, playbackid},\n }])[0].catch(() => {\n // Logging is non-critical; ignore failures so the editor is unaffected.\n return null;\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 {number} contextid\n * @param {string} playbackid\n */\nconst insertShortcode = (editor, modal, bookmark, contextid, playbackid) => {\n editor.selection.moveToBookmark(bookmark);\n editor.execCommand('mceInsertContent', false, `{fastpix:pb_${playbackid}}`);\n editor.selection.moveToBookmark(bookmark);\n logInsertion(contextid, playbackid);\n modal.destroy();\n};\n\n/**\n * Wire the client-side title filter. The list is course-scoped and small, so the\n * already-fetched cards are filtered in the browser by their title text — no\n * extra web service call. The search box and the \"no matches\" notice live\n * outside the list region so they survive the list render.\n *\n * @param {HTMLElement} root The modal root element.\n * @param {HTMLElement} region The list region holding the rendered cards.\n */\nconst wireSearch = (root, region) => {\n const search = root.querySelector(searchSelector);\n const wrap = root.querySelector(searchWrapSelector);\n const nomatches = root.querySelector(noMatchesSelector);\n const cards = region.querySelectorAll('[data-playbackid]');\n\n // With nothing to filter, leave the search box hidden — the empty-state\n // message already explains why the list is empty.\n if (!search || !cards.length) {\n return;\n }\n if (wrap) {\n wrap.classList.remove('d-none');\n }\n\n search.addEventListener('input', () => {\n const needle = search.value.trim().toLowerCase();\n let visible = 0;\n cards.forEach((card) => {\n const match = needle === '' || card.textContent.trim().toLowerCase().includes(needle);\n card.closest('li').classList.toggle('d-none', !match);\n if (match) {\n visible += 1;\n }\n });\n if (nomatches) {\n nomatches.classList.toggle('d-none', !(needle !== '' && visible === 0));\n }\n });\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, contextid, 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 wireSearch(root, region);\n};\n"],"names":["e","_modal","__esModule","default","_exports","handleAction","editor","displayDialogue","insertShortcode","modal","bookmark","contextid","playbackid","selection","moveToBookmark","execCommand","logInsertion","fetchMany","methodname","args","catch","destroy","async","getBookmark","getContextId","FastpixModal","create","templateContext","elementid","id","root","getRoot","region","querySelector","videos","addEventListener","choice","target","closest","preventDefault","dataset","then","result","fetchVideos","error","message","getString","component","textContent","html","js","renderForPromise","hasvideos","length","replaceNodeContents","wireSearch","search","wrap","nomatches","cards","querySelectorAll","classList","remove","needle","value","trim","toLowerCase","visible","forEach","card","match","includes","toggle"],"mappings":"+KA6BmC,IAAAA;;;;;;;;0FAAnCC,QAAmCD,EAAnCC,SAAmCD,EAAAE,WAAAF,EAAAG,CAAAA,QAAAH,GAcjCI,SAAAC,aAF2BC,SACzBC,gBAAgBD,SAUpB,MA+BME,gBAAkBA,CAACF,OAAQG,MAAOC,SAAUC,UAAWC,cACzDN,OAAOO,UAAUC,eAAeJ,UAChCJ,OAAOS,YAAY,oBAAoB,EAAO,eAAeH,eAC7DN,OAAOO,UAAUC,eAAeJ,UAtBfM,EAACL,UAAWC,eAC7B,EAAAK,MAAAA,MAAU,CAAC,CACPC,WAAY,sCACZC,KAAM,CAACR,oBAAWC,0BAClB,GAAGQ,MAAM,IAEF,OAiBXJ,CAAaL,UAAWC,YACxBH,MAAMY,WAgDJd,gBAAkBe,eAEpB,MAAMZ,SAAWJ,OAAOO,UAAUU,cAC5BZ,WAAY,EAAAa,SAAYA,cAAClB,QAEzBG,YAAcgB,OAAYtB,QAACuB,OAAO,CACpCC,gBAAiB,CACbC,UAAWtB,OAAOuB,MAIpBC,KAAOrB,MAAMsB,UAAU,GACvBC,OAASF,KAAKG,cArHG,sCAkIvB,IAAIC,OATJF,OAAOG,iBAAiB,QAAUnC,IAC9B,MAAMoC,OAASpC,EAAEqC,OAAOC,QAAQ,qBAC3BF,SAGLpC,EAAEuC,iBACF/B,gBAAgBF,OAAQG,MAAOC,SAAUC,UAAWyB,OAAOI,QAAQ5B,eAIvE,IACIsB,YA/GavB,aAAc,EAAAM,MAAAA,MAAU,CAAC,CAC1CC,WAAY,6BACZC,KAAM,CAACR,wBACP,GAAG8B,KAAMC,QAAWA,OAAOR,QA4GRS,CAAYhC,UAC9B,CAAC,MAAOiC,OACL,MAAMC,cAAgB,EAAAC,gBAAU,YAAaC,QAAAA,WAE7C,YADAf,OAAOgB,YAAcH,QAEzB,CAEA,MAAMI,KAACA,KAAIC,GAAEA,UAAY,EAAAC,WAAAA,kBAAiB,GAAGJ,QAAAA,mBAAoB,CAC7Db,cACAkB,UAAWlB,OAAOmB,OAAS,KAE/B,EAAAC,gCAAoBtB,OAAQiB,KAAMC,IA1EnBK,EAACzB,KAAME,UACtB,MAAMwB,OAAS1B,KAAKG,cArED,wCAsEbwB,KAAO3B,KAAKG,cArEK,6CAsEjByB,UAAY5B,KAAKG,cArED,2CAsEhB0B,MAAQ3B,OAAO4B,iBAAiB,qBAIjCJ,QAAWG,MAAMN,SAGlBI,MACAA,KAAKI,UAAUC,OAAO,UAG1BN,OAAOrB,iBAAiB,QAAS,KAC7B,MAAM4B,OAASP,OAAOQ,MAAMC,OAAOC,cACnC,IAAIC,QAAU,EACdR,MAAMS,QAASC,OACX,MAAMC,MAAmB,KAAXP,QAAiBM,KAAKrB,YAAYiB,OAAOC,cAAcK,SAASR,QAC9EM,KAAK/B,QAAQ,MAAMuB,UAAUW,OAAO,UAAWF,OAC3CA,QACAH,SAAW,KAGfT,WACAA,UAAUG,UAAUW,OAAO,WAAuB,KAAXT,QAA6B,IAAZI,cAkDhEZ,CAAWzB,KAAME,QACnB"} \ No newline at end of file diff --git a/amd/src/ui.js b/amd/src/ui.js index 6ca0b01..db42c24 100644 --- a/amd/src/ui.js +++ b/amd/src/ui.js @@ -30,6 +30,9 @@ import {getContextId} from './options'; import FastpixModal from './modal'; const listRegionSelector = '[data-region="fastpix-video-list"]'; +const searchSelector = '[data-region="fastpix-video-search"]'; +const searchWrapSelector = '[data-region="fastpix-video-search-wrap"]'; +const noMatchesSelector = '[data-region="fastpix-video-nomatches"]'; /** * Open the picker for the current editor. @@ -52,21 +55,80 @@ const fetchVideos = (contextid) => fetchMany([{ args: {contextid}, }])[0].then((result) => result.videos); +/** + * Record that the author inserted a shortcode. Best-effort telemetry: a failure + * here must never block the insertion, so the rejection is swallowed. + * + * @param {number} contextid + * @param {string} playbackid + */ +const logInsertion = (contextid, playbackid) => { + fetchMany([{ + methodname: 'tiny_fastpix_log_shortcode_inserted', + args: {contextid, playbackid}, + }])[0].catch(() => { + // Logging is non-critical; ignore failures so the editor is unaffected. + return null; + }); +}; + /** * Insert the shortcode for the chosen video and close the modal. * * @param {TinyMCE} editor * @param {object} modal * @param {string} bookmark + * @param {number} contextid * @param {string} playbackid */ -const insertShortcode = (editor, modal, bookmark, playbackid) => { +const insertShortcode = (editor, modal, bookmark, contextid, playbackid) => { editor.selection.moveToBookmark(bookmark); editor.execCommand('mceInsertContent', false, `{fastpix:pb_${playbackid}}`); editor.selection.moveToBookmark(bookmark); + logInsertion(contextid, playbackid); modal.destroy(); }; +/** + * Wire the client-side title filter. The list is course-scoped and small, so the + * already-fetched cards are filtered in the browser by their title text — no + * extra web service call. The search box and the "no matches" notice live + * outside the list region so they survive the list render. + * + * @param {HTMLElement} root The modal root element. + * @param {HTMLElement} region The list region holding the rendered cards. + */ +const wireSearch = (root, region) => { + const search = root.querySelector(searchSelector); + const wrap = root.querySelector(searchWrapSelector); + const nomatches = root.querySelector(noMatchesSelector); + const cards = region.querySelectorAll('[data-playbackid]'); + + // With nothing to filter, leave the search box hidden — the empty-state + // message already explains why the list is empty. + if (!search || !cards.length) { + return; + } + if (wrap) { + wrap.classList.remove('d-none'); + } + + search.addEventListener('input', () => { + const needle = search.value.trim().toLowerCase(); + let visible = 0; + cards.forEach((card) => { + const match = needle === '' || card.textContent.trim().toLowerCase().includes(needle); + card.closest('li').classList.toggle('d-none', !match); + if (match) { + visible += 1; + } + }); + if (nomatches) { + nomatches.classList.toggle('d-none', !(needle !== '' && visible === 0)); + } + }); +}; + /** * Build and show the picker dialogue. * @@ -94,7 +156,7 @@ const displayDialogue = async(editor) => { return; } e.preventDefault(); - insertShortcode(editor, modal, bookmark, choice.dataset.playbackid); + insertShortcode(editor, modal, bookmark, contextid, choice.dataset.playbackid); }); let videos; @@ -111,4 +173,6 @@ const displayDialogue = async(editor) => { hasvideos: videos.length > 0, }); replaceNodeContents(region, html, js); + + wireSearch(root, region); }; diff --git a/classes/event/picker_opened.php b/classes/event/picker_opened.php new file mode 100644 index 0000000..25336c0 --- /dev/null +++ b/classes/event/picker_opened.php @@ -0,0 +1,76 @@ +. + +namespace tiny_fastpix\event; + +/** + * Picker-opened event for the FastPix TinyMCE plugin. + * + * @package tiny_fastpix + * @copyright 2026 FastPix Inc. + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +/** + * Fired when a capable author opens the picker in a course (the get_my_videos + * web service is the server-side moment the picker loads its list). Conceptually + * the suite's "tiny.picker.opened" signal. + */ +class picker_opened extends \core\event\base { + /** + * Build the event for an editor context, carrying the resolved course id. + * + * @param \context $context The editor context the picker opened in. + * @param int $courseid The course the picker is scoped to. + * @return self The created event. + */ + public static function create_from_context(\context $context, int $courseid): self { + return self::create([ + 'context' => $context, + 'other' => ['courseid' => $courseid], + ]); + } + + /** + * Initialise the event data. + * + * @return void + */ + protected function init() { + $this->data['crud'] = 'r'; + $this->data['edulevel'] = self::LEVEL_OTHER; + } + + /** + * Return the localised event name. + * + * @return string The event name. + */ + public static function get_name() { + return get_string('eventpickeropened', 'tiny_fastpix'); + } + + /** + * Return a human-readable description of the event. + * + * @return string The event description. + */ + public function get_description() { + $courseid = (int)($this->other['courseid'] ?? 0); + return "The user with id '$this->userid' opened the FastPix video picker " . + "in the course with id '$courseid'."; + } +} diff --git a/classes/event/shortcode_inserted.php b/classes/event/shortcode_inserted.php new file mode 100644 index 0000000..d2a84b0 --- /dev/null +++ b/classes/event/shortcode_inserted.php @@ -0,0 +1,77 @@ +. + +namespace tiny_fastpix\event; + +/** + * Shortcode-inserted event for the FastPix TinyMCE plugin. + * + * @package tiny_fastpix + * @copyright 2026 FastPix Inc. + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +/** + * Fired when an author picks a video and the {fastpix:pb_} shortcode is + * inserted at the cursor. Conceptually the suite's "tiny.shortcode.inserted" + * signal. The insertion itself is client-side, so the editor reports it through + * the tiny_fastpix_log_shortcode_inserted web service. + */ +class shortcode_inserted extends \core\event\base { + /** + * Build the event for an editor context and the chosen playback id. + * + * @param \context $context The editor context the shortcode was inserted in. + * @param string $playbackid The bare playback id (no pb_ prefix). + * @return self The created event. + */ + public static function create_from_context(\context $context, string $playbackid): self { + return self::create([ + 'context' => $context, + 'other' => ['playbackid' => $playbackid], + ]); + } + + /** + * Initialise the event data. + * + * @return void + */ + protected function init() { + $this->data['crud'] = 'c'; + $this->data['edulevel'] = self::LEVEL_OTHER; + } + + /** + * Return the localised event name. + * + * @return string The event name. + */ + public static function get_name() { + return get_string('eventshortcodeinserted', 'tiny_fastpix'); + } + + /** + * Return a human-readable description of the event. + * + * @return string The event description. + */ + public function get_description() { + $playbackid = (string)($this->other['playbackid'] ?? ''); + return "The user with id '$this->userid' inserted a FastPix video shortcode " . + "for the playback id '$playbackid'."; + } +} diff --git a/classes/external/get_my_videos.php b/classes/external/get_my_videos.php index 7d5c1bb..3048744 100644 --- a/classes/external/get_my_videos.php +++ b/classes/external/get_my_videos.php @@ -98,6 +98,10 @@ public static function execute(int $contextid): array { $context = \context::instance_by_id($params['contextid']); self::validate_context($context); + // validate_context() already enforces login, but the auth chain is kept + // explicit to match the suite-wide external-function contract: + // validate_parameters -> validate_context -> require_login -> capability. + require_login(null, false); // Course scope: the picker only lists videos belonging to the course the // editor lives in. Outside a course (user/system context) nothing is in @@ -109,6 +113,10 @@ public static function execute(int $contextid): array { require_capability('mod/fastpix:uploadmedia', $coursecontext); $courseid = (int)$coursecontext->instanceid; + // The picker opens client-side, but this read is the server-side moment + // its list loads, so it is where we record the "picker opened" signal. + \tiny_fastpix\event\picker_opened::create_from_context($context, $courseid)->trigger(); + // Owner + ready scope from local_fastpix's own list method (no direct // local_fastpix table queries). $ownedbyid = []; diff --git a/classes/external/log_shortcode_inserted.php b/classes/external/log_shortcode_inserted.php new file mode 100644 index 0000000..1ce49d5 --- /dev/null +++ b/classes/external/log_shortcode_inserted.php @@ -0,0 +1,102 @@ +. + +/** + * External function: record that an author inserted a FastPix shortcode. + * + * @package tiny_fastpix + * @copyright 2026 FastPix Inc. + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace tiny_fastpix\external; + +use core_external\external_api; +use core_external\external_function_parameters; +use core_external\external_single_structure; +use core_external\external_value; + +/** + * Records the "shortcode inserted" signal for the picker. + * + * The insertion happens entirely in the editor (client-side), so the JS calls + * this after it writes {fastpix:pb_} at the cursor. It stores nothing of its + * own — it only triggers a Moodle event for the site log. It is gated by the + * same mod/fastpix:uploadmedia capability (resolved at the course context) as + * the picker itself, and require_sesskey() guards the state-changing call. + * + * @package tiny_fastpix + * @copyright 2026 FastPix Inc. + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class log_shortcode_inserted extends external_api { + /** + * Parameter definition. + * + * @return external_function_parameters + */ + 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.'), + 'playbackid' => new external_value(PARAM_ALPHANUMEXT, 'The bare playback id that was inserted (no pb_ prefix).'), + ]); + } + + /** + * Return-value definition. + * + * @return external_single_structure + */ + public static function execute_returns(): external_single_structure { + return new external_single_structure([ + 'logged' => new external_value(PARAM_BOOL, 'Whether the event was recorded.'), + ]); + } + + /** + * Record the shortcode insertion as a Moodle event. + * + * @param int $contextid The context the editor is used in. + * @param string $playbackid The bare playback id that was inserted. + * @return array Whether the event was recorded. + */ + public static function execute(int $contextid, string $playbackid): array { + $params = self::validate_parameters( + self::execute_parameters(), + ['contextid' => $contextid, 'playbackid' => $playbackid] + ); + + $context = \context::instance_by_id($params['contextid']); + self::validate_context($context); + // Explicit auth chain (validate_context already logs in, but the contract + // is kept visible): validate_context -> require_login -> require_sesskey + // (write) -> require_capability. + require_login(null, false); + require_sesskey(); + + // Insertion only makes sense inside a course (the picker is course-scoped + // and the capability lives there); outside one there is nothing to log. + $coursecontext = $context->get_course_context(false); + if (!$coursecontext) { + return ['logged' => false]; + } + require_capability('mod/fastpix:uploadmedia', $coursecontext); + + \tiny_fastpix\event\shortcode_inserted::create_from_context($context, $params['playbackid'])->trigger(); + + return ['logged' => true]; + } +} diff --git a/db/services.php b/db/services.php index 01a5e18..2a6d6e9 100644 --- a/db/services.php +++ b/db/services.php @@ -33,4 +33,12 @@ 'ajax' => true, 'capabilities' => 'mod/fastpix:uploadmedia', ], + 'tiny_fastpix_log_shortcode_inserted' => [ + 'classname' => 'tiny_fastpix\external\log_shortcode_inserted', + 'methodname' => 'execute', + 'description' => 'Record that an author inserted a FastPix shortcode from the picker.', + 'type' => 'write', + 'ajax' => true, + 'capabilities' => 'mod/fastpix:uploadmedia', + ], ]; diff --git a/lang/en/tiny_fastpix.php b/lang/en/tiny_fastpix.php index d044bf1..b73274c 100644 --- a/lang/en/tiny_fastpix.php +++ b/lang/en/tiny_fastpix.php @@ -25,10 +25,15 @@ defined('MOODLE_INTERNAL') || die(); $string['buttontitle'] = 'Insert FastPix video'; +$string['eventpickeropened'] = 'FastPix video picker opened'; +$string['eventshortcodeinserted'] = 'FastPix video shortcode inserted'; $string['loaderror'] = 'Could not load your videos. Please try again.'; $string['loading'] = 'Loading your videos…'; $string['modaltitle'] = 'Insert FastPix video'; +$string['nomatches'] = 'No videos match your search.'; $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['searchlabel'] = 'Search videos'; +$string['searchplaceholder'] = 'Search your videos…'; $string['untitledvideo'] = 'Untitled video'; diff --git a/templates/modal.mustache b/templates/modal.mustache index bcb6017..2468c2b 100644 --- a/templates/modal.mustache +++ b/templates/modal.mustache @@ -35,9 +35,23 @@ {{/title}} {{$body}} +
+ + +
{{#str}} loading, tiny_fastpix {{/str}}
+
+ {{#str}} nomatches, tiny_fastpix {{/str}} +
{{/body}} {{$footer}} diff --git a/tests/behat/insert_shortcode.feature b/tests/behat/insert_shortcode.feature index 0754a29..2f02cc7 100644 --- a/tests/behat/insert_shortcode.feature +++ b/tests/behat/insert_shortcode.feature @@ -105,3 +105,20 @@ Feature: Insert a FastPix shortcode via the TinyMCE picker And I press "Save and display" And I navigate to "Settings" in current page administration Then the field "Description" matches expression "/\{fastpix:pb_keepme01\}/" + + # --------------------------------------------------------------------------- + # Search box (client-side filter). Typing in the search field narrows the + # listed videos by title; a non-matching video is hidden from the dialogue. + # --------------------------------------------------------------------------- + Scenario: The picker search box filters the listed videos by title + Given the following "tiny_fastpix > assets" exist: + | user | course | playback_id | title | + | teacher1 | C1 | second02 | Algebra basics | + And I am on the "PageName1" "page activity" page logged in as teacher1 + And I navigate to "Settings" in current page administration + When I click on the "Insert FastPix video" button for the "Description" TinyMCE editor + Then I should see "My lecture" in the "Insert FastPix video" "dialogue" + And I should see "Algebra basics" in the "Insert FastPix video" "dialogue" + When I set the field "Search videos" to "algebra" + Then I should see "Algebra basics" in the "Insert FastPix video" "dialogue" + And I should not see "My lecture" in the "Insert FastPix video" "dialogue" diff --git a/tests/external/get_my_videos_test.php b/tests/external/get_my_videos_test.php index 7030e55..eaebea4 100644 --- a/tests/external/get_my_videos_test.php +++ b/tests/external/get_my_videos_test.php @@ -254,6 +254,67 @@ public function test_excludes_unembeddable_states(): void { $this->assertCount(0, $result['videos']); } + /** + * Opening the picker (calling the service) in a course fires the + * picker_opened event, scoped to the editor context and carrying the course. + */ + public function test_picker_opened_event_fired(): void { + $this->resetAfterTest(); + [$context, $teacher, $course] = $this->teacher_in_course(); + $this->setUser($teacher); + + $sink = $this->redirectEvents(); + $this->list_videos($context); + $events = $sink->get_events(); + $sink->close(); + + $opened = array_values(array_filter($events, static function ($e) { + return $e instanceof \tiny_fastpix\event\picker_opened; + })); + $this->assertCount(1, $opened); + $this->assertSame($context->id, $opened[0]->contextid); + $this->assertSame((int)$course->id, (int)$opened[0]->other['courseid']); + // Exercise the event's display strings (name + description). + $this->assertNotEmpty(\tiny_fastpix\event\picker_opened::get_name()); + $this->assertStringContainsString((string)$course->id, $opened[0]->get_description()); + } + + /** + * Outside a course there is no picker_opened event (nothing is in scope). + */ + public function test_picker_opened_event_not_fired_outside_course(): void { + $this->resetAfterTest(); + $user = $this->getDataGenerator()->create_user(); + $this->setUser($user); + + $sink = $this->redirectEvents(); + $this->list_videos(\context_user::instance($user->id)); + $events = $sink->get_events(); + $sink->close(); + + $opened = array_filter($events, static function ($e) { + return $e instanceof \tiny_fastpix\event\picker_opened; + }); + $this->assertCount(0, $opened); + } + + /** + * An unauthenticated caller is rejected before any data is returned (the + * explicit require_login in the auth chain; also enforced by + * validate_context). Guards the contract against accidental removal. + */ + public function test_rejects_unauthenticated_caller(): void { + $this->resetAfterTest(); + $course = $this->getDataGenerator()->create_course(); + $context = \context_course::instance($course->id); + // The not-logged-in user. + $this->setUser(0); + + // require_login / validate_context both raise a moodle_exception subclass. + $this->expectException(\moodle_exception::class); + get_my_videos::execute($context->id); + } + /** * A user without mod/fastpix:uploadmedia in the course is refused. */ diff --git a/tests/external/log_shortcode_inserted_test.php b/tests/external/log_shortcode_inserted_test.php new file mode 100644 index 0000000..24f740d --- /dev/null +++ b/tests/external/log_shortcode_inserted_test.php @@ -0,0 +1,144 @@ +. + +namespace tiny_fastpix\external; + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once($CFG->dirroot . '/webservice/tests/helpers.php'); + +/** + * Tests for the tiny_fastpix_log_shortcode_inserted web service. + * + * The service records the "shortcode inserted" signal as a Moodle event. It is + * gated by mod/fastpix:uploadmedia at the course context and protected by + * require_sesskey(); these tests lock that gate plus the event payload. + * + * @package tiny_fastpix + * @copyright 2026 FastPix Inc. + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \tiny_fastpix\external\log_shortcode_inserted + */ +final class log_shortcode_inserted_test extends \externallib_advanced_testcase { + /** + * Create a course with an editing teacher enrolled, set as the current user, + * with a valid sesskey ready for the state-changing call. + * + * @return array [\context_course $context, \stdClass $teacher, \stdClass $course] + */ + private function teacher_in_course(): array { + $course = $this->getDataGenerator()->create_course(); + $teacher = $this->getDataGenerator()->create_user(); + $this->getDataGenerator()->enrol_user($teacher->id, $course->id, 'editingteacher'); + $this->setUser($teacher); + $_GET['sesskey'] = sesskey(); + return [\context_course::instance($course->id), $teacher, $course]; + } + + /** + * Run the service and clean the return value. + * + * @param \context $context The editor context. + * @param string $playbackid The playback id inserted. + * @return array The cleaned result. + */ + private function log(\context $context, string $playbackid): array { + $result = log_shortcode_inserted::execute($context->id, $playbackid); + return \core_external\external_api::clean_returnvalue( + log_shortcode_inserted::execute_returns(), + $result + ); + } + + /** + * The happy path: a capable teacher's insertion is logged and fires the + * shortcode_inserted event carrying the context and playback id. + */ + public function test_logs_and_fires_event(): void { + $this->resetAfterTest(); + [$context, $teacher] = $this->teacher_in_course(); + + $sink = $this->redirectEvents(); + $result = $this->log($context, 'keepme01'); + $events = $sink->get_events(); + $sink->close(); + + $this->assertTrue($result['logged']); + + $inserted = array_values(array_filter($events, static function ($e) { + return $e instanceof \tiny_fastpix\event\shortcode_inserted; + })); + $this->assertCount(1, $inserted); + $this->assertSame($context->id, $inserted[0]->contextid); + $this->assertSame((int)$teacher->id, (int)$inserted[0]->userid); + $this->assertSame('keepme01', $inserted[0]->other['playbackid']); + // Exercise the event's display strings (name + description). + $this->assertNotEmpty(\tiny_fastpix\event\shortcode_inserted::get_name()); + $this->assertStringContainsString('keepme01', $inserted[0]->get_description()); + } + + /** + * Outside a course there is nothing to log: the call returns logged=false + * and fires no event. + */ + public function test_non_course_context_does_not_log(): void { + $this->resetAfterTest(); + $user = $this->getDataGenerator()->create_user(); + $this->setUser($user); + $_GET['sesskey'] = sesskey(); + + $sink = $this->redirectEvents(); + $result = $this->log(\context_user::instance($user->id), 'keepme01'); + $events = $sink->get_events(); + $sink->close(); + + $this->assertFalse($result['logged']); + $inserted = array_filter($events, static function ($e) { + return $e instanceof \tiny_fastpix\event\shortcode_inserted; + }); + $this->assertCount(0, $inserted); + } + + /** + * A user without mod/fastpix:uploadmedia in the course is refused. + */ + public function test_requires_uploadmedia_capability(): 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); + $_GET['sesskey'] = sesskey(); + + $this->expectException(\required_capability_exception::class); + log_shortcode_inserted::execute($context->id, 'keepme01'); + } + + /** + * A missing/incorrect sesskey is rejected (CSRF guard on the write call). + */ + public function test_requires_sesskey(): void { + $this->resetAfterTest(); + [$context] = $this->teacher_in_course(); + unset($_GET['sesskey']); + $_POST['sesskey'] = 'definitely-wrong'; + + $this->expectException(\moodle_exception::class); + log_shortcode_inserted::execute($context->id, 'keepme01'); + } +} diff --git a/version.php b/version.php index 4da2423..7ab0098 100644 --- a/version.php +++ b/version.php @@ -25,7 +25,7 @@ defined('MOODLE_INTERNAL') || die(); $plugin->component = 'tiny_fastpix'; -$plugin->version = 2026061010; +$plugin->version = 2026061501; $plugin->requires = 2024100100; // Moodle 4.5 LTS. $plugin->maturity = MATURITY_STABLE; $plugin->release = '1.1.0'; From 1f400d722570f1962b9419d6a8cc2293c8d3ae63 Mon Sep 17 00:00:00 2001 From: Tharun Budidha Date: Tue, 16 Jun 2026 10:08:25 +0530 Subject: [PATCH 2/3] Fix CI: capitalize inline comments and rebuild AMD --- 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 +- classes/external/get_my_videos.php | 4 ++-- tests/external/get_my_videos_test.php | 2 +- 16 files changed, 20 insertions(+), 38 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 21d1bc4..5fa652d 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 insertShortcode=(editor,modal,bookmark,contextid,playbackid)=>{editor.selection.moveToBookmark(bookmark),editor.execCommand("mceInsertContent",!1,`{fastpix:pb_${playbackid}}`),editor.selection.moveToBookmark(bookmark),((contextid,playbackid)=>{(0,_ajax.call)([{methodname:"tiny_fastpix_log_shortcode_inserted",args:{contextid:contextid,playbackid:playbackid}}])[0].catch(()=>null)})(contextid,playbackid),modal.destroy()},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"]');let videos;region.addEventListener("click",e=>{const choice=e.target.closest("[data-playbackid]");choice&&(e.preventDefault(),insertShortcode(editor,modal,bookmark,contextid,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),((root,region)=>{const search=root.querySelector('[data-region="fastpix-video-search"]'),wrap=root.querySelector('[data-region="fastpix-video-search-wrap"]'),nomatches=root.querySelector('[data-region="fastpix-video-nomatches"]'),cards=region.querySelectorAll("[data-playbackid]");search&&cards.length&&(wrap&&wrap.classList.remove("d-none"),search.addEventListener("input",()=>{const needle=search.value.trim().toLowerCase();let visible=0;cards.forEach(card=>{const match=""===needle||card.textContent.trim().toLowerCase().includes(needle);card.closest("li").classList.toggle("d-none",!match),match&&(visible+=1)}),nomatches&&nomatches.classList.toggle("d-none",!(""!==needle&&0===visible))}))})(root,region)}}); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.handleAction=void 0,_modal=(obj=_modal)&&obj.__esModule?obj:{default:obj};_exports.handleAction=editor=>{displayDialogue(editor)};const insertShortcode=(editor,modal,bookmark,contextid,playbackid)=>{editor.selection.moveToBookmark(bookmark),editor.execCommand("mceInsertContent",!1,"{fastpix:pb_".concat(playbackid,"}")),editor.selection.moveToBookmark(bookmark),((contextid,playbackid)=>{(0,_ajax.call)([{methodname:"tiny_fastpix_log_shortcode_inserted",args:{contextid:contextid,playbackid:playbackid}}])[0].catch((()=>null))})(contextid,playbackid),modal.destroy()},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"]');let videos;region.addEventListener("click",(e=>{const choice=e.target.closest("[data-playbackid]");choice&&(e.preventDefault(),insertShortcode(editor,modal,bookmark,contextid,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),((root,region)=>{const search=root.querySelector('[data-region="fastpix-video-search"]'),wrap=root.querySelector('[data-region="fastpix-video-search-wrap"]'),nomatches=root.querySelector('[data-region="fastpix-video-nomatches"]'),cards=region.querySelectorAll("[data-playbackid]");search&&cards.length&&(wrap&&wrap.classList.remove("d-none"),search.addEventListener("input",(()=>{const needle=search.value.trim().toLowerCase();let visible=0;cards.forEach((card=>{const match=""===needle||card.textContent.trim().toLowerCase().includes(needle);card.closest("li").classList.toggle("d-none",!match),match&&(visible+=1)})),nomatches&&nomatches.classList.toggle("d-none",!(""!==needle&&0===visible))})))})(root,region)}})); //# 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 ca74907..15a365a 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\"]';\nconst searchSelector = '[data-region=\"fastpix-video-search\"]';\nconst searchWrapSelector = '[data-region=\"fastpix-video-search-wrap\"]';\nconst noMatchesSelector = '[data-region=\"fastpix-video-nomatches\"]';\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 * Record that the author inserted a shortcode. Best-effort telemetry: a failure\n * here must never block the insertion, so the rejection is swallowed.\n *\n * @param {number} contextid\n * @param {string} playbackid\n */\nconst logInsertion = (contextid, playbackid) => {\n fetchMany([{\n methodname: 'tiny_fastpix_log_shortcode_inserted',\n args: {contextid, playbackid},\n }])[0].catch(() => {\n // Logging is non-critical; ignore failures so the editor is unaffected.\n return null;\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 {number} contextid\n * @param {string} playbackid\n */\nconst insertShortcode = (editor, modal, bookmark, contextid, playbackid) => {\n editor.selection.moveToBookmark(bookmark);\n editor.execCommand('mceInsertContent', false, `{fastpix:pb_${playbackid}}`);\n editor.selection.moveToBookmark(bookmark);\n logInsertion(contextid, playbackid);\n modal.destroy();\n};\n\n/**\n * Wire the client-side title filter. The list is course-scoped and small, so the\n * already-fetched cards are filtered in the browser by their title text — no\n * extra web service call. The search box and the \"no matches\" notice live\n * outside the list region so they survive the list render.\n *\n * @param {HTMLElement} root The modal root element.\n * @param {HTMLElement} region The list region holding the rendered cards.\n */\nconst wireSearch = (root, region) => {\n const search = root.querySelector(searchSelector);\n const wrap = root.querySelector(searchWrapSelector);\n const nomatches = root.querySelector(noMatchesSelector);\n const cards = region.querySelectorAll('[data-playbackid]');\n\n // With nothing to filter, leave the search box hidden — the empty-state\n // message already explains why the list is empty.\n if (!search || !cards.length) {\n return;\n }\n if (wrap) {\n wrap.classList.remove('d-none');\n }\n\n search.addEventListener('input', () => {\n const needle = search.value.trim().toLowerCase();\n let visible = 0;\n cards.forEach((card) => {\n const match = needle === '' || card.textContent.trim().toLowerCase().includes(needle);\n card.closest('li').classList.toggle('d-none', !match);\n if (match) {\n visible += 1;\n }\n });\n if (nomatches) {\n nomatches.classList.toggle('d-none', !(needle !== '' && visible === 0));\n }\n });\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, contextid, 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 wireSearch(root, region);\n};\n"],"names":["e","_modal","__esModule","default","_exports","handleAction","editor","displayDialogue","insertShortcode","modal","bookmark","contextid","playbackid","selection","moveToBookmark","execCommand","logInsertion","fetchMany","methodname","args","catch","destroy","async","getBookmark","getContextId","FastpixModal","create","templateContext","elementid","id","root","getRoot","region","querySelector","videos","addEventListener","choice","target","closest","preventDefault","dataset","then","result","fetchVideos","error","message","getString","component","textContent","html","js","renderForPromise","hasvideos","length","replaceNodeContents","wireSearch","search","wrap","nomatches","cards","querySelectorAll","classList","remove","needle","value","trim","toLowerCase","visible","forEach","card","match","includes","toggle"],"mappings":"+KA6BmC,IAAAA;;;;;;;;0FAAnCC,QAAmCD,EAAnCC,SAAmCD,EAAAE,WAAAF,EAAAG,CAAAA,QAAAH,GAcjCI,SAAAC,aAF2BC,SACzBC,gBAAgBD,SAUpB,MA+BME,gBAAkBA,CAACF,OAAQG,MAAOC,SAAUC,UAAWC,cACzDN,OAAOO,UAAUC,eAAeJ,UAChCJ,OAAOS,YAAY,oBAAoB,EAAO,eAAeH,eAC7DN,OAAOO,UAAUC,eAAeJ,UAtBfM,EAACL,UAAWC,eAC7B,EAAAK,MAAAA,MAAU,CAAC,CACPC,WAAY,sCACZC,KAAM,CAACR,oBAAWC,0BAClB,GAAGQ,MAAM,IAEF,OAiBXJ,CAAaL,UAAWC,YACxBH,MAAMY,WAgDJd,gBAAkBe,eAEpB,MAAMZ,SAAWJ,OAAOO,UAAUU,cAC5BZ,WAAY,EAAAa,SAAYA,cAAClB,QAEzBG,YAAcgB,OAAYtB,QAACuB,OAAO,CACpCC,gBAAiB,CACbC,UAAWtB,OAAOuB,MAIpBC,KAAOrB,MAAMsB,UAAU,GACvBC,OAASF,KAAKG,cArHG,sCAkIvB,IAAIC,OATJF,OAAOG,iBAAiB,QAAUnC,IAC9B,MAAMoC,OAASpC,EAAEqC,OAAOC,QAAQ,qBAC3BF,SAGLpC,EAAEuC,iBACF/B,gBAAgBF,OAAQG,MAAOC,SAAUC,UAAWyB,OAAOI,QAAQ5B,eAIvE,IACIsB,YA/GavB,aAAc,EAAAM,MAAAA,MAAU,CAAC,CAC1CC,WAAY,6BACZC,KAAM,CAACR,wBACP,GAAG8B,KAAMC,QAAWA,OAAOR,QA4GRS,CAAYhC,UAC9B,CAAC,MAAOiC,OACL,MAAMC,cAAgB,EAAAC,gBAAU,YAAaC,QAAAA,WAE7C,YADAf,OAAOgB,YAAcH,QAEzB,CAEA,MAAMI,KAACA,KAAIC,GAAEA,UAAY,EAAAC,WAAAA,kBAAiB,GAAGJ,QAAAA,mBAAoB,CAC7Db,cACAkB,UAAWlB,OAAOmB,OAAS,KAE/B,EAAAC,gCAAoBtB,OAAQiB,KAAMC,IA1EnBK,EAACzB,KAAME,UACtB,MAAMwB,OAAS1B,KAAKG,cArED,wCAsEbwB,KAAO3B,KAAKG,cArEK,6CAsEjByB,UAAY5B,KAAKG,cArED,2CAsEhB0B,MAAQ3B,OAAO4B,iBAAiB,qBAIjCJ,QAAWG,MAAMN,SAGlBI,MACAA,KAAKI,UAAUC,OAAO,UAG1BN,OAAOrB,iBAAiB,QAAS,KAC7B,MAAM4B,OAASP,OAAOQ,MAAMC,OAAOC,cACnC,IAAIC,QAAU,EACdR,MAAMS,QAASC,OACX,MAAMC,MAAmB,KAAXP,QAAiBM,KAAKrB,YAAYiB,OAAOC,cAAcK,SAASR,QAC9EM,KAAK/B,QAAQ,MAAMuB,UAAUW,OAAO,UAAWF,OAC3CA,QACAH,SAAW,KAGfT,WACAA,UAAUG,UAAUW,OAAO,WAAuB,KAAXT,QAA6B,IAAZI,cAkDhEZ,CAAWzB,KAAME,QACnB"} \ 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\"]';\nconst searchSelector = '[data-region=\"fastpix-video-search\"]';\nconst searchWrapSelector = '[data-region=\"fastpix-video-search-wrap\"]';\nconst noMatchesSelector = '[data-region=\"fastpix-video-nomatches\"]';\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 * Record that the author inserted a shortcode. Best-effort telemetry: a failure\n * here must never block the insertion, so the rejection is swallowed.\n *\n * @param {number} contextid\n * @param {string} playbackid\n */\nconst logInsertion = (contextid, playbackid) => {\n fetchMany([{\n methodname: 'tiny_fastpix_log_shortcode_inserted',\n args: {contextid, playbackid},\n }])[0].catch(() => {\n // Logging is non-critical; ignore failures so the editor is unaffected.\n return null;\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 {number} contextid\n * @param {string} playbackid\n */\nconst insertShortcode = (editor, modal, bookmark, contextid, playbackid) => {\n editor.selection.moveToBookmark(bookmark);\n editor.execCommand('mceInsertContent', false, `{fastpix:pb_${playbackid}}`);\n editor.selection.moveToBookmark(bookmark);\n logInsertion(contextid, playbackid);\n modal.destroy();\n};\n\n/**\n * Wire the client-side title filter. The list is course-scoped and small, so the\n * already-fetched cards are filtered in the browser by their title text — no\n * extra web service call. The search box and the \"no matches\" notice live\n * outside the list region so they survive the list render.\n *\n * @param {HTMLElement} root The modal root element.\n * @param {HTMLElement} region The list region holding the rendered cards.\n */\nconst wireSearch = (root, region) => {\n const search = root.querySelector(searchSelector);\n const wrap = root.querySelector(searchWrapSelector);\n const nomatches = root.querySelector(noMatchesSelector);\n const cards = region.querySelectorAll('[data-playbackid]');\n\n // With nothing to filter, leave the search box hidden — the empty-state\n // message already explains why the list is empty.\n if (!search || !cards.length) {\n return;\n }\n if (wrap) {\n wrap.classList.remove('d-none');\n }\n\n search.addEventListener('input', () => {\n const needle = search.value.trim().toLowerCase();\n let visible = 0;\n cards.forEach((card) => {\n const match = needle === '' || card.textContent.trim().toLowerCase().includes(needle);\n card.closest('li').classList.toggle('d-none', !match);\n if (match) {\n visible += 1;\n }\n });\n if (nomatches) {\n nomatches.classList.toggle('d-none', !(needle !== '' && visible === 0));\n }\n });\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, contextid, 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 wireSearch(root, region);\n};\n"],"names":["editor","displayDialogue","insertShortcode","modal","bookmark","contextid","playbackid","selection","moveToBookmark","execCommand","methodname","args","catch","logInsertion","destroy","async","getBookmark","FastpixModal","create","templateContext","elementid","id","root","getRoot","region","querySelector","videos","addEventListener","e","choice","target","closest","preventDefault","dataset","then","result","fetchVideos","error","message","component","textContent","html","js","hasvideos","length","search","wrap","nomatches","cards","querySelectorAll","classList","remove","needle","value","trim","toLowerCase","visible","forEach","card","match","includes","toggle","wireSearch"],"mappings":";;;;;;;;sKAyC6BA,SACzBC,gBAAgBD,eAyCdE,gBAAkB,CAACF,OAAQG,MAAOC,SAAUC,UAAWC,cACzDN,OAAOO,UAAUC,eAAeJ,UAChCJ,OAAOS,YAAY,oBAAoB,wBAAsBH,iBAC7DN,OAAOO,UAAUC,eAAeJ,UAtBf,EAACC,UAAWC,6BACnB,CAAC,CACPI,WAAY,sCACZC,KAAM,CAACN,UAAAA,UAAWC,WAAAA,eAClB,GAAGM,OAAM,IAEF,QAiBXC,CAAaR,UAAWC,YACxBH,MAAMW,WAgDJb,gBAAkBc,MAAAA,eAEdX,SAAWJ,OAAOO,UAAUS,cAC5BX,WAAY,yBAAaL,QAEzBG,YAAcc,eAAaC,OAAO,CACpCC,gBAAiB,CACbC,UAAWpB,OAAOqB,MAIpBC,KAAOnB,MAAMoB,UAAU,GACvBC,OAASF,KAAKG,cArHG,0CAkInBC,OATJF,OAAOG,iBAAiB,SAAUC,UACxBC,OAASD,EAAEE,OAAOC,QAAQ,qBAC3BF,SAGLD,EAAEI,iBACF9B,gBAAgBF,OAAQG,MAAOC,SAAUC,UAAWwB,OAAOI,QAAQ3B,oBAKnEoB,YA/GarB,CAAAA,YAAc,cAAU,CAAC,CAC1CK,WAAY,6BACZC,KAAM,CAACN,UAAAA,cACP,GAAG6B,MAAMC,QAAWA,OAAOT,SA4GRU,CAAY/B,WAC7B,MAAOgC,aACCC,cAAgB,kBAAU,YAAaC,+BAC7Cf,OAAOgB,YAAcF,eAInBG,KAACA,KAADC,GAAOA,UAAY,yCAAoBH,6BAAoB,CAC7Db,OAAAA,OACAiB,UAAWjB,OAAOkB,OAAS,uCAEXpB,OAAQiB,KAAMC,IA1EnB,EAACpB,KAAME,gBAChBqB,OAASvB,KAAKG,cArED,wCAsEbqB,KAAOxB,KAAKG,cArEK,6CAsEjBsB,UAAYzB,KAAKG,cArED,2CAsEhBuB,MAAQxB,OAAOyB,iBAAiB,qBAIjCJ,QAAWG,MAAMJ,SAGlBE,MACAA,KAAKI,UAAUC,OAAO,UAG1BN,OAAOlB,iBAAiB,SAAS,WACvByB,OAASP,OAAOQ,MAAMC,OAAOC,kBAC/BC,QAAU,EACdR,MAAMS,SAASC,aACLC,MAAmB,KAAXP,QAAiBM,KAAKlB,YAAYc,OAAOC,cAAcK,SAASR,QAC9EM,KAAK3B,QAAQ,MAAMmB,UAAUW,OAAO,UAAWF,OAC3CA,QACAH,SAAW,MAGfT,WACAA,UAAUG,UAAUW,OAAO,WAAuB,KAAXT,QAA6B,IAAZI,eAkDhEM,CAAWxC,KAAME"} \ No newline at end of file diff --git a/classes/external/get_my_videos.php b/classes/external/get_my_videos.php index 3048744..a61e152 100644 --- a/classes/external/get_my_videos.php +++ b/classes/external/get_my_videos.php @@ -98,8 +98,8 @@ public static function execute(int $contextid): array { $context = \context::instance_by_id($params['contextid']); self::validate_context($context); - // validate_context() already enforces login, but the auth chain is kept - // explicit to match the suite-wide external-function contract: + // The validate_context() call already enforces login, but the auth chain + // is kept explicit to match the suite-wide external-function contract: // validate_parameters -> validate_context -> require_login -> capability. require_login(null, false); diff --git a/tests/external/get_my_videos_test.php b/tests/external/get_my_videos_test.php index eaebea4..5f7772e 100644 --- a/tests/external/get_my_videos_test.php +++ b/tests/external/get_my_videos_test.php @@ -310,7 +310,7 @@ public function test_rejects_unauthenticated_caller(): void { // The not-logged-in user. $this->setUser(0); - // require_login / validate_context both raise a moodle_exception subclass. + // Both require_login and validate_context raise a moodle_exception subclass. $this->expectException(\moodle_exception::class); get_my_videos::execute($context->id); } From aeb710d67cf6f4da708ff0bad4a1e30c845004e7 Mon Sep 17 00:00:00 2001 From: Tharun Budidha Date: Tue, 16 Jun 2026 10:26:25 +0530 Subject: [PATCH 3/3] fix test files --- tests/event/picker_opened_test.php | 52 +++++++++++++++++++++++++ tests/event/shortcode_inserted_test.php | 52 +++++++++++++++++++++++++ 2 files changed, 104 insertions(+) create mode 100644 tests/event/picker_opened_test.php create mode 100644 tests/event/shortcode_inserted_test.php diff --git a/tests/event/picker_opened_test.php b/tests/event/picker_opened_test.php new file mode 100644 index 0000000..17d9cdc --- /dev/null +++ b/tests/event/picker_opened_test.php @@ -0,0 +1,52 @@ +. + +namespace tiny_fastpix\event; + +/** + * Tests for the picker_opened event. + * + * @package tiny_fastpix + * @copyright 2026 FastPix Inc. + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \tiny_fastpix\event\picker_opened + */ +final class picker_opened_test extends \advanced_testcase { + /** + * The event triggers with the editor context and the course id, and exposes + * a localised name and a description naming the course. + */ + public function test_event_payload_and_strings(): void { + $this->resetAfterTest(); + $course = $this->getDataGenerator()->create_course(); + $context = \context_course::instance($course->id); + $user = $this->getDataGenerator()->create_user(); + $this->setUser($user); + + $sink = $this->redirectEvents(); + picker_opened::create_from_context($context, (int)$course->id)->trigger(); + $events = $sink->get_events(); + $sink->close(); + + $this->assertCount(1, $events); + $event = $events[0]; + $this->assertInstanceOf(picker_opened::class, $event); + $this->assertSame($context->id, $event->contextid); + $this->assertSame((int)$course->id, (int)$event->other['courseid']); + $this->assertNotEmpty(picker_opened::get_name()); + $this->assertStringContainsString((string)$course->id, $event->get_description()); + } +} diff --git a/tests/event/shortcode_inserted_test.php b/tests/event/shortcode_inserted_test.php new file mode 100644 index 0000000..fefb1b0 --- /dev/null +++ b/tests/event/shortcode_inserted_test.php @@ -0,0 +1,52 @@ +. + +namespace tiny_fastpix\event; + +/** + * Tests for the shortcode_inserted event. + * + * @package tiny_fastpix + * @copyright 2026 FastPix Inc. + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \tiny_fastpix\event\shortcode_inserted + */ +final class shortcode_inserted_test extends \advanced_testcase { + /** + * The event triggers with the editor context and the playback id, and + * exposes a localised name and a description naming the playback id. + */ + public function test_event_payload_and_strings(): void { + $this->resetAfterTest(); + $course = $this->getDataGenerator()->create_course(); + $context = \context_course::instance($course->id); + $user = $this->getDataGenerator()->create_user(); + $this->setUser($user); + + $sink = $this->redirectEvents(); + shortcode_inserted::create_from_context($context, 'keepme01')->trigger(); + $events = $sink->get_events(); + $sink->close(); + + $this->assertCount(1, $events); + $event = $events[0]; + $this->assertInstanceOf(shortcode_inserted::class, $event); + $this->assertSame($context->id, $event->contextid); + $this->assertSame('keepme01', $event->other['playbackid']); + $this->assertNotEmpty(shortcode_inserted::get_name()); + $this->assertStringContainsString('keepme01', $event->get_description()); + } +}