From d672dfa5b94d71a6ef042aa09bd05ca97a238fd6 Mon Sep 17 00:00:00 2001 From: Lauri Eskola Date: Fri, 14 Jan 2022 20:22:55 +0200 Subject: [PATCH] Issue #3228778 by nod_, Wim Leers: Drupal-specific CKEditor 5 plugins should be able to use Drupal's JS translation API: Drupal.t() (cherry picked from commit 6ec3148701e4947f70269eb8e4f3531e48fc9b6e) --- .../modules/ckeditor5/js/build/drupalMedia.js | 2 +- .../src/drupallinkmedia/drupallinkmediaui.js | 3 +- .../drupalMedia/src/drupalmediaediting.js | 4 +- .../drupalMedia/src/drupalmediatoolbar.js | 3 +- .../drupalMedia/src/drupalmediaui.js | 2 +- .../mediaimagetextalternativeui.js | 3 +- .../ui/textalternativeformview.js | 9 +-- .../JSTranslationTest.php | 70 +++++++++++++++++++ 8 files changed, 80 insertions(+), 16 deletions(-) create mode 100644 core/modules/ckeditor5/tests/src/FunctionalJavascript/JSTranslationTest.php diff --git a/core/modules/ckeditor5/js/build/drupalMedia.js b/core/modules/ckeditor5/js/build/drupalMedia.js index ee2cc68094a..fd1540e85d9 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 function(){var e={"ckeditor5/src/core.js":function(e,t,i){e.exports=i("dll-reference CKEditor5.dll")("./src/core.js")},"ckeditor5/src/ui.js":function(e,t,i){e.exports=i("dll-reference CKEditor5.dll")("./src/ui.js")},"ckeditor5/src/utils.js":function(e,t,i){e.exports=i("dll-reference CKEditor5.dll")("./src/utils.js")},"ckeditor5/src/widget.js":function(e,t,i){e.exports=i("dll-reference CKEditor5.dll")("./src/widget.js")},"dll-reference CKEditor5.dll":function(e){"use strict";e.exports=CKEditor5.dll}},t={};function i(r){var n=t[r];if(void 0!==n)return n.exports;var a=t[r]={exports:{}};return e[r](a,a.exports,i),a.exports}i.d=function(e,t){for(var r in t)i.o(t,r)&&!i.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},i.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)};var r={};return function(){"use strict";i.d(r,{default:function(){return y}});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)),{}),r=Object.keys(e).reduce(((t,r)=>(i[r]&&(t[i[r]]=e[r]),t)),{});this.editor.model.change((e=>{this.editor.model.insertContent(function(e,t){return e.createElement("drupalMedia",t)}(e,r))}))}refresh(){const e=this.editor.model,t=e.document.selection,i=e.schema.findAllowedParent(t.getFirstPosition(),"drupalMedia");this.isEnabled=null!==i}}class a extends e.Plugin{static get requires(){return[t.Widget]}init(){this.attrs={drupalMediaAlt:"alt",drupalMediaAlign:"data-align",drupalMediaCaption:"data-caption",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=this.editor.t("Preview failed"),this.themeError=i||`\n

${this.editor.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,t){const i=await fetch(`${e}?${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 r=i.createContainerElement("div",{class:"drupal-media"}),n=i.createRawElement("div",{"data-drupal-media-preview":"loading"},(t=>{this.previewURL?this._fetchPreview(this.previewURL,{text:this._renderElement(e),uuid:e.getAttribute("drupalMediaEntityUuid")}).then((({label:e,preview:i})=>{t.innerHTML=i,t.setAttribute("aria-label",e),t.setAttribute("data-drupal-media-preview","ready")})):(t.innerHTML=this.themeError,t.setAttribute("aria-label","drupal-media"),t.setAttribute("data-drupal-media-preview","unavailable"))}));return i.insert(i.createPositionAt(r,0),n),i.setCustomProperty("drupalMedia",!0,r),(0,t.toWidget)(r,i,{label:"media widget"})}}),Object.keys(this.attrs).forEach((t=>{e.attributeToAttribute({model:{key:t,name:"drupalMedia"},view:{name:"drupal-media",key:this.attrs[t]}})}))}_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 s=i("ckeditor5/src/ui.js");class o extends e.Plugin{init(){const e=this.editor,t=this.editor.config.get("drupalMedia");if(!t)return;const{libraryURL:i,openDialog:r,dialogSettings:n={}}=t;i&&"function"==typeof r&&e.ui.componentFactory.add("drupalMedia",(t=>{const a=e.commands.get("insertDrupalMedia"),o=new s.ButtonView(t);return o.set({label:e.t("Insert Drupal Media"),icon:'\n',tooltip:!0}),o.bind("isOn","isEnabled").to(a,"value","isEnabled"),this.listenTo(o,"execute",(()=>{r(i,(({attributes:t})=>{e.execute("insertDrupalMedia",t)}),n)})),o}))}}function l(e){return!!e&&e.is("element","drupalMedia")}function d(e){const i=e.getSelectedElement();return i&&function(e){return(0,t.isWidget)(e)&&!!e.getCustomProperty("drupalMedia")}(i)?i:null}class u extends e.Plugin{static get requires(){return[t.WidgetToolbarRepository]}static get pluginName(){return"DrupalMediaToolbar"}afterInit(){const e=this.editor,{t:i}=e;e.plugins.get(t.WidgetToolbarRepository).register("drupalMedia",{ariaLabel:i("Drupal Media toolbar"),items:e.config.get("drupalMedia.toolbar")||[],getRelatedElement:e=>d(e)})}}class c extends e.Command{refresh(){const e=this.editor.model.document.selection.getSelectedElement();this.isEnabled=!1,l(e)&&this._isMediaImage(e).then((e=>{this.isEnabled=e})),l(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)}))}async _isMediaImage(e){const t=this.editor.config.get("drupalMedia");if(!t)return null;const{isMediaUrl:i}=t,r=new URLSearchParams({uuid:e.getAttribute("drupalMediaEntityUuid")}),n=await fetch(`${i}&${r}`);return n.ok?JSON.parse(await n.text()):null}}class m extends e.Plugin{static get pluginName(){return"MediaImageTextAlternativeEditing"}init(){this.editor.commands.add("mediaImageTextAlternative",new c(this.editor))}}function h(e){const t=e.editing.view,i=s.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 p=i("ckeditor5/src/utils.js");class f extends s.View{constructor(t){super(t);const i=this.locale.t;this.focusTracker=new p.FocusTracker,this.keystrokes=new p.KeystrokeHandler,this.labeledInput=this._createLabeledInputView(),this.saveButtonView=this._createButton(i("Save"),e.icons.check,"ck-button-save"),this.saveButtonView.type="submit",this.cancelButtonView=this._createButton(i("Cancel"),e.icons.cancel,"ck-button-cancel","cancel"),this._focusables=new s.ViewCollection,this._focusCycler=new s.FocusCycler({focusables:this._focusables,focusTracker:this.focusTracker,keystrokeHandler:this.keystrokes,actions:{focusPrevious:"shift + tab",focusNext:"tab"}}),this.setTemplate({tag:"form",attributes:{class:["ck","ck-text-alternative-form","ck-responsive-form"],tabindex:"-1"},children:[this.labeledInput,this.saveButtonView,this.cancelButtonView]}),(0,s.injectCssTransitionDisabler)(this)}render(){super.render(),this.keystrokes.listenTo(this.element),(0,s.submitHandler)({view:this}),[this.labeledInput,this.saveButtonView,this.cancelButtonView].forEach((e=>{this._focusables.add(e),this.focusTracker.add(e.element)}))}_createButton(e,t,i,r){const n=new s.ButtonView(this.locale);return n.set({label:e,icon:t,tooltip:!0}),n.extendTemplate({attributes:{class:i}}),r&&n.delegate("execute").to(this,r),n}_createLabeledInputView(){const e=this.locale.t,t=new s.LabeledFieldView(this.locale,s.createLabeledInputText);return t.label=e("Override text alternative"),t}}class g extends e.Plugin{static get requires(){return[s.ContextualBalloon]}static get pluginName(){return"MediaImageTextAlternativeUi"}init(){this._createButton(),this._createForm()}destroy(){super.destroy(),this._form.destroy()}_createButton(){const t=this.editor,i=t.t;t.ui.componentFactory.add("mediaImageTextAlternative",(r=>{const n=t.commands.get("mediaImageTextAlternative"),a=new s.ButtonView(r);return a.set({label:i("Override media image text alternative"),icon:e.icons.lowVision,tooltip:!0}),a.bind("isVisible").to(n,"isEnabled"),this.listenTo(a,"execute",(()=>{this._showForm()})),a}))}_createForm(){const e=this.editor,t=e.editing.view.document;this._balloon=this.editor.plugins.get("ContextualBalloon"),this._form=new f(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",(()=>{d(t.selection)?this._isVisible&&function(e){const t=e.plugins.get("ContextualBalloon");if(d(e.editing.view.document.selection)){const i=h(e);t.updatePosition(i)}}(e):this._hideForm(!0)})),(0,s.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=this._form.labeledInput;this._form.disableCssTransitions(),this._isInBalloon||this._balloon.add({view:this._form,position:h(e)}),i.fieldView.element.value=t.value||"",i.fieldView.value=i.fieldView.element.value,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 b extends e.Plugin{static get requires(){return[m,g]}static get pluginName(){return"MediaImageTextAlternative"}}function w(e,t,i){if(t.attributes)for(const[r,n]of Object.entries(t.attributes))e.setAttribute(r,n,i);t.styles&&e.setStyle(t.styles,i),t.classes&&e.addClass(t.classes,i)}function v(e){return t=>{t.on("element:drupal-media",((t,i,r)=>{const n=i.viewItem.parent;n.is("element","a")&&function(t,n){const a=e._consumeAllowedAttributes(t,r);a&&r.writer.setAttribute(n,a,i.modelRange)}(n,"htmlLinkAttributes")}),{priority:"low"})}}class M extends e.Plugin{init(){const{editor:e}=this;if(!e.plugins.has("GeneralHtmlSupport"))return;const{schema:t}=e.model,{conversion:i}=e,r=e.plugins.get("DataFilter");t.extend("drupalMedia",{allowAttributes:["htmlLinkAttributes"]}),i.for("upcast").add(v(r)),i.for("editingDowncast").add((e=>e.on("attribute:linkHref:drupalMedia",((e,t,i)=>{if(!i.consumable.consume(t.item,"attribute:htmlLinkAttributes:drupalMedia"))return;const r=i.mapper.toViewElement(t.item),n=function(e,t,i){const r=e.createRangeOn(t);for(const{item:e}of r.getWalker())if(e.is("element",i))return e}(i.writer,r,"a");w(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 r=i.mapper.toViewElement(t.item).parent;w(i.writer,t.item.getAttribute("htmlLinkAttributes"),r)}),{priority:"low"})))}static get pluginName(){return"DrupalMediaGeneralHtmlSupport"}}class k extends e.Plugin{static get requires(){return[a,M,o,u,b]}}function _(){return e=>{e.on("element:a",((e,t,i)=>{const r=t.viewItem,n=(a=r,Array.from(a.getChildren()).find((e=>"drupal-media"===e.name)));var a;if(!n)return;if(!i.consumable.consume(r,{attributes:["href"]}))return;const s=r.getAttribute("href");if(!s)return;const o=i.convertItem(n,t.modelCursor);t.modelRange=o.modelRange,t.modelCursor=o.modelCursor;const l=t.modelCursor.nodeBefore;l&&l.is("element","drupalMedia")&&i.writer.setAttribute("linkHref",s,l)}),{priority:"high"})}}class A 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:r}=i;if(!i.consumable.consume(t.item,e.name))return;const n=i.mapper.toViewElement(t.item),a=Array.from(n.getChildren()).find((e=>"a"===e.name));if(a)t.attributeNewValue?r.setAttribute("href",t.attributeNewValue,a):(r.move(r.createRangeIn(a),r.createPositionAt(n,0)),r.remove(a));else{const e=Array.from(n.getChildren()).find((e=>e.getAttribute("data-drupal-media-preview"))),i=r.createContainerElement("a",{href:t.attributeNewValue});r.insert(r.createPositionAt(n,0),i),r.move(r.createRangeOn(e),r.createPositionAt(i,0))}}),{priority:"high"})})),e.conversion.for("dataDowncast").add((e=>{e.on("attribute:linkHref:drupalMedia",((e,t,i)=>{const{writer:r}=i;if(!i.consumable.consume(t.item,e.name))return;const n=i.mapper.toViewElement(t.item),a=r.createContainerElement("a",{href:t.attributeNewValue});r.insert(r.createPositionBefore(n),a),r.move(r.createRangeOn(n),r.createPositionAt(a,0))}),{priority:"high"})}))}}class x 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,{t:t}=e;e.ui.componentFactory.add("drupalLinkMedia",(i=>{const r=new s.ButtonView(i),n=e.plugins.get("LinkUI"),a=e.commands.get("link");return r.set({isEnabled:!0,label:t("Link media"),icon:'\n',keystroke:"Ctrl+K",tooltip:!0,isToggleable:!0}),r.bind("isEnabled").to(a,"isEnabled"),r.bind("isOn").to(a,"value",(e=>!!e)),this.listenTo(r,"execute",(()=>{this._isSelectedLinkedMedia(e.model.document.selection)?n._addActionsView():n._showUI(!0)})),r}))}_isSelectedLinkedMedia(e){const t=e.getSelectedElement();return!!t&&t.is("element","drupalMedia")&&t.hasAttribute("linkHref")}}class E extends e.Plugin{static get requires(){return[A,x]}static get pluginName(){return"DrupalLinkMedia"}}var y={DrupalMedia:k,MediaImageTextAlternative:b,MediaImageTextAlternativeEditing:m,MediaImageTextAlternativeUi:g,DrupalLinkMedia:E}}(),r=r.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 function(){var e={"ckeditor5/src/core.js":function(e,t,i){e.exports=i("dll-reference CKEditor5.dll")("./src/core.js")},"ckeditor5/src/ui.js":function(e,t,i){e.exports=i("dll-reference CKEditor5.dll")("./src/ui.js")},"ckeditor5/src/utils.js":function(e,t,i){e.exports=i("dll-reference CKEditor5.dll")("./src/utils.js")},"ckeditor5/src/widget.js":function(e,t,i){e.exports=i("dll-reference CKEditor5.dll")("./src/widget.js")},"dll-reference CKEditor5.dll":function(e){"use strict";e.exports=CKEditor5.dll}},t={};function i(r){var a=t[r];if(void 0!==a)return a.exports;var n=t[r]={exports:{}};return e[r](n,n.exports,i),n.exports}i.d=function(e,t){for(var r in t)i.o(t,r)&&!i.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},i.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)};var r={};return function(){"use strict";i.d(r,{default:function(){return y}});var e=i("ckeditor5/src/core.js"),t=i("ckeditor5/src/widget.js");class a 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)),{}),r=Object.keys(e).reduce(((t,r)=>(i[r]&&(t[i[r]]=e[r]),t)),{});this.editor.model.change((e=>{this.editor.model.insertContent(function(e,t){return e.createElement("drupalMedia",t)}(e,r))}))}refresh(){const e=this.editor.model,t=e.document.selection,i=e.schema.findAllowedParent(t.getFirstPosition(),"drupalMedia");this.isEnabled=null!==i}}class n extends e.Plugin{static get requires(){return[t.Widget]}init(){this.attrs={drupalMediaAlt:"alt",drupalMediaAlign:"data-align",drupalMediaCaption:"data-caption",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 a(this.editor))}async _fetchPreview(e,t){const i=await fetch(`${e}?${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 r=i.createContainerElement("div",{class:"drupal-media"}),a=i.createRawElement("div",{"data-drupal-media-preview":"loading"},(t=>{this.previewURL?this._fetchPreview(this.previewURL,{text:this._renderElement(e),uuid:e.getAttribute("drupalMediaEntityUuid")}).then((({label:e,preview:i})=>{t.innerHTML=i,t.setAttribute("aria-label",e),t.setAttribute("data-drupal-media-preview","ready")})):(t.innerHTML=this.themeError,t.setAttribute("aria-label","drupal-media"),t.setAttribute("data-drupal-media-preview","unavailable"))}));return i.insert(i.createPositionAt(r,0),a),i.setCustomProperty("drupalMedia",!0,r),(0,t.toWidget)(r,i,{label:"media widget"})}}),Object.keys(this.attrs).forEach((t=>{e.attributeToAttribute({model:{key:t,name:"drupalMedia"},view:{name:"drupal-media",key:this.attrs[t]}})}))}_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 s=i("ckeditor5/src/ui.js");class o extends e.Plugin{init(){const e=this.editor,t=this.editor.config.get("drupalMedia");if(!t)return;const{libraryURL:i,openDialog:r,dialogSettings:a={}}=t;i&&"function"==typeof r&&e.ui.componentFactory.add("drupalMedia",(t=>{const n=e.commands.get("insertDrupalMedia"),o=new s.ButtonView(t);return o.set({label:Drupal.t("Insert Drupal Media"),icon:'\n',tooltip:!0}),o.bind("isOn","isEnabled").to(n,"value","isEnabled"),this.listenTo(o,"execute",(()=>{r(i,(({attributes:t})=>{e.execute("insertDrupalMedia",t)}),a)})),o}))}}function l(e){return!!e&&e.is("element","drupalMedia")}function d(e){const i=e.getSelectedElement();return i&&function(e){return(0,t.isWidget)(e)&&!!e.getCustomProperty("drupalMedia")}(i)?i:null}class u extends e.Plugin{static get requires(){return[t.WidgetToolbarRepository]}static get pluginName(){return"DrupalMediaToolbar"}afterInit(){const e=this.editor;e.plugins.get(t.WidgetToolbarRepository).register("drupalMedia",{ariaLabel:Drupal.t("Drupal Media toolbar"),items:e.config.get("drupalMedia.toolbar")||[],getRelatedElement:e=>d(e)})}}class c extends e.Command{refresh(){const e=this.editor.model.document.selection.getSelectedElement();this.isEnabled=!1,l(e)&&this._isMediaImage(e).then((e=>{this.isEnabled=e})),l(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)}))}async _isMediaImage(e){const t=this.editor.config.get("drupalMedia");if(!t)return null;const{isMediaUrl:i}=t,r=new URLSearchParams({uuid:e.getAttribute("drupalMediaEntityUuid")}),a=await fetch(`${i}&${r}`);return a.ok?JSON.parse(await a.text()):null}}class m extends e.Plugin{static get pluginName(){return"MediaImageTextAlternativeEditing"}init(){this.editor.commands.add("mediaImageTextAlternative",new c(this.editor))}}function h(e){const t=e.editing.view,i=s.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 p=i("ckeditor5/src/utils.js");class f extends s.View{constructor(t){super(t),this.focusTracker=new p.FocusTracker,this.keystrokes=new p.KeystrokeHandler,this.labeledInput=this._createLabeledInputView(),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 s.ViewCollection,this._focusCycler=new s.FocusCycler({focusables:this._focusables,focusTracker:this.focusTracker,keystrokeHandler:this.keystrokes,actions:{focusPrevious:"shift + tab",focusNext:"tab"}}),this.setTemplate({tag:"form",attributes:{class:["ck","ck-text-alternative-form","ck-responsive-form"],tabindex:"-1"},children:[this.labeledInput,this.saveButtonView,this.cancelButtonView]}),(0,s.injectCssTransitionDisabler)(this)}render(){super.render(),this.keystrokes.listenTo(this.element),(0,s.submitHandler)({view:this}),[this.labeledInput,this.saveButtonView,this.cancelButtonView].forEach((e=>{this._focusables.add(e),this.focusTracker.add(e.element)}))}_createButton(e,t,i,r){const a=new s.ButtonView(this.locale);return a.set({label:e,icon:t,tooltip:!0}),a.extendTemplate({attributes:{class:i}}),r&&a.delegate("execute").to(this,r),a}_createLabeledInputView(){const e=new s.LabeledFieldView(this.locale,s.createLabeledInputText);return e.label=Drupal.t("Override text alternative"),e}}class g extends e.Plugin{static get requires(){return[s.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 r=t.commands.get("mediaImageTextAlternative"),a=new s.ButtonView(i);return a.set({label:Drupal.t("Override media image text alternative"),icon:e.icons.lowVision,tooltip:!0}),a.bind("isVisible").to(r,"isEnabled"),this.listenTo(a,"execute",(()=>{this._showForm()})),a}))}_createForm(){const e=this.editor,t=e.editing.view.document;this._balloon=this.editor.plugins.get("ContextualBalloon"),this._form=new f(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",(()=>{d(t.selection)?this._isVisible&&function(e){const t=e.plugins.get("ContextualBalloon");if(d(e.editing.view.document.selection)){const i=h(e);t.updatePosition(i)}}(e):this._hideForm(!0)})),(0,s.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=this._form.labeledInput;this._form.disableCssTransitions(),this._isInBalloon||this._balloon.add({view:this._form,position:h(e)}),i.fieldView.element.value=t.value||"",i.fieldView.value=i.fieldView.element.value,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 b extends e.Plugin{static get requires(){return[m,g]}static get pluginName(){return"MediaImageTextAlternative"}}function w(e,t,i){if(t.attributes)for(const[r,a]of Object.entries(t.attributes))e.setAttribute(r,a,i);t.styles&&e.setStyle(t.styles,i),t.classes&&e.addClass(t.classes,i)}function v(e){return t=>{t.on("element:drupal-media",((t,i,r)=>{const a=i.viewItem.parent;a.is("element","a")&&function(t,a){const n=e._consumeAllowedAttributes(t,r);n&&r.writer.setAttribute(a,n,i.modelRange)}(a,"htmlLinkAttributes")}),{priority:"low"})}}class M extends e.Plugin{init(){const{editor:e}=this;if(!e.plugins.has("GeneralHtmlSupport"))return;const{schema:t}=e.model,{conversion:i}=e,r=e.plugins.get("DataFilter");t.extend("drupalMedia",{allowAttributes:["htmlLinkAttributes"]}),i.for("upcast").add(v(r)),i.for("editingDowncast").add((e=>e.on("attribute:linkHref:drupalMedia",((e,t,i)=>{if(!i.consumable.consume(t.item,"attribute:htmlLinkAttributes:drupalMedia"))return;const r=i.mapper.toViewElement(t.item),a=function(e,t,i){const r=e.createRangeOn(t);for(const{item:e}of r.getWalker())if(e.is("element",i))return e}(i.writer,r,"a");w(i.writer,t.item.getAttribute("htmlLinkAttributes"),a)}),{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 r=i.mapper.toViewElement(t.item).parent;w(i.writer,t.item.getAttribute("htmlLinkAttributes"),r)}),{priority:"low"})))}static get pluginName(){return"DrupalMediaGeneralHtmlSupport"}}class k extends e.Plugin{static get requires(){return[n,M,o,u,b]}}function _(){return e=>{e.on("element:a",((e,t,i)=>{const r=t.viewItem,a=(n=r,Array.from(n.getChildren()).find((e=>"drupal-media"===e.name)));var n;if(!a)return;if(!i.consumable.consume(r,{attributes:["href"]}))return;const s=r.getAttribute("href");if(!s)return;const o=i.convertItem(a,t.modelCursor);t.modelRange=o.modelRange,t.modelCursor=o.modelCursor;const l=t.modelCursor.nodeBefore;l&&l.is("element","drupalMedia")&&i.writer.setAttribute("linkHref",s,l)}),{priority:"high"})}}class A 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:r}=i;if(!i.consumable.consume(t.item,e.name))return;const a=i.mapper.toViewElement(t.item),n=Array.from(a.getChildren()).find((e=>"a"===e.name));if(n)t.attributeNewValue?r.setAttribute("href",t.attributeNewValue,n):(r.move(r.createRangeIn(n),r.createPositionAt(a,0)),r.remove(n));else{const e=Array.from(a.getChildren()).find((e=>e.getAttribute("data-drupal-media-preview"))),i=r.createContainerElement("a",{href:t.attributeNewValue});r.insert(r.createPositionAt(a,0),i),r.move(r.createRangeOn(e),r.createPositionAt(i,0))}}),{priority:"high"})})),e.conversion.for("dataDowncast").add((e=>{e.on("attribute:linkHref:drupalMedia",((e,t,i)=>{const{writer:r}=i;if(!i.consumable.consume(t.item,e.name))return;const a=i.mapper.toViewElement(t.item),n=r.createContainerElement("a",{href:t.attributeNewValue});r.insert(r.createPositionBefore(a),n),r.move(r.createRangeOn(a),r.createPositionAt(n,0))}),{priority:"high"})}))}}class x 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 s.ButtonView(t),r=e.plugins.get("LinkUI"),a=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(a,"isEnabled"),i.bind("isOn").to(a,"value",(e=>!!e)),this.listenTo(i,"execute",(()=>{this._isSelectedLinkedMedia(e.model.document.selection)?r._addActionsView():r._showUI(!0)})),i}))}_isSelectedLinkedMedia(e){const t=e.getSelectedElement();return!!t&&t.is("element","drupalMedia")&&t.hasAttribute("linkHref")}}class E extends e.Plugin{static get requires(){return[A,x]}static get pluginName(){return"DrupalLinkMedia"}}var y={DrupalMedia:k,MediaImageTextAlternative:b,MediaImageTextAlternativeEditing:m,MediaImageTextAlternativeUi:g,DrupalLinkMedia:E}}(),r=r.default}()})); \ No newline at end of file diff --git a/core/modules/ckeditor5/js/ckeditor5_plugins/drupalMedia/src/drupallinkmedia/drupallinkmediaui.js b/core/modules/ckeditor5/js/ckeditor5_plugins/drupalMedia/src/drupallinkmedia/drupallinkmediaui.js index 68a01a85b2c..1764e04c28a 100644 --- a/core/modules/ckeditor5/js/ckeditor5_plugins/drupalMedia/src/drupallinkmedia/drupallinkmediaui.js +++ b/core/modules/ckeditor5/js/ckeditor5_plugins/drupalMedia/src/drupallinkmedia/drupallinkmediaui.js @@ -60,7 +60,6 @@ export default class DrupalLinkMediaUI extends Plugin { */ _createToolbarLinkMediaButton() { const { editor } = this; - const { t } = editor; editor.ui.componentFactory.add('drupalLinkMedia', (locale) => { const button = new ButtonView(locale); @@ -69,7 +68,7 @@ export default class DrupalLinkMediaUI extends Plugin { button.set({ isEnabled: true, - label: t('Link media'), + label: Drupal.t('Link media'), icon: linkIcon, keystroke: LINK_KEYSTROKE, tooltip: true, diff --git a/core/modules/ckeditor5/js/ckeditor5_plugins/drupalMedia/src/drupalmediaediting.js b/core/modules/ckeditor5/js/ckeditor5_plugins/drupalMedia/src/drupalmediaediting.js index 70c05e0093f..8133f431410 100644 --- a/core/modules/ckeditor5/js/ckeditor5_plugins/drupalMedia/src/drupalmediaediting.js +++ b/core/modules/ckeditor5/js/ckeditor5_plugins/drupalMedia/src/drupalmediaediting.js @@ -29,11 +29,11 @@ export default class DrupalMediaEditing extends Plugin { } const { previewURL, themeError } = options; this.previewURL = previewURL; - this.labelError = this.editor.t('Preview failed'); + this.labelError = Drupal.t('Preview failed'); this.themeError = themeError || ` -

${this.editor.t( +

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

`; diff --git a/core/modules/ckeditor5/js/ckeditor5_plugins/drupalMedia/src/drupalmediatoolbar.js b/core/modules/ckeditor5/js/ckeditor5_plugins/drupalMedia/src/drupalmediatoolbar.js index 694ff4eca78..4da4b6ecae5 100644 --- a/core/modules/ckeditor5/js/ckeditor5_plugins/drupalMedia/src/drupalmediatoolbar.js +++ b/core/modules/ckeditor5/js/ckeditor5_plugins/drupalMedia/src/drupalmediatoolbar.js @@ -18,11 +18,10 @@ export default class DrupalMediaToolbar extends Plugin { afterInit() { const editor = this.editor; - const { t } = editor; const widgetToolbarRepository = editor.plugins.get(WidgetToolbarRepository); widgetToolbarRepository.register('drupalMedia', { - ariaLabel: t('Drupal Media toolbar'), + ariaLabel: Drupal.t('Drupal Media toolbar'), items: editor.config.get('drupalMedia.toolbar') || [], // Get the selected image or an image containing the figcaption with the selection inside. getRelatedElement: (selection) => getSelectedDrupalMediaWidget(selection), diff --git a/core/modules/ckeditor5/js/ckeditor5_plugins/drupalMedia/src/drupalmediaui.js b/core/modules/ckeditor5/js/ckeditor5_plugins/drupalMedia/src/drupalmediaui.js index 5e10d0ec719..466990f4e26 100644 --- a/core/modules/ckeditor5/js/ckeditor5_plugins/drupalMedia/src/drupalmediaui.js +++ b/core/modules/ckeditor5/js/ckeditor5_plugins/drupalMedia/src/drupalmediaui.js @@ -27,7 +27,7 @@ export default class DrupalMediaUI extends Plugin { const buttonView = new ButtonView(locale); buttonView.set({ - label: editor.t('Insert Drupal Media'), + label: Drupal.t('Insert Drupal Media'), icon: mediaIcon, tooltip: true, }); diff --git a/core/modules/ckeditor5/js/ckeditor5_plugins/drupalMedia/src/mediaimagetextalternative/mediaimagetextalternativeui.js b/core/modules/ckeditor5/js/ckeditor5_plugins/drupalMedia/src/mediaimagetextalternative/mediaimagetextalternativeui.js index 28ca24ef5ab..619f5c33a20 100644 --- a/core/modules/ckeditor5/js/ckeditor5_plugins/drupalMedia/src/mediaimagetextalternative/mediaimagetextalternativeui.js +++ b/core/modules/ckeditor5/js/ckeditor5_plugins/drupalMedia/src/mediaimagetextalternative/mediaimagetextalternativeui.js @@ -58,14 +58,13 @@ export default class MediaImageTextAlternativeUi extends Plugin { */ _createButton() { const editor = this.editor; - const t = editor.t; editor.ui.componentFactory.add('mediaImageTextAlternative', (locale) => { const command = editor.commands.get('mediaImageTextAlternative'); const view = new ButtonView(locale); view.set({ - label: t('Override media image text alternative'), + label: Drupal.t('Override media image text alternative'), icon: icons.lowVision, tooltip: true, }); diff --git a/core/modules/ckeditor5/js/ckeditor5_plugins/drupalMedia/src/mediaimagetextalternative/ui/textalternativeformview.js b/core/modules/ckeditor5/js/ckeditor5_plugins/drupalMedia/src/mediaimagetextalternative/ui/textalternativeformview.js index 49d865f8172..74758111f8a 100644 --- a/core/modules/ckeditor5/js/ckeditor5_plugins/drupalMedia/src/mediaimagetextalternative/ui/textalternativeformview.js +++ b/core/modules/ckeditor5/js/ckeditor5_plugins/drupalMedia/src/mediaimagetextalternative/ui/textalternativeformview.js @@ -24,8 +24,6 @@ export default class TextAlternativeFormView extends View { constructor(locale) { super(locale); - const t = this.locale.t; - /** * Tracks information about the DOM focus in the form. */ @@ -45,7 +43,7 @@ export default class TextAlternativeFormView extends View { * A button used to submit the form. */ this.saveButtonView = this._createButton( - t('Save'), + Drupal.t('Save'), icons.check, 'ck-button-save', ); @@ -55,7 +53,7 @@ export default class TextAlternativeFormView extends View { * A button used to cancel the form. */ this.cancelButtonView = this._createButton( - t('Cancel'), + Drupal.t('Cancel'), icons.cancel, 'ck-button-cancel', 'cancel', @@ -160,13 +158,12 @@ export default class TextAlternativeFormView extends View { * Labeled field view instance. */ _createLabeledInputView() { - const t = this.locale.t; const labeledInput = new LabeledFieldView( this.locale, createLabeledInputText, ); - labeledInput.label = t('Override text alternative'); + labeledInput.label = Drupal.t('Override text alternative'); return labeledInput; } diff --git a/core/modules/ckeditor5/tests/src/FunctionalJavascript/JSTranslationTest.php b/core/modules/ckeditor5/tests/src/FunctionalJavascript/JSTranslationTest.php new file mode 100644 index 00000000000..2b7392c1e14 --- /dev/null +++ b/core/modules/ckeditor5/tests/src/FunctionalJavascript/JSTranslationTest.php @@ -0,0 +1,70 @@ +createMediaType('image', ['id' => 'image', 'label' => 'Image']); + } + + /** + * Integration test to ensure that CKEditor 5 Plugins translations are loaded. + */ + public function test(): void { + $page = $this->getSession()->getPage(); + $assert_session = $this->assertSession(); + + $this->createNewTextFormat($page, $assert_session); + $this->assertNotEmpty($assert_session->waitForElement('css', '.ckeditor5-toolbar-item-drupalMedia')); + $this->click('#edit-filters-media-embed-status'); + $assert_session->assertWaitOnAjaxRequest(); + $this->triggerKeyUp('.ckeditor5-toolbar-item-drupalMedia', 'ArrowDown'); + $assert_session->assertWaitOnAjaxRequest(); + $this->saveNewTextFormat($page, $assert_session); + + $langcode = 'fr'; + ConfigurableLanguage::createFromLangcode($langcode)->save(); + $this->config('system.site')->set('default_langcode', $langcode)->save(); + + // Visit a page that will trigger a JavaScript file parsing for + // translatable strings. + $this->drupalGet('node/add'); + $this->assertNotEmpty($assert_session->waitForElement('css', '.ck-editor')); + + // Ensure a string from the CKEditor 5 plugin is picked up by translation. + // @see core/modules/ckeditor5/js/ckeditor5_plugins/drupalMedia/src/drupalmediatoolbar.js + $locale_storage = $this->container->get('locale.storage'); + $string = $locale_storage->findString(['source' => 'Drupal Media toolbar', 'context' => '']); + $this->assertNotEmpty($string, 'String from JavaScript file saved.'); + } + +}