From 27a2da76a74fc66c3bd38e9173b9090cfb57341f Mon Sep 17 00:00:00 2001 From: Mo Alsharaf Date: Mon, 26 Aug 2024 11:26:31 +1200 Subject: [PATCH] Fix TinyMCE edit link when link contains html (#1814) --- client/dist/js/TinyMCE_sslink.js | 2 +- client/src/legacy/TinyMCE_sslink.js | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/client/dist/js/TinyMCE_sslink.js b/client/dist/js/TinyMCE_sslink.js index c08260fb5..e1c556aa4 100644 --- a/client/dist/js/TinyMCE_sslink.js +++ b/client/dist/js/TinyMCE_sslink.js @@ -1 +1 @@ -!function(){"use strict";var t={265:function(t){t.exports=ShortcodeSerialiser},196:function(t){t.exports=TinyMCEActionRegistrar},754:function(t){t.exports=i18n},311:function(t){t.exports=jQuery}},e={};function n(i){var r=e[i];if(void 0!==r)return r.exports;var o=e[i]={exports:{}};return t[i](o,o.exports,n),o.exports}!function(){var t=o(n(196)),e=o(n(311)),i=n(265),r=o(n(754));function o(t){return t&&t.__esModule?t:{default:t}}const s={init(n){function i(){return t.default.getSortedActions("sslink",n.getParam("editorIdentifier"),!0).map((t=>Object.assign({},t,{onAction:()=>t.onAction(n)})))}const o=navigator.platform.toUpperCase().includes("MAC")?"⌘":"Ctrl",s=r.default._t("Admin.INSERT_LINK","Insert link"),l=r.default.inject(r.default._t("Admin.INSERT_LINK_WITH_SHORTCUT","Insert link {shortcut}"),{shortcut:`[${o}+K]`});return n.addShortcut("Meta+k","Open link menu",(()=>{(0,e.default)(`[aria-label^="${s}"] > button`,n.container).first().click()})),n.ui.registry.addMenuButton("sslink",{icon:"link",tooltip:l,fetch:t=>t(i())}),n.ui.registry.addNestedMenuItem("sslink",{icon:"link",text:s,getSubmenuItems:i}),n.ui.registry.addButton("sslink-edit",{text:r.default._t("Admin.EDIT_LINK","Edit link"),onAction:function(){const e=tinymce.activeEditor.selection.getNode().getAttribute("href");e&&n.execCommand(t.default.getEditorCommandFromUrl(e))}}),n.ui.registry.addButton("sslink-remove",{text:r.default._t("Admin.REMOVE_LINK","Remove link"),onAction:()=>this.handleRemoveLinkClick(n)}),n.ui.registry.addContextToolbar("sslink",{predicate:t=>n.dom.is(t,"a[href]"),position:"node",scope:"node",items:"sslink-edit sslink-remove"}),{getMetadata(){return{name:"Silverstripe Link",url:"https://docs.silverstripe.org/en/4/developer_guides/forms/field_types/htmleditorfield"}}}},handleRemoveLinkClick(t){const e=t.execCommand("unlink"),n=t.selection.getNode();return n&&void 0!==n.normalize&&n.normalize(),e}};e.default.entwine("ss",(t=>{t(".insert-link__dialog-wrapper").entwine({Element:null,Data:{},Bookmark:null,onunmatch(){this._clearModal()},_clearModal(){const t=this.getReactRoot();t&&(t.unmount(),this.setReactRoot(null))},open(){const t=this.getElement().getEditor().getInstance();this.setBookmark(t.selection.getBookmark(2,!0)),this.renderModal(!0)},close(){this.setData({}),this.renderModal(!1)},renderModal(){},checkNodeMatches(t,e){return t===e||1===e.children.length&&t===e.children[0]},linkCanWrapSelection(t,e){const n=t.getSelection()||"",i=e.getNode();if(n)return""!==n.trim();const r=document.createElement(i.nodeName);if(r.textContent="Check the outer HTML",r.outerHTML.includes("Check the outer HTML"))return!1;if(this.checkNodeMatches(i,e.getSel().focusNode)&&this.checkNodeMatches(i,e.getSel().anchorNode)){if(1===tinymce.activeEditor.dom.createFragment(`${i.outerHTML}`).childNodes.length)return!0}return!1},getRequireLinkText(){const t=this.getElement().getEditor(),e=t.getInstance().selection,n=this.linkCanWrapSelection(t,e);return"A"!==e.getNode().tagName&&!n},handleInsert(t){this.getElement().getEditor().getInstance().selection.moveToBookmark(this.getBookmark());const e=this.buildAttributes(t),n=(0,i.createHTMLSanitiser)()(t.Text);return this.insertLinkInEditor(e,n),this.close(),Promise.resolve()},buildAttributes(t){let{Anchor:e,Link:n,TargetBlank:i,Description:r}=t,o=e&&e.length?`#${e}`:"";o=o.replace(/^#+/,"#");return{href:`${n}${o}`,target:i?"_blank":"",title:r}},insertLinkInEditor(t,e){const n=this.getElement().getEditor();n.insertLink(t,null,e),n.addUndo(),n.repaint();const i=n.getInstance().selection;setTimeout((()=>i&&i.collapse()),0)},getOriginalAttributes(){const e=this.getElement().getEditor(),n=t(e.getSelectedNode()),i=(n.attr("href")||"").split("#");return{Link:i[0]||"",Anchor:i[1]||"",Description:n.attr("title"),TargetBlank:!!n.attr("target")}}})})),tinymce.PluginManager.add("sslink",(t=>s.init(t)))}()}(); \ No newline at end of file +!function(){"use strict";var t={265:function(t){t.exports=ShortcodeSerialiser},196:function(t){t.exports=TinyMCEActionRegistrar},754:function(t){t.exports=i18n},311:function(t){t.exports=jQuery}},e={};function n(i){var o=e[i];if(void 0!==o)return o.exports;var r=e[i]={exports:{}};return t[i](r,r.exports,n),r.exports}!function(){var t=r(n(196)),e=r(n(311)),i=n(265),o=r(n(754));function r(t){return t&&t.__esModule?t:{default:t}}const s={init(n){function i(){return t.default.getSortedActions("sslink",n.getParam("editorIdentifier"),!0).map((t=>Object.assign({},t,{onAction:()=>t.onAction(n)})))}const r=navigator.platform.toUpperCase().includes("MAC")?"⌘":"Ctrl",s=o.default._t("Admin.INSERT_LINK","Insert link"),l=o.default.inject(o.default._t("Admin.INSERT_LINK_WITH_SHORTCUT","Insert link {shortcut}"),{shortcut:`[${r}+K]`});return n.addShortcut("Meta+k","Open link menu",(()=>{(0,e.default)(`[aria-label^="${s}"] > button`,n.container).first().click()})),n.ui.registry.addMenuButton("sslink",{icon:"link",tooltip:l,fetch:t=>t(i())}),n.ui.registry.addNestedMenuItem("sslink",{icon:"link",text:s,getSubmenuItems:i}),n.ui.registry.addButton("sslink-edit",{text:o.default._t("Admin.EDIT_LINK","Edit link"),onAction:function(){const i=(0,e.default)(tinymce.activeEditor.selection.getNode()).closest("a");tinymce.activeEditor.selection.select(i[0]);const o=tinymce.activeEditor.selection.getNode().getAttribute("href");o&&n.execCommand(t.default.getEditorCommandFromUrl(o))}}),n.ui.registry.addButton("sslink-remove",{text:o.default._t("Admin.REMOVE_LINK","Remove link"),onAction:()=>this.handleRemoveLinkClick(n)}),n.ui.registry.addContextToolbar("sslink",{predicate:t=>n.dom.is(t,"a[href]"),position:"node",scope:"node",items:"sslink-edit sslink-remove"}),{getMetadata(){return{name:"Silverstripe Link",url:"https://docs.silverstripe.org/en/4/developer_guides/forms/field_types/htmleditorfield"}}}},handleRemoveLinkClick(t){const e=t.execCommand("unlink"),n=t.selection.getNode();return n&&void 0!==n.normalize&&n.normalize(),e}};e.default.entwine("ss",(t=>{t(".insert-link__dialog-wrapper").entwine({Element:null,Data:{},Bookmark:null,onunmatch(){this._clearModal()},_clearModal(){const t=this.getReactRoot();t&&(t.unmount(),this.setReactRoot(null))},open(){const t=this.getElement().getEditor().getInstance();this.setBookmark(t.selection.getBookmark(2,!0)),this.renderModal(!0)},close(){this.setData({}),this.renderModal(!1)},renderModal(){},checkNodeMatches(t,e){return t===e||1===e.children.length&&t===e.children[0]},linkCanWrapSelection(t,e){const n=t.getSelection()||"",i=e.getNode();if(n)return""!==n.trim();const o=document.createElement(i.nodeName);if(o.textContent="Check the outer HTML",o.outerHTML.includes("Check the outer HTML"))return!1;if(this.checkNodeMatches(i,e.getSel().focusNode)&&this.checkNodeMatches(i,e.getSel().anchorNode)){if(1===tinymce.activeEditor.dom.createFragment(`${i.outerHTML}`).childNodes.length)return!0}return!1},getRequireLinkText(){const e=this.getElement().getEditor();let n=e.getInstance().selection;const i=t(n.getNode()).closest("a");e.getInstance().selection.select(i[0]),n=e.getInstance().selection;const o=this.linkCanWrapSelection(e,n);return"A"!==n.getNode().tagName&&!o},handleInsert(t){this.getElement().getEditor().getInstance().selection.moveToBookmark(this.getBookmark());const e=this.buildAttributes(t),n=(0,i.createHTMLSanitiser)()(t.Text);return this.insertLinkInEditor(e,n),this.close(),Promise.resolve()},buildAttributes(t){let{Anchor:e,Link:n,TargetBlank:i,Description:o}=t,r=e&&e.length?`#${e}`:"";r=r.replace(/^#+/,"#");return{href:`${n}${r}`,target:i?"_blank":"",title:o}},insertLinkInEditor(t,e){const n=this.getElement().getEditor();n.insertLink(t,null,e),n.addUndo(),n.repaint();const i=n.getInstance().selection;setTimeout((()=>i&&i.collapse()),0)},getOriginalAttributes(){const e=this.getElement().getEditor(),n=t(e.getSelectedNode()),i=(n.attr("href")||"").split("#");return{Link:i[0]||"",Anchor:i[1]||"",Description:n.attr("title"),TargetBlank:!!n.attr("target")}}})})),tinymce.PluginManager.add("sslink",(t=>s.init(t)))}()}(); \ No newline at end of file diff --git a/client/src/legacy/TinyMCE_sslink.js b/client/src/legacy/TinyMCE_sslink.js index 374311cb0..35e55da32 100644 --- a/client/src/legacy/TinyMCE_sslink.js +++ b/client/src/legacy/TinyMCE_sslink.js @@ -35,6 +35,9 @@ const plugin = { // Callback for opening the edit link dialog form function openLinkDialog() { + // Find "a" node (we might have clicked on a child element e.g. "span") + const linkNode = jQuery(tinymce.activeEditor.selection.getNode()).closest('a'); + tinymce.activeEditor.selection.select(linkNode[0]); const node = tinymce.activeEditor.selection.getNode(); const href = node.getAttribute('href'); @@ -192,7 +195,11 @@ jQuery.entwine('ss', ($) => { */ getRequireLinkText() { const editor = this.getElement().getEditor(); - const selection = editor.getInstance().selection; + let selection = editor.getInstance().selection; + const node = $(selection.getNode()).closest('a'); + editor.getInstance().selection.select(node[0]); + + selection = editor.getInstance().selection; const isValidSelection = this.linkCanWrapSelection(editor, selection); const tagName = selection.getNode().tagName; const requireLinkText = tagName !== 'A' && !isValidSelection;