14 lines
14 KiB
JavaScript
14 lines
14 KiB
JavaScript
/*
|
|
* This file is part of the TYPO3 CMS project.
|
|
*
|
|
* It is free software; you can redistribute it and/or modify it under
|
|
* the terms of the GNU General Public License, either version 2
|
|
* of the License, or any later version.
|
|
*
|
|
* For the full copyright and license information, please read the
|
|
* LICENSE.txt file that was distributed with this source code.
|
|
*
|
|
* The TYPO3 project - inspiring people to share!
|
|
*/
|
|
import*as d from"@ckeditor/ckeditor5-ui";import*as h from"@ckeditor/ckeditor5-core";import*as U from"@ckeditor/ckeditor5-engine";import*as f from"@ckeditor/ckeditor5-typing";import*as y from"@ckeditor/ckeditor5-widget";import*as v from"@ckeditor/ckeditor5-utils";import*as b from"@ckeditor/ckeditor5-link";import{LinkUtils as k}from"@ckeditor/ckeditor5-link";import p from"@typo3/backend/modal.js";import{IconLink as I,IconUnlink as R,IconPencil as V}from"@ckeditor/ckeditor5-icons";const C=["href","title","class","target","rel","download"];function x(c){return"link"+(c.charAt(0).toUpperCase()+c.slice(1))}function A(c){return c.startsWith("link")&&c.length>=5?c.charAt(4).toLowerCase()+c.slice(5):c}class E extends h.Command{constructor(){super(...arguments),this.attrs={}}refresh(){const e=this.editor.model,n=e.document.selection,t=n.getSelectedElement()||v.first(n.getSelectedBlocks()),o=k.isLinkableElement(t,e.schema)?t:n;o===t?(this.value=t.getAttribute("linkHref"),this.isEnabled=e.schema.checkAttribute(t,"linkHref")):(this.value=n.getAttribute("linkHref"),this.isEnabled=e.schema.checkAttributeInSelection(n,"linkHref"));const s=this.editor.plugins.get("GeneralHtmlSupport").getGhsAttributeNameForElement("a"),l={};for(const a of this.getLinkAttributesAllowedOnText(e.schema))if(a!=="linkHref")if(a===s){const r=o.getAttribute(a);r?.classes&&r.classes.length!==0&&(l.class=r.classes.join(" "))}else{const r=o.getAttribute(a);r!==void 0&&(l[A(a)]=r)}this.attrs=l}execute(e,n={}){const t=this.editor.model,o=t.document.selection;t.change(i=>{if(o.isCollapsed){const s=o.getFirstPosition();if(o.hasAttribute("linkHref")){const l=f.findAttributeRange(s,"linkHref",o.getAttribute("linkHref"),t);i.setAttribute("linkHref",e,l);for(const[a,r]of Object.entries(this.composeLinkAttributes(n)))r!==null?i.setAttribute(a,r,l):i.removeAttribute(a,l);i.setSelection(i.createPositionAfter(l.end.nodeBefore))}else if(e!==""){const l=v.toMap(o.getAttributes());l.set("linkHref",e);for(const[r,m]of Object.entries(this.composeLinkAttributes(n)))m!==null&&l.set(r,m);const{end:a}=t.insertContent(i.createText(e,l),s);i.setSelection(a)}this.removeLinkAttributesFromSelection(i,this.getLinkAttributesAllowedOnText(t.schema))}else{const s=t.schema.getValidRanges(o.getRanges(),"linkHref"),l=[];for(const r of o.getSelectedBlocks())t.schema.checkAttribute(r,"linkHref")&&l.push(i.createRangeOn(r));const a=l.slice();for(const r of s)this.isRangeToUpdate(r,l)&&a.push(r);for(const r of a){i.setAttribute("linkHref",e,r);for(const[m,g]of Object.entries(this.composeLinkAttributes(n)))g!==null?i.setAttribute(m,g,r):i.removeAttribute(m,r)}}})}getLinkAttributesAllowedOnText(e){return e.getDefinition("$text").allowAttributes.filter(t=>t.startsWith("link")||t==="htmlA")}removeLinkAttributesFromSelection(e,n){e.removeSelectionAttribute("linkHref");for(const t of n)e.removeSelectionAttribute(t)}composeLinkAttributes(e){const n={};for(const[t,o]of Object.entries(e.attrs))if(t==="linkClass"){const s=this.editor.plugins.get("GeneralHtmlSupport").getGhsAttributeNameForElement("a"),l=this.editor.model.document.selection;let a;l.hasAttribute(s)?a={...l.getAttribute(s)}:a={};const r=o.replace(/\s+/g," ").trim();r!==""?a.classes=r.split(" "):"classes"in a&&delete a.classes,n[s]=Object.keys(a).length!==0?a:null}else n[t]=o!==""?o:null;return n}isRangeToUpdate(e,n){for(const t of n)if(t.containsRange(e))return!1;return!0}}class w extends h.Command{refresh(){const e=this.editor.model,n=e.document.selection,t=n.getSelectedElement();k.isLinkableElement(t,e.schema)?this.isEnabled=e.schema.checkAttribute(t,"linkHref"):this.isEnabled=e.schema.checkAttributeInSelection(n,"linkHref")}execute(){const e=this.editor.model,n=e.document.selection;e.change(t=>{const o=n.isCollapsed?[f.findAttributeRange(n.getFirstPosition(),"linkHref",n.getAttribute("linkHref"),e)]:e.schema.getValidRanges(n.getRanges(),"linkHref");for(const i of o)t.removeAttribute("linkHref",i),t.removeAttribute("linkTarget",i),t.removeAttribute("linkTitle",i),t.removeAttribute("linkRel",i),t.removeAttribute("linkDownload",i),t.removeAttribute("linkDataRteError",i)})}}class T extends h.Plugin{static{this.pluginName="Typo3LinkEditing"}init(){const e=this.editor;window.editor=e,e.model.schema.extend("$text",{allowAttributes:["linkTitle","linkTarget","linkRel","linkDownload","linkDataRteError"]}),e.plugins.get("DataFilter").loadAllowedConfig([{name:"a",classes:!0}]),e.conversion.for("downcast").attributeToElement({model:"linkDataRteError",view:(t,{writer:o})=>{const i=o.createAttributeElement("a",{"data-rte-error":t},{priority:5});return o.setCustomProperty("linkDataRteError",!0,i),i}}),e.conversion.for("upcast").elementToAttribute({view:{name:"a",attributes:{"data-rte-error":!0}},model:{key:"linkDataRteError",value:t=>t.getAttribute("data-rte-error")}}),e.conversion.for("downcast").attributeToElement({model:"linkTitle",view:(t,{writer:o})=>{const i=o.createAttributeElement("a",{title:t},{priority:5});return o.setCustomProperty("linkTitle",!0,i),i}}),e.conversion.for("upcast").elementToAttribute({view:{name:"a",attributes:{title:!0}},model:{key:"linkTitle",value:t=>t.getAttribute("title")}}),e.conversion.for("downcast").attributeToElement({model:"linkTarget",view:(t,{writer:o})=>{const i=o.createAttributeElement("a",{target:t},{priority:5});return o.setCustomProperty("linkTarget",!0,i),i}}),e.conversion.for("upcast").elementToAttribute({view:{name:"a",attributes:{target:!0}},model:{key:"linkTarget",value:t=>t.getAttribute("target")}}),e.conversion.for("downcast").attributeToElement({model:"linkRel",view:(t,{writer:o})=>{const i=o.createAttributeElement("a",{rel:t},{priority:5});return o.setCustomProperty("linkRel",!0,i),i}}),e.conversion.for("upcast").elementToAttribute({view:{name:"a",attributes:{rel:!0}},model:{key:"linkRel",value:t=>t.getAttribute("rel")}}),e.conversion.for("downcast").attributeToElement({model:"linkDownload",view:(t,{writer:o})=>{const i=o.createAttributeElement("a",{download:t==="true"?"":t},{priority:5});return o.setCustomProperty("linkDownload",!0,i),i}}),e.conversion.for("upcast").elementToAttribute({view:{name:"a",attributes:{download:!0}},model:{key:"linkDownload",value:t=>{const o=t.getAttribute("download");return o===""?"true":o}}}),e.commands.add("link",new E(e)),e.commands.add("unlink",new w(e))}}class L extends d.ButtonView{constructor(e){super(e);const n=this.bindTemplate;this.set({href:void 0,withText:!0}),this.setTemplate({tag:"span",attributes:{class:["ck-link-toolbar__preview"],title:n.to("href")},children:[{text:n.to("href")}]})}}const u="link-ui";class S extends h.Plugin{static get requires(){return[d.ContextualBalloon,b.LinkEditing]}static get pluginName(){return"Typo3LinkUI"}init(){const e=this.editor;e.editing.view.addObserver(U.ClickObserver),this.balloon=e.plugins.get(d.ContextualBalloon),this.createToolbarLinkButtons(),this.enableUserBalloonInteractions(),e.conversion.for("editingDowncast").markerToHighlight({model:u,view:{classes:["ck-fake-link-selection"]}}),e.conversion.for("editingDowncast").markerToElement({model:u,view:{name:"span",classes:["ck-fake-link-selection","ck-fake-link-selection_collapsed"]}})}createToolbarView(){const e=this.editor,n=new d.ToolbarView(e.locale),t=e.config.get("link.toolbar");return n.fillFromConfig(t,e.ui.componentFactory),n.keystrokes.set("Esc",(o,i)=>{this.hideUI(),i()}),e.ui.addToolbar(n,{isContextual:!0,beforeFocus:()=>{this.getSelectedLinkElement()&&!this.isToolbarVisible()&&this.showUI()},afterBlur:()=>{this.hideUI()}}),n}createToolbarLinkButtons(){const e=this.editor,n=e.commands.get("link"),t=e.t;e.keystrokes.set(k.LINK_KEYSTROKE,(o,i)=>{i(),n.isEnabled&&this.showUI()}),e.ui.componentFactory.add("link",o=>{const i=new d.ButtonView(o);return i.isEnabled=!0,i.label=t("Link"),i.icon=I,i.keystroke=k.LINK_KEYSTROKE,i.tooltip=!0,i.isToggleable=!0,i.bind("isEnabled").to(n,"isEnabled"),i.bind("isOn").to(n,"value",s=>!!s),this.listenTo(i,"execute",()=>this.showUI()),i}),e.ui.componentFactory.add("linkPreview",o=>{const i=new L(o),s=e.config.get("link.allowedProtocols"),l=e.commands.get("link");i.bind("isEnabled").to(l,"value",r=>!!r),i.bind("href").to(l,"value",r=>r&&k.ensureSafeUrl(r,s)),i.icon=void 0;const a=r=>{if(!r){i.label=void 0;return}i.label=r};return a(l.value),this.listenTo(l,"change:value",(r,m,g)=>{a(g)}),i}),e.ui.componentFactory.add("unlink",o=>{const i=e.commands.get("unlink"),s=new d.ButtonView(o),l=o.t;return s.set({label:l("Unlink"),icon:R,tooltip:!0}),s.bind("isEnabled").to(i),this.listenTo(s,"execute",()=>{e.execute("unlink"),this.hideUI()}),s}),e.ui.componentFactory.add("editLink",o=>{const i=e.commands.get("link"),s=new d.ButtonView(o),l=o.t;return s.set({label:l("Edit link"),icon:V,tooltip:!0}),s.bind("isEnabled").to(i),this.listenTo(s,"execute",()=>{this.openLinkBrowser(e)}),s})}enableUserBalloonInteractions(){const e=this.editor.editing.view.document;this.listenTo(e,"click",()=>{this.getSelectedLinkElement()&&this.showUI()}),this.editor.keystrokes.set("Esc",(n,t)=>{this.isUIVisible()&&(this.hideUI(),t())})}addToolbarView(){this.toolbarView||(this.toolbarView=this.createToolbarView()),!this.isToolbarInPanel()&&this.balloon.add({view:this.toolbarView,position:this.getBalloonPositionData(),balloonClassName:"ck-toolbar-container"})}hideUI(e=!0){const n=this.editor;this.stopListening(n.ui,"update"),this.stopListening(this.balloon,"change:visibleView"),e&&n.editing.view.focus(),this.isToolbarInPanel()&&this.balloon.remove(this.toolbarView),this.hideFakeVisualSelection()}showUI(){this.getSelectedLinkElement()?(this.addToolbarView(),this.balloon.showStack("main")):(this.showFakeVisualSelection(),this.openLinkBrowser(this.editor)),this.startUpdatingUI()}startUpdatingUI(){const e=this.editor,n=e.editing.view.document;let t=this.getSelectedLinkElement(),o=s();const i=()=>{const l=this.getSelectedLinkElement(),a=s();t&&!l||!t&&a!==o?this.hideUI():this.isUIVisible()&&this.balloon.updatePosition(this.getBalloonPositionData()),t=l,o=a};function s(){return n.selection.focus.getAncestors().reverse().find(l=>l.is("element"))}this.listenTo(e.ui,"update",i),this.listenTo(this.balloon,"change:visibleView",i)}isToolbarInPanel(){return!!this.toolbarView&&this.balloon.hasView(this.toolbarView)}isToolbarVisible(){return!!this.toolbarView&&this.balloon.visibleView===this.toolbarView}isUIVisible(){return this.isToolbarVisible()}getBalloonPositionData(){const e=this.editor.editing.view,n=this.editor.model,t=e.document;let o=null;if(n.markers.has(u)){const i=Array.from(this.editor.editing.mapper.markerNameToElements(u)),s=e.createRange(e.createPositionBefore(i[0]),e.createPositionAfter(i[i.length-1]));o=e.domConverter.viewRangeToDom(s)}else o=()=>{const i=this.getSelectedLinkElement();return i?e.domConverter.mapViewToDom(i):e.domConverter.viewRangeToDom(t.selection.getFirstRange())};return{target:o}}getSelectedLinkElement(){const e=this.editor.editing.view,n=e.document.selection,t=n.getSelectedElement();if(n.isCollapsed||t&&y.isWidget(t))return this.findLinkElementAncestor(n.getFirstPosition());{const o=n.getFirstRange().getTrimmed(),i=this.findLinkElementAncestor(o.start),s=this.findLinkElementAncestor(o.end);return!i||i!=s?null:e.createRangeIn(i).getTrimmed().isEqual(o)?i:null}}showFakeVisualSelection(){const e=this.editor.model;e.change(n=>{const t=e.document.selection.getFirstRange();if(e.markers.has(u))n.updateMarker(u,{range:t});else if(t.start.isAtEnd){const o=t.start.getLastMatchingPosition(({item:i})=>!e.schema.isContent(i),{startPosition:null,boundaries:t});n.addMarker(u,{usingOperation:!1,affectsData:!1,range:n.createRange(o,t.end)})}else n.addMarker(u,{usingOperation:!1,affectsData:!1,range:t})})}hideFakeVisualSelection(){const e=this.editor.model;e.markers.has(u)&&e.change(n=>{n.removeMarker(u)})}findLinkElementAncestor(e){return e.getAncestors().find(n=>k.isLinkElement(n))}expandSelectionToFullLink(){const e=this.editor.model,n=e.document.selection;if(!n.hasAttribute("linkHref"))return;const t=n.getAttribute("linkHref");t&&e.change(o=>{const i=n.getFirstPosition(),s=f.findAttributeRange(i,"linkHref",t,e);s&&o.setSelection(s)})}openLinkBrowser(e){const n=e.commands.get("link");let t="";if(n.value){this.expandSelectionToFullLink(),t+="&P[curUrl][url]="+encodeURIComponent(n.value);for(const[o,i]of Object.entries(n.attrs))t+="&P[curUrl]["+encodeURIComponent(o)+"]="+encodeURIComponent(i)}this.openElementBrowser(e,"Link",this.makeUrlFromModulePath(e,e.config.get("typo3link")?.routeUrl,t))}makeUrlFromModulePath(e,n,t){return n+(n.indexOf("?")===-1?"?":"&")+"&contentsLanguage=en&editorId=123"+(t||"")}openElementBrowser(e,n,t){p.advanced({type:p.types.iframe,title:n,content:t,size:p.sizes.large,callback:i=>{i.userData.editor=e,i.userData.selectionStartPosition=e.model.document.selection.getFirstPosition(),i.userData.selectionEndPosition=e.model.document.selection.getLastPosition(),i.querySelector(".t3js-modal-body")?.setAttribute("id","123")}}).addEventListener("typo3-modal-hide",()=>{this.hideUI(!1)})}}class P extends h.Plugin{static{this.overrides=[b.Link]}static get requires(){return["GeneralHtmlSupport",b.LinkEditing,b.AutoLink,T,S]}static get pluginName(){return"Typo3Link"}}export{C as LINK_ALLOWED_ATTRIBUTES,P as Typo3Link,E as Typo3LinkCommand,T as Typo3LinkEditing,L as Typo3LinkPreviewButtonView,S as Typo3LinkUI,w as Typo3UnlinkCommand,x as addLinkPrefix,P as default,A as removeLinkPrefix};
|