diff --git a/lib/classes/plugin_manager.php b/lib/classes/plugin_manager.php index 29443c3fe0712..0af212945ad6c 100644 --- a/lib/classes/plugin_manager.php +++ b/lib/classes/plugin_manager.php @@ -2034,6 +2034,7 @@ public static function standard_plugins_list($type) { 'h5p', 'media', 'recordrtc', + 'link' ], 'theme' => array( diff --git a/lib/editor/tiny/classes/manager.php b/lib/editor/tiny/classes/manager.php index 5f7241bcec761..b6d16ced508a3 100644 --- a/lib/editor/tiny/classes/manager.php +++ b/lib/editor/tiny/classes/manager.php @@ -497,6 +497,9 @@ protected function get_disabled_tinymce_plugins(): array { // Disable the preview plugin as it does not support Moodle filters. 'preview', + + // Use the Moodle link plugin instead. + 'link', ]; } diff --git a/lib/editor/tiny/plugins/link/amd/build/commands.min.js b/lib/editor/tiny/plugins/link/amd/build/commands.min.js new file mode 100644 index 0000000000000..e0811d90f4c2f --- /dev/null +++ b/lib/editor/tiny/plugins/link/amd/build/commands.min.js @@ -0,0 +1,3 @@ +define("tiny_link/commands",["exports","core/str","tiny_link/common","tiny_link/ui","tiny_link/link"],(function(_exports,_str,_common,_ui,_link){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.getSetup=void 0;_exports.getSetup=async()=>{const[linkButtonText,unlinkButtonText]=await Promise.all([(0,_str.get_string)("link",_common.component),(0,_str.get_string)("unlink",_common.component)]);return editor=>{editor.ui.registry.addToggleButton(_common.linkButtonShortName,{icon:"link",tooltip:linkButtonText,onAction:()=>{(0,_ui.handleAction)(editor)},onSetup:(0,_link.toggleActiveState)(editor)}),editor.ui.registry.addMenuItem(_common.linkButtonShortName,{icon:"link",shortcut:"Meta+K",text:linkButtonText,onAction:()=>{(0,_ui.handleAction)(editor)}}),editor.ui.registry.addToggleButton(_common.unlinkButtonShortName,{icon:"unlink",tooltip:unlinkButtonText,onAction:()=>{(0,_ui.handleAction)(editor,!0)},onSetup:(0,_link.toggleActiveState)(editor)}),editor.shortcuts.add("Meta+K","Shortcut for create link",(()=>{(0,_ui.handleAction)(editor)}))}}})); + +//# sourceMappingURL=commands.min.js.map \ No newline at end of file diff --git a/lib/editor/tiny/plugins/link/amd/build/commands.min.js.map b/lib/editor/tiny/plugins/link/amd/build/commands.min.js.map new file mode 100644 index 0000000000000..26c41337d229d --- /dev/null +++ b/lib/editor/tiny/plugins/link/amd/build/commands.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"commands.min.js","sources":["../src/commands.js"],"sourcesContent":["// This file is part of Moodle - http://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\nimport {get_string as getString} from 'core/str';\nimport {component, linkButtonShortName, unlinkButtonShortName} from 'tiny_link/common';\nimport {handleAction} from 'tiny_link/ui';\nimport {toggleActiveState} from 'tiny_link/link';\n\n/**\n * Tiny Link commands.\n *\n * @module tiny_link/commands\n * @copyright 2023 Huong Nguyen \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nexport const getSetup = async() => {\n const [\n linkButtonText,\n unlinkButtonText,\n ] = await Promise.all([\n getString('link', component),\n getString('unlink', component),\n ]);\n\n return (editor) => {\n // Register Link button.\n editor.ui.registry.addToggleButton(linkButtonShortName, {\n icon: 'link',\n tooltip: linkButtonText,\n onAction: () => {\n handleAction(editor);\n },\n onSetup: toggleActiveState(editor),\n });\n\n // Register the Link menu item.\n editor.ui.registry.addMenuItem(linkButtonShortName, {\n icon: 'link',\n shortcut: 'Meta+K',\n text: linkButtonText,\n onAction: () => {\n handleAction(editor);\n },\n });\n\n // Register Unlink button.\n editor.ui.registry.addToggleButton(unlinkButtonShortName, {\n icon: 'unlink',\n tooltip: unlinkButtonText,\n onAction: () => {\n handleAction(editor, true);\n },\n onSetup: toggleActiveState(editor),\n });\n\n // Register shortcut.\n editor.shortcuts.add('Meta+K', 'Shortcut for create link', () => {\n handleAction(editor);\n });\n };\n};\n"],"names":["async","linkButtonText","unlinkButtonText","Promise","all","component","editor","ui","registry","addToggleButton","linkButtonShortName","icon","tooltip","onAction","onSetup","addMenuItem","shortcut","text","unlinkButtonShortName","shortcuts","add"],"mappings":"oPA4BwBA,gBAEhBC,eACAC,wBACMC,QAAQC,IAAI,EAClB,mBAAU,OAAQC,oBAClB,mBAAU,SAAUA,4BAGhBC,SAEJA,OAAOC,GAAGC,SAASC,gBAAgBC,4BAAqB,CACpDC,KAAM,OACNC,QAASX,eACTY,SAAU,0BACOP,SAEjBQ,SAAS,2BAAkBR,UAI/BA,OAAOC,GAAGC,SAASO,YAAYL,4BAAqB,CAChDC,KAAM,OACNK,SAAU,SACVC,KAAMhB,eACNY,SAAU,0BACOP,WAKrBA,OAAOC,GAAGC,SAASC,gBAAgBS,8BAAuB,CACtDP,KAAM,SACNC,QAASV,iBACTW,SAAU,0BACOP,QAAQ,IAEzBQ,SAAS,2BAAkBR,UAI/BA,OAAOa,UAAUC,IAAI,SAAU,4BAA4B,0BAC1Cd"} \ No newline at end of file diff --git a/lib/editor/tiny/plugins/link/amd/build/common.min.js b/lib/editor/tiny/plugins/link/amd/build/common.min.js new file mode 100644 index 0000000000000..68c888a302944 --- /dev/null +++ b/lib/editor/tiny/plugins/link/amd/build/common.min.js @@ -0,0 +1,3 @@ +define("tiny_link/common",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0;return _exports.default={pluginName:"tiny_link/plugin",component:"tiny_link",linkButtonName:"link",linkButtonShortName:"tiny_link_link",unlinkButtonName:"unlink",unlinkButtonShortName:"tiny_link_unlink"},_exports.default})); + +//# sourceMappingURL=common.min.js.map \ No newline at end of file diff --git a/lib/editor/tiny/plugins/link/amd/build/common.min.js.map b/lib/editor/tiny/plugins/link/amd/build/common.min.js.map new file mode 100644 index 0000000000000..8d18b959e8abd --- /dev/null +++ b/lib/editor/tiny/plugins/link/amd/build/common.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"common.min.js","sources":["../src/common.js"],"sourcesContent":["// This file is part of Moodle - http://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 Link common values.\n *\n * @module tiny_link/common\n * @copyright 2023 Huong Nguyen \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nexport default {\n pluginName: 'tiny_link/plugin',\n component: 'tiny_link',\n linkButtonName: 'link',\n linkButtonShortName: 'tiny_link_link',\n unlinkButtonName: 'unlink',\n unlinkButtonShortName: 'tiny_link_unlink',\n};\n"],"names":["pluginName","component","linkButtonName","linkButtonShortName","unlinkButtonName","unlinkButtonShortName"],"mappings":"kKAuBe,CACXA,WAAY,mBACZC,UAAW,YACXC,eAAgB,OAChBC,oBAAqB,iBACrBC,iBAAkB,SAClBC,sBAAuB"} \ No newline at end of file diff --git a/lib/editor/tiny/plugins/link/amd/build/configuration.min.js b/lib/editor/tiny/plugins/link/amd/build/configuration.min.js new file mode 100644 index 0000000000000..4522dffcb3311 --- /dev/null +++ b/lib/editor/tiny/plugins/link/amd/build/configuration.min.js @@ -0,0 +1,3 @@ +define("tiny_link/configuration",["exports","tiny_link/common","editor_tiny/utils"],(function(_exports,_common,_utils){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.configure=void 0;_exports.configure=instanceConfig=>{return{menu:(menu=instanceConfig.menu,menu.insert.items.match(/\blink\b/)?menu.insert.items=menu.insert.items.replace(/\blink\b/,_common.linkButtonShortName):menu.insert.items="".concat(_common.linkButtonShortName," ").concat(menu.insert.items),menu),toolbar:(0,_utils.addToolbarButtons)(instanceConfig.toolbar,"content",[_common.linkButtonShortName,_common.unlinkButtonShortName])};var menu}})); + +//# sourceMappingURL=configuration.min.js.map \ No newline at end of file diff --git a/lib/editor/tiny/plugins/link/amd/build/configuration.min.js.map b/lib/editor/tiny/plugins/link/amd/build/configuration.min.js.map new file mode 100644 index 0000000000000..fff59358d9ebc --- /dev/null +++ b/lib/editor/tiny/plugins/link/amd/build/configuration.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"configuration.min.js","sources":["../src/configuration.js"],"sourcesContent":["// This file is part of Moodle - http://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 Link configuration.\n *\n * @module tiny_link/configuration\n * @copyright 2023 Huong Nguyen \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {linkButtonShortName, unlinkButtonShortName} from 'tiny_link/common';\nimport {addToolbarButtons} from 'editor_tiny/utils';\n\nconst configureMenu = (menu) => {\n // Replace the standard Link plugin with the Moodle link.\n if (menu.insert.items.match(/\\blink\\b/)) {\n menu.insert.items = menu.insert.items.replace(/\\blink\\b/, linkButtonShortName);\n } else {\n menu.insert.items = `${linkButtonShortName} ${menu.insert.items}`;\n }\n\n return menu;\n};\n\nexport const configure = (instanceConfig) => {\n // Update the instance configuration to add the Link option to the menus and toolbars.\n return {\n menu: configureMenu(instanceConfig.menu),\n toolbar: addToolbarButtons(instanceConfig.toolbar, 'content', [linkButtonShortName, unlinkButtonShortName]),\n };\n};\n"],"names":["instanceConfig","menu","insert","items","match","replace","linkButtonShortName","toolbar","unlinkButtonShortName"],"mappings":"4NAqC0BA,uBAEf,CACHC,MAdeA,KAcKD,eAAeC,KAZnCA,KAAKC,OAAOC,MAAMC,MAAM,YACxBH,KAAKC,OAAOC,MAAQF,KAAKC,OAAOC,MAAME,QAAQ,WAAYC,6BAE1DL,KAAKC,OAAOC,gBAAWG,wCAAuBL,KAAKC,OAAOC,OAGvDF,MAOHM,SAAS,4BAAkBP,eAAeO,QAAS,UAAW,CAACD,4BAAqBE,iCAfrEP,IAAAA"} \ No newline at end of file diff --git a/lib/editor/tiny/plugins/link/amd/build/link.min.js b/lib/editor/tiny/plugins/link/amd/build/link.min.js new file mode 100644 index 0000000000000..e364925466667 --- /dev/null +++ b/lib/editor/tiny/plugins/link/amd/build/link.min.js @@ -0,0 +1,10 @@ +define("tiny_link/link",["exports","core/templates","core/pending","tiny_link/selectors"],(function(_exports,_templates,_pending,_selectors){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}} +/** + * Link helper for Tiny Link plugin. + * + * @module tiny_link/link + * @copyright 2023 Huong Nguyen + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.unSetLink=_exports.toggleActiveState=_exports.setLink=_exports.getCurrentLinkData=void 0,_templates=_interopRequireDefault(_templates),_pending=_interopRequireDefault(_pending),_selectors=_interopRequireDefault(_selectors);_exports.setLink=(currentForm,editor)=>{let value=currentForm.querySelector(_selectors.default.elements.urlEntry).value;if(""!==value){const pendingPromise=new _pending.default("tiny_link/setLink");value=value.trim();new RegExp(/^[a-zA-Z]*\.*\/|^#|^[a-zA-Z]*:/).test(value)||(value="http://"+value),setLinkOnSelection(currentForm,editor,value).then(pendingPromise.resolve)}};_exports.unSetLink=editor=>{if(editor.hasPlugin("rtc",!0))editor.execCommand("unlink");else{const dom=editor.dom,selection=editor.selection,bookmark=selection.getBookmark(),rng=selection.getRng().cloneRange(),startAnchorElm=dom.getParent(rng.startContainer,"a[href]",editor.getBody()),endAnchorElm=dom.getParent(rng.endContainer,"a[href]",editor.getBody());startAnchorElm&&rng.setStartBefore(startAnchorElm),endAnchorElm&&rng.setEndAfter(endAnchorElm),selection.setRng(rng),editor.execCommand("unlink"),selection.moveToBookmark(bookmark)}};const setLinkOnSelection=async(currentForm,editor,url)=>{const urlText=currentForm.querySelector(_selectors.default.elements.urlText),target=currentForm.querySelector(_selectors.default.elements.openInNewWindow);let textToDisplay=urlText.value.replace(/(<([^>]+)>)/gi,"").trim();""===textToDisplay&&(textToDisplay=url);const context={url:url,newwindow:target.checked};urlText.getAttribute("data-link-on-element")?(context.title=textToDisplay,context.name=editor.selection.getNode().outerHTML):context.name=textToDisplay;const{html:html}=await _templates.default.renderForPromise("tiny_link/embed_link",context),currentLink=getSelectedLink(editor);currentLink?currentLink.outerHTML=html:editor.insertContent(html)};_exports.getCurrentLinkData=editor=>{let properties={};const link=getSelectedLink(editor);if(link){const url=link.getAttribute("href"),target=link.getAttribute("target"),textToDisplay=link.innerText,title=link.getAttribute("title");""!==url&&(properties.url=url),"_blank"===target&&(properties.newwindow=!0),title&&""!==title?properties.urltext=title.trim():""!==textToDisplay&&(properties.urltext=textToDisplay.trim())}else{const selectedNode=editor.selection.getNode();if(selectedNode){const textToDisplay=selectedNode.textContent;""!==textToDisplay?(properties.urltext=textToDisplay.trim(),properties.hasTextToDisplay=!0,properties.hasPlainTextSelected=!0):selectedNode.getAttribute("data-mce-selected")&&(properties.setLinkOnElement=!0)}}return properties};const getSelectedLink=editor=>getAnchorElement(editor),getAnchorElement=(editor,selectedElm)=>(selectedElm=selectedElm||editor.selection.getNode(),editor.dom.getParent(selectedElm,"a[href]"));_exports.toggleActiveState=editor=>api=>{const updateState=()=>api.setActive(!editor.mode.isReadOnly()&&((editor,selectedElm)=>null!==getAnchorElement(editor,selectedElm))(editor,editor.selection.getNode()));return updateState(),((editor,toggler)=>(editor.on("NodeChange",toggler),()=>editor.off("NodeChange",toggler)))(editor,updateState)}})); + +//# sourceMappingURL=link.min.js.map \ No newline at end of file diff --git a/lib/editor/tiny/plugins/link/amd/build/link.min.js.map b/lib/editor/tiny/plugins/link/amd/build/link.min.js.map new file mode 100644 index 0000000000000..7a0fc238b4a5f --- /dev/null +++ b/lib/editor/tiny/plugins/link/amd/build/link.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"link.min.js","sources":["../src/link.js"],"sourcesContent":["// This file is part of Moodle - http://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 * Link helper for Tiny Link plugin.\n *\n * @module tiny_link/link\n * @copyright 2023 Huong Nguyen \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Templates from 'core/templates';\nimport Pending from 'core/pending';\nimport Selectors from 'tiny_link/selectors';\n\n/**\n * Handle insertion of a new link, or update of an existing one.\n *\n * @param {Element} currentForm\n * @param {TinyMCE} editor\n */\nexport const setLink = (currentForm, editor) => {\n const input = currentForm.querySelector(Selectors.elements.urlEntry);\n let value = input.value;\n\n if (value !== '') {\n const pendingPromise = new Pending('tiny_link/setLink');\n // We add a prefix if it is not already prefixed.\n value = value.trim();\n const expr = new RegExp(/^[a-zA-Z]*\\.*\\/|^#|^[a-zA-Z]*:/);\n if (!expr.test(value)) {\n value = 'http://' + value;\n }\n\n // Add the link.\n setLinkOnSelection(currentForm, editor, value).then(pendingPromise.resolve);\n }\n};\n\n/**\n * Handle unlink of a link\n *\n * @param {TinyMCE} editor\n */\nexport const unSetLink = (editor) => {\n if (editor.hasPlugin('rtc', true)) {\n editor.execCommand('unlink');\n } else {\n const dom = editor.dom;\n const selection = editor.selection;\n const bookmark = selection.getBookmark();\n const rng = selection.getRng().cloneRange();\n const startAnchorElm = dom.getParent(rng.startContainer, 'a[href]', editor.getBody());\n const endAnchorElm = dom.getParent(rng.endContainer, 'a[href]', editor.getBody());\n if (startAnchorElm) {\n rng.setStartBefore(startAnchorElm);\n }\n if (endAnchorElm) {\n rng.setEndAfter(endAnchorElm);\n }\n selection.setRng(rng);\n editor.execCommand('unlink');\n selection.moveToBookmark(bookmark);\n }\n};\n\n/**\n * Final step setting the anchor on the selection.\n *\n * @param {Element} currentForm\n * @param {TinyMCE} editor\n * @param {String} url URL the link will point to.\n */\nconst setLinkOnSelection = async(currentForm, editor, url) => {\n const urlText = currentForm.querySelector(Selectors.elements.urlText);\n const target = currentForm.querySelector(Selectors.elements.openInNewWindow);\n let textToDisplay = urlText.value.replace(/(<([^>]+)>)/gi, \"\").trim();\n\n if (textToDisplay === '') {\n textToDisplay = url;\n }\n\n const context = {\n url: url,\n newwindow: target.checked,\n };\n if (urlText.getAttribute('data-link-on-element')) {\n context.title = textToDisplay;\n context.name = editor.selection.getNode().outerHTML;\n } else {\n context.name = textToDisplay;\n }\n const {html} = await Templates.renderForPromise('tiny_link/embed_link', context);\n const currentLink = getSelectedLink(editor);\n if (currentLink) {\n currentLink.outerHTML = html;\n } else {\n editor.insertContent(html);\n }\n};\n\n/**\n * Get current link data.\n *\n * @param {TinyMCE} editor\n * @returns {{}}\n */\nexport const getCurrentLinkData = (editor) => {\n let properties = {};\n const link = getSelectedLink(editor);\n if (link) {\n const url = link.getAttribute('href');\n const target = link.getAttribute('target');\n const textToDisplay = link.innerText;\n const title = link.getAttribute('title');\n\n if (url !== '') {\n properties.url = url;\n }\n if (target === '_blank') {\n properties.newwindow = true;\n }\n if (title && title !== '') {\n properties.urltext = title.trim();\n } else if (textToDisplay !== '') {\n properties.urltext = textToDisplay.trim();\n }\n } else {\n // Check if the user is selecting some text before clicking on the Link button.\n const selectedNode = editor.selection.getNode();\n if (selectedNode) {\n const textToDisplay = selectedNode.textContent;\n if (textToDisplay !== '') {\n properties.urltext = textToDisplay.trim();\n properties.hasTextToDisplay = true;\n properties.hasPlainTextSelected = true;\n } else {\n if (selectedNode.getAttribute('data-mce-selected')) {\n properties.setLinkOnElement = true;\n }\n }\n }\n }\n\n return properties;\n};\n\n/**\n * Get selected link.\n *\n * @param {TinyMCE} editor\n * @returns {Element}\n */\nconst getSelectedLink = (editor) => {\n return getAnchorElement(editor);\n};\n\n/**\n * Get anchor element.\n *\n * @param {TinyMCE} editor\n * @param {Element} selectedElm\n * @returns {Element}\n */\nconst getAnchorElement = (editor, selectedElm) => {\n selectedElm = selectedElm || editor.selection.getNode();\n return editor.dom.getParent(selectedElm, 'a[href]');\n};\n\n/**\n * Check the current selected element is an anchor or not.\n *\n * @param {TinyMCE} editor\n * @param {Element} selectedElm\n * @returns {boolean}\n */\nconst isInAnchor = (editor, selectedElm) => getAnchorElement(editor, selectedElm) !== null;\n\n/**\n * Change state of button.\n *\n * @param {TinyMCE} editor\n * @param {function()} toggler\n * @returns {function()}\n */\nconst toggleState = (editor, toggler) => {\n editor.on('NodeChange', toggler);\n return () => editor.off('NodeChange', toggler);\n};\n\n/**\n * Change the active state of button.\n *\n * @param {TinyMCE} editor\n * @returns {function(*): function(): *}\n */\nexport const toggleActiveState = (editor) => (api) => {\n const updateState = () => api.setActive(!editor.mode.isReadOnly() && isInAnchor(editor, editor.selection.getNode()));\n updateState();\n return toggleState(editor, updateState);\n};\n"],"names":["currentForm","editor","value","querySelector","Selectors","elements","urlEntry","pendingPromise","Pending","trim","RegExp","test","setLinkOnSelection","then","resolve","hasPlugin","execCommand","dom","selection","bookmark","getBookmark","rng","getRng","cloneRange","startAnchorElm","getParent","startContainer","getBody","endAnchorElm","endContainer","setStartBefore","setEndAfter","setRng","moveToBookmark","async","url","urlText","target","openInNewWindow","textToDisplay","replace","context","newwindow","checked","getAttribute","title","name","getNode","outerHTML","html","Templates","renderForPromise","currentLink","getSelectedLink","insertContent","properties","link","innerText","urltext","selectedNode","textContent","hasTextToDisplay","hasPlainTextSelected","setLinkOnElement","getAnchorElement","selectedElm","api","updateState","setActive","mode","isReadOnly","isInAnchor","toggler","on","off","toggleState"],"mappings":";;;;;;;sTAiCuB,CAACA,YAAaC,cAE7BC,MADUF,YAAYG,cAAcC,mBAAUC,SAASC,UACzCJ,SAEJ,KAAVA,MAAc,OACRK,eAAiB,IAAIC,iBAAQ,qBAEnCN,MAAQA,MAAMO,OACD,IAAIC,OAAO,kCACdC,KAAKT,SACXA,MAAQ,UAAYA,OAIxBU,mBAAmBZ,YAAaC,OAAQC,OAAOW,KAAKN,eAAeO,8BASjDb,YAClBA,OAAOc,UAAU,OAAO,GACxBd,OAAOe,YAAY,cAChB,OACGC,IAAMhB,OAAOgB,IACbC,UAAYjB,OAAOiB,UACnBC,SAAWD,UAAUE,cACrBC,IAAMH,UAAUI,SAASC,aACzBC,eAAiBP,IAAIQ,UAAUJ,IAAIK,eAAgB,UAAWzB,OAAO0B,WACrEC,aAAeX,IAAIQ,UAAUJ,IAAIQ,aAAc,UAAW5B,OAAO0B,WACnEH,gBACAH,IAAIS,eAAeN,gBAEnBI,cACAP,IAAIU,YAAYH,cAEpBV,UAAUc,OAAOX,KACjBpB,OAAOe,YAAY,UACnBE,UAAUe,eAAed,kBAW3BP,mBAAqBsB,MAAMlC,YAAaC,OAAQkC,aAC5CC,QAAUpC,YAAYG,cAAcC,mBAAUC,SAAS+B,SACvDC,OAASrC,YAAYG,cAAcC,mBAAUC,SAASiC,qBACxDC,cAAgBH,QAAQlC,MAAMsC,QAAQ,gBAAiB,IAAI/B,OAEzC,KAAlB8B,gBACAA,cAAgBJ,WAGdM,QAAU,CACZN,IAAKA,IACLO,UAAWL,OAAOM,SAElBP,QAAQQ,aAAa,yBACrBH,QAAQI,MAAQN,cAChBE,QAAQK,KAAO7C,OAAOiB,UAAU6B,UAAUC,WAE1CP,QAAQK,KAAOP,oBAEbU,KAACA,YAAcC,mBAAUC,iBAAiB,uBAAwBV,SAClEW,YAAcC,gBAAgBpD,QAChCmD,YACAA,YAAYJ,UAAYC,KAExBhD,OAAOqD,cAAcL,mCAUMhD,aAC3BsD,WAAa,SACXC,KAAOH,gBAAgBpD,WACzBuD,KAAM,OACArB,IAAMqB,KAAKZ,aAAa,QACxBP,OAASmB,KAAKZ,aAAa,UAC3BL,cAAgBiB,KAAKC,UACrBZ,MAAQW,KAAKZ,aAAa,SAEpB,KAART,MACAoB,WAAWpB,IAAMA,KAEN,WAAXE,SACAkB,WAAWb,WAAY,GAEvBG,OAAmB,KAAVA,MACTU,WAAWG,QAAUb,MAAMpC,OACF,KAAlB8B,gBACPgB,WAAWG,QAAUnB,cAAc9B,YAEpC,OAEGkD,aAAe1D,OAAOiB,UAAU6B,aAClCY,aAAc,OACRpB,cAAgBoB,aAAaC,YACb,KAAlBrB,eACAgB,WAAWG,QAAUnB,cAAc9B,OACnC8C,WAAWM,kBAAmB,EAC9BN,WAAWO,sBAAuB,GAE9BH,aAAaf,aAAa,uBAC1BW,WAAWQ,kBAAmB,WAMvCR,kBASLF,gBAAmBpD,QACd+D,iBAAiB/D,QAUtB+D,iBAAmB,CAAC/D,OAAQgE,eAC9BA,YAAcA,aAAehE,OAAOiB,UAAU6B,UACvC9C,OAAOgB,IAAIQ,UAAUwC,YAAa,uCA8BXhE,QAAYiE,YACpCC,YAAc,IAAMD,IAAIE,WAAWnE,OAAOoE,KAAKC,cArBtC,EAACrE,OAAQgE,cAA0D,OAA1CD,iBAAiB/D,OAAQgE,aAqBIM,CAAWtE,OAAQA,OAAOiB,UAAU6B,mBACzGoB,cAbgB,EAAClE,OAAQuE,WACzBvE,OAAOwE,GAAG,aAAcD,SACjB,IAAMvE,OAAOyE,IAAI,aAAcF,UAY/BG,CAAY1E,OAAQkE"} \ No newline at end of file diff --git a/lib/editor/tiny/plugins/link/amd/build/modal.min.js b/lib/editor/tiny/plugins/link/amd/build/modal.min.js new file mode 100644 index 0000000000000..0ad22fde30bd9 --- /dev/null +++ b/lib/editor/tiny/plugins/link/amd/build/modal.min.js @@ -0,0 +1,3 @@ +define("tiny_link/modal",["exports","core/modal","core/modal_registry"],(function(_exports,_modal,_modal_registry){var _class;function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default: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=_interopRequireDefault(_modal),_modal_registry=_interopRequireDefault(_modal_registry);const LinkModal=(_defineProperty(_class=class extends _modal.default{registerEventListeners(){super.registerEventListeners(),this.registerCloseOnSave(),this.registerCloseOnCancel()}},"TYPE","tiny_link/modal"),_defineProperty(_class,"TEMPLATE","tiny_link/modal"),_class);_modal_registry.default.register(LinkModal.TYPE,LinkModal,LinkModal.TEMPLATE);var _default=LinkModal;return _exports.default=_default,_exports.default})); + +//# sourceMappingURL=modal.min.js.map \ No newline at end of file diff --git a/lib/editor/tiny/plugins/link/amd/build/modal.min.js.map b/lib/editor/tiny/plugins/link/amd/build/modal.min.js.map new file mode 100644 index 0000000000000..61bea15a29104 --- /dev/null +++ b/lib/editor/tiny/plugins/link/amd/build/modal.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"modal.min.js","sources":["../src/modal.js"],"sourcesContent":["// This file is part of Moodle - http://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 * Link Modal for Tiny.\n *\n * @module tiny_link/modal\n * @copyright 2023 Huong Nguyen \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Modal from 'core/modal';\nimport ModalRegistry from 'core/modal_registry';\n\nconst LinkModal = class extends Modal {\n static TYPE = 'tiny_link/modal';\n static TEMPLATE = 'tiny_link/modal';\n\n registerEventListeners() {\n // Call the parent registration.\n super.registerEventListeners();\n\n // Register to close on save/cancel.\n this.registerCloseOnSave();\n this.registerCloseOnCancel();\n }\n};\n\nModalRegistry.register(LinkModal.TYPE, LinkModal, LinkModal.TEMPLATE);\n\nexport default LinkModal;\n"],"names":["LinkModal","Modal","registerEventListeners","registerCloseOnSave","registerCloseOnCancel","register","TYPE","TEMPLATE"],"mappings":"wiBA0BMA,kCAAY,cAAcC,eAI5BC,+BAEUA,8BAGDC,2BACAC,iCATK,qDACI,mDAYRC,SAASL,UAAUM,KAAMN,UAAWA,UAAUO,uBAE7CP"} \ No newline at end of file diff --git a/lib/editor/tiny/plugins/link/amd/build/options.min.js b/lib/editor/tiny/plugins/link/amd/build/options.min.js new file mode 100644 index 0000000000000..40fac09438825 --- /dev/null +++ b/lib/editor/tiny/plugins/link/amd/build/options.min.js @@ -0,0 +1,11 @@ +define("tiny_link/options",["exports","editor_tiny/options","tiny_link/common"],(function(_exports,_options,_common){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.register=_exports.getPermissions=void 0; +/** + * Options helper for Tiny Link plugin. + * + * @module tiny_link/options + * @copyright 2023 Huong Nguyen + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +const dataName=(0,_options.getPluginOptionName)(_common.pluginName,"data"),permissionsName=(0,_options.getPluginOptionName)(_common.pluginName,"permissions");_exports.register=editor=>{const registerOption=editor.options.register;registerOption(permissionsName,{processor:"object",default:{filepicker:!1}}),registerOption(dataName,{processor:"object"})};_exports.getPermissions=editor=>editor.options.get(permissionsName)})); + +//# sourceMappingURL=options.min.js.map \ No newline at end of file diff --git a/lib/editor/tiny/plugins/link/amd/build/options.min.js.map b/lib/editor/tiny/plugins/link/amd/build/options.min.js.map new file mode 100644 index 0000000000000..63eb92f51a830 --- /dev/null +++ b/lib/editor/tiny/plugins/link/amd/build/options.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"options.min.js","sources":["../src/options.js"],"sourcesContent":["// This file is part of Moodle - http://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 Tiny Link plugin.\n *\n * @module tiny_link/options\n * @copyright 2023 Huong Nguyen \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 'tiny_link/common';\n\nconst dataName = getPluginOptionName(pluginName, 'data');\nconst permissionsName = getPluginOptionName(pluginName, 'permissions');\n\n/**\n * Register the options for the Tiny Link plugin.\n *\n * @param {TinyMCE} editor\n */\nexport const register = (editor) => {\n const registerOption = editor.options.register;\n\n registerOption(permissionsName, {\n processor: 'object',\n \"default\": {\n filepicker: false,\n },\n });\n\n registerOption(dataName, {\n processor: 'object',\n });\n};\n\n/**\n * Get the permissions configuration for the Tiny Link plugin.\n *\n * @param {TinyMCE} editor\n * @returns {object}\n */\nexport const getPermissions = (editor) => editor.options.get(permissionsName);\n"],"names":["dataName","pluginName","permissionsName","editor","registerOption","options","register","processor","filepicker","get"],"mappings":";;;;;;;;MA0BMA,UAAW,gCAAoBC,mBAAY,QAC3CC,iBAAkB,gCAAoBD,mBAAY,iCAO/BE,eACfC,eAAiBD,OAAOE,QAAQC,SAEtCF,eAAeF,gBAAiB,CAC5BK,UAAW,iBACA,CACPC,YAAY,KAIpBJ,eAAeJ,SAAU,CACrBO,UAAW,oCAUYJ,QAAWA,OAAOE,QAAQI,IAAIP"} \ No newline at end of file diff --git a/lib/editor/tiny/plugins/link/amd/build/plugin.min.js b/lib/editor/tiny/plugins/link/amd/build/plugin.min.js new file mode 100644 index 0000000000000..d5b90b81dbf95 --- /dev/null +++ b/lib/editor/tiny/plugins/link/amd/build/plugin.min.js @@ -0,0 +1,10 @@ +define("tiny_link/plugin",["exports","editor_tiny/loader","editor_tiny/utils","tiny_link/common","tiny_link/commands","tiny_link/configuration","tiny_link/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 Link plugin for Moodle. + * + * @module tiny_link/plugin + * @copyright 2023 Huong Nguyen + * @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("".concat(_common.component,"/plugin"),(editor=>(Options.register(editor),setupCommands(editor),pluginMetadata))),resolve(["".concat(_common.component,"/plugin"),Configuration])}));return _exports.default=_default,_exports.default})); + +//# sourceMappingURL=plugin.min.js.map \ No newline at end of file diff --git a/lib/editor/tiny/plugins/link/amd/build/plugin.min.js.map b/lib/editor/tiny/plugins/link/amd/build/plugin.min.js.map new file mode 100644 index 0000000000000..980545c2cceb6 --- /dev/null +++ b/lib/editor/tiny/plugins/link/amd/build/plugin.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"plugin.min.js","sources":["../src/plugin.js"],"sourcesContent":["// This file is part of Moodle - http://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\nimport {getTinyMCE} from 'editor_tiny/loader';\nimport {getPluginMetadata} from 'editor_tiny/utils';\n\nimport {component, pluginName} from 'tiny_link/common';\nimport * as Commands from 'tiny_link/commands';\nimport * as Configuration from 'tiny_link/configuration';\nimport * as Options from 'tiny_link/options';\n\n/**\n * Tiny Link plugin for Moodle.\n *\n * @module tiny_link/plugin\n * @copyright 2023 Huong Nguyen \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\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 tinyMCE.PluginManager.add(`${component}/plugin`, (editor) => {\n // Register options.\n Options.register(editor);\n // Setup the Commands (buttons, menu items, and so on).\n setupCommands(editor);\n\n return pluginMetadata;\n });\n\n // Resolve the Link Plugin and include configuration.\n resolve([`${component}/plugin`, Configuration]);\n});\n"],"names":["Promise","async","tinyMCE","setupCommands","pluginMetadata","all","Commands","getSetup","component","pluginName","PluginManager","add","editor","Options","register","resolve","Configuration"],"mappings":";;;;;;;2OA+Be,IAAIA,SAAQC,MAAAA,gBAEnBC,QACAC,cACAC,sBACMJ,QAAQK,IAAI,EAClB,wBACAC,SAASC,YACT,4BAAkBC,kBAAWC,sBAGjCP,QAAQQ,cAAcC,cAAOH,8BAAqBI,SAE9CC,QAAQC,SAASF,QAEjBT,cAAcS,QAEPR,kBAIXW,QAAQ,WAAIP,6BAAoBQ"} \ No newline at end of file diff --git a/lib/editor/tiny/plugins/link/amd/build/selectors.min.js b/lib/editor/tiny/plugins/link/amd/build/selectors.min.js new file mode 100644 index 0000000000000..345a71a700960 --- /dev/null +++ b/lib/editor/tiny/plugins/link/amd/build/selectors.min.js @@ -0,0 +1,3 @@ +define("tiny_link/selectors",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0;return _exports.default={actions:{submit:'[data-action="save"]',linkBrowser:".openlinkbrowser"},elements:{urlEntry:".tiny_link_urlentry",urlText:".tiny_link_urltext",openInNewWindow:".tiny_link_newwindow"}},_exports.default})); + +//# sourceMappingURL=selectors.min.js.map \ No newline at end of file diff --git a/lib/editor/tiny/plugins/link/amd/build/selectors.min.js.map b/lib/editor/tiny/plugins/link/amd/build/selectors.min.js.map new file mode 100644 index 0000000000000..7459f6568b99f --- /dev/null +++ b/lib/editor/tiny/plugins/link/amd/build/selectors.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"selectors.min.js","sources":["../src/selectors.js"],"sourcesContent":["// This file is part of Moodle - http://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 Link plugin helper function to build queryable data selectors.\n *\n * @module tiny_link/selectors\n * @copyright 2023 Huong Nguyen \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nexport default {\n actions: {\n submit: '[data-action=\"save\"]',\n linkBrowser: '.openlinkbrowser',\n },\n elements: {\n urlEntry: '.tiny_link_urlentry',\n urlText: '.tiny_link_urltext',\n openInNewWindow: '.tiny_link_newwindow',\n }\n};\n"],"names":["actions","submit","linkBrowser","elements","urlEntry","urlText","openInNewWindow"],"mappings":"qKAuBe,CACXA,QAAS,CACLC,OAAQ,uBACRC,YAAa,oBAEjBC,SAAU,CACNC,SAAU,sBACVC,QAAS,qBACTC,gBAAiB"} \ No newline at end of file diff --git a/lib/editor/tiny/plugins/link/amd/build/ui.min.js b/lib/editor/tiny/plugins/link/amd/build/ui.min.js new file mode 100644 index 0000000000000..7ba19ad979941 --- /dev/null +++ b/lib/editor/tiny/plugins/link/amd/build/ui.min.js @@ -0,0 +1,10 @@ +define("tiny_link/ui",["exports","core/modal_factory","core/modal_events","editor_tiny/utils","tiny_link/modal","tiny_link/options","tiny_link/link","tiny_link/selectors"],(function(_exports,_modal_factory,_modal_events,_utils,_modal,_options,_link,_selectors){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}} +/** + * Tiny Link UI. + * + * @module tiny_link/ui + * @copyright 2023 Huong Nguyen + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.handleAction=void 0,_modal_factory=_interopRequireDefault(_modal_factory),_modal_events=_interopRequireDefault(_modal_events),_modal=_interopRequireDefault(_modal),_selectors=_interopRequireDefault(_selectors);_exports.handleAction=function(editor){let unlink=arguments.length>1&&void 0!==arguments[1]&&arguments[1];unlink?(0,_link.unSetLink)(editor):displayDialogue(editor)};const displayDialogue=async editor=>{const modalPromises=await _modal_factory.default.create({type:_modal.default.TYPE,templateContext:getTemplateContext(editor),large:!1});modalPromises.show();const $root=await modalPromises.getRoot(),root=$root[0],currentForm=root.querySelector("form");$root.on(_modal_events.default.hidden,(()=>{modalPromises.destroy()})),root.addEventListener("click",(e=>{const submitAction=e.target.closest(_selectors.default.actions.submit),linkBrowserAction=e.target.closest(_selectors.default.actions.linkBrowser);submitAction&&(e.preventDefault(),(0,_link.setLink)(currentForm,editor),modalPromises.destroy()),linkBrowserAction&&(e.preventDefault(),(0,_utils.displayFilepicker)(editor,"link").then((params=>(filePickerCallback(params,currentForm,editor),modalPromises.destroy()))).catch())}));const linkTitle=root.querySelector(_selectors.default.elements.urlText),linkUrl=root.querySelector(_selectors.default.elements.urlEntry);linkTitle.addEventListener("change",(()=>{linkTitle.value.length>0?linkTitle.dataset.useLinkAsText="false":(linkTitle.dataset.useLinkAsText="true",linkTitle.value=linkUrl.value)})),linkUrl.addEventListener("keyup",(()=>{updateTextToDisplay(currentForm)}))},getTemplateContext=editor=>{const data=(0,_link.getCurrentLinkData)(editor);return Object.assign({},{elementid:editor.id,showfilepicker:(0,_options.getPermissions)(editor).filepicker,isupdating:Object.keys(data).length>0},data)},filePickerCallback=(params,currentForm,editor)=>{if(params.url){currentForm.querySelector(_selectors.default.elements.urlEntry).value=params.url,(0,_link.setLink)(currentForm,editor)}},updateTextToDisplay=currentForm=>{const urlEntry=currentForm.querySelector(_selectors.default.elements.urlEntry),urlText=currentForm.querySelector(_selectors.default.elements.urlText);"true"===urlText.dataset.useLinkAsText&&(urlText.value=urlEntry.value)}})); + +//# sourceMappingURL=ui.min.js.map \ No newline at end of file diff --git a/lib/editor/tiny/plugins/link/amd/build/ui.min.js.map b/lib/editor/tiny/plugins/link/amd/build/ui.min.js.map new file mode 100644 index 0000000000000..2bcf2018505b1 --- /dev/null +++ b/lib/editor/tiny/plugins/link/amd/build/ui.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"ui.min.js","sources":["../src/ui.js"],"sourcesContent":["// This file is part of Moodle - http://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 Link UI.\n *\n * @module tiny_link/ui\n * @copyright 2023 Huong Nguyen \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport ModalFactory from 'core/modal_factory';\nimport ModalEvents from 'core/modal_events';\nimport {displayFilepicker} from 'editor_tiny/utils';\nimport LinkModal from 'tiny_link/modal';\nimport {getPermissions} from \"tiny_link/options\";\nimport {setLink, getCurrentLinkData, unSetLink} from \"tiny_link/link\";\nimport Selectors from 'tiny_link/selectors';\n\n/**\n * Handle action.\n *\n * @param {TinyMCE} editor\n * @param {boolean} unlink\n */\nexport const handleAction = (editor, unlink = false) => {\n if (!unlink) {\n displayDialogue(editor);\n } else {\n unSetLink(editor);\n }\n};\n\n/**\n * Display the link dialogue.\n *\n * @param {TinyMCE} editor\n * @returns {Promise}\n */\nconst displayDialogue = async(editor) => {\n const modalPromises = await ModalFactory.create({\n type: LinkModal.TYPE,\n templateContext: getTemplateContext(editor),\n large: false,\n });\n\n modalPromises.show();\n const $root = await modalPromises.getRoot();\n const root = $root[0];\n const currentForm = root.querySelector('form');\n\n $root.on(ModalEvents.hidden, () => {\n modalPromises.destroy();\n });\n\n root.addEventListener('click', (e) => {\n const submitAction = e.target.closest(Selectors.actions.submit);\n const linkBrowserAction = e.target.closest(Selectors.actions.linkBrowser);\n if (submitAction) {\n e.preventDefault();\n setLink(currentForm, editor);\n modalPromises.destroy();\n }\n if (linkBrowserAction) {\n e.preventDefault();\n displayFilepicker(editor, 'link').then((params) => {\n filePickerCallback(params, currentForm, editor);\n return modalPromises.destroy();\n }).catch();\n }\n });\n\n const linkTitle = root.querySelector(Selectors.elements.urlText);\n const linkUrl = root.querySelector(Selectors.elements.urlEntry);\n linkTitle.addEventListener('change', () => {\n if (linkTitle.value.length > 0) {\n linkTitle.dataset.useLinkAsText = 'false';\n } else {\n linkTitle.dataset.useLinkAsText = 'true';\n linkTitle.value = linkUrl.value;\n }\n });\n\n linkUrl.addEventListener('keyup', () => {\n updateTextToDisplay(currentForm);\n });\n};\n\n/**\n * Get template context.\n *\n * @param {TinyMCE} editor\n * @returns {Object}\n */\nconst getTemplateContext = (editor) => {\n const data = getCurrentLinkData(editor);\n\n return Object.assign({}, {\n elementid: editor.id,\n showfilepicker: getPermissions(editor).filepicker,\n isupdating: Object.keys(data).length > 0,\n }, data);\n};\n\n/**\n * Update the dialogue after a link was selected in the File Picker.\n *\n * @param {Object} params\n * @param {Element} currentForm\n * @param {TinyMCE} editor\n */\nconst filePickerCallback = (params, currentForm, editor) => {\n if (params.url) {\n const inputUrl = currentForm.querySelector(Selectors.elements.urlEntry);\n inputUrl.value = params.url;\n setLink(currentForm, editor);\n }\n};\n\n/**\n * Update the text to display if the user does not provide the custom text.\n *\n * @param {Element} currentForm\n */\nconst updateTextToDisplay = (currentForm) => {\n const urlEntry = currentForm.querySelector(Selectors.elements.urlEntry);\n const urlText = currentForm.querySelector(Selectors.elements.urlText);\n if (urlText.dataset.useLinkAsText === 'true') {\n urlText.value = urlEntry.value;\n }\n};\n"],"names":["editor","unlink","displayDialogue","async","modalPromises","ModalFactory","create","type","LinkModal","TYPE","templateContext","getTemplateContext","large","show","$root","getRoot","root","currentForm","querySelector","on","ModalEvents","hidden","destroy","addEventListener","e","submitAction","target","closest","Selectors","actions","submit","linkBrowserAction","linkBrowser","preventDefault","then","params","filePickerCallback","catch","linkTitle","elements","urlText","linkUrl","urlEntry","value","length","dataset","useLinkAsText","updateTextToDisplay","data","Object","assign","elementid","id","showfilepicker","filepicker","isupdating","keys","url"],"mappings":";;;;;;;8SAqC4B,SAACA,YAAQC,+DAC5BA,2BAGSD,QAFVE,gBAAgBF,eAYlBE,gBAAkBC,MAAAA,eACdC,oBAAsBC,uBAAaC,OAAO,CAC5CC,KAAMC,eAAUC,KAChBC,gBAAiBC,mBAAmBX,QACpCY,OAAO,IAGXR,cAAcS,aACRC,YAAcV,cAAcW,UAC5BC,KAAOF,MAAM,GACbG,YAAcD,KAAKE,cAAc,QAEvCJ,MAAMK,GAAGC,sBAAYC,QAAQ,KACzBjB,cAAckB,aAGlBN,KAAKO,iBAAiB,SAAUC,UACtBC,aAAeD,EAAEE,OAAOC,QAAQC,mBAAUC,QAAQC,QAClDC,kBAAoBP,EAAEE,OAAOC,QAAQC,mBAAUC,QAAQG,aACzDP,eACAD,EAAES,mCACMhB,YAAajB,QACrBI,cAAckB,WAEdS,oBACAP,EAAES,8CACgBjC,OAAQ,QAAQkC,MAAMC,SACpCC,mBAAmBD,OAAQlB,YAAajB,QACjCI,cAAckB,aACtBe,kBAILC,UAAYtB,KAAKE,cAAcU,mBAAUW,SAASC,SAClDC,QAAUzB,KAAKE,cAAcU,mBAAUW,SAASG,UACtDJ,UAAUf,iBAAiB,UAAU,KAC7Be,UAAUK,MAAMC,OAAS,EACzBN,UAAUO,QAAQC,cAAgB,SAElCR,UAAUO,QAAQC,cAAgB,OAClCR,UAAUK,MAAQF,QAAQE,UAIlCF,QAAQlB,iBAAiB,SAAS,KAC9BwB,oBAAoB9B,iBAUtBN,mBAAsBX,eAClBgD,MAAO,4BAAmBhD,eAEzBiD,OAAOC,OAAO,GAAI,CACrBC,UAAWnD,OAAOoD,GAClBC,gBAAgB,2BAAerD,QAAQsD,WACvCC,WAAYN,OAAOO,KAAKR,MAAMJ,OAAS,GACxCI,OAUDZ,mBAAqB,CAACD,OAAQlB,YAAajB,aACzCmC,OAAOsB,IAAK,CACKxC,YAAYC,cAAcU,mBAAUW,SAASG,UACrDC,MAAQR,OAAOsB,sBAChBxC,YAAajB,UASvB+C,oBAAuB9B,oBACnByB,SAAWzB,YAAYC,cAAcU,mBAAUW,SAASG,UACxDF,QAAUvB,YAAYC,cAAcU,mBAAUW,SAASC,SACvB,SAAlCA,QAAQK,QAAQC,gBAChBN,QAAQG,MAAQD,SAASC"} \ No newline at end of file diff --git a/lib/editor/tiny/plugins/link/amd/src/commands.js b/lib/editor/tiny/plugins/link/amd/src/commands.js new file mode 100644 index 0000000000000..6f3bbe1ea96c1 --- /dev/null +++ b/lib/editor/tiny/plugins/link/amd/src/commands.js @@ -0,0 +1,74 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +import {get_string as getString} from 'core/str'; +import {component, linkButtonShortName, unlinkButtonShortName} from 'tiny_link/common'; +import {handleAction} from 'tiny_link/ui'; +import {toggleActiveState} from 'tiny_link/link'; + +/** + * Tiny Link commands. + * + * @module tiny_link/commands + * @copyright 2023 Huong Nguyen + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +export const getSetup = async() => { + const [ + linkButtonText, + unlinkButtonText, + ] = await Promise.all([ + getString('link', component), + getString('unlink', component), + ]); + + return (editor) => { + // Register Link button. + editor.ui.registry.addToggleButton(linkButtonShortName, { + icon: 'link', + tooltip: linkButtonText, + onAction: () => { + handleAction(editor); + }, + onSetup: toggleActiveState(editor), + }); + + // Register the Link menu item. + editor.ui.registry.addMenuItem(linkButtonShortName, { + icon: 'link', + shortcut: 'Meta+K', + text: linkButtonText, + onAction: () => { + handleAction(editor); + }, + }); + + // Register Unlink button. + editor.ui.registry.addToggleButton(unlinkButtonShortName, { + icon: 'unlink', + tooltip: unlinkButtonText, + onAction: () => { + handleAction(editor, true); + }, + onSetup: toggleActiveState(editor), + }); + + // Register shortcut. + editor.shortcuts.add('Meta+K', 'Shortcut for create link', () => { + handleAction(editor); + }); + }; +}; diff --git a/lib/editor/tiny/plugins/link/amd/src/common.js b/lib/editor/tiny/plugins/link/amd/src/common.js new file mode 100644 index 0000000000000..eeadbba032574 --- /dev/null +++ b/lib/editor/tiny/plugins/link/amd/src/common.js @@ -0,0 +1,31 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * Tiny Link common values. + * + * @module tiny_link/common + * @copyright 2023 Huong Nguyen + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +export default { + pluginName: 'tiny_link/plugin', + component: 'tiny_link', + linkButtonName: 'link', + linkButtonShortName: 'tiny_link_link', + unlinkButtonName: 'unlink', + unlinkButtonShortName: 'tiny_link_unlink', +}; diff --git a/lib/editor/tiny/plugins/link/amd/src/configuration.js b/lib/editor/tiny/plugins/link/amd/src/configuration.js new file mode 100644 index 0000000000000..89abcd2dcb313 --- /dev/null +++ b/lib/editor/tiny/plugins/link/amd/src/configuration.js @@ -0,0 +1,44 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * Tiny Link configuration. + * + * @module tiny_link/configuration + * @copyright 2023 Huong Nguyen + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +import {linkButtonShortName, unlinkButtonShortName} from 'tiny_link/common'; +import {addToolbarButtons} from 'editor_tiny/utils'; + +const configureMenu = (menu) => { + // Replace the standard Link plugin with the Moodle link. + if (menu.insert.items.match(/\blink\b/)) { + menu.insert.items = menu.insert.items.replace(/\blink\b/, linkButtonShortName); + } else { + menu.insert.items = `${linkButtonShortName} ${menu.insert.items}`; + } + + return menu; +}; + +export const configure = (instanceConfig) => { + // Update the instance configuration to add the Link option to the menus and toolbars. + return { + menu: configureMenu(instanceConfig.menu), + toolbar: addToolbarButtons(instanceConfig.toolbar, 'content', [linkButtonShortName, unlinkButtonShortName]), + }; +}; diff --git a/lib/editor/tiny/plugins/link/amd/src/link.js b/lib/editor/tiny/plugins/link/amd/src/link.js new file mode 100644 index 0000000000000..55a1150ddf69a --- /dev/null +++ b/lib/editor/tiny/plugins/link/amd/src/link.js @@ -0,0 +1,213 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * Link helper for Tiny Link plugin. + * + * @module tiny_link/link + * @copyright 2023 Huong Nguyen + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +import Templates from 'core/templates'; +import Pending from 'core/pending'; +import Selectors from 'tiny_link/selectors'; + +/** + * Handle insertion of a new link, or update of an existing one. + * + * @param {Element} currentForm + * @param {TinyMCE} editor + */ +export const setLink = (currentForm, editor) => { + const input = currentForm.querySelector(Selectors.elements.urlEntry); + let value = input.value; + + if (value !== '') { + const pendingPromise = new Pending('tiny_link/setLink'); + // We add a prefix if it is not already prefixed. + value = value.trim(); + const expr = new RegExp(/^[a-zA-Z]*\.*\/|^#|^[a-zA-Z]*:/); + if (!expr.test(value)) { + value = 'http://' + value; + } + + // Add the link. + setLinkOnSelection(currentForm, editor, value).then(pendingPromise.resolve); + } +}; + +/** + * Handle unlink of a link + * + * @param {TinyMCE} editor + */ +export const unSetLink = (editor) => { + if (editor.hasPlugin('rtc', true)) { + editor.execCommand('unlink'); + } else { + const dom = editor.dom; + const selection = editor.selection; + const bookmark = selection.getBookmark(); + const rng = selection.getRng().cloneRange(); + const startAnchorElm = dom.getParent(rng.startContainer, 'a[href]', editor.getBody()); + const endAnchorElm = dom.getParent(rng.endContainer, 'a[href]', editor.getBody()); + if (startAnchorElm) { + rng.setStartBefore(startAnchorElm); + } + if (endAnchorElm) { + rng.setEndAfter(endAnchorElm); + } + selection.setRng(rng); + editor.execCommand('unlink'); + selection.moveToBookmark(bookmark); + } +}; + +/** + * Final step setting the anchor on the selection. + * + * @param {Element} currentForm + * @param {TinyMCE} editor + * @param {String} url URL the link will point to. + */ +const setLinkOnSelection = async(currentForm, editor, url) => { + const urlText = currentForm.querySelector(Selectors.elements.urlText); + const target = currentForm.querySelector(Selectors.elements.openInNewWindow); + let textToDisplay = urlText.value.replace(/(<([^>]+)>)/gi, "").trim(); + + if (textToDisplay === '') { + textToDisplay = url; + } + + const context = { + url: url, + newwindow: target.checked, + }; + if (urlText.getAttribute('data-link-on-element')) { + context.title = textToDisplay; + context.name = editor.selection.getNode().outerHTML; + } else { + context.name = textToDisplay; + } + const {html} = await Templates.renderForPromise('tiny_link/embed_link', context); + const currentLink = getSelectedLink(editor); + if (currentLink) { + currentLink.outerHTML = html; + } else { + editor.insertContent(html); + } +}; + +/** + * Get current link data. + * + * @param {TinyMCE} editor + * @returns {{}} + */ +export const getCurrentLinkData = (editor) => { + let properties = {}; + const link = getSelectedLink(editor); + if (link) { + const url = link.getAttribute('href'); + const target = link.getAttribute('target'); + const textToDisplay = link.innerText; + const title = link.getAttribute('title'); + + if (url !== '') { + properties.url = url; + } + if (target === '_blank') { + properties.newwindow = true; + } + if (title && title !== '') { + properties.urltext = title.trim(); + } else if (textToDisplay !== '') { + properties.urltext = textToDisplay.trim(); + } + } else { + // Check if the user is selecting some text before clicking on the Link button. + const selectedNode = editor.selection.getNode(); + if (selectedNode) { + const textToDisplay = selectedNode.textContent; + if (textToDisplay !== '') { + properties.urltext = textToDisplay.trim(); + properties.hasTextToDisplay = true; + properties.hasPlainTextSelected = true; + } else { + if (selectedNode.getAttribute('data-mce-selected')) { + properties.setLinkOnElement = true; + } + } + } + } + + return properties; +}; + +/** + * Get selected link. + * + * @param {TinyMCE} editor + * @returns {Element} + */ +const getSelectedLink = (editor) => { + return getAnchorElement(editor); +}; + +/** + * Get anchor element. + * + * @param {TinyMCE} editor + * @param {Element} selectedElm + * @returns {Element} + */ +const getAnchorElement = (editor, selectedElm) => { + selectedElm = selectedElm || editor.selection.getNode(); + return editor.dom.getParent(selectedElm, 'a[href]'); +}; + +/** + * Check the current selected element is an anchor or not. + * + * @param {TinyMCE} editor + * @param {Element} selectedElm + * @returns {boolean} + */ +const isInAnchor = (editor, selectedElm) => getAnchorElement(editor, selectedElm) !== null; + +/** + * Change state of button. + * + * @param {TinyMCE} editor + * @param {function()} toggler + * @returns {function()} + */ +const toggleState = (editor, toggler) => { + editor.on('NodeChange', toggler); + return () => editor.off('NodeChange', toggler); +}; + +/** + * Change the active state of button. + * + * @param {TinyMCE} editor + * @returns {function(*): function(): *} + */ +export const toggleActiveState = (editor) => (api) => { + const updateState = () => api.setActive(!editor.mode.isReadOnly() && isInAnchor(editor, editor.selection.getNode())); + updateState(); + return toggleState(editor, updateState); +}; diff --git a/lib/editor/tiny/plugins/link/amd/src/modal.js b/lib/editor/tiny/plugins/link/amd/src/modal.js new file mode 100644 index 0000000000000..d09cbacce572e --- /dev/null +++ b/lib/editor/tiny/plugins/link/amd/src/modal.js @@ -0,0 +1,43 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * Link Modal for Tiny. + * + * @module tiny_link/modal + * @copyright 2023 Huong Nguyen + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +import Modal from 'core/modal'; +import ModalRegistry from 'core/modal_registry'; + +const LinkModal = class extends Modal { + static TYPE = 'tiny_link/modal'; + static TEMPLATE = 'tiny_link/modal'; + + registerEventListeners() { + // Call the parent registration. + super.registerEventListeners(); + + // Register to close on save/cancel. + this.registerCloseOnSave(); + this.registerCloseOnCancel(); + } +}; + +ModalRegistry.register(LinkModal.TYPE, LinkModal, LinkModal.TEMPLATE); + +export default LinkModal; diff --git a/lib/editor/tiny/plugins/link/amd/src/options.js b/lib/editor/tiny/plugins/link/amd/src/options.js new file mode 100644 index 0000000000000..68e5d93eadff0 --- /dev/null +++ b/lib/editor/tiny/plugins/link/amd/src/options.js @@ -0,0 +1,56 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * Options helper for Tiny Link plugin. + * + * @module tiny_link/options + * @copyright 2023 Huong Nguyen + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +import {getPluginOptionName} from 'editor_tiny/options'; +import {pluginName} from 'tiny_link/common'; + +const dataName = getPluginOptionName(pluginName, 'data'); +const permissionsName = getPluginOptionName(pluginName, 'permissions'); + +/** + * Register the options for the Tiny Link plugin. + * + * @param {TinyMCE} editor + */ +export const register = (editor) => { + const registerOption = editor.options.register; + + registerOption(permissionsName, { + processor: 'object', + "default": { + filepicker: false, + }, + }); + + registerOption(dataName, { + processor: 'object', + }); +}; + +/** + * Get the permissions configuration for the Tiny Link plugin. + * + * @param {TinyMCE} editor + * @returns {object} + */ +export const getPermissions = (editor) => editor.options.get(permissionsName); diff --git a/lib/editor/tiny/plugins/link/amd/src/plugin.js b/lib/editor/tiny/plugins/link/amd/src/plugin.js new file mode 100644 index 0000000000000..abe4262e4056c --- /dev/null +++ b/lib/editor/tiny/plugins/link/amd/src/plugin.js @@ -0,0 +1,54 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +import {getTinyMCE} from 'editor_tiny/loader'; +import {getPluginMetadata} from 'editor_tiny/utils'; + +import {component, pluginName} from 'tiny_link/common'; +import * as Commands from 'tiny_link/commands'; +import * as Configuration from 'tiny_link/configuration'; +import * as Options from 'tiny_link/options'; + +/** + * Tiny Link plugin for Moodle. + * + * @module tiny_link/plugin + * @copyright 2023 Huong Nguyen + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +export default new Promise(async(resolve) => { + const [ + tinyMCE, + setupCommands, + pluginMetadata, + ] = await Promise.all([ + getTinyMCE(), + Commands.getSetup(), + getPluginMetadata(component, pluginName), + ]); + + tinyMCE.PluginManager.add(`${component}/plugin`, (editor) => { + // Register options. + Options.register(editor); + // Setup the Commands (buttons, menu items, and so on). + setupCommands(editor); + + return pluginMetadata; + }); + + // Resolve the Link Plugin and include configuration. + resolve([`${component}/plugin`, Configuration]); +}); diff --git a/lib/editor/tiny/plugins/link/amd/src/selectors.js b/lib/editor/tiny/plugins/link/amd/src/selectors.js new file mode 100644 index 0000000000000..f7f0b7bbc666c --- /dev/null +++ b/lib/editor/tiny/plugins/link/amd/src/selectors.js @@ -0,0 +1,34 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * Tiny Link plugin helper function to build queryable data selectors. + * + * @module tiny_link/selectors + * @copyright 2023 Huong Nguyen + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +export default { + actions: { + submit: '[data-action="save"]', + linkBrowser: '.openlinkbrowser', + }, + elements: { + urlEntry: '.tiny_link_urlentry', + urlText: '.tiny_link_urltext', + openInNewWindow: '.tiny_link_newwindow', + } +}; diff --git a/lib/editor/tiny/plugins/link/amd/src/ui.js b/lib/editor/tiny/plugins/link/amd/src/ui.js new file mode 100644 index 0000000000000..b7db7c40df0b9 --- /dev/null +++ b/lib/editor/tiny/plugins/link/amd/src/ui.js @@ -0,0 +1,143 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * Tiny Link UI. + * + * @module tiny_link/ui + * @copyright 2023 Huong Nguyen + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +import ModalFactory from 'core/modal_factory'; +import ModalEvents from 'core/modal_events'; +import {displayFilepicker} from 'editor_tiny/utils'; +import LinkModal from 'tiny_link/modal'; +import {getPermissions} from "tiny_link/options"; +import {setLink, getCurrentLinkData, unSetLink} from "tiny_link/link"; +import Selectors from 'tiny_link/selectors'; + +/** + * Handle action. + * + * @param {TinyMCE} editor + * @param {boolean} unlink + */ +export const handleAction = (editor, unlink = false) => { + if (!unlink) { + displayDialogue(editor); + } else { + unSetLink(editor); + } +}; + +/** + * Display the link dialogue. + * + * @param {TinyMCE} editor + * @returns {Promise} + */ +const displayDialogue = async(editor) => { + const modalPromises = await ModalFactory.create({ + type: LinkModal.TYPE, + templateContext: getTemplateContext(editor), + large: false, + }); + + modalPromises.show(); + const $root = await modalPromises.getRoot(); + const root = $root[0]; + const currentForm = root.querySelector('form'); + + $root.on(ModalEvents.hidden, () => { + modalPromises.destroy(); + }); + + root.addEventListener('click', (e) => { + const submitAction = e.target.closest(Selectors.actions.submit); + const linkBrowserAction = e.target.closest(Selectors.actions.linkBrowser); + if (submitAction) { + e.preventDefault(); + setLink(currentForm, editor); + modalPromises.destroy(); + } + if (linkBrowserAction) { + e.preventDefault(); + displayFilepicker(editor, 'link').then((params) => { + filePickerCallback(params, currentForm, editor); + return modalPromises.destroy(); + }).catch(); + } + }); + + const linkTitle = root.querySelector(Selectors.elements.urlText); + const linkUrl = root.querySelector(Selectors.elements.urlEntry); + linkTitle.addEventListener('change', () => { + if (linkTitle.value.length > 0) { + linkTitle.dataset.useLinkAsText = 'false'; + } else { + linkTitle.dataset.useLinkAsText = 'true'; + linkTitle.value = linkUrl.value; + } + }); + + linkUrl.addEventListener('keyup', () => { + updateTextToDisplay(currentForm); + }); +}; + +/** + * Get template context. + * + * @param {TinyMCE} editor + * @returns {Object} + */ +const getTemplateContext = (editor) => { + const data = getCurrentLinkData(editor); + + return Object.assign({}, { + elementid: editor.id, + showfilepicker: getPermissions(editor).filepicker, + isupdating: Object.keys(data).length > 0, + }, data); +}; + +/** + * Update the dialogue after a link was selected in the File Picker. + * + * @param {Object} params + * @param {Element} currentForm + * @param {TinyMCE} editor + */ +const filePickerCallback = (params, currentForm, editor) => { + if (params.url) { + const inputUrl = currentForm.querySelector(Selectors.elements.urlEntry); + inputUrl.value = params.url; + setLink(currentForm, editor); + } +}; + +/** + * Update the text to display if the user does not provide the custom text. + * + * @param {Element} currentForm + */ +const updateTextToDisplay = (currentForm) => { + const urlEntry = currentForm.querySelector(Selectors.elements.urlEntry); + const urlText = currentForm.querySelector(Selectors.elements.urlText); + if (urlText.dataset.useLinkAsText === 'true') { + urlText.value = urlEntry.value; + } +}; diff --git a/lib/editor/tiny/plugins/link/classes/plugininfo.php b/lib/editor/tiny/plugins/link/classes/plugininfo.php new file mode 100644 index 0000000000000..65b5aca9343b6 --- /dev/null +++ b/lib/editor/tiny/plugins/link/classes/plugininfo.php @@ -0,0 +1,89 @@ +. + +/** + * Tiny Link plugin. + * + * @package tiny_link + * @copyright 2022 Huong Nguyen + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace tiny_link; + +use context; +use context_system; +use editor_tiny\editor; +use editor_tiny\plugin; +use editor_tiny\plugin_with_buttons; +use editor_tiny\plugin_with_configuration; +use editor_tiny\plugin_with_menuitems; + +/** + * Tiny link plugin. + * + * @package tiny_link + * @copyright 2023 Huong Nguyen + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class plugininfo extends plugin implements plugin_with_buttons, plugin_with_menuitems, plugin_with_configuration { + + /** + * Get a list of the buttons provided by this plugin. + * + * @return string[] + */ + public static function get_available_buttons(): array { + return [ + 'tiny_link/tiny_link_link', + 'tiny_link/tiny_link_unlink', + ]; + } + + /** + * Get a list of the menu items provided by this plugin. + * + * @return string[] + */ + public static function get_available_menuitems(): array { + return [ + 'tiny_link/tiny_link_link', + ]; + } + + /** + * Get a list of the menu items provided by this plugin. + * + * @param context $context The context that the editor is used within + * @param array $options The options passed in when requesting the editor + * @param array $fpoptions The filepicker options passed in when requesting the editor + * @param editor $editor The editor instance in which the plugin is initialised + * @return array + */ + public static function get_plugin_configuration_for_context( + context $context, + array $options, + array $fpoptions, + ?editor $editor = null + ): array { + // TODO Fetch the actual permissions. + $permissions['filepicker'] = true; + + return [ + 'permissions' => $permissions, + ]; + } +} diff --git a/lib/editor/tiny/plugins/link/classes/privacy/provider.php b/lib/editor/tiny/plugins/link/classes/privacy/provider.php new file mode 100644 index 0000000000000..ec0ddd331813b --- /dev/null +++ b/lib/editor/tiny/plugins/link/classes/privacy/provider.php @@ -0,0 +1,45 @@ +. + +/** + * Privacy Subsystem implementation for the Link plugin for TinyMCE. + * + * @package tiny_link + * @copyright 2022 Huong Nguyen + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace tiny_link\privacy; + +/** + * Privacy Subsystem implementation for the Link plugin for TinyMCE. + * + * @package tiny_link + * @copyright 2023 Huong Nguyen + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class provider implements \core_privacy\local\metadata\null_provider { + + /** + * Get the language string identifier with the component's language + * file to explain why this plugin stores no data. + * + * @return string + */ + public static function get_reason(): string { + return 'privacy:metadata'; + } +} diff --git a/lib/editor/tiny/plugins/link/lang/en/tiny_link.php b/lib/editor/tiny/plugins/link/lang/en/tiny_link.php new file mode 100644 index 0000000000000..756b0382f85d1 --- /dev/null +++ b/lib/editor/tiny/plugins/link/lang/en/tiny_link.php @@ -0,0 +1,34 @@ +. + +/** + * Strings for component 'tiny_link', language 'en'. + * + * @package tiny_link + * @copyright 2023 Huong Nguyen + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +$string['browserepositories'] = 'Browse repositories...'; +$string['createlink'] = 'Create link'; +$string['enterurl'] = 'Enter a URL'; +$string['openinnewwindow'] = 'Open in new window'; +$string['pluginname'] = 'Tiny link'; +$string['link'] = 'Link'; +$string['unlink'] = 'Unlink'; +$string['updatelink'] = 'Update link'; +$string['privacy:metadata'] = 'The link plugin for TinyMCE does not store any personal data.'; +$string['texttodisplay'] = 'Text to display'; diff --git a/lib/editor/tiny/plugins/link/templates/embed_link.mustache b/lib/editor/tiny/plugins/link/templates/embed_link.mustache new file mode 100644 index 0000000000000..3720cc6eafa1c --- /dev/null +++ b/lib/editor/tiny/plugins/link/templates/embed_link.mustache @@ -0,0 +1,33 @@ +{{! + This file is part of Moodle - http://moodle.org/ + + Moodle is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Moodle is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Moodle. If not, see . +}} +{{! + @template tiny_link/embed_link + + Embed media link template. + + Example context (json): + { + + } +}} +{{! + }}{{#name}}{{{.}}}{{/name}}{{! + }}{{^name}}{{url}}{{/name}}{{! +}} diff --git a/lib/editor/tiny/plugins/link/templates/modal.mustache b/lib/editor/tiny/plugins/link/templates/modal.mustache new file mode 100644 index 0000000000000..bd27117871d39 --- /dev/null +++ b/lib/editor/tiny/plugins/link/templates/modal.mustache @@ -0,0 +1,89 @@ +{{! + This file is part of Moodle - http://moodle.org/ + + Moodle is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Moodle is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Moodle. If not, see . +}} +{{! + @template tiny_link/modal + + Modal to manage a link within the Tiny Editor. + + Classes required for JS: + * none + + Data attributes required for JS: + * none + + Context variables required for this template: + + Example context (json): + { + "elementid": "exampleId", + "setLinkOnElement": false, + "showfilepicker": true, + "urltext": "Abc", + "url": "https://moodle.org/", + "newwindow": false + } +}} +{{< core/modal }} + + {{$title}} + {{#str}} createlink, tiny_link {{/str}} + {{/title}} + + {{$body}} +
+
+ + +
+
+ + {{#showfilepicker}} +
+ + + + +
+ {{/showfilepicker}} + {{^showfilepicker}} + + {{/showfilepicker}} +
+
+ + +
+
+ {{/body}} + + {{$footer}} + + {{/footer}} + +{{/ core/modal }} diff --git a/lib/editor/tiny/plugins/link/tests/behat/link.feature b/lib/editor/tiny/plugins/link/tests/behat/link.feature new file mode 100644 index 0000000000000..45109fe9e7e12 --- /dev/null +++ b/lib/editor/tiny/plugins/link/tests/behat/link.feature @@ -0,0 +1,158 @@ +@editor @editor_tiny @tiny_link @_file_upload +Feature: Add links to TinyMCE + To write rich text - I need to add links. + + @javascript + Scenario: Insert a link + Given the following "blocks" exist: + | blockname | contextlevel | reference | pagetypepattern | defaultregion | + | private_files | System | 1 | my-index | side-post | + And I log in as "admin" + And I follow "Manage private files..." + And I upload "lib/editor/tiny/tests/behat/fixtures/moodle-logo.png" file to "Files" filemanager + And I click on "Save changes" "button" + And I open my profile in edit mode + And I set the field "Description" to "Super cool" + When I select the "p" element in position "0" of the "Description" TinyMCE editor + And I click on the "Link" button for the "Description" TinyMCE editor + Then the field "Text to display" matches value "Super cool" + And I click on "Browse repositories..." "button" in the "Create link" "dialogue" + And I select "Private files" repository in file picker + And I click on "moodle-logo.png" "link" + And I click on "Select this file" "button" + And I click on "Update profile" "button" + And I follow "Preferences" in the user menu + And I follow "Editor preferences" + And I set the field "Text editor" to "Plain text area" + And I press "Save changes" + And I click on "Edit profile" "link" in the "region-main" "region" + And I should see "Super cool" + + @javascript + Scenario: Insert a link without providing text to display + Given I log in as "admin" + When I open my profile in edit mode + And I click on the "Link" button for the "Description" TinyMCE editor + And I set the field "URL" to "https://moodle.org/" + Then the field "Text to display" matches value "https://moodle.org/" + And I click on "Create link" "button" in the "Create link" "dialogue" + And the field "Description" matches value "

https://moodle.org/

" + And I select the "a" element in position "0" of the "Description" TinyMCE editor + And I click on the "Link" button for the "Description" TinyMCE editor + And the field "Text to display" matches value "https://moodle.org/" + And the field "URL" matches value "https://moodle.org/" + And I click on "Close" "button" in the "Create link" "dialogue" + + @javascript + Scenario: Insert a link with providing text to display + Given I log in as "admin" + When I open my profile in edit mode + And I click on "Link" "button" + And I set the field "Text to display" to "Moodle - Open-source learning platform" + And I set the field "Enter a URL" to "https://moodle.org/" + And I click on "Create link" "button" in the "Create link" "dialogue" + Then the field "Description" matches value "

Moodle - Open-source learning platform

" + And I select the "a" element in position "0" of the "Description" TinyMCE editor + And I click on the "Link" button for the "Description" TinyMCE editor + And the field "Text to display" matches value "Moodle - Open-source learning platform" + And the field "Enter a URL" matches value "https://moodle.org/" + And I click on "Close" "button" in the "Create link" "dialogue" + + @javascript + Scenario: Edit a link that already had a custom text to display + Given I log in as "admin" + And I follow "Preferences" in the user menu + And I follow "Editor preferences" + And I set the field "Text editor" to "Plain text area" + And I press "Save changes" + And I click on "Edit profile" "link" in the "region-main" "region" + And I set the field "Description" to "Moodle - Open-source learning platform" + And I click on "Update profile" "button" + And I follow "Preferences" in the user menu + And I follow "Editor preferences" + And I set the field "Text editor" to "TinyMCE editor" + And I press "Save changes" + When I click on "Edit profile" "link" in the "region-main" "region" + Then the field "Description" matches value "

Moodle - Open-source learning platform

" + And I select the "a" element in position "0" of the "Description" TinyMCE editor + And I click on the "Link" button for the "Description" TinyMCE editor + And the field "Text to display" matches value "Moodle - Open-source learning platform" + And the field "Enter a URL" matches value "https://moodle.org/" + + @javascript + Scenario: Insert and update link in the TinyMCE editor + Given I log in as "admin" + When I open my profile in edit mode + And I click on "Link" "button" + And I set the field "Text to display" to "Moodle - Open-source learning platform" + And I set the field "Enter a URL" to "https://moodle.org/" + And I click on "Create link" "button" in the "Create link" "dialogue" + Then the field "Description" matches value "

Moodle - Open-source learning platform

" + And I select the "a" element in position "0" of the "Description" TinyMCE editor + And I click on the "Link" button for the "Description" TinyMCE editor + And the field "Text to display" matches value "Moodle - Open-source learning platform" + And the field "Enter a URL" matches value "https://moodle.org/" + And I set the field "Enter a URL" to "https://moodle.com/" + And "Create link" "button" should not exist in the "Create link" "dialogue" + And "Update link" "button" should exist in the "Create link" "dialogue" + And I click on "Update link" "button" in the "Create link" "dialogue" + And the field "Description" matches value "

Moodle - Open-source learning platform

" + + @javascript + Scenario: Insert a link for an image using TinyMCE editor + Given I log in as "admin" + And I follow "Private files" in the user menu + And I upload "lib/editor/tiny/tests/behat/fixtures/moodle-logo.png" file to "Files" filemanager + And I click on "Save changes" "button" + And I open my profile in edit mode + And I click on the "Image" button for the "Description" TinyMCE editor + And I click on "Browse repositories..." "button" in the "Image properties" "dialogue" + And I select "Private files" repository in file picker + And I click on "moodle-logo.png" "link" + And I click on "Select this file" "button" + And I set the field "Describe this image for someone who cannot see it" to "It's the Moodle" + And I click on "Save image" "button" in the "Image properties" "dialogue" + And I select the "img" element in position "0" of the "Description" TinyMCE editor + And I click on the "Link" button for the "Description" TinyMCE editor + And I set the field "Enter a URL" to "https://moodle.org/" + And I set the field "Text to display" to "Moodle - Open-source learning platform" + And I click on "Update link" "button" in the "Create link" "dialogue" + # TODO: Verify the HTML by the improved code plugin in MDL-75265 + And I click on "Update profile" "button" + And I follow "Preferences" in the user menu + And I follow "Editor preferences" + And I set the field "Text editor" to "Plain text area" + And I press "Save changes" + When I click on "Edit profile" "link" in the "region-main" "region" + Then I should see "Moodle - Open-source learning platform" + And I click on "Update profile" "button" + And I follow "Preferences" in the user menu + And I follow "Editor preferences" + And I set the field "Text editor" to "TinyMCE editor" + And I press "Save changes" + And I click on "Edit profile" "link" in the "region-main" "region" + And I select the "a" element in position "0" of the "Description" TinyMCE editor + When I click on the "Unlink" button for the "Description" TinyMCE editor + Then the field "Description" matches value "

Moodle - Open-source learning platform

" diff --git a/lib/editor/tiny/plugins/link/version.php b/lib/editor/tiny/plugins/link/version.php new file mode 100644 index 0000000000000..01f37cc35cded --- /dev/null +++ b/lib/editor/tiny/plugins/link/version.php @@ -0,0 +1,29 @@ +. + +/** + * Tiny Link plugin version details. + * + * @package tiny_link + * @copyright 2023 Huong Nguyen + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$plugin->version = 2022112800; +$plugin->requires = 2022111800; +$plugin->component = 'tiny_link';