From 24fac7d85da3451b19e7352edd0897fb549a0770 Mon Sep 17 00:00:00 2001 From: bnjmnm Date: Fri, 25 Feb 2022 11:44:06 -0500 Subject: [PATCH] Issue #3227822 by lauriii, Wim Leers: [GHS] Ensure GHS works with our custom plugins, to allow adding additional attributes --- .../modules/ckeditor5/js/build/drupalMedia.js | 2 +- .../src/drupalmediageneralhtmlsupport.js | 98 +++++++++-- .../FunctionalJavascript/CKEditor5Test.php | 2 +- .../src/FunctionalJavascript/EmphasisTest.php | 164 ++++++++++++++++++ .../src/FunctionalJavascript/ImageTest.php | 41 +++++ .../src/FunctionalJavascript/MediaTest.php | 28 +++ 6 files changed, 318 insertions(+), 17 deletions(-) create mode 100644 core/modules/ckeditor5/tests/src/FunctionalJavascript/EmphasisTest.php diff --git a/core/modules/ckeditor5/js/build/drupalMedia.js b/core/modules/ckeditor5/js/build/drupalMedia.js index 31e3cbed4fe..94773754f75 100644 --- a/core/modules/ckeditor5/js/build/drupalMedia.js +++ b/core/modules/ckeditor5/js/build/drupalMedia.js @@ -1 +1 @@ -!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.CKEditor5=t():(e.CKEditor5=e.CKEditor5||{},e.CKEditor5.drupalMedia=t())}(self,(function(){return(()=>{var e={"ckeditor5/src/core.js":(e,t,i)=>{e.exports=i("dll-reference CKEditor5.dll")("./src/core.js")},"ckeditor5/src/engine.js":(e,t,i)=>{e.exports=i("dll-reference CKEditor5.dll")("./src/engine.js")},"ckeditor5/src/ui.js":(e,t,i)=>{e.exports=i("dll-reference CKEditor5.dll")("./src/ui.js")},"ckeditor5/src/utils.js":(e,t,i)=>{e.exports=i("dll-reference CKEditor5.dll")("./src/utils.js")},"ckeditor5/src/widget.js":(e,t,i)=>{e.exports=i("dll-reference CKEditor5.dll")("./src/widget.js")},"dll-reference CKEditor5.dll":e=>{"use strict";e.exports=CKEditor5.dll}},t={};function i(a){var n=t[a];if(void 0!==n)return n.exports;var r=t[a]={exports:{}};return e[a](r,r.exports,i),r.exports}i.d=(e,t)=>{for(var a in t)i.o(t,a)&&!i.o(e,a)&&Object.defineProperty(e,a,{enumerable:!0,get:t[a]})},i.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t);var a={};return(()=>{"use strict";i.d(a,{default:()=>ae});var e=i("ckeditor5/src/core.js"),t=i("ckeditor5/src/widget.js");class n extends e.Command{execute(e){const t=this.editor.plugins.get("DrupalMediaEditing"),i=Object.entries(t.attrs).reduce(((e,[t,i])=>(e[i]=t,e)),{}),a=Object.keys(e).reduce(((t,a)=>(i[a]&&(t[i[a]]=e[a]),t)),{});if(this.editor.plugins.has("DrupalElementStyleEditing")){const t=this.editor.plugins.get("DrupalElementStyleEditing");for(const i of t.normalizedStyles)if(e[i.attributeName]&&i.attributeValue===e[i.attributeName]){a.drupalElementStyle=i.name;break}}this.editor.model.change((e=>{this.editor.model.insertContent(function(e,t){return e.createElement("drupalMedia",t)}(e,a))}))}refresh(){const e=this.editor.model,t=e.document.selection,i=e.schema.findAllowedParent(t.getFirstPosition(),"drupalMedia");this.isEnabled=null!==i}}function r(e){return!!e&&e.is("element","drupalMedia")}function o(e){const i=e.getSelectedElement();return i&&function(e){return(0,t.isWidget)(e)&&!!e.getCustomProperty("drupalMedia")}(i)?i:null}function l(e){const t=typeof e;return null!=e&&("object"===t||"function"===t)}function s(e){for(const t of e){if(t.hasAttribute("data-drupal-media-preview"))return t;if(t.childCount){const e=s(t.getChildren());if(e)return e}}return null}class d extends e.Plugin{static get requires(){return[t.Widget]}init(){this.attrs={drupalMediaAlt:"alt",drupalMediaEntityType:"data-entity-type",drupalMediaEntityUuid:"data-entity-uuid",drupalMediaViewMode:"data-view-mode"};const e=this.editor.config.get("drupalMedia");if(!e)return;const{previewURL:t,themeError:i}=e;this.previewUrl=t,this.labelError=Drupal.t("Preview failed"),this.themeError=i||`\n

${Drupal.t("An error occurred while trying to preview the media. Please save your work and reload this page.")}

\n `,this._defineSchema(),this._defineConverters(),this.editor.commands.add("insertDrupalMedia",new n(this.editor))}async _fetchPreview(e){const t={text:this._renderElement(e),uuid:e.getAttribute("drupalMediaEntityUuid")},i=await fetch(`${this.previewUrl}?${new URLSearchParams(t)}`,{headers:{"X-Drupal-MediaPreview-CSRF-Token":this.editor.config.get("drupalMedia").previewCsrfToken}});if(i.ok){return{label:i.headers.get("drupal-media-label"),preview:await i.text()}}return{label:this.labelError,preview:this.themeError}}_defineSchema(){this.editor.model.schema.register("drupalMedia",{allowWhere:"$block",isObject:!0,isContent:!0,allowAttributes:Object.keys(this.attrs)})}_defineConverters(){const e=this.editor.conversion;e.for("upcast").elementToElement({view:{name:"drupal-media"},model:"drupalMedia"}),e.for("dataDowncast").elementToElement({model:"drupalMedia",view:{name:"drupal-media"}}),e.for("editingDowncast").elementToElement({model:"drupalMedia",view:(e,{writer:i})=>{const a=i.createContainerElement("figure",{class:"drupal-media"});if(!this.previewUrl){const e=i.createRawElement("div",{"data-drupal-media-preview":"unavailable"});i.insert(i.createPositionAt(a,0),e)}return i.setCustomProperty("drupalMedia",!0,a),(0,t.toWidget)(a,i,{label:Drupal.t("Media widget")})}}).add((e=>{const t=(e,t,i)=>{const a=i.writer,n=t.item,r=i.mapper.toViewElement(t.item);let o=s(r.getChildren());if(o){if("ready"!==o.getAttribute("data-drupal-media-preview"))return;a.setAttribute("data-drupal-media-preview","loading",o)}else o=a.createRawElement("div",{"data-drupal-media-preview":"loading"}),a.insert(a.createPositionAt(r,0),o);this._fetchPreview(n).then((({label:e,preview:t})=>{o&&this.editor.editing.view.change((i=>{const a=i.createRawElement("div",{"data-drupal-media-preview":"ready","aria-label":e},(e=>{e.innerHTML=t}));i.insert(i.createPositionBefore(o),a),i.remove(o)}))}))};return e.on("attribute:drupalMediaEntityUuid:drupalMedia",t),e.on("attribute:drupalMediaViewMode:drupalMedia",t),e.on("attribute:drupalMediaEntityType:drupalMedia",t),e.on("attribute:drupalMediaAlt:drupalMedia",t),e})),e.for("editingDowncast").add((e=>{e.on("attribute:drupalElementStyle:drupalMedia",((e,t,i)=>{const a={alignLeft:"drupal-media-style-align-left",alignRight:"drupal-media-style-align-right",alignCenter:"drupal-media-style-align-center"},n=i.mapper.toViewElement(t.item),r=i.writer;a[t.attributeOldValue]&&r.removeClass(a[t.attributeOldValue],n),a[t.attributeNewValue]&&i.consumable.consume(t.item,e.name)&&r.addClass(a[t.attributeNewValue],n)}))})),Object.keys(this.attrs).forEach((t=>{const i={model:{key:t,name:"drupalMedia"},view:{name:"drupal-media",key:this.attrs[t]}};e.for("dataDowncast").attributeToAttribute(i),e.for("upcast").attributeToAttribute(i)}))}_renderElement(e){const t=e.getAttributes();let i="{this.attrs[e[0]]&&"drupalMediaCaption"!==e[0]&&(i+=` ${this.attrs[e[0]]}="${e[1]}"`)})),i+=">",i}static get pluginName(){return"DrupalMediaEditing"}}var u=i("ckeditor5/src/ui.js");class c extends e.Plugin{init(){const e=this.editor,t=this.editor.config.get("drupalMedia");if(!t)return;const{libraryURL:i,openDialog:a,dialogSettings:n={}}=t;i&&"function"==typeof a&&e.ui.componentFactory.add("drupalMedia",(t=>{const r=e.commands.get("insertDrupalMedia"),o=new u.ButtonView(t);return o.set({label:Drupal.t("Insert Drupal Media"),icon:'\n',tooltip:!0}),o.bind("isOn","isEnabled").to(r,"value","isEnabled"),this.listenTo(o,"execute",(()=>{a(i,(({attributes:t})=>{e.execute("insertDrupalMedia",t)}),n)})),o}))}}class m extends e.Plugin{static get requires(){return[t.WidgetToolbarRepository]}static get pluginName(){return"DrupalMediaToolbar"}afterInit(){const{editor:e}=this;var i;e.plugins.get(t.WidgetToolbarRepository).register("drupalMedia",{ariaLabel:Drupal.t("Drupal Media toolbar"),items:(i=e.config.get("drupalMedia.toolbar"),i.map((e=>l(e)?e.name:e))||[]),getRelatedElement:e=>o(e)})}}const p="METADATA_ERROR";class g extends e.Command{refresh(){const e=this.editor.model.document.selection.getSelectedElement();this.isEnabled=r(e)&&e.getAttribute("drupalMediaIsImage")&&e.getAttribute("drupalMediaIsImage")!==p,r(e)&&e.hasAttribute("drupalMediaAlt")?this.value=e.getAttribute("drupalMediaAlt"):this.value=!1}execute(e){const{model:t}=this.editor,i=t.document.selection.getSelectedElement();e.newValue=e.newValue.trim(),t.change((t=>{e.newValue.length>0?t.setAttribute("drupalMediaAlt",e.newValue,i):t.removeAttribute("drupalMediaAlt",i)}))}}class h extends e.Plugin{init(){this._data=new WeakMap}getMetadata(e){if(this._data.get(e))return new Promise((t=>{t(this._data.get(e))}));const t=this.editor.config.get("drupalMedia");if(!t)return new Promise(((e,t)=>{t(new Error("drupalMedia configuration is required for parsing metadata."))}));if(!e.hasAttribute("drupalMediaEntityUuid"))return new Promise(((e,t)=>{t(new Error("drupalMedia element must have drupalMediaEntityUuid attribute to retrieve metadata."))}));const{metadataUrl:i}=t;return(async e=>{const t=await fetch(e);if(t.ok)return JSON.parse(await t.text());throw new Error("Fetching media embed metadata from the server failed.")})(`${i}&${new URLSearchParams({uuid:e.getAttribute("drupalMediaEntityUuid")})}`).then((t=>(this._data.set(e,t),t)))}static get pluginName(){return"DrupalMediaMetadataRepository"}}class f extends e.Plugin{static get requires(){return[h]}static get pluginName(){return"MediaImageTextAlternativeEditing"}_upcastDrupalMediaIsImage(e){const{model:t,plugins:i}=this.editor;i.get("DrupalMediaMetadataRepository").getMetadata(e).then((i=>{e&&t.enqueueChange("transparent",(t=>{t.setAttribute("drupalMediaIsImage",!!i.imageSourceMetadata,e)}))})).catch((i=>{e&&(console.warn(i.toString()),t.enqueueChange("transparent",(t=>{t.setAttribute("drupalMediaIsImage",p,e)})))}))}init(){const{editor:e,editor:{model:t,conversion:i}}=this;t.schema.extend("drupalMedia",{allowAttributes:["drupalMediaIsImage"]}),this.listenTo(t,"insertContent",((e,[t])=>{r(t)&&this._upcastDrupalMediaIsImage(t)})),i.for("upcast").add((e=>{e.on("element:drupal-media",((e,t)=>{const[i]=t.modelRange.getItems();r(i)&&this._upcastDrupalMediaIsImage(i)}),{priority:"lowest"})})),i.for("editingDowncast").add((e=>{e.on("attribute:drupalMediaIsImage",((e,t,i)=>{const{writer:a,mapper:n}=i,r=n.toViewElement(t.item);if(t.attributeNewValue!==p){const e=Array.from(r.getChildren()).find((e=>e.getCustomProperty("drupalMediaMetadataError")));return void(e&&(a.setCustomProperty("widgetLabel",e.getCustomProperty("drupalMediaOriginalWidgetLabel"),e),a.removeElement(e)))}const o=Drupal.t("Not all functionality may be available because some information could not be retrieved."),l=new u.TooltipView;l.text=o,l.position="sw";const s=new u.Template({tag:"span",children:[{tag:"span",attributes:{class:"drupal-media__metadata-error-icon"}},l]}).render(),d=a.createRawElement("div",{class:"drupal-media__metadata-error"},((e,t)=>{t.setContentOf(e,s.outerHTML)}));a.setCustomProperty("drupalMediaMetadataError",!0,d);const c=r.getCustomProperty("widgetLabel");a.setCustomProperty("drupalMediaOriginalWidgetLabel",c,d),a.setCustomProperty("widgetLabel",`${c} (${o})`,r),a.insert(a.createPositionAt(r,0),d)}),{priority:"low"})})),e.commands.add("mediaImageTextAlternative",new g(this.editor))}}function b(e){const t=e.editing.view,i=u.BalloonPanelView.defaultPositions;return{target:t.domConverter.viewToDom(t.document.selection.getSelectedElement()),positions:[i.northArrowSouth,i.northArrowSouthWest,i.northArrowSouthEast,i.southArrowNorth,i.southArrowNorthWest,i.southArrowNorthEast]}}var w=i("ckeditor5/src/utils.js");class v extends u.View{constructor(t){super(t),this.focusTracker=new w.FocusTracker,this.keystrokes=new w.KeystrokeHandler,this.labeledInput=this._createLabeledInputView(),this.set("defaultAltText",void 0),this.defaultAltTextView=this._createDefaultAltTextView(),this.saveButtonView=this._createButton(Drupal.t("Save"),e.icons.check,"ck-button-save"),this.saveButtonView.type="submit",this.cancelButtonView=this._createButton(Drupal.t("Cancel"),e.icons.cancel,"ck-button-cancel","cancel"),this._focusables=new u.ViewCollection,this._focusCycler=new u.FocusCycler({focusables:this._focusables,focusTracker:this.focusTracker,keystrokeHandler:this.keystrokes,actions:{focusPrevious:"shift + tab",focusNext:"tab"}}),this.setTemplate({tag:"form",attributes:{class:["ck","ck-media-alternative-text-form","ck-vertical-form"],tabindex:"-1"},children:[this.defaultAltTextView,this.labeledInput,this.saveButtonView,this.cancelButtonView]}),(0,u.injectCssTransitionDisabler)(this)}render(){super.render(),this.keystrokes.listenTo(this.element),(0,u.submitHandler)({view:this}),[this.labeledInput,this.saveButtonView,this.cancelButtonView].forEach((e=>{this._focusables.add(e),this.focusTracker.add(e.element)}))}_createButton(e,t,i,a){const n=new u.ButtonView(this.locale);return n.set({label:e,icon:t,tooltip:!0}),n.extendTemplate({attributes:{class:i}}),a&&n.delegate("execute").to(this,a),n}_createLabeledInputView(){const e=new u.LabeledFieldView(this.locale,u.createLabeledInputText);return e.label=Drupal.t("Alternative text override"),e}_createDefaultAltTextView(){const e=u.Template.bind(this,this);return new u.Template({tag:"div",attributes:{class:["ck-media-alternative-text-form__default-alt-text",e.if("defaultAltText","ck-hidden",(e=>!e))]},children:[{tag:"strong",attributes:{class:"ck-media-alternative-text-form__default-alt-text-label"},children:[Drupal.t("Default alternative text:")]}," ",{tag:"span",attributes:{class:"ck-media-alternative-text-form__default-alt-text-value"},children:[{text:[e.to("defaultAltText")]}]}]})}}class E extends e.Plugin{static get requires(){return[u.ContextualBalloon]}static get pluginName(){return"MediaImageTextAlternativeUi"}init(){this._createButton(),this._createForm()}destroy(){super.destroy(),this._form.destroy()}_createButton(){const t=this.editor;t.ui.componentFactory.add("mediaImageTextAlternative",(i=>{const a=t.commands.get("mediaImageTextAlternative"),n=new u.ButtonView(i);return n.set({label:Drupal.t("Override media image alternative text"),icon:e.icons.lowVision,tooltip:!0}),n.bind("isVisible").to(a,"isEnabled"),this.listenTo(n,"execute",(()=>{this._showForm()})),n}))}_createForm(){const e=this.editor,t=e.editing.view.document;this._balloon=this.editor.plugins.get("ContextualBalloon"),this._form=new v(e.locale),this._form.render(),this.listenTo(this._form,"submit",(()=>{e.execute("mediaImageTextAlternative",{newValue:this._form.labeledInput.fieldView.element.value}),this._hideForm(!0)})),this.listenTo(this._form,"cancel",(()=>{this._hideForm(!0)})),this._form.keystrokes.set("Esc",((e,t)=>{this._hideForm(!0),t()})),this.listenTo(e.ui,"update",(()=>{o(t.selection)?this._isVisible&&function(e){const t=e.plugins.get("ContextualBalloon");if(o(e.editing.view.document.selection)){const i=b(e);t.updatePosition(i)}}(e):this._hideForm(!0)})),(0,u.clickOutsideHandler)({emitter:this._form,activator:()=>this._isVisible,contextElements:[this._balloon.view.element],callback:()=>this._hideForm()})}_showForm(){if(this._isVisible)return;const e=this.editor,t=e.commands.get("mediaImageTextAlternative"),i=e.plugins.get("DrupalMediaMetadataRepository"),a=this._form.labeledInput;this._form.disableCssTransitions(),this._isInBalloon||this._balloon.add({view:this._form,position:b(e)}),a.fieldView.element.value=t.value||"",a.fieldView.value=a.fieldView.element.value,this._form.defaultAltText="";const n=e.model.document.selection.getSelectedElement();r(n)&&i.getMetadata(n).then((e=>{this._form.defaultAltText=e.imageSourceMetadata?e.imageSourceMetadata.alt:""})).catch((e=>{console.warn(e.toString())})),this._form.labeledInput.fieldView.select(),this._form.enableCssTransitions()}_hideForm(e){this._isInBalloon&&(this._form.focusTracker.isFocused&&this._form.saveButtonView.focus(),this._balloon.remove(this._form),e&&this.editor.editing.view.focus())}get _isVisible(){return this._balloon.visibleView===this._form}get _isInBalloon(){return this._balloon.hasView(this._form)}}class y extends e.Plugin{static get requires(){return[f,E]}static get pluginName(){return"MediaImageTextAlternative"}}function M(e,t,i){if(t.attributes)for(const[a,n]of Object.entries(t.attributes))e.setAttribute(a,n,i);t.styles&&e.setStyle(t.styles,i),t.classes&&e.addClass(t.classes,i)}function k(e){return t=>{t.on("element:drupal-media",((t,i,a)=>{const n=i.viewItem.parent;n.is("element","a")&&function(t,n){const r=e._consumeAllowedAttributes(t,a);r&&a.writer.setAttribute(n,r,i.modelRange)}(n,"htmlLinkAttributes")}),{priority:"low"})}}class C extends e.Plugin{init(){const{editor:e}=this;if(!e.plugins.has("GeneralHtmlSupport"))return;const{schema:t}=e.model,{conversion:i}=e,a=e.plugins.get("DataFilter");t.extend("drupalMedia",{allowAttributes:["htmlLinkAttributes"]}),i.for("upcast").add(k(a)),i.for("editingDowncast").add((e=>e.on("attribute:linkHref:drupalMedia",((e,t,i)=>{if(!i.consumable.consume(t.item,"attribute:htmlLinkAttributes:drupalMedia"))return;const a=i.mapper.toViewElement(t.item),n=function(e,t,i){const a=e.createRangeOn(t);for(const{item:e}of a.getWalker())if(e.is("element",i))return e}(i.writer,a,"a");M(i.writer,t.item.getAttribute("htmlLinkAttributes"),n)}),{priority:"low"}))),i.for("dataDowncast").add((e=>e.on("attribute:linkHref:drupalMedia",((e,t,i)=>{if(!i.consumable.consume(t.item,"attribute:htmlLinkAttributes:drupalMedia"))return;const a=i.mapper.toViewElement(t.item).parent;M(i.writer,t.item.getAttribute("htmlLinkAttributes"),a)}),{priority:"low"})))}static get pluginName(){return"DrupalMediaGeneralHtmlSupport"}}class _ extends e.Plugin{static get requires(){return[d,C,c,m,y]}static get pluginName(){return"DrupalMedia"}}function A(){return e=>{e.on("element:a",((e,t,i)=>{const a=t.viewItem,n=(r=a,Array.from(r.getChildren()).find((e=>"drupal-media"===e.name)));var r;if(!n)return;if(!i.consumable.consume(a,{attributes:["href"]}))return;const o=a.getAttribute("href");if(!o)return;const l=i.convertItem(n,t.modelCursor);t.modelRange=l.modelRange,t.modelCursor=l.modelCursor;const s=t.modelCursor.nodeBefore;s&&s.is("element","drupalMedia")&&i.writer.setAttribute("linkHref",o,s)}),{priority:"high"})}}class x extends e.Plugin{static get requires(){return["LinkEditing","DrupalMediaEditing"]}static get pluginName(){return"DrupalLinkMediaEditing"}init(){const{editor:e}=this;e.model.schema.extend("drupalMedia",{allowAttributes:["linkHref"]}),e.conversion.for("upcast").add(A()),e.conversion.for("editingDowncast").add((e=>{e.on("attribute:linkHref:drupalMedia",((e,t,i)=>{const{writer:a}=i;if(!i.consumable.consume(t.item,e.name))return;const n=i.mapper.toViewElement(t.item),r=Array.from(n.getChildren()).find((e=>"a"===e.name));if(r)t.attributeNewValue?a.setAttribute("href",t.attributeNewValue,r):(a.move(a.createRangeIn(r),a.createPositionAt(n,0)),a.remove(r));else{const e=Array.from(n.getChildren()).find((e=>e.getAttribute("data-drupal-media-preview"))),i=a.createContainerElement("a",{href:t.attributeNewValue});a.insert(a.createPositionAt(n,0),i),a.move(a.createRangeOn(e),a.createPositionAt(i,0))}}),{priority:"high"})})),e.conversion.for("dataDowncast").add((e=>{e.on("attribute:linkHref:drupalMedia",((e,t,i)=>{const{writer:a}=i;if(!i.consumable.consume(t.item,e.name))return;const n=i.mapper.toViewElement(t.item),r=a.createContainerElement("a",{href:t.attributeNewValue});a.insert(a.createPositionBefore(n),r),a.move(a.createRangeOn(n),a.createPositionAt(r,0))}),{priority:"high"})}))}}class D extends e.Plugin{static get requires(){return["LinkEditing","LinkUI","DrupalMediaEditing"]}static get pluginName(){return"DrupalLinkMediaUi"}init(){const{editor:e}=this,t=e.editing.view.document;this.listenTo(t,"click",((t,i)=>{this._isSelectedLinkedMedia(e.model.document.selection)&&(i.preventDefault(),t.stop())}),{priority:"high"}),this._createToolbarLinkMediaButton()}_createToolbarLinkMediaButton(){const{editor:e}=this;e.ui.componentFactory.add("drupalLinkMedia",(t=>{const i=new u.ButtonView(t),a=e.plugins.get("LinkUI"),n=e.commands.get("link");return i.set({isEnabled:!0,label:Drupal.t("Link media"),icon:'\n',keystroke:"Ctrl+K",tooltip:!0,isToggleable:!0}),i.bind("isEnabled").to(n,"isEnabled"),i.bind("isOn").to(n,"value",(e=>!!e)),this.listenTo(i,"execute",(()=>{this._isSelectedLinkedMedia(e.model.document.selection)?a._addActionsView():a._showUI(!0)})),i}))}_isSelectedLinkedMedia(e){const t=e.getSelectedElement();return!!t&&t.is("element","drupalMedia")&&t.hasAttribute("linkHref")}}class S extends e.Plugin{static get requires(){return[x,D]}static get pluginName(){return"DrupalLinkMedia"}}const{objectFullWidth:V,objectInline:I,objectLeft:T,objectRight:L,objectCenter:P,objectBlockLeft:B,objectBlockRight:N}=e.icons,O={inline:{name:"inline",title:"In line",icon:I,modelElements:["imageInline"],isDefault:!0},alignLeft:{name:"alignLeft",title:"Left aligned image",icon:T,modelElements:["imageBlock","imageInline"],className:"image-style-align-left"},alignBlockLeft:{name:"alignBlockLeft",title:"Left aligned image",icon:B,modelElements:["imageBlock"],className:"image-style-block-align-left"},alignCenter:{name:"alignCenter",title:"Centered image",icon:P,modelElements:["imageBlock"],className:"image-style-align-center"},alignRight:{name:"alignRight",title:"Right aligned image",icon:L,modelElements:["imageBlock","imageInline"],className:"image-style-align-right"},alignBlockRight:{name:"alignBlockRight",title:"Right aligned image",icon:N,modelElements:["imageBlock"],className:"image-style-block-align-right"},block:{name:"block",title:"Centered image",icon:P,modelElements:["imageBlock"],isDefault:!0},side:{name:"side",title:"Side image",icon:L,modelElements:["imageBlock"],className:"image-style-side"}},R={full:V,left:B,right:N,center:P,inlineLeft:T,inlineRight:L,inline:I},j=[{name:"imageStyle:wrapText",title:"Wrap text",defaultItem:"imageStyle:alignLeft",items:["imageStyle:alignLeft","imageStyle:alignRight"]},{name:"imageStyle:breakText",title:"Break text",defaultItem:"imageStyle:block",items:["imageStyle:alignBlockLeft","imageStyle:block","imageStyle:alignBlockRight"]}];function F(e){(0,w.logWarning)("image-style-configuration-definition-invalid",e)}const U={normalizeStyles:function(e){return(e.configuredStyles.options||[]).map((e=>function(e){e="string"==typeof e?O[e]?{...O[e]}:{name:e}:function(e,t){const i={...t};for(const a in e)Object.prototype.hasOwnProperty.call(t,a)||(i[a]=e[a]);return i}(O[e.name],e);"string"==typeof e.icon&&(e.icon=R[e.icon]||e.icon);return e}(e))).filter((t=>function(e,{isBlockPluginLoaded:t,isInlinePluginLoaded:i}){const{modelElements:a,name:n}=e;if(!(a&&a.length&&n))return F({style:e}),!1;{const n=[t?"imageBlock":null,i?"imageInline":null];if(!a.some((e=>n.includes(e))))return(0,w.logWarning)("image-style-missing-dependency",{style:e,missingPlugins:a.map((e=>"imageBlock"===e?"ImageBlockEditing":"ImageInlineEditing"))}),!1}return!0}(t,e)))},getDefaultStylesConfiguration:function(e,t){return e&&t?{options:["inline","alignLeft","alignRight","alignCenter","alignBlockLeft","alignBlockRight","block","side"]}:e?{options:["block","side"]}:t?{options:["inline","alignLeft","alignRight"]}:{}},getDefaultDropdownDefinitions:function(e){return e.has("ImageBlockEditing")&&e.has("ImageInlineEditing")?[...j]:[]},warnInvalidStyle:F,DEFAULT_OPTIONS:O,DEFAULT_ICONS:R,DEFAULT_DROPDOWN_DEFINITIONS:j};function H(e,t){const i=e.getSelectedElement();return i&&t.checkAttribute(i,"drupalElementStyle")?i:e.getFirstPosition().findAncestor((e=>t.checkAttribute(e,"drupalElementStyle")))}class W extends e.Command{constructor(e,t){super(e),this._styles=new Map(t.map((e=>[e.name,e])))}refresh(){const e=this.editor,t=H(e.model.document.selection,e.model.schema);this.isEnabled=!!t,this.isEnabled&&t.hasAttribute("drupalElementStyle")?this.value=t.getAttribute("drupalElementStyle"):this.value=!1}execute(e={}){const t=this.editor.model;t.change((i=>{const a=e.value,n=H(t.document.selection,t.schema);!a||this._styles.get(a).isDefault?i.removeAttribute("drupalElementStyle",n):i.setAttribute("drupalElementStyle",a,n)}))}}function q(e,t){for(const i of t)if(i.name===e)return i}class $ extends e.Plugin{init(){const t=this.editor;t.config.define("drupalElementStyles",{options:[]});const i=t.config.get("drupalElementStyles").options;this.normalizedStyles=i.map((t=>("string"==typeof t.icon&&e.icons[t.icon]&&(t.icon=e.icons[t.icon]),t))).filter((e=>e.attributeName&&e.attributeValue?e.modelElements&&Array.isArray(e.modelElements)?!!e.name||(console.warn("drupalElementStyles options must include a name."),!1):(console.warn("drupalElementStyles options must include an array of supported modelElements."),!1):(console.warn("drupalElementStyles options must include attributeName and attributeValue."),!1))),this._setupConversion(),t.commands.add("drupalElementStyle",new W(t,this.normalizedStyles))}_setupConversion(){const e=this.editor,t=e.model.schema,i=(a=this.normalizedStyles,(e,t,i)=>{if(!i.consumable.consume(t.item,e.name))return;const n=q(t.attributeNewValue,a),r=q(t.attributeOldValue,a),o=i.mapper.toViewElement(t.item),l=i.writer;r&&("class"===r.attributeName?l.removeClass(r.attributeValue,o):l.removeAttribute(r.attributeName,o)),n&&("class"===n.attributeName?l.addClass(n.attributeValue,o):l.setAttribute(n.attributeName,n.attributeValue,o))});var a;const n=function(e){const t=e.filter((e=>!e.isDefault));return(e,i,a)=>{if(!i.modelRange)return;const n=i.viewItem,r=(0,w.first)(i.modelRange.getItems());if(r&&a.schema.checkAttribute(r,"drupalElementStyle"))for(const e of t)if("class"===e.attributeName)a.consumable.consume(n,{classes:e.attributeValue})&&a.writer.setAttribute("drupalElementStyle",e.name,r);else if(a.consumable.consume(n,{attributes:[e.attributeName]}))for(const e of t)e.attributeValue===n.getAttribute(e.attributeName)&&a.writer.setAttribute("drupalElementStyle",e.name,r)}}(this.normalizedStyles);e.editing.downcastDispatcher.on("attribute:drupalElementStyle",i),e.data.downcastDispatcher.on("attribute:drupalElementStyle",i);[...new Set(this.normalizedStyles.map((e=>e.modelElements)).flat())].forEach((e=>{t.extend(e,{allowAttributes:"drupalElementStyle"})})),e.data.upcastDispatcher.on("element",n,{priority:"low"})}static get pluginName(){return"DrupalElementStyleEditing"}}const K=e=>e,z=(e,t)=>(e?`${e}: `:"")+t;function Z(e){return`drupalElementStyle:${e}`}class J extends e.Plugin{static get requires(){return[$]}init(){const e=this.editor.plugins,t=this.editor.config.get("drupalMedia.toolbar")||[],i=Object.values(e.get("DrupalElementStyleEditing").normalizedStyles);i.forEach((e=>{this._createButton(e)}));t.filter(l).forEach((e=>{this._createDropdown(e,i)}))}_createDropdown(e,t){const i=this.editor.ui.componentFactory;i.add(e.name,(a=>{let n;const{defaultItem:r,items:o,title:l}=e,s=o.filter((e=>t.find((({name:t})=>Z(t)===e)))).map((e=>{const t=i.create(e);return e===r&&(n=t),t}));o.length!==s.length&&U.warnInvalidStyle({dropdown:e});const d=(0,u.createDropdown)(a,u.SplitButtonView),c=d.buttonView;return(0,u.addToolbarToDropdown)(d,s),c.set({label:z(l,n.label),class:null,tooltip:!0}),c.bind("icon").toMany(s,"isOn",((...e)=>{const t=e.findIndex(K);return t<0?n.icon:s[t].icon})),c.bind("label").toMany(s,"isOn",((...e)=>{const t=e.findIndex(K);return z(l,t<0?n.label:s[t].label)})),c.bind("isOn").toMany(s,"isOn",((...e)=>e.some(K))),c.bind("class").toMany(s,"isOn",((...e)=>e.some(K)?"ck-splitbutton_flatten":null)),c.on("execute",(()=>{s.some((({isOn:e})=>e))?d.isOpen=!d.isOpen:n.fire("execute")})),d.bind("isEnabled").toMany(s,"isEnabled",((...e)=>e.some(K))),d}))}_createButton(e){const t=e.name;this.editor.ui.componentFactory.add(Z(t),(i=>{const a=this.editor.commands.get("drupalElementStyle"),n=new u.ButtonView(i);return n.set({label:e.title,icon:e.icon,tooltip:!0,isToggleable:!0}),n.bind("isEnabled").to(a,"isEnabled"),n.bind("isOn").to(a,"value",(e=>e===t)),n.on("execute",this._executeCommand.bind(this,t)),n}))}_executeCommand(e){this.editor.execute("drupalElementStyle",{value:e}),this.editor.editing.view.focus()}static get pluginName(){return"DrupalElementStyleUi"}}class G extends e.Plugin{static get requires(){return[$,J]}static get pluginName(){return"DrupalElementStyle"}}var X=i("ckeditor5/src/engine.js");function Q(e){for(const t of e.getChildren())if(t&&t.is("element","caption"))return t;return null}class Y extends e.Command{refresh(){const e=this.editor.model.document.selection.getSelectedElement();this.isEnabled=r(e),this.isEnabled?this.value=!!Q(e):this.value=!1}execute(e={}){const{focusCaptionOnShow:t}=e;this.editor.model.change((e=>{this.value?this._hideDrupalMediaCaption(e):this._showDrupalMediaCaption(e,t)}))}_showDrupalMediaCaption(e,t){const i=this.editor.model.document.selection,a=this.editor.plugins.get("DrupalMediaCaptionEditing"),n=i.getSelectedElement(),r=a._getSavedCaption(n)||e.createElement("caption");e.append(r,n),t&&e.setSelection(r,"in")}_hideDrupalMediaCaption(e){const t=this.editor,i=t.model.document.selection,a=t.plugins.get("DrupalMediaCaptionEditing"),n=i.getSelectedElement();if(n){const t=Q(n);a._saveCaption(n,t),e.setSelection(n,"on"),e.remove(t)}}}class ee extends e.Plugin{static get requires(){return[]}static get pluginName(){return"DrupalMediaCaptionEditing"}constructor(e){super(e),this._savedCaptionsMap=new WeakMap}init(){const e=this.editor,t=e.model.schema;t.isRegistered("caption")?t.extend("caption",{allowIn:"drupalMedia"}):t.register("caption",{allowIn:"drupalMedia",allowContentOf:"$block",isLimit:!0}),e.commands.add("toggleMediaCaption",new Y(e)),this._setupConversion()}_setupConversion(){const e=this.editor,i=e.editing.view;var a;e.conversion.for("upcast").add(function(e){const t=(t,i,a)=>{const{viewItem:n}=i,{writer:r,consumable:o}=a;if(!i.modelRange||!o.consume(n,{attributes:["data-caption"]}))return;const l=r.createElement("caption"),s=i.modelRange.start.nodeAfter,d=e.data.processor.toView(n.getAttribute("data-caption")),u=r.createDocumentFragment();a.consumable.constructor.createFrom(d,a.consumable),a.convertChildren(d,u);for(const e of Array.from(u.getChildren()))r.append(e,l);r.append(l,s)};return e=>{e.on("element:drupal-media",t,{priority:"low"})}}(e)),e.conversion.for("editingDowncast").elementToElement({model:"caption",view:(e,{writer:a})=>{if(!r(e.parent))return null;const n=a.createEditableElement("figcaption");return(0,X.enablePlaceholder)({view:i,element:n,text:Drupal.t("Enter media caption"),keepOnFocus:!0}),(0,t.toWidgetEditable)(n,a)}}),e.editing.mapper.on("modelToViewPosition",(a=i,(e,t)=>{const i=t.modelPosition,n=i.parent;if(!r(n))return;const o=t.mapper.toViewElement(n);t.viewPosition=a.createPositionAt(o,i.offset+1)})),e.conversion.for("dataDowncast").add(function(e){return t=>{t.on("insert:caption",((t,i,a)=>{const{consumable:n,writer:o,mapper:l}=a;if(!r(i.item.parent)||!n.consume(i.item,"insert"))return;const s=e.model.createRangeIn(i.item),d=o.createDocumentFragment();l.bindElements(i.item,d);for(const{item:t}of Array.from(s)){const i={item:t,range:e.model.createRangeOn(t)},n=`insert:${t.name||"$text"}`;e.data.downcastDispatcher.fire(n,i,a);for(const n of t.getAttributeKeys())Object.assign(i,{attributeKey:n,attributeOldValue:null,attributeNewValue:i.item.getAttribute(n)}),e.data.downcastDispatcher.fire(`attribute:${n}`,i,a)}for(const e of o.createRangeIn(d).getItems())l.unbindViewElement(e);l.unbindViewElement(d);const u=e.data.processor.toData(d);if(u){const e=l.toViewElement(i.item.parent);o.setAttribute("data-caption",u,e)}}))}}(e))}_getSavedCaption(e){const t=this._savedCaptionsMap.get(e);return t?X.Element.fromJSON(t):null}_saveCaption(e,t){this._savedCaptionsMap.set(e,t.toJSON())}}class te extends e.Plugin{static get requires(){return[]}static get pluginName(){return"DrupalMediaCaptionUI"}init(){const{editor:t}=this,i=t.editing.view;t.ui.componentFactory.add("toggleDrupalMediaCaption",(a=>{const n=new u.ButtonView(a),r=t.commands.get("toggleMediaCaption");return n.set({label:Drupal.t("Caption media"),icon:e.icons.caption,tooltip:!0,isToggleable:!0}),n.bind("isOn","isEnabled").to(r,"value","isEnabled"),n.bind("label").to(r,"value",(e=>e?Drupal.t("Toggle caption off"):Drupal.t("Toggle caption on"))),this.listenTo(n,"execute",(()=>{t.execute("toggleMediaCaption",{focusCaptionOnShow:!0});const e=t.model.document.selection.getFirstPosition().findAncestor("caption");if(e){const a=t.editing.mapper.toViewElement(e);i.scrollToTheSelection(),i.change((e=>{e.addClass("drupal-media__caption_highlighted",a)}))}})),n}))}}class ie extends e.Plugin{static get requires(){return[ee,te]}static get pluginName(){return"DrupalMediaCaption"}}const ae={DrupalMedia:_,MediaImageTextAlternative:y,MediaImageTextAlternativeEditing:f,MediaImageTextAlternativeUi:E,DrupalLinkMedia:S,DrupalMediaCaption:ie,DrupalElementStyle:G}})(),a=a.default})()})); \ No newline at end of file +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.CKEditor5=t():(e.CKEditor5=e.CKEditor5||{},e.CKEditor5.drupalMedia=t())}(self,(function(){return(()=>{var e={"ckeditor5/src/core.js":(e,t,i)=>{e.exports=i("dll-reference CKEditor5.dll")("./src/core.js")},"ckeditor5/src/engine.js":(e,t,i)=>{e.exports=i("dll-reference CKEditor5.dll")("./src/engine.js")},"ckeditor5/src/ui.js":(e,t,i)=>{e.exports=i("dll-reference CKEditor5.dll")("./src/ui.js")},"ckeditor5/src/utils.js":(e,t,i)=>{e.exports=i("dll-reference CKEditor5.dll")("./src/utils.js")},"ckeditor5/src/widget.js":(e,t,i)=>{e.exports=i("dll-reference CKEditor5.dll")("./src/widget.js")},"dll-reference CKEditor5.dll":e=>{"use strict";e.exports=CKEditor5.dll}},t={};function i(a){var n=t[a];if(void 0!==n)return n.exports;var r=t[a]={exports:{}};return e[a](r,r.exports,i),r.exports}i.d=(e,t)=>{for(var a in t)i.o(t,a)&&!i.o(e,a)&&Object.defineProperty(e,a,{enumerable:!0,get:t[a]})},i.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t);var a={};return(()=>{"use strict";i.d(a,{default:()=>ae});var e=i("ckeditor5/src/core.js"),t=i("ckeditor5/src/widget.js");class n extends e.Command{execute(e){const t=this.editor.plugins.get("DrupalMediaEditing"),i=Object.entries(t.attrs).reduce(((e,[t,i])=>(e[i]=t,e)),{}),a=Object.keys(e).reduce(((t,a)=>(i[a]&&(t[i[a]]=e[a]),t)),{});if(this.editor.plugins.has("DrupalElementStyleEditing")){const t=this.editor.plugins.get("DrupalElementStyleEditing");for(const i of t.normalizedStyles)if(e[i.attributeName]&&i.attributeValue===e[i.attributeName]){a.drupalElementStyle=i.name;break}}this.editor.model.change((e=>{this.editor.model.insertContent(function(e,t){return e.createElement("drupalMedia",t)}(e,a))}))}refresh(){const e=this.editor.model,t=e.document.selection,i=e.schema.findAllowedParent(t.getFirstPosition(),"drupalMedia");this.isEnabled=null!==i}}function r(e){return!!e&&e.is("element","drupalMedia")}function o(e){const i=e.getSelectedElement();return i&&function(e){return(0,t.isWidget)(e)&&!!e.getCustomProperty("drupalMedia")}(i)?i:null}function l(e){const t=typeof e;return null!=e&&("object"===t||"function"===t)}function s(e){for(const t of e){if(t.hasAttribute("data-drupal-media-preview"))return t;if(t.childCount){const e=s(t.getChildren());if(e)return e}}return null}class d extends e.Plugin{static get requires(){return[t.Widget]}init(){this.attrs={drupalMediaAlt:"alt",drupalMediaEntityType:"data-entity-type",drupalMediaEntityUuid:"data-entity-uuid",drupalMediaViewMode:"data-view-mode"};const e=this.editor.config.get("drupalMedia");if(!e)return;const{previewURL:t,themeError:i}=e;this.previewUrl=t,this.labelError=Drupal.t("Preview failed"),this.themeError=i||`\n

${Drupal.t("An error occurred while trying to preview the media. Please save your work and reload this page.")}

\n `,this._defineSchema(),this._defineConverters(),this.editor.commands.add("insertDrupalMedia",new n(this.editor))}async _fetchPreview(e){const t={text:this._renderElement(e),uuid:e.getAttribute("drupalMediaEntityUuid")},i=await fetch(`${this.previewUrl}?${new URLSearchParams(t)}`,{headers:{"X-Drupal-MediaPreview-CSRF-Token":this.editor.config.get("drupalMedia").previewCsrfToken}});if(i.ok){return{label:i.headers.get("drupal-media-label"),preview:await i.text()}}return{label:this.labelError,preview:this.themeError}}_defineSchema(){this.editor.model.schema.register("drupalMedia",{allowWhere:"$block",isObject:!0,isContent:!0,allowAttributes:Object.keys(this.attrs)})}_defineConverters(){const e=this.editor.conversion;e.for("upcast").elementToElement({view:{name:"drupal-media"},model:"drupalMedia"}),e.for("dataDowncast").elementToElement({model:"drupalMedia",view:{name:"drupal-media"}}),e.for("editingDowncast").elementToElement({model:"drupalMedia",view:(e,{writer:i})=>{const a=i.createContainerElement("figure",{class:"drupal-media"});if(!this.previewUrl){const e=i.createRawElement("div",{"data-drupal-media-preview":"unavailable"});i.insert(i.createPositionAt(a,0),e)}return i.setCustomProperty("drupalMedia",!0,a),(0,t.toWidget)(a,i,{label:Drupal.t("Media widget")})}}).add((e=>{const t=(e,t,i)=>{const a=i.writer,n=t.item,r=i.mapper.toViewElement(t.item);let o=s(r.getChildren());if(o){if("ready"!==o.getAttribute("data-drupal-media-preview"))return;a.setAttribute("data-drupal-media-preview","loading",o)}else o=a.createRawElement("div",{"data-drupal-media-preview":"loading"}),a.insert(a.createPositionAt(r,0),o);this._fetchPreview(n).then((({label:e,preview:t})=>{o&&this.editor.editing.view.change((i=>{const a=i.createRawElement("div",{"data-drupal-media-preview":"ready","aria-label":e},(e=>{e.innerHTML=t}));i.insert(i.createPositionBefore(o),a),i.remove(o)}))}))};return e.on("attribute:drupalMediaEntityUuid:drupalMedia",t),e.on("attribute:drupalMediaViewMode:drupalMedia",t),e.on("attribute:drupalMediaEntityType:drupalMedia",t),e.on("attribute:drupalMediaAlt:drupalMedia",t),e})),e.for("editingDowncast").add((e=>{e.on("attribute:drupalElementStyle:drupalMedia",((e,t,i)=>{const a={alignLeft:"drupal-media-style-align-left",alignRight:"drupal-media-style-align-right",alignCenter:"drupal-media-style-align-center"},n=i.mapper.toViewElement(t.item),r=i.writer;a[t.attributeOldValue]&&r.removeClass(a[t.attributeOldValue],n),a[t.attributeNewValue]&&i.consumable.consume(t.item,e.name)&&r.addClass(a[t.attributeNewValue],n)}))})),Object.keys(this.attrs).forEach((t=>{const i={model:{key:t,name:"drupalMedia"},view:{name:"drupal-media",key:this.attrs[t]}};e.for("dataDowncast").attributeToAttribute(i),e.for("upcast").attributeToAttribute(i)}))}_renderElement(e){const t=e.getAttributes();let i="{this.attrs[e[0]]&&"drupalMediaCaption"!==e[0]&&(i+=` ${this.attrs[e[0]]}="${e[1]}"`)})),i+=">",i}static get pluginName(){return"DrupalMediaEditing"}}var u=i("ckeditor5/src/ui.js");class c extends e.Plugin{init(){const e=this.editor,t=this.editor.config.get("drupalMedia");if(!t)return;const{libraryURL:i,openDialog:a,dialogSettings:n={}}=t;i&&"function"==typeof a&&e.ui.componentFactory.add("drupalMedia",(t=>{const r=e.commands.get("insertDrupalMedia"),o=new u.ButtonView(t);return o.set({label:Drupal.t("Insert Drupal Media"),icon:'\n',tooltip:!0}),o.bind("isOn","isEnabled").to(r,"value","isEnabled"),this.listenTo(o,"execute",(()=>{a(i,(({attributes:t})=>{e.execute("insertDrupalMedia",t)}),n)})),o}))}}class m extends e.Plugin{static get requires(){return[t.WidgetToolbarRepository]}static get pluginName(){return"DrupalMediaToolbar"}afterInit(){const{editor:e}=this;var i;e.plugins.get(t.WidgetToolbarRepository).register("drupalMedia",{ariaLabel:Drupal.t("Drupal Media toolbar"),items:(i=e.config.get("drupalMedia.toolbar"),i.map((e=>l(e)?e.name:e))||[]),getRelatedElement:e=>o(e)})}}const p="METADATA_ERROR";class g extends e.Command{refresh(){const e=this.editor.model.document.selection.getSelectedElement();this.isEnabled=r(e)&&e.getAttribute("drupalMediaIsImage")&&e.getAttribute("drupalMediaIsImage")!==p,r(e)&&e.hasAttribute("drupalMediaAlt")?this.value=e.getAttribute("drupalMediaAlt"):this.value=!1}execute(e){const{model:t}=this.editor,i=t.document.selection.getSelectedElement();e.newValue=e.newValue.trim(),t.change((t=>{e.newValue.length>0?t.setAttribute("drupalMediaAlt",e.newValue,i):t.removeAttribute("drupalMediaAlt",i)}))}}class h extends e.Plugin{init(){this._data=new WeakMap}getMetadata(e){if(this._data.get(e))return new Promise((t=>{t(this._data.get(e))}));const t=this.editor.config.get("drupalMedia");if(!t)return new Promise(((e,t)=>{t(new Error("drupalMedia configuration is required for parsing metadata."))}));if(!e.hasAttribute("drupalMediaEntityUuid"))return new Promise(((e,t)=>{t(new Error("drupalMedia element must have drupalMediaEntityUuid attribute to retrieve metadata."))}));const{metadataUrl:i}=t;return(async e=>{const t=await fetch(e);if(t.ok)return JSON.parse(await t.text());throw new Error("Fetching media embed metadata from the server failed.")})(`${i}&${new URLSearchParams({uuid:e.getAttribute("drupalMediaEntityUuid")})}`).then((t=>(this._data.set(e,t),t)))}static get pluginName(){return"DrupalMediaMetadataRepository"}}class f extends e.Plugin{static get requires(){return[h]}static get pluginName(){return"MediaImageTextAlternativeEditing"}_upcastDrupalMediaIsImage(e){const{model:t,plugins:i}=this.editor;i.get("DrupalMediaMetadataRepository").getMetadata(e).then((i=>{e&&t.enqueueChange("transparent",(t=>{t.setAttribute("drupalMediaIsImage",!!i.imageSourceMetadata,e)}))})).catch((i=>{e&&(console.warn(i.toString()),t.enqueueChange("transparent",(t=>{t.setAttribute("drupalMediaIsImage",p,e)})))}))}init(){const{editor:e,editor:{model:t,conversion:i}}=this;t.schema.extend("drupalMedia",{allowAttributes:["drupalMediaIsImage"]}),this.listenTo(t,"insertContent",((e,[t])=>{r(t)&&this._upcastDrupalMediaIsImage(t)})),i.for("upcast").add((e=>{e.on("element:drupal-media",((e,t)=>{const[i]=t.modelRange.getItems();r(i)&&this._upcastDrupalMediaIsImage(i)}),{priority:"lowest"})})),i.for("editingDowncast").add((e=>{e.on("attribute:drupalMediaIsImage",((e,t,i)=>{const{writer:a,mapper:n}=i,r=n.toViewElement(t.item);if(t.attributeNewValue!==p){const e=Array.from(r.getChildren()).find((e=>e.getCustomProperty("drupalMediaMetadataError")));return void(e&&(a.setCustomProperty("widgetLabel",e.getCustomProperty("drupalMediaOriginalWidgetLabel"),e),a.removeElement(e)))}const o=Drupal.t("Not all functionality may be available because some information could not be retrieved."),l=new u.TooltipView;l.text=o,l.position="sw";const s=new u.Template({tag:"span",children:[{tag:"span",attributes:{class:"drupal-media__metadata-error-icon"}},l]}).render(),d=a.createRawElement("div",{class:"drupal-media__metadata-error"},((e,t)=>{t.setContentOf(e,s.outerHTML)}));a.setCustomProperty("drupalMediaMetadataError",!0,d);const c=r.getCustomProperty("widgetLabel");a.setCustomProperty("drupalMediaOriginalWidgetLabel",c,d),a.setCustomProperty("widgetLabel",`${c} (${o})`,r),a.insert(a.createPositionAt(r,0),d)}),{priority:"low"})})),e.commands.add("mediaImageTextAlternative",new g(this.editor))}}function b(e){const t=e.editing.view,i=u.BalloonPanelView.defaultPositions;return{target:t.domConverter.viewToDom(t.document.selection.getSelectedElement()),positions:[i.northArrowSouth,i.northArrowSouthWest,i.northArrowSouthEast,i.southArrowNorth,i.southArrowNorthWest,i.southArrowNorthEast]}}var w=i("ckeditor5/src/utils.js");class v extends u.View{constructor(t){super(t),this.focusTracker=new w.FocusTracker,this.keystrokes=new w.KeystrokeHandler,this.labeledInput=this._createLabeledInputView(),this.set("defaultAltText",void 0),this.defaultAltTextView=this._createDefaultAltTextView(),this.saveButtonView=this._createButton(Drupal.t("Save"),e.icons.check,"ck-button-save"),this.saveButtonView.type="submit",this.cancelButtonView=this._createButton(Drupal.t("Cancel"),e.icons.cancel,"ck-button-cancel","cancel"),this._focusables=new u.ViewCollection,this._focusCycler=new u.FocusCycler({focusables:this._focusables,focusTracker:this.focusTracker,keystrokeHandler:this.keystrokes,actions:{focusPrevious:"shift + tab",focusNext:"tab"}}),this.setTemplate({tag:"form",attributes:{class:["ck","ck-media-alternative-text-form","ck-vertical-form"],tabindex:"-1"},children:[this.defaultAltTextView,this.labeledInput,this.saveButtonView,this.cancelButtonView]}),(0,u.injectCssTransitionDisabler)(this)}render(){super.render(),this.keystrokes.listenTo(this.element),(0,u.submitHandler)({view:this}),[this.labeledInput,this.saveButtonView,this.cancelButtonView].forEach((e=>{this._focusables.add(e),this.focusTracker.add(e.element)}))}_createButton(e,t,i,a){const n=new u.ButtonView(this.locale);return n.set({label:e,icon:t,tooltip:!0}),n.extendTemplate({attributes:{class:i}}),a&&n.delegate("execute").to(this,a),n}_createLabeledInputView(){const e=new u.LabeledFieldView(this.locale,u.createLabeledInputText);return e.label=Drupal.t("Alternative text override"),e}_createDefaultAltTextView(){const e=u.Template.bind(this,this);return new u.Template({tag:"div",attributes:{class:["ck-media-alternative-text-form__default-alt-text",e.if("defaultAltText","ck-hidden",(e=>!e))]},children:[{tag:"strong",attributes:{class:"ck-media-alternative-text-form__default-alt-text-label"},children:[Drupal.t("Default alternative text:")]}," ",{tag:"span",attributes:{class:"ck-media-alternative-text-form__default-alt-text-value"},children:[{text:[e.to("defaultAltText")]}]}]})}}class E extends e.Plugin{static get requires(){return[u.ContextualBalloon]}static get pluginName(){return"MediaImageTextAlternativeUi"}init(){this._createButton(),this._createForm()}destroy(){super.destroy(),this._form.destroy()}_createButton(){const t=this.editor;t.ui.componentFactory.add("mediaImageTextAlternative",(i=>{const a=t.commands.get("mediaImageTextAlternative"),n=new u.ButtonView(i);return n.set({label:Drupal.t("Override media image alternative text"),icon:e.icons.lowVision,tooltip:!0}),n.bind("isVisible").to(a,"isEnabled"),this.listenTo(n,"execute",(()=>{this._showForm()})),n}))}_createForm(){const e=this.editor,t=e.editing.view.document;this._balloon=this.editor.plugins.get("ContextualBalloon"),this._form=new v(e.locale),this._form.render(),this.listenTo(this._form,"submit",(()=>{e.execute("mediaImageTextAlternative",{newValue:this._form.labeledInput.fieldView.element.value}),this._hideForm(!0)})),this.listenTo(this._form,"cancel",(()=>{this._hideForm(!0)})),this._form.keystrokes.set("Esc",((e,t)=>{this._hideForm(!0),t()})),this.listenTo(e.ui,"update",(()=>{o(t.selection)?this._isVisible&&function(e){const t=e.plugins.get("ContextualBalloon");if(o(e.editing.view.document.selection)){const i=b(e);t.updatePosition(i)}}(e):this._hideForm(!0)})),(0,u.clickOutsideHandler)({emitter:this._form,activator:()=>this._isVisible,contextElements:[this._balloon.view.element],callback:()=>this._hideForm()})}_showForm(){if(this._isVisible)return;const e=this.editor,t=e.commands.get("mediaImageTextAlternative"),i=e.plugins.get("DrupalMediaMetadataRepository"),a=this._form.labeledInput;this._form.disableCssTransitions(),this._isInBalloon||this._balloon.add({view:this._form,position:b(e)}),a.fieldView.element.value=t.value||"",a.fieldView.value=a.fieldView.element.value,this._form.defaultAltText="";const n=e.model.document.selection.getSelectedElement();r(n)&&i.getMetadata(n).then((e=>{this._form.defaultAltText=e.imageSourceMetadata?e.imageSourceMetadata.alt:""})).catch((e=>{console.warn(e.toString())})),this._form.labeledInput.fieldView.select(),this._form.enableCssTransitions()}_hideForm(e){this._isInBalloon&&(this._form.focusTracker.isFocused&&this._form.saveButtonView.focus(),this._balloon.remove(this._form),e&&this.editor.editing.view.focus())}get _isVisible(){return this._balloon.visibleView===this._form}get _isInBalloon(){return this._balloon.hasView(this._form)}}class y extends e.Plugin{static get requires(){return[f,E]}static get pluginName(){return"MediaImageTextAlternative"}}function M(e,t,i){if(t.attributes)for(const[a,n]of Object.entries(t.attributes))e.setAttribute(a,n,i);t.styles&&e.setStyle(t.styles,i),t.classes&&e.addClass(t.classes,i)}function k(e,t,i){if(!i.consumable.consume(t.item,e.name))return;const a=i.mapper.toViewElement(t.item);M(i.writer,t.attributeNewValue,a)}class C extends e.Plugin{constructor(e){if(super(e),!e.plugins.has("GeneralHtmlSupport"))return;e.plugins.has("DataFilter")&&e.plugins.has("DataSchema")||console.error("DataFilter and DataSchema plugins are required for Drupal Media to integrate with General HTML Support plugin.");const{schema:t}=e.model,{conversion:i}=e,a=this.editor.plugins.get("DataFilter");this.editor.plugins.get("DataSchema").registerBlockElement({model:"drupalMedia",view:"drupal-media"}),a.on("register:drupal-media",((e,n)=>{"drupalMedia"===n.model&&(t.extend("drupalMedia",{allowAttributes:["htmlLinkAttributes","htmlAttributes"]}),i.for("upcast").add(function(e){return t=>{t.on("element:drupal-media",((t,i,a)=>{function n(t,n){const r=e._consumeAllowedAttributes(t,a);r&&a.writer.setAttribute(n,r,i.modelRange)}const r=i.viewItem,o=r.parent;n(r,"htmlAttributes"),o.is("element","a")&&n(o,"htmlLinkAttributes")}),{priority:"low"})}}(a)),i.for("editingDowncast").add((e=>{e.on("attribute:linkHref:drupalMedia",((e,t,i)=>{if(!i.consumable.consume(t.item,"attribute:htmlLinkAttributes:drupalMedia"))return;const a=i.mapper.toViewElement(t.item),n=function(e,t,i){const a=e.createRangeOn(t);for(const{item:e}of a.getWalker())if(e.is("element",i))return e}(i.writer,a,"a");M(i.writer,t.item.getAttribute("htmlLinkAttributes"),n)}),{priority:"low"}),e.on("attribute:htmlAttributes:drupalMedia",k,{priority:"low"})})),i.for("dataDowncast").add((e=>{e.on("attribute:linkHref:drupalMedia",((e,t,i)=>{if(!i.consumable.consume(t.item,"attribute:htmlLinkAttributes:drupalMedia"))return;const a=i.mapper.toViewElement(t.item).parent;M(i.writer,t.item.getAttribute("htmlLinkAttributes"),a)}),{priority:"low"}),e.on("attribute:htmlAttributes:drupalMedia",k,{priority:"low"})})),e.stop())}))}static get pluginName(){return"DrupalMediaGeneralHtmlSupport"}}class A extends e.Plugin{static get requires(){return[d,C,c,m,y]}static get pluginName(){return"DrupalMedia"}}function _(){return e=>{e.on("element:a",((e,t,i)=>{const a=t.viewItem,n=(r=a,Array.from(r.getChildren()).find((e=>"drupal-media"===e.name)));var r;if(!n)return;if(!i.consumable.consume(a,{attributes:["href"]}))return;const o=a.getAttribute("href");if(!o)return;const l=i.convertItem(n,t.modelCursor);t.modelRange=l.modelRange,t.modelCursor=l.modelCursor;const s=t.modelCursor.nodeBefore;s&&s.is("element","drupalMedia")&&i.writer.setAttribute("linkHref",o,s)}),{priority:"high"})}}class x extends e.Plugin{static get requires(){return["LinkEditing","DrupalMediaEditing"]}static get pluginName(){return"DrupalLinkMediaEditing"}init(){const{editor:e}=this;e.model.schema.extend("drupalMedia",{allowAttributes:["linkHref"]}),e.conversion.for("upcast").add(_()),e.conversion.for("editingDowncast").add((e=>{e.on("attribute:linkHref:drupalMedia",((e,t,i)=>{const{writer:a}=i;if(!i.consumable.consume(t.item,e.name))return;const n=i.mapper.toViewElement(t.item),r=Array.from(n.getChildren()).find((e=>"a"===e.name));if(r)t.attributeNewValue?a.setAttribute("href",t.attributeNewValue,r):(a.move(a.createRangeIn(r),a.createPositionAt(n,0)),a.remove(r));else{const e=Array.from(n.getChildren()).find((e=>e.getAttribute("data-drupal-media-preview"))),i=a.createContainerElement("a",{href:t.attributeNewValue});a.insert(a.createPositionAt(n,0),i),a.move(a.createRangeOn(e),a.createPositionAt(i,0))}}),{priority:"high"})})),e.conversion.for("dataDowncast").add((e=>{e.on("attribute:linkHref:drupalMedia",((e,t,i)=>{const{writer:a}=i;if(!i.consumable.consume(t.item,e.name))return;const n=i.mapper.toViewElement(t.item),r=a.createContainerElement("a",{href:t.attributeNewValue});a.insert(a.createPositionBefore(n),r),a.move(a.createRangeOn(n),a.createPositionAt(r,0))}),{priority:"high"})}))}}class D extends e.Plugin{static get requires(){return["LinkEditing","LinkUI","DrupalMediaEditing"]}static get pluginName(){return"DrupalLinkMediaUi"}init(){const{editor:e}=this,t=e.editing.view.document;this.listenTo(t,"click",((t,i)=>{this._isSelectedLinkedMedia(e.model.document.selection)&&(i.preventDefault(),t.stop())}),{priority:"high"}),this._createToolbarLinkMediaButton()}_createToolbarLinkMediaButton(){const{editor:e}=this;e.ui.componentFactory.add("drupalLinkMedia",(t=>{const i=new u.ButtonView(t),a=e.plugins.get("LinkUI"),n=e.commands.get("link");return i.set({isEnabled:!0,label:Drupal.t("Link media"),icon:'\n',keystroke:"Ctrl+K",tooltip:!0,isToggleable:!0}),i.bind("isEnabled").to(n,"isEnabled"),i.bind("isOn").to(n,"value",(e=>!!e)),this.listenTo(i,"execute",(()=>{this._isSelectedLinkedMedia(e.model.document.selection)?a._addActionsView():a._showUI(!0)})),i}))}_isSelectedLinkedMedia(e){const t=e.getSelectedElement();return!!t&&t.is("element","drupalMedia")&&t.hasAttribute("linkHref")}}class S extends e.Plugin{static get requires(){return[x,D]}static get pluginName(){return"DrupalLinkMedia"}}const{objectFullWidth:V,objectInline:I,objectLeft:T,objectRight:L,objectCenter:P,objectBlockLeft:B,objectBlockRight:N}=e.icons,O={inline:{name:"inline",title:"In line",icon:I,modelElements:["imageInline"],isDefault:!0},alignLeft:{name:"alignLeft",title:"Left aligned image",icon:T,modelElements:["imageBlock","imageInline"],className:"image-style-align-left"},alignBlockLeft:{name:"alignBlockLeft",title:"Left aligned image",icon:B,modelElements:["imageBlock"],className:"image-style-block-align-left"},alignCenter:{name:"alignCenter",title:"Centered image",icon:P,modelElements:["imageBlock"],className:"image-style-align-center"},alignRight:{name:"alignRight",title:"Right aligned image",icon:L,modelElements:["imageBlock","imageInline"],className:"image-style-align-right"},alignBlockRight:{name:"alignBlockRight",title:"Right aligned image",icon:N,modelElements:["imageBlock"],className:"image-style-block-align-right"},block:{name:"block",title:"Centered image",icon:P,modelElements:["imageBlock"],isDefault:!0},side:{name:"side",title:"Side image",icon:L,modelElements:["imageBlock"],className:"image-style-side"}},R={full:V,left:B,right:N,center:P,inlineLeft:T,inlineRight:L,inline:I},j=[{name:"imageStyle:wrapText",title:"Wrap text",defaultItem:"imageStyle:alignLeft",items:["imageStyle:alignLeft","imageStyle:alignRight"]},{name:"imageStyle:breakText",title:"Break text",defaultItem:"imageStyle:block",items:["imageStyle:alignBlockLeft","imageStyle:block","imageStyle:alignBlockRight"]}];function F(e){(0,w.logWarning)("image-style-configuration-definition-invalid",e)}const U={normalizeStyles:function(e){return(e.configuredStyles.options||[]).map((e=>function(e){e="string"==typeof e?O[e]?{...O[e]}:{name:e}:function(e,t){const i={...t};for(const a in e)Object.prototype.hasOwnProperty.call(t,a)||(i[a]=e[a]);return i}(O[e.name],e);"string"==typeof e.icon&&(e.icon=R[e.icon]||e.icon);return e}(e))).filter((t=>function(e,{isBlockPluginLoaded:t,isInlinePluginLoaded:i}){const{modelElements:a,name:n}=e;if(!(a&&a.length&&n))return F({style:e}),!1;{const n=[t?"imageBlock":null,i?"imageInline":null];if(!a.some((e=>n.includes(e))))return(0,w.logWarning)("image-style-missing-dependency",{style:e,missingPlugins:a.map((e=>"imageBlock"===e?"ImageBlockEditing":"ImageInlineEditing"))}),!1}return!0}(t,e)))},getDefaultStylesConfiguration:function(e,t){return e&&t?{options:["inline","alignLeft","alignRight","alignCenter","alignBlockLeft","alignBlockRight","block","side"]}:e?{options:["block","side"]}:t?{options:["inline","alignLeft","alignRight"]}:{}},getDefaultDropdownDefinitions:function(e){return e.has("ImageBlockEditing")&&e.has("ImageInlineEditing")?[...j]:[]},warnInvalidStyle:F,DEFAULT_OPTIONS:O,DEFAULT_ICONS:R,DEFAULT_DROPDOWN_DEFINITIONS:j};function H(e,t){const i=e.getSelectedElement();return i&&t.checkAttribute(i,"drupalElementStyle")?i:e.getFirstPosition().findAncestor((e=>t.checkAttribute(e,"drupalElementStyle")))}class W extends e.Command{constructor(e,t){super(e),this._styles=new Map(t.map((e=>[e.name,e])))}refresh(){const e=this.editor,t=H(e.model.document.selection,e.model.schema);this.isEnabled=!!t,this.isEnabled&&t.hasAttribute("drupalElementStyle")?this.value=t.getAttribute("drupalElementStyle"):this.value=!1}execute(e={}){const t=this.editor.model;t.change((i=>{const a=e.value,n=H(t.document.selection,t.schema);!a||this._styles.get(a).isDefault?i.removeAttribute("drupalElementStyle",n):i.setAttribute("drupalElementStyle",a,n)}))}}function q(e,t){for(const i of t)if(i.name===e)return i}class $ extends e.Plugin{init(){const t=this.editor;t.config.define("drupalElementStyles",{options:[]});const i=t.config.get("drupalElementStyles").options;this.normalizedStyles=i.map((t=>("string"==typeof t.icon&&e.icons[t.icon]&&(t.icon=e.icons[t.icon]),t))).filter((e=>e.attributeName&&e.attributeValue?e.modelElements&&Array.isArray(e.modelElements)?!!e.name||(console.warn("drupalElementStyles options must include a name."),!1):(console.warn("drupalElementStyles options must include an array of supported modelElements."),!1):(console.warn("drupalElementStyles options must include attributeName and attributeValue."),!1))),this._setupConversion(),t.commands.add("drupalElementStyle",new W(t,this.normalizedStyles))}_setupConversion(){const e=this.editor,t=e.model.schema,i=(a=this.normalizedStyles,(e,t,i)=>{if(!i.consumable.consume(t.item,e.name))return;const n=q(t.attributeNewValue,a),r=q(t.attributeOldValue,a),o=i.mapper.toViewElement(t.item),l=i.writer;r&&("class"===r.attributeName?l.removeClass(r.attributeValue,o):l.removeAttribute(r.attributeName,o)),n&&("class"===n.attributeName?l.addClass(n.attributeValue,o):l.setAttribute(n.attributeName,n.attributeValue,o))});var a;const n=function(e){const t=e.filter((e=>!e.isDefault));return(e,i,a)=>{if(!i.modelRange)return;const n=i.viewItem,r=(0,w.first)(i.modelRange.getItems());if(r&&a.schema.checkAttribute(r,"drupalElementStyle"))for(const e of t)if("class"===e.attributeName)a.consumable.consume(n,{classes:e.attributeValue})&&a.writer.setAttribute("drupalElementStyle",e.name,r);else if(a.consumable.consume(n,{attributes:[e.attributeName]}))for(const e of t)e.attributeValue===n.getAttribute(e.attributeName)&&a.writer.setAttribute("drupalElementStyle",e.name,r)}}(this.normalizedStyles);e.editing.downcastDispatcher.on("attribute:drupalElementStyle",i),e.data.downcastDispatcher.on("attribute:drupalElementStyle",i);[...new Set(this.normalizedStyles.map((e=>e.modelElements)).flat())].forEach((e=>{t.extend(e,{allowAttributes:"drupalElementStyle"})})),e.data.upcastDispatcher.on("element",n,{priority:"low"})}static get pluginName(){return"DrupalElementStyleEditing"}}const K=e=>e,z=(e,t)=>(e?`${e}: `:"")+t;function Z(e){return`drupalElementStyle:${e}`}class G extends e.Plugin{static get requires(){return[$]}init(){const e=this.editor.plugins,t=this.editor.config.get("drupalMedia.toolbar")||[],i=Object.values(e.get("DrupalElementStyleEditing").normalizedStyles);i.forEach((e=>{this._createButton(e)}));t.filter(l).forEach((e=>{this._createDropdown(e,i)}))}_createDropdown(e,t){const i=this.editor.ui.componentFactory;i.add(e.name,(a=>{let n;const{defaultItem:r,items:o,title:l}=e,s=o.filter((e=>t.find((({name:t})=>Z(t)===e)))).map((e=>{const t=i.create(e);return e===r&&(n=t),t}));o.length!==s.length&&U.warnInvalidStyle({dropdown:e});const d=(0,u.createDropdown)(a,u.SplitButtonView),c=d.buttonView;return(0,u.addToolbarToDropdown)(d,s),c.set({label:z(l,n.label),class:null,tooltip:!0}),c.bind("icon").toMany(s,"isOn",((...e)=>{const t=e.findIndex(K);return t<0?n.icon:s[t].icon})),c.bind("label").toMany(s,"isOn",((...e)=>{const t=e.findIndex(K);return z(l,t<0?n.label:s[t].label)})),c.bind("isOn").toMany(s,"isOn",((...e)=>e.some(K))),c.bind("class").toMany(s,"isOn",((...e)=>e.some(K)?"ck-splitbutton_flatten":null)),c.on("execute",(()=>{s.some((({isOn:e})=>e))?d.isOpen=!d.isOpen:n.fire("execute")})),d.bind("isEnabled").toMany(s,"isEnabled",((...e)=>e.some(K))),d}))}_createButton(e){const t=e.name;this.editor.ui.componentFactory.add(Z(t),(i=>{const a=this.editor.commands.get("drupalElementStyle"),n=new u.ButtonView(i);return n.set({label:e.title,icon:e.icon,tooltip:!0,isToggleable:!0}),n.bind("isEnabled").to(a,"isEnabled"),n.bind("isOn").to(a,"value",(e=>e===t)),n.on("execute",this._executeCommand.bind(this,t)),n}))}_executeCommand(e){this.editor.execute("drupalElementStyle",{value:e}),this.editor.editing.view.focus()}static get pluginName(){return"DrupalElementStyleUi"}}class J extends e.Plugin{static get requires(){return[$,G]}static get pluginName(){return"DrupalElementStyle"}}var X=i("ckeditor5/src/engine.js");function Q(e){for(const t of e.getChildren())if(t&&t.is("element","caption"))return t;return null}class Y extends e.Command{refresh(){const e=this.editor.model.document.selection.getSelectedElement();this.isEnabled=r(e),this.isEnabled?this.value=!!Q(e):this.value=!1}execute(e={}){const{focusCaptionOnShow:t}=e;this.editor.model.change((e=>{this.value?this._hideDrupalMediaCaption(e):this._showDrupalMediaCaption(e,t)}))}_showDrupalMediaCaption(e,t){const i=this.editor.model.document.selection,a=this.editor.plugins.get("DrupalMediaCaptionEditing"),n=i.getSelectedElement(),r=a._getSavedCaption(n)||e.createElement("caption");e.append(r,n),t&&e.setSelection(r,"in")}_hideDrupalMediaCaption(e){const t=this.editor,i=t.model.document.selection,a=t.plugins.get("DrupalMediaCaptionEditing"),n=i.getSelectedElement();if(n){const t=Q(n);a._saveCaption(n,t),e.setSelection(n,"on"),e.remove(t)}}}class ee extends e.Plugin{static get requires(){return[]}static get pluginName(){return"DrupalMediaCaptionEditing"}constructor(e){super(e),this._savedCaptionsMap=new WeakMap}init(){const e=this.editor,t=e.model.schema;t.isRegistered("caption")?t.extend("caption",{allowIn:"drupalMedia"}):t.register("caption",{allowIn:"drupalMedia",allowContentOf:"$block",isLimit:!0}),e.commands.add("toggleMediaCaption",new Y(e)),this._setupConversion()}_setupConversion(){const e=this.editor,i=e.editing.view;var a;e.conversion.for("upcast").add(function(e){const t=(t,i,a)=>{const{viewItem:n}=i,{writer:r,consumable:o}=a;if(!i.modelRange||!o.consume(n,{attributes:["data-caption"]}))return;const l=r.createElement("caption"),s=i.modelRange.start.nodeAfter,d=e.data.processor.toView(n.getAttribute("data-caption")),u=r.createDocumentFragment();a.consumable.constructor.createFrom(d,a.consumable),a.convertChildren(d,u);for(const e of Array.from(u.getChildren()))r.append(e,l);r.append(l,s)};return e=>{e.on("element:drupal-media",t,{priority:"low"})}}(e)),e.conversion.for("editingDowncast").elementToElement({model:"caption",view:(e,{writer:a})=>{if(!r(e.parent))return null;const n=a.createEditableElement("figcaption");return(0,X.enablePlaceholder)({view:i,element:n,text:Drupal.t("Enter media caption"),keepOnFocus:!0}),(0,t.toWidgetEditable)(n,a)}}),e.editing.mapper.on("modelToViewPosition",(a=i,(e,t)=>{const i=t.modelPosition,n=i.parent;if(!r(n))return;const o=t.mapper.toViewElement(n);t.viewPosition=a.createPositionAt(o,i.offset+1)})),e.conversion.for("dataDowncast").add(function(e){return t=>{t.on("insert:caption",((t,i,a)=>{const{consumable:n,writer:o,mapper:l}=a;if(!r(i.item.parent)||!n.consume(i.item,"insert"))return;const s=e.model.createRangeIn(i.item),d=o.createDocumentFragment();l.bindElements(i.item,d);for(const{item:t}of Array.from(s)){const i={item:t,range:e.model.createRangeOn(t)},n=`insert:${t.name||"$text"}`;e.data.downcastDispatcher.fire(n,i,a);for(const n of t.getAttributeKeys())Object.assign(i,{attributeKey:n,attributeOldValue:null,attributeNewValue:i.item.getAttribute(n)}),e.data.downcastDispatcher.fire(`attribute:${n}`,i,a)}for(const e of o.createRangeIn(d).getItems())l.unbindViewElement(e);l.unbindViewElement(d);const u=e.data.processor.toData(d);if(u){const e=l.toViewElement(i.item.parent);o.setAttribute("data-caption",u,e)}}))}}(e))}_getSavedCaption(e){const t=this._savedCaptionsMap.get(e);return t?X.Element.fromJSON(t):null}_saveCaption(e,t){this._savedCaptionsMap.set(e,t.toJSON())}}class te extends e.Plugin{static get requires(){return[]}static get pluginName(){return"DrupalMediaCaptionUI"}init(){const{editor:t}=this,i=t.editing.view;t.ui.componentFactory.add("toggleDrupalMediaCaption",(a=>{const n=new u.ButtonView(a),r=t.commands.get("toggleMediaCaption");return n.set({label:Drupal.t("Caption media"),icon:e.icons.caption,tooltip:!0,isToggleable:!0}),n.bind("isOn","isEnabled").to(r,"value","isEnabled"),n.bind("label").to(r,"value",(e=>e?Drupal.t("Toggle caption off"):Drupal.t("Toggle caption on"))),this.listenTo(n,"execute",(()=>{t.execute("toggleMediaCaption",{focusCaptionOnShow:!0});const e=t.model.document.selection.getFirstPosition().findAncestor("caption");if(e){const a=t.editing.mapper.toViewElement(e);i.scrollToTheSelection(),i.change((e=>{e.addClass("drupal-media__caption_highlighted",a)}))}})),n}))}}class ie extends e.Plugin{static get requires(){return[ee,te]}static get pluginName(){return"DrupalMediaCaption"}}const ae={DrupalMedia:A,MediaImageTextAlternative:y,MediaImageTextAlternativeEditing:f,MediaImageTextAlternativeUi:E,DrupalLinkMedia:S,DrupalMediaCaption:ie,DrupalElementStyle:J}})(),a=a.default})()})); \ No newline at end of file diff --git a/core/modules/ckeditor5/js/ckeditor5_plugins/drupalMedia/src/drupalmediageneralhtmlsupport.js b/core/modules/ckeditor5/js/ckeditor5_plugins/drupalMedia/src/drupalmediageneralhtmlsupport.js index a84f378ac8a..b19a92267c8 100644 --- a/core/modules/ckeditor5/js/ckeditor5_plugins/drupalMedia/src/drupalmediageneralhtmlsupport.js +++ b/core/modules/ckeditor5/js/ckeditor5_plugins/drupalMedia/src/drupalmediageneralhtmlsupport.js @@ -1,5 +1,5 @@ /* eslint-disable import/no-extraneous-dependencies */ -// cSpell:words conversionutils datafilter +// cSpell:words conversionutils datafilter eventinfo downcastdispatcher generalhtmlsupport import { Plugin } from 'ckeditor5/src/core'; import { setViewAttributes } from '@ckeditor/ckeditor5-html-support/src/conversionutils'; @@ -38,6 +38,8 @@ function viewToModelDrupalMediaAttributeConverter(dataFilter) { const viewMediaElement = data.viewItem; const viewContainerElement = viewMediaElement.parent; + preserveElementAttributes(viewMediaElement, 'htmlAttributes'); + if (viewContainerElement.is('element', 'a')) { preserveLinkAttributes(viewContainerElement); } @@ -70,6 +72,26 @@ function getDescendantElement(writer, containerElement, elementName) { } } +/** + * Model to view converter for the Drupal Media wrapper attributes. + * + * @param {module:utils/eventinfo~EventInfo} evt + * An object containing information about the fired event. + * @param {Object} data + * Additional information about the change. + * @param {module:engine/conversion/downcastdispatcher~DowncastDispatcher} conversionApi + * Conversion interface to be used by the callback. + */ +function modelToDataAttributeConverter(evt, data, conversionApi) { + if (!conversionApi.consumable.consume(data.item, evt.name)) { + return; + } + + const viewElement = conversionApi.mapper.toViewElement(data.item); + + setViewAttributes(conversionApi.writer, data.attributeNewValue, viewElement); +} + /** * Model to editing view attribute converter. * @@ -77,7 +99,7 @@ function getDescendantElement(writer, containerElement, elementName) { * A function that adds an event listener to downcastDispatcher. */ function modelToEditingViewAttributeConverter() { - return (dispatcher) => + return (dispatcher) => { dispatcher.on( 'attribute:linkHref:drupalMedia', (evt, data, conversionApi) => { @@ -105,6 +127,16 @@ function modelToEditingViewAttributeConverter() { }, { priority: 'low' }, ); + + // Render arbitrary attributes on the CKEditor 5 widget wrapper until + // arbitrary attributes are included as part of the server rendered preview. + // @see https://www.drupal.org/project/drupal/issues/3231337 + dispatcher.on( + 'attribute:htmlAttributes:drupalMedia', + modelToDataAttributeConverter, + { priority: 'low' }, + ); + }; } /** @@ -114,7 +146,7 @@ function modelToEditingViewAttributeConverter() { * function that adds an event listener to downcastDispatcher. */ function modelToDataViewAttributeConverter() { - return (dispatcher) => + return (dispatcher) => { dispatcher.on( 'attribute:linkHref:drupalMedia', (evt, data, conversionApi) => { @@ -137,6 +169,13 @@ function modelToDataViewAttributeConverter() { }, { priority: 'low' }, ); + + dispatcher.on( + 'attribute:htmlAttributes:drupalMedia', + modelToDataAttributeConverter, + { priority: 'low' }, + ); + }; } /** @@ -148,29 +187,58 @@ export default class DrupalMediaGeneralHtmlSupport extends Plugin { /** * @inheritdoc */ - init() { - const { editor } = this; + constructor(editor) { + super(editor); // This plugin is only needed if General HTML Support plugin is loaded. if (!editor.plugins.has('GeneralHtmlSupport')) { return; } + // This plugin works only if `DataFilter` and `DataSchema` plugins are + // loaded. These plugins are dependencies of `GeneralHtmlSupport` meaning + // that these should be available always when `GeneralHtmlSupport` is + // enabled. + if ( + !editor.plugins.has('DataFilter') || + !editor.plugins.has('DataSchema') + ) { + console.error( + 'DataFilter and DataSchema plugins are required for Drupal Media to integrate with General HTML Support plugin.', + ); + } const { schema } = editor.model; const { conversion } = editor; - const dataFilter = editor.plugins.get('DataFilter'); + const dataFilter = this.editor.plugins.get('DataFilter'); + const dataSchema = this.editor.plugins.get('DataSchema'); - schema.extend('drupalMedia', { - allowAttributes: ['htmlLinkAttributes'], + // This needs to be initialized in ::constructor() to ensure this runs + // before the General HTML Support has been initialized. + // @see module:html-support/generalhtmlsupport~GeneralHtmlSupport + dataSchema.registerBlockElement({ + model: 'drupalMedia', + view: 'drupal-media', }); - conversion - .for('upcast') - .add(viewToModelDrupalMediaAttributeConverter(dataFilter)); - conversion - .for('editingDowncast') - .add(modelToEditingViewAttributeConverter()); - conversion.for('dataDowncast').add(modelToDataViewAttributeConverter()); + dataFilter.on('register:drupal-media', (evt, definition) => { + if (definition.model !== 'drupalMedia') { + return; + } + + schema.extend('drupalMedia', { + allowAttributes: ['htmlLinkAttributes', 'htmlAttributes'], + }); + + conversion + .for('upcast') + .add(viewToModelDrupalMediaAttributeConverter(dataFilter)); + conversion + .for('editingDowncast') + .add(modelToEditingViewAttributeConverter()); + conversion.for('dataDowncast').add(modelToDataViewAttributeConverter()); + + evt.stop(); + }); } /** diff --git a/core/modules/ckeditor5/tests/src/FunctionalJavascript/CKEditor5Test.php b/core/modules/ckeditor5/tests/src/FunctionalJavascript/CKEditor5Test.php index 8e9da31cf0b..7a8dcb56c4d 100644 --- a/core/modules/ckeditor5/tests/src/FunctionalJavascript/CKEditor5Test.php +++ b/core/modules/ckeditor5/tests/src/FunctionalJavascript/CKEditor5Test.php @@ -127,7 +127,7 @@ class CKEditor5Test extends CKEditor5TestBase { $image_uuid = $uploaded_image->uuid(); $image_url = $this->container->get('file_url_generator')->generateString($uploaded_image->getFileUri()); $this->drupalGet('node/1'); - $assert_session->elementExists('xpath', sprintf('//img[@alt=" Kittens & llamas are cute" and @data-entity-uuid="%s" and @data-entity-type="file"]', $image_uuid)); + $this->assertNotEmpty($assert_session->waitForElement('xpath', sprintf('//img[@alt=" Kittens & llamas are cute" and @data-entity-uuid="%s" and @data-entity-type="file"]', $image_uuid))); // Drupal CKEditor 5 integrations overrides the CKEditor 5 HTML writer to // escape ampersand characters (&) and the angle brackets (< and >). This is diff --git a/core/modules/ckeditor5/tests/src/FunctionalJavascript/EmphasisTest.php b/core/modules/ckeditor5/tests/src/FunctionalJavascript/EmphasisTest.php new file mode 100644 index 00000000000..19636d7810e --- /dev/null +++ b/core/modules/ckeditor5/tests/src/FunctionalJavascript/EmphasisTest.php @@ -0,0 +1,164 @@ + is converted to in Drupal, so additional coverage + * is provided here to verify successful conversion. + * + * @group ckeditor5 + * @internal + */ +class EmphasisTest extends WebDriverTestBase { + use CKEditor5TestTrait; + + /** + * The user to use during testing. + * + * @var \Drupal\user\UserInterface + */ + protected $adminUser; + + /** + * A host entity with a body field to use the tag in. + * + * @var \Drupal\node\NodeInterface + */ + protected $host; + + /** + * {@inheritdoc} + */ + protected static $modules = [ + 'ckeditor5', + 'node', + 'text', + ]; + + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + + /** + * {@inheritdoc} + */ + protected function setUp(): void { + parent::setUp(); + + FilterFormat::create([ + 'format' => 'test_format', + 'name' => 'Test format', + 'filters' => [ + 'filter_html' => [ + 'status' => TRUE, + 'settings' => [ + 'allowed_html' => '


', + ], + ], + ], + ])->save(); + Editor::create([ + 'editor' => 'ckeditor5', + 'format' => 'test_format', + 'settings' => [ + 'toolbar' => [ + 'items' => [ + 'italic', + 'sourceEditing', + ], + ], + 'plugins' => [ + 'ckeditor5_sourceEditing' => [ + 'allowed_tags' => [], + ], + ], + ], + ])->save(); + $this->assertSame([], array_map( + function (ConstraintViolation $v) { + return (string) $v->getMessage(); + }, + iterator_to_array(CKEditor5::validatePair( + Editor::load('test_format'), + FilterFormat::load('test_format') + )) + )); + $this->adminUser = $this->drupalCreateUser([ + 'use text format test_format', + 'bypass node access', + ]); + + $this->drupalCreateContentType(['type' => 'blog']); + $this->host = $this->createNode([ + 'type' => 'blog', + 'title' => 'Animals with strange names', + 'body' => [ + 'value' => '

This is a test!

', + 'format' => 'test_format', + ], + ]); + $this->host->save(); + + $this->drupalLogin($this->adminUser); + } + + /** + * Ensures that CKEditor italic model is converted to em. + */ + public function testEmphasis() { + $page = $this->getSession()->getPage(); + $assert_session = $this->assertSession(); + + $this->drupalGet($this->host->toUrl('edit-form')); + $this->waitForEditor(); + + $emphasis_element = $assert_session->waitForElementVisible('css', '.ck-content p em'); + $this->assertEquals('test!', $emphasis_element->getText()); + + $xpath = new \DOMXPath($this->getEditorDataAsDom()); + $emphasis_source = $xpath->query('//p/em'); + $this->assertNotEmpty($emphasis_source); + $this->assertEquals('test!', $emphasis_source[0]->textContent); + $page->pressButton('Save'); + + $assert_session->responseContains('

This is a test!

'); + } + + /** + * Tests that arbitrary attributes are allowed via GHS. + */ + public function testEmphasisArbitraryHtml() { + $assert_session = $this->assertSession(); + $editor = Editor::load('test_format'); + $settings = $editor->getSettings(); + + // Allow the data-foo attribute in img via GHS. + $settings['plugins']['ckeditor5_sourceEditing']['allowed_tags'] = ['']; + $editor->setSettings($settings); + $editor->save(); + + // Add data-foo use to an existing em tag. + $original_value = $this->host->body->value; + $this->host->body->value = str_replace('', '', $original_value); + $this->host->save(); + $this->drupalGet($this->host->toUrl('edit-form')); + $this->waitForEditor(); + + $emphasis_element = $assert_session->waitForElementVisible('css', '.ck-content p em'); + $this->assertEquals('bar', $emphasis_element->getAttribute('data-foo')); + + $xpath = new \DOMXPath($this->getEditorDataAsDom()); + $this->assertNotEmpty($xpath->query('//em[@data-foo="bar"]')); + } + +} diff --git a/core/modules/ckeditor5/tests/src/FunctionalJavascript/ImageTest.php b/core/modules/ckeditor5/tests/src/FunctionalJavascript/ImageTest.php index 4f3301a6bc3..37d8a6b2043 100644 --- a/core/modules/ckeditor5/tests/src/FunctionalJavascript/ImageTest.php +++ b/core/modules/ckeditor5/tests/src/FunctionalJavascript/ImageTest.php @@ -140,6 +140,47 @@ class ImageTest extends CKEditor5TestBase { $this->drupalLogin($this->adminUser); } + /** + * Tests that arbitrary attributes are allowed via GHS. + * + * @dataProvider providerLinkability + */ + public function testImageArbitraryHtml(string $image_type, bool $unrestricted) { + $editor = Editor::load('test_format'); + $settings = $editor->getSettings(); + + // Allow the data-foo attribute in img via GHS. + $settings['plugins']['ckeditor5_sourceEditing']['allowed_tags'] = ['']; + $editor->setSettings($settings); + $editor->save(); + + // Disable filter_html. + if ($unrestricted) { + FilterFormat::load('test_format') + ->setFilterConfig('filter_html', ['status' => FALSE]) + ->save(); + } + + // Make the test content have either a block image or an inline image. + $img_tag = 'drupalimage test image'; + $this->host->body->value .= $image_type === 'block' + ? $img_tag + : "

$img_tag

"; + $this->host->save(); + + $expected_widget_selector = $image_type === 'block' ? 'image img' : 'image-inline'; + + $this->drupalGet($this->host->toUrl('edit-form')); + $this->waitForEditor(); + + $drupalimage = $this->assertSession()->waitForElementVisible('css', ".ck-content .ck-widget.$expected_widget_selector"); + $this->assertNotEmpty($drupalimage); + $this->assertEquals('bar', $drupalimage->getAttribute('data-foo')); + + $xpath = new \DOMXPath($this->getEditorDataAsDom()); + $this->assertNotEmpty($xpath->query('//img[@data-foo="bar"]')); + } + /** * Tests linkability of the image CKEditor widget. * diff --git a/core/modules/ckeditor5/tests/src/FunctionalJavascript/MediaTest.php b/core/modules/ckeditor5/tests/src/FunctionalJavascript/MediaTest.php index bcc3e260917..ddba5d4af5a 100644 --- a/core/modules/ckeditor5/tests/src/FunctionalJavascript/MediaTest.php +++ b/core/modules/ckeditor5/tests/src/FunctionalJavascript/MediaTest.php @@ -188,6 +188,34 @@ class MediaTest extends WebDriverTestBase { $assert_session->elementExists('css', '.ck-widget.drupal-media'); } + /** + * Tests that arbitrary attributes are allowed via GHS. + */ + public function testMediaArbitraryHtml() { + $assert_session = $this->assertSession(); + + $editor = Editor::load('test_format'); + $settings = $editor->getSettings(); + + // Allow the data-foo attribute in drupal-media via GHS. + $settings['plugins']['ckeditor5_sourceEditing']['allowed_tags'] = ['']; + $editor->setSettings($settings); + $editor->save(); + + // Add data-foo use to an existing drupal-media tag. + $original_value = $this->host->body->value; + $this->host->body->value = str_replace('drupal-media', 'drupal-media data-foo="bar" ', $original_value); + $this->host->save(); + $this->drupalGet($this->host->toUrl('edit-form')); + + // Confirm data-foo is present in the upcasted drupal-media. + $upcasted_media = $assert_session->waitForElementVisible('css', '.ck-widget.drupal-media'); + $this->assertEquals('bar', $upcasted_media->getAttribute('data-foo')); + + // Confirm data-foo is not stripped from source. + $this->assertSourceAttributeSame('data-foo', 'bar'); + } + /** * Tests that failed media embed preview requests inform the end user. */