From 6ea99ba11e2fc3b2376ffef7ad303f66b5e0b2f7 Mon Sep 17 00:00:00 2001 From: yusufwib01 Date: Mon, 21 Jul 2025 16:38:54 +0700 Subject: [PATCH] MDL-85718 tiny_link: Improve image link behavior in TinyMCE --- lib/editor/tiny/plugins/link/amd/build/link.min.js | 2 +- lib/editor/tiny/plugins/link/amd/build/link.min.js.map | 2 +- lib/editor/tiny/plugins/link/amd/build/ui.min.js | 2 +- lib/editor/tiny/plugins/link/amd/build/ui.min.js.map | 2 +- lib/editor/tiny/plugins/link/amd/src/link.js | 6 ++++-- lib/editor/tiny/plugins/link/amd/src/ui.js | 2 +- lib/editor/tiny/plugins/link/templates/modal.mustache | 3 ++- lib/editor/tiny/plugins/link/tests/behat/link.feature | 2 +- 8 files changed, 12 insertions(+), 9 deletions(-) diff --git a/lib/editor/tiny/plugins/link/amd/build/link.min.js b/lib/editor/tiny/plugins/link/amd/build/link.min.js index 5c802133e08..9265807d147 100644 --- a/lib/editor/tiny/plugins/link/amd/build/link.min.js +++ b/lib/editor/tiny/plugins/link/amd/build/link.min.js @@ -5,6 +5,6 @@ define("tiny_link/link",["exports","core/templates","core/pending","tiny_link/se * @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=getTextSelection(editor);""!==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]")),getTextSelection=editor=>{let selText="";const sel=editor.selection.getSel(),rangeCount=sel.rangeCount;if(rangeCount){let rangeTexts=[];for(let i=0;iapi=>{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)}})); + */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),selectedNode=editor.selection.getNode(),isImage=selectedNode&&"img"===selectedNode.nodeName.toLowerCase();let textToDisplay=urlText.value.replace(/(<([^>]+)>)/gi,"").trim();""===textToDisplay&&(textToDisplay=url);const context={url:url,newwindow:target.checked};urlText.getAttribute("data-link-on-element")||isImage?(context.title=textToDisplay,context.name=selectedNode.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=getTextSelection(editor);""!==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]")),getTextSelection=editor=>{let selText="";const sel=editor.selection.getSel(),rangeCount=sel.rangeCount;if(rangeCount){let rangeTexts=[];for(let i=0;iapi=>{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 index 10615d8b9ac..2e86a65a877 100644 --- a/lib/editor/tiny/plugins/link/amd/build/link.min.js.map +++ b/lib/editor/tiny/plugins/link/amd/build/link.min.js.map @@ -1 +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 = getTextSelection(editor);\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 * Get only the selected text.\n * In some cases, window.getSelection() is not run as expected. We should only get the text value\n * For ex: \"XYZ\"Some text here\n * window.getSelection() will return XYZSome text here\n *\n * @param {TinyMCE} editor\n * @return {string} Selected text\n */\nconst getTextSelection = (editor) => {\n let selText = '';\n const sel = editor.selection.getSel();\n const rangeCount = sel.rangeCount;\n if (rangeCount) {\n let rangeTexts = [];\n for (let i = 0; i < rangeCount; ++i) {\n rangeTexts.push('' + sel.getRangeAt(i));\n }\n selText = rangeTexts.join('');\n }\n\n return selText;\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","getTextSelection","hasTextToDisplay","hasPlainTextSelected","setLinkOnElement","getAnchorElement","selectedElm","selText","sel","getSel","rangeCount","rangeTexts","i","push","getRangeAt","join","api","updateState","setActive","mode","isReadOnly","isInAnchor","toggler","on","off","toggleState"],"mappings":"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"} \ No newline at end of file +{"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 const selectedNode = editor.selection.getNode();\n const isImage = selectedNode && selectedNode.nodeName.toLowerCase() === 'img';\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') || isImage) {\n context.title = textToDisplay;\n context.name = selectedNode.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 = getTextSelection(editor);\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 * Get only the selected text.\n * In some cases, window.getSelection() is not run as expected. We should only get the text value\n * For ex: \"XYZ\"Some text here\n * window.getSelection() will return XYZSome text here\n *\n * @param {TinyMCE} editor\n * @return {string} Selected text\n */\nconst getTextSelection = (editor) => {\n let selText = '';\n const sel = editor.selection.getSel();\n const rangeCount = sel.rangeCount;\n if (rangeCount) {\n let rangeTexts = [];\n for (let i = 0; i < rangeCount; ++i) {\n rangeTexts.push('' + sel.getRangeAt(i));\n }\n selText = rangeTexts.join('');\n }\n\n return selText;\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","selectedNode","getNode","isImage","nodeName","toLowerCase","textToDisplay","replace","context","newwindow","checked","getAttribute","title","name","outerHTML","html","Templates","renderForPromise","currentLink","getSelectedLink","insertContent","properties","link","innerText","urltext","getTextSelection","hasTextToDisplay","hasPlainTextSelected","setLinkOnElement","getAnchorElement","selectedElm","selText","sel","getSel","rangeCount","rangeTexts","i","push","getRangeAt","join","api","updateState","setActive","mode","isReadOnly","isInAnchor","toggler","on","off","toggleState"],"mappings":"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"} \ 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 index e73e174887d..10c3c39c547 100644 --- a/lib/editor/tiny/plugins/link/amd/build/ui.min.js +++ b/lib/editor/tiny/plugins/link/amd/build/ui.min.js @@ -5,6 +5,6 @@ define("tiny_link/ui",["exports","editor_tiny/options","editor_tiny/utils","tiny * @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=_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 modal=await _modal.default.create({templateContext:getTemplateContext(editor)}),root=(await modal.getRoot())[0],currentForm=root.querySelector("form");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),modal.destroy()),linkBrowserAction&&(e.preventDefault(),(0,_utils.displayFilepicker)(editor,"link").then((params=>(filePickerCallback(params,currentForm,editor),modal.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,_options2.getPermissions)(editor).filepicker&&void 0!==(0,_options.getFilePicker)(editor,"link"),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)}})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.handleAction=void 0,_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 modal=await _modal.default.create({templateContext:getTemplateContext(editor)}),root=(await modal.getRoot())[0],currentForm=root.querySelector("form");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),modal.destroy()),linkBrowserAction&&(e.preventDefault(),(0,_utils.displayFilepicker)(editor,"link").then((params=>(filePickerCallback(params,currentForm,editor),modal.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,_options2.getPermissions)(editor).filepicker&&void 0!==(0,_options.getFilePicker)(editor,"link"),isupdating:!!data.url},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 index 2d26432eb5f..e1fc3c1482e 100644 --- a/lib/editor/tiny/plugins/link/amd/build/ui.min.js.map +++ b/lib/editor/tiny/plugins/link/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 - 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 {getFilePicker} from 'editor_tiny/options';\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 modal = await LinkModal.create({\n templateContext: getTemplateContext(editor),\n });\n\n const $root = await modal.getRoot();\n const root = $root[0];\n const currentForm = root.querySelector('form');\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 modal.destroy();\n }\n if (linkBrowserAction) {\n e.preventDefault();\n displayFilepicker(editor, 'link').then((params) => {\n filePickerCallback(params, currentForm, editor);\n return modal.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 (typeof getFilePicker(editor, 'link') !== 'undefined'),\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","modal","LinkModal","create","templateContext","getTemplateContext","root","getRoot","currentForm","querySelector","addEventListener","e","submitAction","target","closest","Selectors","actions","submit","linkBrowserAction","linkBrowser","preventDefault","destroy","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":";;;;;;;oMAoC4B,SAACA,YAAQC,+DAC5BA,2BAGSD,QAFVE,gBAAgBF,eAYlBE,gBAAkBC,MAAAA,eACdC,YAAcC,eAAUC,OAAO,CACjCC,gBAAiBC,mBAAmBR,UAIlCS,YADcL,MAAMM,WACP,GACbC,YAAcF,KAAKG,cAAc,QAEvCH,KAAKI,iBAAiB,SAAUC,UACtBC,aAAeD,EAAEE,OAAOC,QAAQC,mBAAUC,QAAQC,QAClDC,kBAAoBP,EAAEE,OAAOC,QAAQC,mBAAUC,QAAQG,aACzDP,eACAD,EAAES,mCACMZ,YAAaX,QACrBI,MAAMoB,WAENH,oBACAP,EAAES,8CACgBvB,OAAQ,QAAQyB,MAAMC,SACpCC,mBAAmBD,OAAQf,YAAaX,QACjCI,MAAMoB,aACdI,kBAILC,UAAYpB,KAAKG,cAAcM,mBAAUY,SAASC,SAClDC,QAAUvB,KAAKG,cAAcM,mBAAUY,SAASG,UACtDJ,UAAUhB,iBAAiB,UAAU,KAC7BgB,UAAUK,MAAMC,OAAS,EACzBN,UAAUO,QAAQC,cAAgB,SAElCR,UAAUO,QAAQC,cAAgB,OAClCR,UAAUK,MAAQF,QAAQE,UAIlCF,QAAQnB,iBAAiB,SAAS,KAC9ByB,oBAAoB3B,iBAUtBH,mBAAsBR,eAClBuC,MAAO,4BAAmBvC,eAEzBwC,OAAOC,OAAO,GAAI,CACrBC,UAAW1C,OAAO2C,GAClBC,gBAAgB,4BAAe5C,QAAQ6C,iBACO,KAAlC,0BAAc7C,OAAQ,QAClC8C,WAAYN,OAAOO,KAAKR,MAAMJ,OAAS,GACxCI,OAUDZ,mBAAqB,CAACD,OAAQf,YAAaX,aACzC0B,OAAOsB,IAAK,CACKrC,YAAYC,cAAcM,mBAAUY,SAASG,UACrDC,MAAQR,OAAOsB,sBAChBrC,YAAaX,UASvBsC,oBAAuB3B,oBACnBsB,SAAWtB,YAAYC,cAAcM,mBAAUY,SAASG,UACxDF,QAAUpB,YAAYC,cAAcM,mBAAUY,SAASC,SACvB,SAAlCA,QAAQK,QAAQC,gBAChBN,QAAQG,MAAQD,SAASC"} \ No newline at end of file +{"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 {getFilePicker} from 'editor_tiny/options';\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 modal = await LinkModal.create({\n templateContext: getTemplateContext(editor),\n });\n\n const $root = await modal.getRoot();\n const root = $root[0];\n const currentForm = root.querySelector('form');\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 modal.destroy();\n }\n if (linkBrowserAction) {\n e.preventDefault();\n displayFilepicker(editor, 'link').then((params) => {\n filePickerCallback(params, currentForm, editor);\n return modal.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 (typeof getFilePicker(editor, 'link') !== 'undefined'),\n isupdating: !!data.url,\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","modal","LinkModal","create","templateContext","getTemplateContext","root","getRoot","currentForm","querySelector","addEventListener","e","submitAction","target","closest","Selectors","actions","submit","linkBrowserAction","linkBrowser","preventDefault","destroy","then","params","filePickerCallback","catch","linkTitle","elements","urlText","linkUrl","urlEntry","value","length","dataset","useLinkAsText","updateTextToDisplay","data","Object","assign","elementid","id","showfilepicker","filepicker","isupdating","url"],"mappings":";;;;;;;oMAoC4B,SAACA,YAAQC,+DAC5BA,2BAGSD,QAFVE,gBAAgBF,eAYlBE,gBAAkBC,MAAAA,eACdC,YAAcC,eAAUC,OAAO,CACjCC,gBAAiBC,mBAAmBR,UAIlCS,YADcL,MAAMM,WACP,GACbC,YAAcF,KAAKG,cAAc,QAEvCH,KAAKI,iBAAiB,SAAUC,UACtBC,aAAeD,EAAEE,OAAOC,QAAQC,mBAAUC,QAAQC,QAClDC,kBAAoBP,EAAEE,OAAOC,QAAQC,mBAAUC,QAAQG,aACzDP,eACAD,EAAES,mCACMZ,YAAaX,QACrBI,MAAMoB,WAENH,oBACAP,EAAES,8CACgBvB,OAAQ,QAAQyB,MAAMC,SACpCC,mBAAmBD,OAAQf,YAAaX,QACjCI,MAAMoB,aACdI,kBAILC,UAAYpB,KAAKG,cAAcM,mBAAUY,SAASC,SAClDC,QAAUvB,KAAKG,cAAcM,mBAAUY,SAASG,UACtDJ,UAAUhB,iBAAiB,UAAU,KAC7BgB,UAAUK,MAAMC,OAAS,EACzBN,UAAUO,QAAQC,cAAgB,SAElCR,UAAUO,QAAQC,cAAgB,OAClCR,UAAUK,MAAQF,QAAQE,UAIlCF,QAAQnB,iBAAiB,SAAS,KAC9ByB,oBAAoB3B,iBAUtBH,mBAAsBR,eAClBuC,MAAO,4BAAmBvC,eAEzBwC,OAAOC,OAAO,GAAI,CACrBC,UAAW1C,OAAO2C,GAClBC,gBAAgB,4BAAe5C,QAAQ6C,iBACO,KAAlC,0BAAc7C,OAAQ,QAClC8C,aAAcP,KAAKQ,KACpBR,OAUDZ,mBAAqB,CAACD,OAAQf,YAAaX,aACzC0B,OAAOqB,IAAK,CACKpC,YAAYC,cAAcM,mBAAUY,SAASG,UACrDC,MAAQR,OAAOqB,sBAChBpC,YAAaX,UASvBsC,oBAAuB3B,oBACnBsB,SAAWtB,YAAYC,cAAcM,mBAAUY,SAASG,UACxDF,QAAUpB,YAAYC,cAAcM,mBAAUY,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/link.js b/lib/editor/tiny/plugins/link/amd/src/link.js index 433818d54fd..94a0f1e01c9 100644 --- a/lib/editor/tiny/plugins/link/amd/src/link.js +++ b/lib/editor/tiny/plugins/link/amd/src/link.js @@ -86,6 +86,8 @@ export const unSetLink = (editor) => { const setLinkOnSelection = async(currentForm, editor, url) => { const urlText = currentForm.querySelector(Selectors.elements.urlText); const target = currentForm.querySelector(Selectors.elements.openInNewWindow); + const selectedNode = editor.selection.getNode(); + const isImage = selectedNode && selectedNode.nodeName.toLowerCase() === 'img'; let textToDisplay = urlText.value.replace(/(<([^>]+)>)/gi, "").trim(); if (textToDisplay === '') { @@ -96,9 +98,9 @@ const setLinkOnSelection = async(currentForm, editor, url) => { url: url, newwindow: target.checked, }; - if (urlText.getAttribute('data-link-on-element')) { + if (urlText.getAttribute('data-link-on-element') || isImage) { context.title = textToDisplay; - context.name = editor.selection.getNode().outerHTML; + context.name = selectedNode.outerHTML; } else { context.name = textToDisplay; } diff --git a/lib/editor/tiny/plugins/link/amd/src/ui.js b/lib/editor/tiny/plugins/link/amd/src/ui.js index 10a26a7b096..a9e42daf172 100644 --- a/lib/editor/tiny/plugins/link/amd/src/ui.js +++ b/lib/editor/tiny/plugins/link/amd/src/ui.js @@ -103,7 +103,7 @@ const getTemplateContext = (editor) => { elementid: editor.id, showfilepicker: getPermissions(editor).filepicker && (typeof getFilePicker(editor, 'link') !== 'undefined'), - isupdating: Object.keys(data).length > 0, + isupdating: !!data.url, }, data); }; diff --git a/lib/editor/tiny/plugins/link/templates/modal.mustache b/lib/editor/tiny/plugins/link/templates/modal.mustache index f1880fd68ef..16e448c54dc 100644 --- a/lib/editor/tiny/plugins/link/templates/modal.mustache +++ b/lib/editor/tiny/plugins/link/templates/modal.mustache @@ -34,7 +34,8 @@ "showfilepicker": true, "urltext": "Abc", "url": "https://moodle.org/", - "newwindow": false + "newwindow": false, + "isupdating": true } }} {{< core/modal }} diff --git a/lib/editor/tiny/plugins/link/tests/behat/link.feature b/lib/editor/tiny/plugins/link/tests/behat/link.feature index 76c1907cc5e..40e17848cd8 100644 --- a/lib/editor/tiny/plugins/link/tests/behat/link.feature +++ b/lib/editor/tiny/plugins/link/tests/behat/link.feature @@ -113,7 +113,7 @@ Feature: Add links to TinyMCE 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" + And I click on "Create 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