From e16ff43cfdf22c19df589817db92f7d5eacb180d Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Mon, 8 Jul 2024 23:18:03 +0800 Subject: [PATCH] MDL-77706 atto_link: Work around Mozilla bug 1906559 This upstream bug prevents creation of an anchor with a hyperlink where the content has a block-like display. The workaround is to wrap the content in a span, set the display to inline, call the `createLink` command on the span, move the content out of the span, and then remove it. This is only done for Firefox-based browsers. --- .../moodle-atto_link-button-debug.js | 29 +++++++++++++++++-- .../moodle-atto_link-button-min.js | 3 +- .../moodle-atto_link-button.js | 29 +++++++++++++++++-- .../plugins/link/yui/src/button/js/button.js | 29 +++++++++++++++++-- 4 files changed, 83 insertions(+), 7 deletions(-) diff --git a/lib/editor/atto/plugins/link/yui/build/moodle-atto_link-button/moodle-atto_link-button-debug.js b/lib/editor/atto/plugins/link/yui/build/moodle-atto_link-button/moodle-atto_link-button-debug.js index e71f4d768e5..f8bc8a04b5c 100644 --- a/lib/editor/atto/plugins/link/yui/build/moodle-atto_link-button/moodle-atto_link-button-debug.js +++ b/lib/editor/atto/plugins/link/yui/build/moodle-atto_link-button/moodle-atto_link-button-debug.js @@ -317,8 +317,33 @@ Y.namespace('M.atto_link').Button = Y.Base.create('button', Y.M.editor_atto.Edit selectednode = host.insertContentAtFocusPoint(link.get('outerHTML')); host.setSelection(host.getSelectionFromNode(selectednode)); } else { - document.execCommand('unlink', false, null); - document.execCommand('createLink', false, url); + if (Y.UA.gecko > 0) { + // For Firefox / Gecko we need to wrap the selection in a span so we can surround it with an anchor. + // This relates to https://bugzilla.mozilla.org/show_bug.cgi?id=1906559. + var originalSelection = document.getSelection(); + var wrapper = document.createElement('span'); + wrapper.setAttribute('data-wrapper', ''); + wrapper.style.display = 'inline'; + + var i; + for (i = 0; i < originalSelection.rangeCount; i++) { + originalSelection.getRangeAt(i).surroundContents(wrapper); + } + host.setSelection(host.getSelectionFromNode(Y.one(wrapper))); + + document.execCommand('unlink', false, null); + document.execCommand('createLink', false, url); + + var anchorNode = wrapper.parentNode; + wrapper.children.forEach(function(child) { + anchorNode.appendChild(child); + }); + wrapper.remove(); + + } else { + document.execCommand('unlink', false, null); + document.execCommand('createLink', false, url); + } // Now set the target. selectednode = host.getSelectionParentNode(); diff --git a/lib/editor/atto/plugins/link/yui/build/moodle-atto_link-button/moodle-atto_link-button-min.js b/lib/editor/atto/plugins/link/yui/build/moodle-atto_link-button/moodle-atto_link-button-min.js index f585967fa68..2f4ef75749d 100644 --- a/lib/editor/atto/plugins/link/yui/build/moodle-atto_link-button/moodle-atto_link-button-min.js +++ b/lib/editor/atto/plugins/link/yui/build/moodle-atto_link-button/moodle-atto_link-button-min.js @@ -1 +1,2 @@ -YUI.add("moodle-atto_link-button",function(s,t){var n="atto_link",i={NEWWINDOW:"atto_link_openinnewwindow",URLINPUT:"atto_link_urlentry",URLTEXT:"atto_link_urltext"},a=".atto_link_openinnewwindow",o=".atto_link_urlentry",c=".atto_link_urltext",l=".submit",r=".openlinkbrowser";s.namespace("M.atto_link").Button=s.Base.create("button",s.M.editor_atto.EditorPlugin,[],{_currentSelection:null,_content:null,_hasTextToDisplay:!1,_hasPlainTextSelected:!1,initializer:function(){this.addButton({icon:"e/insert_edit_link",keys:"75",callback:this._displayDialogue,tags:"a",tagMatchRequiresAll:!1}),this.addButton({buttonName:"unlink",callback:this._unlink,icon:"e/remove_link",title:"unlink",tags:"a",tagMatchRequiresAll:!1})},_displayDialogue:function(){var t;this._currentSelection=this.get("host").getSelection(),!1!==this._currentSelection&&((t=this.getDialogue({headerContent:M.util.get_string("createlink",n),width:"auto",focusAfterHide:!0,focusOnShowSelector:o})).set("bodyContent",this._getDialogueContent()),this._resolveAnchors(),t.show())},_resolveAnchors:function(){var t,e,n,i=this.get("host").getSelectionParentNode();i&&(0<(i=this._findSelectedAnchors(s.one(i))).length?(i=i[0],this._currentSelection=this.get("host").getSelectionFromNode(i),t=i.getAttribute("href"),e=i.getAttribute("target"),n=i.get("innerText"),i=i.getAttribute("title"),""!==t&&this._content.one(o).setAttribute("value",t),""!==n?this._content.one(c).set("value",n):""!==i&&this._content.one(c).set("value",i),"_blank"===e?this._content.one(a).setAttribute("checked","checked"):this._content.one(a).removeAttribute("checked")):""!==(n=this._getTextSelection())&&(this._hasTextToDisplay=!0,this._hasPlainTextSelected=!0,this._content.one(c).set("value",n)))},_filepickerCallback:function(t){this.getDialogue().set("focusAfterHide",null).hide(),""!==t.url&&(this._setLinkOnSelection(t.url),this.markUpdated())},_setLink:function(t){t.preventDefault(),this.getDialogue({focusAfterHide:null}).hide(),""!==(t=this._content.one(o).get("value"))&&(t=t.trim(),new RegExp(/^[a-zA-Z]*\.*\/|^#|^[a-zA-Z]*:/).test(t)||(t="http://"+t),this._setLinkOnSelection(t),this.markUpdated())},_setLinkOnSelection:function(t){var e,n,i,o,l=this.get("host");if(this.editor.focus(),l.setSelection(this._currentSelection),n=!this._currentSelection[0].collapsed,i=this._content.one(c),""===(o=i.get("value").replace(/(<([^>]+)>)/gi,"").trim())&&(o=t),n?(document.execCommand("unlink",!1,null),document.execCommand("createLink",!1,t),e=l.getSelectionParentNode()):((i=s.Node.create(""+o+"")).setAttribute("href",t),e=l.insertContentAtFocusPoint(i.get("outerHTML")),l.setSelection(l.getSelectionFromNode(e))),e)return t=this._findSelectedAnchors(s.one(e)),s.Array.each(t,function(t){this._content.one(a).get("checked")?t.setAttribute("target","_blank"):t.removeAttribute("target"),n&&o&&(this._hasPlainTextSelected?t.set("innerText",o):t.setAttribute("title",o))},this),e},_findSelectedAnchors:function(t){var e,n,i=t.get("tagName");return i&&"a"===i.toLowerCase()?[t]:(n=[],t.all("a").each(function(t){!e&&this.get("host").selectionContainsNode(t)&&n.push(t)},this),0
{{#if showFilepicker}}
{{else}}
{{/if}}

');return this._content=s.Node.create(e({showFilepicker:t,component:n,CSS:i})),this._content.one(o).on("keyup",this._updateTextToDisplay,this),this._content.one(o).on("change",this._updateTextToDisplay,this),this._content.one(c).on("keyup",this._setTextToDisplayState,this),this._content.one(l).on("click",this._setLink,this),t&&this._content.one(r).on("click",function(t){t.preventDefault(),this.get("host").showFilepicker("link",this._filepickerCallback,this)},this),this._content},_unlink:function(){var e=this.get("host"),t=e.getSelection();t&&t.length&&(t[0].startOffset===t[0].endOffset?(t=e.getSelectedNodes())&&(t.each(function(t){t=t.ancestor("a",!0);t&&(e.setSelection(e.getSelectionFromNode(t)),document.execCommand("unlink",!1,null))},this),this.markUpdated()):(document.execCommand("unlink",!1,null),this.markUpdated()))},_setTextToDisplayState:function(){var t=this._content.one(c).get("value");this._hasTextToDisplay=""!==t},_updateTextToDisplay:function(){var t=this._content.one(o),e=this._content.one(c),t=t.get("value");this._hasTextToDisplay||e.set("value",t)},_getTextSelection:function(){var t,e,n="",i=window.getSelection(),o=i.rangeCount;if(o){for(t=[],e=0;e]+)>)/gi,"").trim())&&(o=t),n){if(0"+o+"")).setAttribute("href",t),e=r.insertContentAtFocusPoint(i.get("outerHTML")),r.setSelection(r.getSelectionFromNode(e));if(e)return t=this._findSelectedAnchors(u.one(e)),u.Array.each(t,function(t){this._content.one(h).get("checked")?t.setAttribute("target","_blank"):t.removeAttribute("target"),n&&o&&(this._hasPlainTextSelected?t.set("innerText",o):t.setAttribute("title",o))},this),e},_findSelectedAnchors:function(t){var e,n,i=t.get("tagName");return i&&"a"===i.toLowerCase()?[t]:(n=[],t.all("a").each(function(t){!e&&this.get("host").selectionContainsNode(t)&&n.push(t)},this),0
{{#if showFilepicker}}
{{else}}
{{/if}}

');return this._content=u.Node.create(e({showFilepicker:t,component:n,CSS:i})),this._content.one(o).on("keyup",this._updateTextToDisplay,this),this._content.one(o).on("change",this._updateTextToDisplay,this),this._content.one(d).on("keyup",this._setTextToDisplayState,this),this._content.one(l).on("click",this._setLink,this),t&&this._content.one(s).on("click",function(t){t.preventDefault(),this.get("host").showFilepicker("link",this._filepickerCallback,this)},this),this._content},_unlink:function(){var e=this.get("host"),t=e.getSelection();t&&t.length&&(t[0].startOffset===t[0].endOffset?(t=e.getSelectedNodes())&&(t.each(function(t){t=t.ancestor("a",!0);t&&(e.setSelection(e.getSelectionFromNode(t)),document.execCommand("unlink",!1,null))},this),this.markUpdated()):(document.execCommand("unlink",!1,null),this.markUpdated()))},_setTextToDisplayState:function(){var t=this._content.one(d).get("value");this._hasTextToDisplay=""!==t},_updateTextToDisplay:function(){ +var t=this._content.one(o),e=this._content.one(d),t=t.get("value");this._hasTextToDisplay||e.set("value",t)},_getTextSelection:function(){var t,e,n="",i=window.getSelection(),o=i.rangeCount;if(o){for(t=[],e=0;e 0) { + // For Firefox / Gecko we need to wrap the selection in a span so we can surround it with an anchor. + // This relates to https://bugzilla.mozilla.org/show_bug.cgi?id=1906559. + var originalSelection = document.getSelection(); + var wrapper = document.createElement('span'); + wrapper.setAttribute('data-wrapper', ''); + wrapper.style.display = 'inline'; + + var i; + for (i = 0; i < originalSelection.rangeCount; i++) { + originalSelection.getRangeAt(i).surroundContents(wrapper); + } + host.setSelection(host.getSelectionFromNode(Y.one(wrapper))); + + document.execCommand('unlink', false, null); + document.execCommand('createLink', false, url); + + var anchorNode = wrapper.parentNode; + wrapper.children.forEach(function(child) { + anchorNode.appendChild(child); + }); + wrapper.remove(); + + } else { + document.execCommand('unlink', false, null); + document.execCommand('createLink', false, url); + } // Now set the target. selectednode = host.getSelectionParentNode(); diff --git a/lib/editor/atto/plugins/link/yui/src/button/js/button.js b/lib/editor/atto/plugins/link/yui/src/button/js/button.js index db182c031b7..5ad77e94758 100644 --- a/lib/editor/atto/plugins/link/yui/src/button/js/button.js +++ b/lib/editor/atto/plugins/link/yui/src/button/js/button.js @@ -315,8 +315,33 @@ Y.namespace('M.atto_link').Button = Y.Base.create('button', Y.M.editor_atto.Edit selectednode = host.insertContentAtFocusPoint(link.get('outerHTML')); host.setSelection(host.getSelectionFromNode(selectednode)); } else { - document.execCommand('unlink', false, null); - document.execCommand('createLink', false, url); + if (Y.UA.gecko > 0) { + // For Firefox / Gecko we need to wrap the selection in a span so we can surround it with an anchor. + // This relates to https://bugzilla.mozilla.org/show_bug.cgi?id=1906559. + var originalSelection = document.getSelection(); + var wrapper = document.createElement('span'); + wrapper.setAttribute('data-wrapper', ''); + wrapper.style.display = 'inline'; + + var i; + for (i = 0; i < originalSelection.rangeCount; i++) { + originalSelection.getRangeAt(i).surroundContents(wrapper); + } + host.setSelection(host.getSelectionFromNode(Y.one(wrapper))); + + document.execCommand('unlink', false, null); + document.execCommand('createLink', false, url); + + var anchorNode = wrapper.parentNode; + wrapper.children.forEach(function(child) { + anchorNode.appendChild(child); + }); + wrapper.remove(); + + } else { + document.execCommand('unlink', false, null); + document.execCommand('createLink', false, url); + } // Now set the target. selectednode = host.getSelectionParentNode();