From cd56e424604c93e3c0a952e76322a20b940b923d Mon Sep 17 00:00:00 2001 From: Sybille Peters <sypets@gmx.de> Date: Sat, 15 Jul 2023 09:54:21 +0200 Subject: [PATCH] [BUGFIX] Mark broken links in RTE again MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The possibility to mark broken links with the `data-rte-error` attribute and styling was removed in TYPO3 v12. This is now added again to allow for linkvalidator and other extensions to mark broken links which will then be visibly styled differently when editing in the RTE. This affects only the backend. The attribute data-rte-error is removed when persisting the content to the database. The logic for adding this attribute is still in place: The linkvalidator uses the event `BrokenLinkAnalysisEvent` which is dispatched in `RteHtmlParser`. A marked broken link may look like this: ```html <a href="t3://page?uid=42" data-rte-error="External link is broken">link</a> ``` Note that the functionality got disabled with the new ckeditor in #96874 while the implementing plugin "showbrokenlinks" was removed later with #98497. The CSS is therefore restored from the state as of before #98497. Resolves: #101357 Related: #96874 Related: #98497 Releases: main, 12.4 Change-Id: I898b1de1b9bb9bf334740dc3b7ec8eb70627bed4 Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/80892 Tested-by: core-ci <typo3@b13.com> Tested-by: Stefan Bürk <stefan@buerk.tech> Reviewed-by: Stefan Bürk <stefan@buerk.tech> --- .../rte_ckeditor/plugin/typo3-link.ts | 18 +++++++++++++++++- .../Resources/Public/Css/contents.css | 10 ++++++++++ .../Public/JavaScript/plugin/typo3-link.js | 2 +- 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/Build/Sources/TypeScript/rte_ckeditor/plugin/typo3-link.ts b/Build/Sources/TypeScript/rte_ckeditor/plugin/typo3-link.ts index 4beb5755cc32..8f8af4687a10 100644 --- a/Build/Sources/TypeScript/rte_ckeditor/plugin/typo3-link.ts +++ b/Build/Sources/TypeScript/rte_ckeditor/plugin/typo3-link.ts @@ -195,7 +195,23 @@ export class Typo3LinkEditing extends Core.Plugin { (window as any).editor = editor; // @todo YAML additionalAttributes is not implemented yet - editor.model.schema.extend('$text', { allowAttributes: ['linkTitle', 'linkClass', 'linkTarget', 'linkRel'] }); + editor.model.schema.extend('$text', { allowAttributes: ['linkTitle', 'linkClass', 'linkTarget', 'linkRel', 'linkDataRteError'] }); + + // linkDataRteError <=> data-rte-error + // This is used for marking broken links (e.g. by linkvalidator) when editing in RTE. + // Broken links are styled differently. This will not get persisted to the database. + editor.conversion.for('downcast').attributeToElement({ + model: 'linkDataRteError', + view: (value: string|null, { writer }) => { + const linkElement = writer.createAttributeElement('a', { 'data-rte-error': value }, { priority: 5 }); + writer.setCustomProperty('linkDataRteError', true, linkElement); + return linkElement; + } + }); + editor.conversion.for('upcast').elementToAttribute({ + view: { name: 'a', attributes: { 'data-rte-error': true } }, + model: { key: 'linkDataRteError', value: (viewElement: ViewElement) => viewElement.getAttribute('data-rte-error') } + }); // linkTitle <=> title editor.conversion.for('downcast').attributeToElement({ diff --git a/typo3/sysext/rte_ckeditor/Resources/Public/Css/contents.css b/typo3/sysext/rte_ckeditor/Resources/Public/Css/contents.css index d55e1f29f2cf..e74f6692bb0f 100644 --- a/typo3/sysext/rte_ckeditor/Resources/Public/Css/contents.css +++ b/typo3/sysext/rte_ckeditor/Resources/Public/Css/contents.css @@ -42,6 +42,16 @@ a { text-decoration: underline; } +/** + * Show broken links with red border and yellow background + */ +a[data-rte-error] +{ + background-color: yellow!important; + border: 2px red solid!important; + color: black!important; +} + strong { font-weight: bold; } diff --git a/typo3/sysext/rte_ckeditor/Resources/Public/JavaScript/plugin/typo3-link.js b/typo3/sysext/rte_ckeditor/Resources/Public/JavaScript/plugin/typo3-link.js index ea52c0db6209..f37b7015d749 100644 --- a/typo3/sysext/rte_ckeditor/Resources/Public/JavaScript/plugin/typo3-link.js +++ b/typo3/sysext/rte_ckeditor/Resources/Public/JavaScript/plugin/typo3-link.js @@ -10,4 +10,4 @@ * * The TYPO3 project - inspiring people to share! */ -import{UI,Core,Engine,Typing,Link,LinkUtils,LinkActionsView,Widget,Utils}from"@typo3/ckeditor5-bundle.js";import{default as modalObject}from"@typo3/backend/modal.js";const linkIcon='<svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="m11.077 15 .991-1.416a.75.75 0 1 1 1.229.86l-1.148 1.64a.748.748 0 0 1-.217.206 5.251 5.251 0 0 1-8.503-5.955.741.741 0 0 1 .12-.274l1.147-1.639a.75.75 0 1 1 1.228.86L4.933 10.7l.006.003a3.75 3.75 0 0 0 6.132 4.294l.006.004zm5.494-5.335a.748.748 0 0 1-.12.274l-1.147 1.639a.75.75 0 1 1-1.228-.86l.86-1.23a3.75 3.75 0 0 0-6.144-4.301l-.86 1.229a.75.75 0 0 1-1.229-.86l1.148-1.64a.748.748 0 0 1 .217-.206 5.251 5.251 0 0 1 8.503 5.955zm-4.563-2.532a.75.75 0 0 1 .184 1.045l-3.155 4.505a.75.75 0 1 1-1.229-.86l3.155-4.506a.75.75 0 0 1 1.045-.184z"/></svg>';export const LINK_ALLOWED_ATTRIBUTES=["href","title","class","target","rel"];export function addLinkPrefix(e){return"link"+(e.charAt(0).toUpperCase()+e.slice(1))}export class Typo3TextView extends UI.View{constructor(e){super(e),this.set("text",void 0);const t=this.bindTemplate;this.setTemplate({tag:"span",attributes:{class:["ck","ck-linktext"],title:t.to("text")},children:[{text:t.to("text")}]})}}export class Typo3LinkCommand extends Core.Command{refresh(){const e=this.editor.model,t=e.document.selection,i=t.getSelectedElement()||Utils.first(t.getSelectedBlocks());LinkUtils.isLinkableElement(i,e.schema)?(this.value=i.getAttribute("linkHref"),this.isEnabled=e.schema.checkAttribute(i,"linkHref")):(this.value=t.getAttribute("linkHref"),this.isEnabled=e.schema.checkAttributeInSelection(t,"linkHref"))}execute(e,t={}){const i=this.editor.model,n=i.document.selection;i.change((o=>{if(n.isCollapsed){const s=n.getFirstPosition();if(n.hasAttribute("linkHref")){const r=Typing.findAttributeRange(s,"linkHref",n.getAttribute("linkHref"),i);o.setAttribute("linkHref",e,r);for(const[e,i]of Object.entries(t.attrs))o.setAttribute(e,i,r);o.setSelection(o.createPositionAfter(r.end.nodeBefore))}else if(""!==e){const r=Utils.toMap(n.getAttributes());r.set("linkHref",e);for(const[e,i]of Object.entries(t.attrs))r.set(e,i);const{end:l}=i.insertContent(o.createText(e,r),s);o.setSelection(l)}o.removeSelectionAttribute("linkHref")}else{const s=i.schema.getValidRanges(n.getRanges(),"linkHref"),r=[];for(const e of n.getSelectedBlocks())i.schema.checkAttribute(e,"linkHref")&&r.push(o.createRangeOn(e));const l=r.slice();for(const e of s)this.isRangeToUpdate(e,r)&&l.push(e);for(const i of l){o.setAttribute("linkHref",e,i);for(const[e,n]of Object.entries(t.attrs))o.setAttribute(e,n,i)}}}))}isRangeToUpdate(e,t){for(const i of t)if(i.containsRange(e))return!1;return!0}}export class Typo3UnlinkCommand extends Core.Command{refresh(){const e=this.editor.model,t=e.document.selection,i=t.getSelectedElement();LinkUtils.isLinkableElement(i,e.schema)?(this.value=i.getAttribute("linkHref"),this.isEnabled=e.schema.checkAttribute(i,"linkHref")):(this.value=t.getAttribute("linkHref"),this.isEnabled=e.schema.checkAttributeInSelection(t,"linkHref"))}execute(){const e=this.editor.model,t=e.document.selection;e.change((i=>{const n=t.isCollapsed?[Typing.findAttributeRange(t.getFirstPosition(),"linkHref",t.getAttribute("linkHref"),e)]:e.schema.getValidRanges(t.getRanges(),"linkHref");for(const e of n)i.removeAttribute("linkHref",e),i.removeAttribute("linkTarget",e),i.removeAttribute("linkClass",e),i.removeAttribute("linkTitle",e),i.removeAttribute("linkRel",e)}))}}class Typo3LinkEditing extends Core.Plugin{init(){const e=this.editor;window.editor=e,e.model.schema.extend("$text",{allowAttributes:["linkTitle","linkClass","linkTarget","linkRel"]}),e.conversion.for("downcast").attributeToElement({model:"linkTitle",view:(e,{writer:t})=>{const i=t.createAttributeElement("a",{title:e},{priority:5});return t.setCustomProperty("linkTitle",!0,i),i}}),e.conversion.for("upcast").elementToAttribute({view:{name:"a",attributes:{title:!0}},model:{key:"linkTitle",value:e=>e.getAttribute("title")}}),e.conversion.for("downcast").attributeToElement({model:"linkClass",view:(e,{writer:t})=>{const i=t.createAttributeElement("a",{class:e},{priority:5});return t.setCustomProperty("linkClass",!0,i),i}}),e.conversion.for("upcast").elementToAttribute({view:{name:"a",attributes:{class:!0}},model:{key:"linkClass",value:e=>e.getAttribute("class")}}),e.conversion.for("downcast").attributeToElement({model:"linkTarget",view:(e,{writer:t})=>{const i=t.createAttributeElement("a",{target:e},{priority:5});return t.setCustomProperty("linkTarget",!0,i),i}}),e.conversion.for("upcast").elementToAttribute({view:{name:"a",attributes:{target:!0}},model:{key:"linkTarget",value:e=>e.getAttribute("target")}}),e.conversion.for("downcast").attributeToElement({model:"linkRel",view:(e,{writer:t})=>{const i=t.createAttributeElement("a",{rel:e},{priority:5});return t.setCustomProperty("linkRel",!0,i),i}}),e.conversion.for("upcast").elementToAttribute({view:{name:"a",attributes:{rel:!0}},model:{key:"linkRel",value:e=>e.getAttribute("rel")}}),e.commands.add("link",new Typo3LinkCommand(e)),e.commands.add("unlink",new Typo3UnlinkCommand(e))}}Typo3LinkEditing.pluginName="Typo3LinkEditing";export{Typo3LinkEditing};export class Typo3LinkActionsView extends LinkActionsView{_createPreviewButton(){const e=new Typo3TextView(this.locale),t=this.t;return e.bind("text").to(this,"href",(e=>e||t("This link has no URL"))),e}}const VISUAL_SELECTION_MARKER_NAME="link-ui";class Typo3LinkUI extends Core.Plugin{init(){const e=this.editor;e.editing.view.addObserver(Engine.ClickObserver),this.actionsView=this.createActionsView(),this.balloon=e.plugins.get(UI.ContextualBalloon),this.createToolbarLinkButtons(),this.enableUserBalloonInteractions(),e.conversion.for("editingDowncast").markerToHighlight({model:"link-ui",view:{classes:["ck-fake-link-selection"]}}),e.conversion.for("editingDowncast").markerToElement({model:"link-ui",view:{name:"span",classes:["ck-fake-link-selection","ck-fake-link-selection_collapsed"]}})}createActionsView(){const e=this.editor,t=new Typo3LinkActionsView(e.locale),i=e.commands.get("link"),n=e.commands.get("unlink");return t.bind("href").to(i,"value"),t.editButtonView.bind("isEnabled").to(i),t.unlinkButtonView.bind("isEnabled").to(n),this.listenTo(t,"edit",(()=>{this.openLinkBrowser(e)})),this.listenTo(t,"unlink",(()=>{e.execute("unlink"),this.hideUI()})),t.keystrokes.set("Esc",((e,t)=>{this.hideUI(),t()})),t}createToolbarLinkButtons(){const e=this.editor,t=e.commands.get("link"),i=e.t;e.keystrokes.set(LinkUtils.LINK_KEYSTROKE,((e,i)=>{i(),t.isEnabled&&this.showUI()})),e.ui.componentFactory.add("link",(e=>{const n=new UI.ButtonView(e);return n.isEnabled=!0,n.label=i("Link"),n.icon=linkIcon,n.keystroke=LinkUtils.LINK_KEYSTROKE,n.tooltip=!0,n.isToggleable=!0,n.bind("isEnabled").to(t,"isEnabled"),n.bind("isOn").to(t,"value",(e=>!!e)),this.listenTo(n,"execute",(()=>this.showUI())),n}))}enableUserBalloonInteractions(){const e=this.editor.editing.view.document;this.listenTo(e,"click",(()=>{this.getSelectedLinkElement()&&this.showUI()})),this.editor.keystrokes.set("Esc",((e,t)=>{this.isUIVisible()&&(this.hideUI(),t())}))}addActionsView(){this.areActionsInPanel()||this.balloon.add({view:this.actionsView,position:this.getBalloonPositionData()})}hideUI(){if(!this.isUIInPanel())return;const e=this.editor;this.stopListening(e.ui,"update"),this.stopListening(this.balloon,"change:visibleView"),e.editing.view.focus(),this.balloon.remove(this.actionsView),this.hideFakeVisualSelection()}showUI(){this.getSelectedLinkElement()?(this.addActionsView(),this.balloon.showStack("main")):(this.showFakeVisualSelection(),this.openLinkBrowser(this.editor)),this.startUpdatingUI()}startUpdatingUI(){const e=this.editor,t=e.editing.view.document;let i=this.getSelectedLinkElement(),n=s();const o=()=>{const e=this.getSelectedLinkElement(),t=s();i&&!e||!i&&t!==n?this.hideUI():this.isUIVisible()&&this.balloon.updatePosition(this.getBalloonPositionData()),i=e,n=t};function s(){return t.selection.focus.getAncestors().reverse().find((e=>e.is("element")))}this.listenTo(e.ui,"update",o),this.listenTo(this.balloon,"change:visibleView",o)}areActionsInPanel(){return this.balloon.hasView(this.actionsView)}areActionsVisible(){return this.balloon.visibleView===this.actionsView}isUIInPanel(){return this.areActionsInPanel()}isUIVisible(){return this.areActionsVisible()}getBalloonPositionData(){const e=this.editor.editing.view,t=this.editor.model,i=e.document;let n=null;if(t.markers.has("link-ui")){const t=Array.from(this.editor.editing.mapper.markerNameToElements("link-ui")),i=e.createRange(e.createPositionBefore(t[0]),e.createPositionAfter(t[t.length-1]));n=e.domConverter.viewRangeToDom(i)}else n=()=>{const t=this.getSelectedLinkElement();return t?e.domConverter.mapViewToDom(t):e.domConverter.viewRangeToDom(i.selection.getFirstRange())};return{target:n}}getSelectedLinkElement(){const e=this.editor.editing.view,t=e.document.selection,i=t.getSelectedElement();if(t.isCollapsed||i&&Widget.isWidget(i))return this.findLinkElementAncestor(t.getFirstPosition());{const i=t.getFirstRange().getTrimmed(),n=this.findLinkElementAncestor(i.start),o=this.findLinkElementAncestor(i.end);return n&&n==o&&e.createRangeIn(n).getTrimmed().isEqual(i)?n:null}}showFakeVisualSelection(){const e=this.editor.model;e.change((t=>{const i=e.document.selection.getFirstRange();if(e.markers.has("link-ui"))t.updateMarker("link-ui",{range:i});else if(i.start.isAtEnd){const n=i.start.getLastMatchingPosition((({item:t})=>!e.schema.isContent(t)),{startPosition:null,boundaries:i});t.addMarker("link-ui",{usingOperation:!1,affectsData:!1,range:t.createRange(n,i.end)})}else t.addMarker("link-ui",{usingOperation:!1,affectsData:!1,range:i})}))}hideFakeVisualSelection(){const e=this.editor.model;e.markers.has("link-ui")&&e.change((e=>{e.removeMarker("link-ui")}))}findLinkElementAncestor(e){return e.getAncestors().find((e=>LinkUtils.isLinkElement(e)))}openLinkBrowser(e){const t=this.getSelectedLinkElement();let i="";t&&(i+="&P[curUrl][url]="+encodeURIComponent(t.getAttribute("href")),["target","class","title","rel"].forEach((e=>{const n=t.getAttribute(e);n&&(i+="&P[curUrl]["+e+"]="+encodeURIComponent(n))}))),this.openElementBrowser(e,"Link",this.makeUrlFromModulePath(e,e.config.get("typo3link")?.routeUrl,i))}makeUrlFromModulePath(e,t,i){return t+(-1===t.indexOf("?")?"?":"&")+"&contentsLanguage=en&editorId=123"+(i||"")}openElementBrowser(e,t,i){modalObject.advanced({type:modalObject.types.iframe,title:t,content:i,size:modalObject.sizes.large,callback:t=>{t.userData.editor=e,t.userData.selectionStartPosition=e.model.document.selection.getFirstPosition(),t.userData.selectionEndPosition=e.model.document.selection.getLastPosition(),t.querySelector(".t3js-modal-body")?.setAttribute("id","123")}})}}Typo3LinkUI.pluginName="Typo3LinkUI",Typo3LinkUI.requires=[UI.ContextualBalloon];export{Typo3LinkUI};class Typo3Link extends Core.Plugin{}Typo3Link.pluginName="Typo3Link",Typo3Link.requires=[Link.LinkEditing,Link.AutoLink,Typo3LinkEditing,Typo3LinkUI],Typo3Link.overrides=[Link.Link];export default Typo3Link; \ No newline at end of file +import{UI,Core,Engine,Typing,Link,LinkUtils,LinkActionsView,Widget,Utils}from"@typo3/ckeditor5-bundle.js";import{default as modalObject}from"@typo3/backend/modal.js";const linkIcon='<svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="m11.077 15 .991-1.416a.75.75 0 1 1 1.229.86l-1.148 1.64a.748.748 0 0 1-.217.206 5.251 5.251 0 0 1-8.503-5.955.741.741 0 0 1 .12-.274l1.147-1.639a.75.75 0 1 1 1.228.86L4.933 10.7l.006.003a3.75 3.75 0 0 0 6.132 4.294l.006.004zm5.494-5.335a.748.748 0 0 1-.12.274l-1.147 1.639a.75.75 0 1 1-1.228-.86l.86-1.23a3.75 3.75 0 0 0-6.144-4.301l-.86 1.229a.75.75 0 0 1-1.229-.86l1.148-1.64a.748.748 0 0 1 .217-.206 5.251 5.251 0 0 1 8.503 5.955zm-4.563-2.532a.75.75 0 0 1 .184 1.045l-3.155 4.505a.75.75 0 1 1-1.229-.86l3.155-4.506a.75.75 0 0 1 1.045-.184z"/></svg>';export const LINK_ALLOWED_ATTRIBUTES=["href","title","class","target","rel"];export function addLinkPrefix(e){return"link"+(e.charAt(0).toUpperCase()+e.slice(1))}export class Typo3TextView extends UI.View{constructor(e){super(e),this.set("text",void 0);const t=this.bindTemplate;this.setTemplate({tag:"span",attributes:{class:["ck","ck-linktext"],title:t.to("text")},children:[{text:t.to("text")}]})}}export class Typo3LinkCommand extends Core.Command{refresh(){const e=this.editor.model,t=e.document.selection,i=t.getSelectedElement()||Utils.first(t.getSelectedBlocks());LinkUtils.isLinkableElement(i,e.schema)?(this.value=i.getAttribute("linkHref"),this.isEnabled=e.schema.checkAttribute(i,"linkHref")):(this.value=t.getAttribute("linkHref"),this.isEnabled=e.schema.checkAttributeInSelection(t,"linkHref"))}execute(e,t={}){const i=this.editor.model,n=i.document.selection;i.change((o=>{if(n.isCollapsed){const s=n.getFirstPosition();if(n.hasAttribute("linkHref")){const r=Typing.findAttributeRange(s,"linkHref",n.getAttribute("linkHref"),i);o.setAttribute("linkHref",e,r);for(const[e,i]of Object.entries(t.attrs))o.setAttribute(e,i,r);o.setSelection(o.createPositionAfter(r.end.nodeBefore))}else if(""!==e){const r=Utils.toMap(n.getAttributes());r.set("linkHref",e);for(const[e,i]of Object.entries(t.attrs))r.set(e,i);const{end:l}=i.insertContent(o.createText(e,r),s);o.setSelection(l)}o.removeSelectionAttribute("linkHref")}else{const s=i.schema.getValidRanges(n.getRanges(),"linkHref"),r=[];for(const e of n.getSelectedBlocks())i.schema.checkAttribute(e,"linkHref")&&r.push(o.createRangeOn(e));const l=r.slice();for(const e of s)this.isRangeToUpdate(e,r)&&l.push(e);for(const i of l){o.setAttribute("linkHref",e,i);for(const[e,n]of Object.entries(t.attrs))o.setAttribute(e,n,i)}}}))}isRangeToUpdate(e,t){for(const i of t)if(i.containsRange(e))return!1;return!0}}export class Typo3UnlinkCommand extends Core.Command{refresh(){const e=this.editor.model,t=e.document.selection,i=t.getSelectedElement();LinkUtils.isLinkableElement(i,e.schema)?(this.value=i.getAttribute("linkHref"),this.isEnabled=e.schema.checkAttribute(i,"linkHref")):(this.value=t.getAttribute("linkHref"),this.isEnabled=e.schema.checkAttributeInSelection(t,"linkHref"))}execute(){const e=this.editor.model,t=e.document.selection;e.change((i=>{const n=t.isCollapsed?[Typing.findAttributeRange(t.getFirstPosition(),"linkHref",t.getAttribute("linkHref"),e)]:e.schema.getValidRanges(t.getRanges(),"linkHref");for(const e of n)i.removeAttribute("linkHref",e),i.removeAttribute("linkTarget",e),i.removeAttribute("linkClass",e),i.removeAttribute("linkTitle",e),i.removeAttribute("linkRel",e)}))}}class Typo3LinkEditing extends Core.Plugin{init(){const e=this.editor;window.editor=e,e.model.schema.extend("$text",{allowAttributes:["linkTitle","linkClass","linkTarget","linkRel","linkDataRteError"]}),e.conversion.for("downcast").attributeToElement({model:"linkDataRteError",view:(e,{writer:t})=>{const i=t.createAttributeElement("a",{"data-rte-error":e},{priority:5});return t.setCustomProperty("linkDataRteError",!0,i),i}}),e.conversion.for("upcast").elementToAttribute({view:{name:"a",attributes:{"data-rte-error":!0}},model:{key:"linkDataRteError",value:e=>e.getAttribute("data-rte-error")}}),e.conversion.for("downcast").attributeToElement({model:"linkTitle",view:(e,{writer:t})=>{const i=t.createAttributeElement("a",{title:e},{priority:5});return t.setCustomProperty("linkTitle",!0,i),i}}),e.conversion.for("upcast").elementToAttribute({view:{name:"a",attributes:{title:!0}},model:{key:"linkTitle",value:e=>e.getAttribute("title")}}),e.conversion.for("downcast").attributeToElement({model:"linkClass",view:(e,{writer:t})=>{const i=t.createAttributeElement("a",{class:e},{priority:5});return t.setCustomProperty("linkClass",!0,i),i}}),e.conversion.for("upcast").elementToAttribute({view:{name:"a",attributes:{class:!0}},model:{key:"linkClass",value:e=>e.getAttribute("class")}}),e.conversion.for("downcast").attributeToElement({model:"linkTarget",view:(e,{writer:t})=>{const i=t.createAttributeElement("a",{target:e},{priority:5});return t.setCustomProperty("linkTarget",!0,i),i}}),e.conversion.for("upcast").elementToAttribute({view:{name:"a",attributes:{target:!0}},model:{key:"linkTarget",value:e=>e.getAttribute("target")}}),e.conversion.for("downcast").attributeToElement({model:"linkRel",view:(e,{writer:t})=>{const i=t.createAttributeElement("a",{rel:e},{priority:5});return t.setCustomProperty("linkRel",!0,i),i}}),e.conversion.for("upcast").elementToAttribute({view:{name:"a",attributes:{rel:!0}},model:{key:"linkRel",value:e=>e.getAttribute("rel")}}),e.commands.add("link",new Typo3LinkCommand(e)),e.commands.add("unlink",new Typo3UnlinkCommand(e))}}Typo3LinkEditing.pluginName="Typo3LinkEditing";export{Typo3LinkEditing};export class Typo3LinkActionsView extends LinkActionsView{_createPreviewButton(){const e=new Typo3TextView(this.locale),t=this.t;return e.bind("text").to(this,"href",(e=>e||t("This link has no URL"))),e}}const VISUAL_SELECTION_MARKER_NAME="link-ui";class Typo3LinkUI extends Core.Plugin{init(){const e=this.editor;e.editing.view.addObserver(Engine.ClickObserver),this.actionsView=this.createActionsView(),this.balloon=e.plugins.get(UI.ContextualBalloon),this.createToolbarLinkButtons(),this.enableUserBalloonInteractions(),e.conversion.for("editingDowncast").markerToHighlight({model:"link-ui",view:{classes:["ck-fake-link-selection"]}}),e.conversion.for("editingDowncast").markerToElement({model:"link-ui",view:{name:"span",classes:["ck-fake-link-selection","ck-fake-link-selection_collapsed"]}})}createActionsView(){const e=this.editor,t=new Typo3LinkActionsView(e.locale),i=e.commands.get("link"),n=e.commands.get("unlink");return t.bind("href").to(i,"value"),t.editButtonView.bind("isEnabled").to(i),t.unlinkButtonView.bind("isEnabled").to(n),this.listenTo(t,"edit",(()=>{this.openLinkBrowser(e)})),this.listenTo(t,"unlink",(()=>{e.execute("unlink"),this.hideUI()})),t.keystrokes.set("Esc",((e,t)=>{this.hideUI(),t()})),t}createToolbarLinkButtons(){const e=this.editor,t=e.commands.get("link"),i=e.t;e.keystrokes.set(LinkUtils.LINK_KEYSTROKE,((e,i)=>{i(),t.isEnabled&&this.showUI()})),e.ui.componentFactory.add("link",(e=>{const n=new UI.ButtonView(e);return n.isEnabled=!0,n.label=i("Link"),n.icon=linkIcon,n.keystroke=LinkUtils.LINK_KEYSTROKE,n.tooltip=!0,n.isToggleable=!0,n.bind("isEnabled").to(t,"isEnabled"),n.bind("isOn").to(t,"value",(e=>!!e)),this.listenTo(n,"execute",(()=>this.showUI())),n}))}enableUserBalloonInteractions(){const e=this.editor.editing.view.document;this.listenTo(e,"click",(()=>{this.getSelectedLinkElement()&&this.showUI()})),this.editor.keystrokes.set("Esc",((e,t)=>{this.isUIVisible()&&(this.hideUI(),t())}))}addActionsView(){this.areActionsInPanel()||this.balloon.add({view:this.actionsView,position:this.getBalloonPositionData()})}hideUI(){if(!this.isUIInPanel())return;const e=this.editor;this.stopListening(e.ui,"update"),this.stopListening(this.balloon,"change:visibleView"),e.editing.view.focus(),this.balloon.remove(this.actionsView),this.hideFakeVisualSelection()}showUI(){this.getSelectedLinkElement()?(this.addActionsView(),this.balloon.showStack("main")):(this.showFakeVisualSelection(),this.openLinkBrowser(this.editor)),this.startUpdatingUI()}startUpdatingUI(){const e=this.editor,t=e.editing.view.document;let i=this.getSelectedLinkElement(),n=s();const o=()=>{const e=this.getSelectedLinkElement(),t=s();i&&!e||!i&&t!==n?this.hideUI():this.isUIVisible()&&this.balloon.updatePosition(this.getBalloonPositionData()),i=e,n=t};function s(){return t.selection.focus.getAncestors().reverse().find((e=>e.is("element")))}this.listenTo(e.ui,"update",o),this.listenTo(this.balloon,"change:visibleView",o)}areActionsInPanel(){return this.balloon.hasView(this.actionsView)}areActionsVisible(){return this.balloon.visibleView===this.actionsView}isUIInPanel(){return this.areActionsInPanel()}isUIVisible(){return this.areActionsVisible()}getBalloonPositionData(){const e=this.editor.editing.view,t=this.editor.model,i=e.document;let n=null;if(t.markers.has("link-ui")){const t=Array.from(this.editor.editing.mapper.markerNameToElements("link-ui")),i=e.createRange(e.createPositionBefore(t[0]),e.createPositionAfter(t[t.length-1]));n=e.domConverter.viewRangeToDom(i)}else n=()=>{const t=this.getSelectedLinkElement();return t?e.domConverter.mapViewToDom(t):e.domConverter.viewRangeToDom(i.selection.getFirstRange())};return{target:n}}getSelectedLinkElement(){const e=this.editor.editing.view,t=e.document.selection,i=t.getSelectedElement();if(t.isCollapsed||i&&Widget.isWidget(i))return this.findLinkElementAncestor(t.getFirstPosition());{const i=t.getFirstRange().getTrimmed(),n=this.findLinkElementAncestor(i.start),o=this.findLinkElementAncestor(i.end);return n&&n==o&&e.createRangeIn(n).getTrimmed().isEqual(i)?n:null}}showFakeVisualSelection(){const e=this.editor.model;e.change((t=>{const i=e.document.selection.getFirstRange();if(e.markers.has("link-ui"))t.updateMarker("link-ui",{range:i});else if(i.start.isAtEnd){const n=i.start.getLastMatchingPosition((({item:t})=>!e.schema.isContent(t)),{startPosition:null,boundaries:i});t.addMarker("link-ui",{usingOperation:!1,affectsData:!1,range:t.createRange(n,i.end)})}else t.addMarker("link-ui",{usingOperation:!1,affectsData:!1,range:i})}))}hideFakeVisualSelection(){const e=this.editor.model;e.markers.has("link-ui")&&e.change((e=>{e.removeMarker("link-ui")}))}findLinkElementAncestor(e){return e.getAncestors().find((e=>LinkUtils.isLinkElement(e)))}openLinkBrowser(e){const t=this.getSelectedLinkElement();let i="";t&&(i+="&P[curUrl][url]="+encodeURIComponent(t.getAttribute("href")),["target","class","title","rel"].forEach((e=>{const n=t.getAttribute(e);n&&(i+="&P[curUrl]["+e+"]="+encodeURIComponent(n))}))),this.openElementBrowser(e,"Link",this.makeUrlFromModulePath(e,e.config.get("typo3link")?.routeUrl,i))}makeUrlFromModulePath(e,t,i){return t+(-1===t.indexOf("?")?"?":"&")+"&contentsLanguage=en&editorId=123"+(i||"")}openElementBrowser(e,t,i){modalObject.advanced({type:modalObject.types.iframe,title:t,content:i,size:modalObject.sizes.large,callback:t=>{t.userData.editor=e,t.userData.selectionStartPosition=e.model.document.selection.getFirstPosition(),t.userData.selectionEndPosition=e.model.document.selection.getLastPosition(),t.querySelector(".t3js-modal-body")?.setAttribute("id","123")}})}}Typo3LinkUI.pluginName="Typo3LinkUI",Typo3LinkUI.requires=[UI.ContextualBalloon];export{Typo3LinkUI};class Typo3Link extends Core.Plugin{}Typo3Link.pluginName="Typo3Link",Typo3Link.requires=[Link.LinkEditing,Link.AutoLink,Typo3LinkEditing,Typo3LinkUI],Typo3Link.overrides=[Link.Link];export default Typo3Link; \ No newline at end of file -- GitLab