@ckeditor/ckeditor5-html-embed 30.0.0 → 31.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,4 +2,4 @@
2
2
  * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved.
3
3
  * For licensing, see LICENSE.md.
4
4
  */
5
- !function(e){const t=e.en=e.en||{};t.dictionary=Object.assign(t.dictionary||{},{"Edit source":"Edit source","Empty snippet content":"Empty snippet content","HTML snippet":"HTML snippet","Insert HTML":"Insert HTML","No preview available":"No preview available","Paste raw HTML here...":"Paste raw HTML here...","Save changes":"Save changes"})}(window.CKEDITOR_TRANSLATIONS||(window.CKEDITOR_TRANSLATIONS={})),window.CKEditor5=window.CKEditor5||{},window.CKEditor5.htmlEmbed=function(e){var t={};function n(r){if(t[r])return t[r].exports;var a=t[r]={i:r,l:!1,exports:{}};return e[r].call(a.exports,a,a.exports,n),a.l=!0,a.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var a in e)n.d(r,a,function(t){return e[t]}.bind(null,a));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=8)}([function(e,t,n){e.exports=n(3)("./src/core.js")},function(e,t,n){e.exports=n(3)("./src/utils.js")},function(e,t,n){e.exports=n(3)("./src/widget.js")},function(e,t){e.exports=CKEditor5.dll},function(e,t,n){e.exports=n(3)("./src/ui.js")},function(e,t,n){var r=n(6),a=n(7);"string"==typeof(a=a.__esModule?a.default:a)&&(a=[[e.i,a,""]]);var i={injectType:"singletonStyleTag",attributes:{"data-cke":!0},insert:"head",singleton:!0};r(a,i);e.exports=a.locals||{}},function(e,t,n){"use strict";var r,a=function(){return void 0===r&&(r=Boolean(window&&document&&document.all&&!window.atob)),r},i=function(){var e={};return function(t){if(void 0===e[t]){var n=document.querySelector(t);if(window.HTMLIFrameElement&&n instanceof window.HTMLIFrameElement)try{n=n.contentDocument.head}catch(e){n=null}e[t]=n}return e[t]}}(),o=[];function c(e){for(var t=-1,n=0;n<o.length;n++)if(o[n].identifier===e){t=n;break}return t}function l(e,t){for(var n={},r=[],a=0;a<e.length;a++){var i=e[a],l=t.base?i[0]+t.base:i[0],d=n[l]||0,s="".concat(l," ").concat(d);n[l]=d+1;var m=c(s),u={css:i[1],media:i[2],sourceMap:i[3]};-1!==m?(o[m].references++,o[m].updater(u)):o.push({identifier:s,updater:w(u,t),references:1}),r.push(s)}return r}function d(e){var t=document.createElement("style"),r=e.attributes||{};if(void 0===r.nonce){var a=n.nc;a&&(r.nonce=a)}if(Object.keys(r).forEach((function(e){t.setAttribute(e,r[e])})),"function"==typeof e.insert)e.insert(t);else{var o=i(e.insert||"head");if(!o)throw new Error("Couldn't find a style target. This probably means that the value for the 'insert' parameter is invalid.");o.appendChild(t)}return t}var s,m=(s=[],function(e,t){return s[e]=t,s.filter(Boolean).join("\n")});function u(e,t,n,r){var a=n?"":r.media?"@media ".concat(r.media," {").concat(r.css,"}"):r.css;if(e.styleSheet)e.styleSheet.cssText=m(t,a);else{var i=document.createTextNode(a),o=e.childNodes;o[t]&&e.removeChild(o[t]),o.length?e.insertBefore(i,o[t]):e.appendChild(i)}}function b(e,t,n){var r=n.css,a=n.media,i=n.sourceMap;if(a?e.setAttribute("media",a):e.removeAttribute("media"),i&&"undefined"!=typeof btoa&&(r+="\n/*# sourceMappingURL=data:application/json;base64,".concat(btoa(unescape(encodeURIComponent(JSON.stringify(i))))," */")),e.styleSheet)e.styleSheet.cssText=r;else{for(;e.firstChild;)e.removeChild(e.firstChild);e.appendChild(document.createTextNode(r))}}var p=null,h=0;function w(e,t){var n,r,a;if(t.singleton){var i=h++;n=p||(p=d(t)),r=u.bind(null,n,i,!1),a=u.bind(null,n,i,!0)}else n=d(t),r=b.bind(null,n,t),a=function(){!function(e){if(null===e.parentNode)return!1;e.parentNode.removeChild(e)}(n)};return r(e),function(t){if(t){if(t.css===e.css&&t.media===e.media&&t.sourceMap===e.sourceMap)return;r(e=t)}else a()}}e.exports=function(e,t){(t=t||{}).singleton||"boolean"==typeof t.singleton||(t.singleton=a());var n=l(e=e||[],t);return function(e){if(e=e||[],"[object Array]"===Object.prototype.toString.call(e)){for(var r=0;r<n.length;r++){var a=c(n[r]);o[a].references--}for(var i=l(e,t),d=0;d<n.length;d++){var s=c(n[d]);0===o[s].references&&(o[s].updater(),o.splice(s,1))}n=i}}}},function(e,t){e.exports=".ck-widget.raw-html-embed{margin:.9em auto;position:relative;display:flow-root;min-width:15em;font-style:normal}.ck-widget.raw-html-embed:before{position:absolute;z-index:1}.ck-widget.raw-html-embed .raw-html-embed__buttons-wrapper{position:absolute;display:flex;flex-direction:column}.ck-widget.raw-html-embed .raw-html-embed__preview{position:relative;overflow:hidden;display:flex}.ck-widget.raw-html-embed .raw-html-embed__preview-content{width:100%;position:relative;margin:auto;display:table;border-collapse:separate;border-spacing:7px}.ck-widget.raw-html-embed .raw-html-embed__preview-placeholder{position:absolute;left:0;top:0;right:0;bottom:0;display:flex;align-items:center;justify-content:center}:root{--ck-html-embed-content-width:calc(100% - var(--ck-icon-size)*1.5);--ck-html-embed-source-height:10em;--ck-html-embed-unfocused-outline-width:1px;--ck-html-embed-content-min-height:calc(var(--ck-icon-size) + var(--ck-spacing-standard));--ck-html-embed-source-disabled-background:var(--ck-color-base-foreground);--ck-html-embed-source-disabled-color:#737373}.ck-widget.raw-html-embed{font-size:var(--ck-font-size-base);background-color:var(--ck-color-base-foreground)}.ck-widget.raw-html-embed:not(.ck-widget_selected):not(:hover){outline:var(--ck-html-embed-unfocused-outline-width) dashed var(--ck-color-widget-blurred-border)}.ck-widget.raw-html-embed[dir=ltr]{text-align:left}.ck-widget.raw-html-embed[dir=rtl]{text-align:right}.ck-widget.raw-html-embed:before{content:attr(data-html-embed-label);top:calc(var(--ck-html-embed-unfocused-outline-width)*-1);left:var(--ck-spacing-standard);background:#999;transition:background var(--ck-widget-handler-animation-duration) var(--ck-widget-handler-animation-curve);padding:calc(var(--ck-spacing-tiny) + var(--ck-html-embed-unfocused-outline-width)) var(--ck-spacing-small) var(--ck-spacing-tiny);border-radius:0 0 var(--ck-border-radius) var(--ck-border-radius);color:var(--ck-color-base-background);font-size:var(--ck-font-size-tiny);font-family:var(--ck-font-face)}.ck-widget.raw-html-embed[dir=rtl]:before{left:auto;right:var(--ck-spacing-standard)}.ck-widget.raw-html-embed[dir=ltr] .ck-widget__type-around .ck-widget__type-around__button.ck-widget__type-around__button_before{margin-left:50px}.ck.ck-editor__editable.ck-blurred .ck-widget.raw-html-embed.ck-widget_selected:before{top:0;padding:var(--ck-spacing-tiny) var(--ck-spacing-small)}.ck.ck-editor__editable:not(.ck-blurred) .ck-widget.raw-html-embed.ck-widget_selected:before{top:0;padding:var(--ck-spacing-tiny) var(--ck-spacing-small);background:var(--ck-color-focus-border)}.ck.ck-editor__editable .ck-widget.raw-html-embed:not(.ck-widget_selected):hover:before{top:0;padding:var(--ck-spacing-tiny) var(--ck-spacing-small)}.ck-widget.raw-html-embed .raw-html-embed__content-wrapper{padding:var(--ck-spacing-standard)}.ck-widget.raw-html-embed .raw-html-embed__buttons-wrapper{top:var(--ck-spacing-standard);right:var(--ck-spacing-standard)}.ck-widget.raw-html-embed .raw-html-embed__buttons-wrapper .ck-button.raw-html-embed__save-button{color:var(--ck-color-button-save)}.ck-widget.raw-html-embed .raw-html-embed__buttons-wrapper .ck-button.raw-html-embed__cancel-button{color:var(--ck-color-button-cancel)}.ck-widget.raw-html-embed .raw-html-embed__buttons-wrapper .ck-button:not(:first-child){margin-top:var(--ck-spacing-small)}.ck-widget.raw-html-embed[dir=rtl] .raw-html-embed__buttons-wrapper{left:var(--ck-spacing-standard);right:auto}.ck-widget.raw-html-embed .raw-html-embed__source{box-sizing:border-box;height:var(--ck-html-embed-source-height);width:var(--ck-html-embed-content-width);resize:none;min-width:0;padding:var(--ck-spacing-standard);font-family:monospace;tab-size:4;white-space:pre-wrap;font-size:var(--ck-font-size-base);text-align:left;direction:ltr}.ck-widget.raw-html-embed .raw-html-embed__source[disabled]{background:var(--ck-html-embed-source-disabled-background);color:var(--ck-html-embed-source-disabled-color);-webkit-text-fill-color:var(--ck-html-embed-source-disabled-color);opacity:1}.ck-widget.raw-html-embed .raw-html-embed__preview{min-height:var(--ck-html-embed-content-min-height);width:var(--ck-html-embed-content-width)}.ck-editor__editable:not(.ck-read-only) .ck-widget.raw-html-embed .raw-html-embed__preview{pointer-events:none}.ck-widget.raw-html-embed .raw-html-embed__preview-content{box-sizing:border-box;background-color:var(--ck-color-base-foreground)}.ck-widget.raw-html-embed .raw-html-embed__preview-content>*{margin-left:auto;margin-right:auto}.ck-widget.raw-html-embed .raw-html-embed__preview-placeholder{color:var(--ck-html-embed-source-disabled-color)}"},function(e,t,n){"use strict";n.r(t),n.d(t,"HtmlEmbed",(function(){return b})),n.d(t,"HtmlEmbedEditing",(function(){return s})),n.d(t,"HtmlEmbedUI",(function(){return u}));var r=n(0),a=n(2),i=n(4),o=n(1);class c extends r.Command{refresh(){const e=this.editor.model,t=e.schema,n=e.document.selection;this.isEnabled=function(e,t,n){const r=function(e,t){const n=Object(a.findOptimalInsertionRange)(e,t).start.parent;if(n.isEmpty&&!n.is("element","$root"))return n.parent;return n}(e,n);return t.checkChild(r,"rawHtml")}(n,t,e)}execute(){const e=this.editor.model;e.change(t=>{const n=t.createElement("rawHtml");e.insertContent(n),t.setSelection(n,"on")})}}class l extends r.Command{refresh(){const e=d(this.editor.model.document.selection);this.isEnabled=!!e}execute(e){const t=this.editor.model,n=d(t.document.selection);t.change(t=>{t.setAttribute("value",e,n)})}}function d(e){const t=e.getSelectedElement();return t&&t.is("element","rawHtml")?t:null}n(5);class s extends r.Plugin{static get pluginName(){return"HtmlEmbedEditing"}constructor(e){super(e),e.config.define("htmlEmbed",{showPreviews:!1,sanitizeHtml:e=>(Object(o.logWarning)("html-embed-provide-sanitize-function"),{html:e,hasChanged:!1})})}init(){const e=this.editor;e.model.schema.register("rawHtml",{isObject:!0,allowWhere:"$block",allowAttributes:["value"]}),e.commands.add("updateHtmlEmbed",new l(e)),e.commands.add("insertHtmlEmbed",new c(e)),this._setupConversion()}_setupConversion(){const e=this.editor,t=e.t,n=e.editing.view,r=e.config.get("htmlEmbed");function i({domElement:e,editor:n,state:r,props:a}){e.textContent="";const i=e.ownerDocument;let l;if(r.isEditable){const t={isDisabled:!1,placeholder:a.textareaPlaceholder};l=c({domDocument:i,state:r,props:t}),e.append(l)}else if(r.showPreviews){const c={sanitizeHtml:a.sanitizeHtml};e.append(function({domDocument:e,state:n,props:r,editor:a}){const i=r.sanitizeHtml(n.getRawHtmlValue()),c=n.getRawHtmlValue().length>0?t("No preview available"):t("Empty snippet content"),l=Object(o.createElement)(e,"div",{class:"ck ck-reset_all raw-html-embed__preview-placeholder"},c),d=Object(o.createElement)(e,"div",{class:"raw-html-embed__preview-content",dir:a.locale.contentLanguageDirection}),s=e.createRange().createContextualFragment(i.html);d.appendChild(s);return Object(o.createElement)(e,"div",{class:"raw-html-embed__preview"},[l,d])}({domDocument:i,state:r,props:c,editor:n}))}else{const t={isDisabled:!0,placeholder:a.textareaPlaceholder};e.append(c({domDocument:i,state:r,props:t}))}const d={onEditClick:a.onEditClick,onSaveClick:()=>{a.onSaveClick(l.value)},onCancelClick:a.onCancelClick};e.prepend(function({editor:e,domDocument:t,state:n,props:r}){const a=Object(o.createElement)(t,"div",{class:"raw-html-embed__buttons-wrapper"}),i=m(e,"edit"),c=m(e,"save"),l=m(e,"cancel");if(n.isEditable){const e=c.cloneNode(!0),t=l.cloneNode(!0);e.addEventListener("click",e=>{e.preventDefault(),r.onSaveClick()}),t.addEventListener("click",e=>{e.preventDefault(),r.onCancelClick()}),a.appendChild(e),a.appendChild(t)}else{const e=i.cloneNode(!0);e.addEventListener("click",e=>{e.preventDefault(),r.onEditClick()}),a.appendChild(e)}return a}({editor:n,domDocument:i,state:r,props:d}))}function c({domDocument:e,state:t,props:n}){const r=Object(o.createElement)(e,"textarea",{placeholder:n.placeholder,class:"ck ck-reset ck-input ck-input-text raw-html-embed__source"});return r.disabled=n.isDisabled,r.value=t.getRawHtmlValue(),r}e.data.registerRawContentMatcher({name:"div",classes:"raw-html-embed"}),e.conversion.for("upcast").elementToElement({view:{name:"div",classes:"raw-html-embed"},model:(e,{writer:t})=>t.createElement("rawHtml",{value:e.getCustomProperty("$rawContent")})}),e.conversion.for("dataDowncast").elementToElement({model:"rawHtml",view:(e,{writer:t})=>t.createRawElement("div",{class:"raw-html-embed"},(function(t){t.innerHTML=e.getAttribute("value")||""}))}),e.conversion.for("editingDowncast").elementToElement({triggerBy:{attributes:["value"]},model:"rawHtml",view:(o,{writer:c})=>{let l,d,s;const m=c.createContainerElement("div",{class:"raw-html-embed","data-html-embed-label":t("HTML snippet"),dir:e.locale.uiLanguageDirection}),u=c.createRawElement("div",{class:"raw-html-embed__content-wrapper"},(function(t){l=t,i({domElement:t,editor:e,state:d,props:s}),l.addEventListener("mousedown",()=>{if(d.isEditable){const t=e.model;t.document.selection.getSelectedElement()!==o&&t.change(e=>e.setSelection(o,"on"))}},!0)})),b={makeEditable(){d=Object.assign({},d,{isEditable:!0}),i({domElement:l,editor:e,state:d,props:s}),n.change(e=>{e.setAttribute("data-cke-ignore-events","true",u)}),l.querySelector("textarea").focus()},save(t){t!==d.getRawHtmlValue()?(e.execute("updateHtmlEmbed",t),e.editing.view.focus()):this.cancel()},cancel(){d=Object.assign({},d,{isEditable:!1}),i({domElement:l,editor:e,state:d,props:s}),e.editing.view.focus(),n.change(e=>{e.removeAttribute("data-cke-ignore-events",u)})}};return d={showPreviews:r.showPreviews,isEditable:!1,getRawHtmlValue:()=>o.getAttribute("value")||""},s={sanitizeHtml:r.sanitizeHtml,textareaPlaceholder:t("Paste raw HTML here..."),onEditClick(){b.makeEditable()},onSaveClick(e){b.save(e)},onCancelClick(){b.cancel()}},c.insert(c.createPositionAt(m,0),u),c.setCustomProperty("rawHtmlApi",b,m),c.setCustomProperty("rawHtml",!0,m),Object(a.toWidget)(m,c,{widgetLabel:t("HTML snippet"),hasSelectionHandle:!0})}})}}function m(e,t){const n=e.locale.t,a=new i.ButtonView(e.locale),o=e.commands.get("updateHtmlEmbed");return a.set({tooltipPosition:"rtl"===e.locale.uiLanguageDirection?"e":"w",icon:r.icons.pencil,tooltip:!0}),a.render(),"edit"===t?a.set({icon:r.icons.pencil,label:n("Edit source"),class:"raw-html-embed__edit-button"}):"save"===t?(a.set({icon:r.icons.check,label:n("Save changes"),class:"raw-html-embed__save-button"}),a.bind("isEnabled").to(o,"isEnabled")):a.set({icon:r.icons.cancel,label:n("Cancel"),class:"raw-html-embed__cancel-button"}),a.destroy(),a.element.cloneNode(!0)}class u extends r.Plugin{static get pluginName(){return"HtmlEmbedUI"}init(){const e=this.editor,t=e.t;e.ui.componentFactory.add("htmlEmbed",n=>{const r=e.commands.get("insertHtmlEmbed"),a=new i.ButtonView(n);return a.set({label:t("Insert HTML"),icon:'<svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M17 0a2 2 0 0 1 2 2v7a1 1 0 0 1 1 1v5a1 1 0 0 1-.883.993l-.118.006L19 17a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2l-.001-1.001-.116-.006A1 1 0 0 1 0 15v-5a1 1 0 0 1 .999-1L1 2a2 2 0 0 1 2-2h14zm.499 15.999h-15L2.5 17a.5.5 0 0 0 .5.5h14a.5.5 0 0 0 .5-.5l-.001-1.001zm-3.478-6.013-.014.014H14v.007l-1.525 1.525-1.46-1.46-.015.013V10h-1v5h1v-3.53l1.428 1.43.048.043.131-.129L14 11.421V15h1v-5h-.965l-.014-.014zM2 10H1v5h1v-2h2v2h1v-5H4v2H2v-2zm7 0H6v1h1v4h1v-4h1v-1zm8 0h-1v5h3v-1h-2v-4zm0-8.5H3a.5.5 0 0 0-.5.5l-.001 6.999h15L17.5 2a.5.5 0 0 0-.5-.5zM10 7v1H4V7h6zm3-2v1H4V5h9zm-3-2v1H4V3h6z"/></svg>',tooltip:!0}),a.bind("isEnabled").to(r,"isEnabled"),this.listenTo(a,"execute",()=>{e.execute("insertHtmlEmbed"),e.editing.view.focus();e.editing.view.document.selection.getSelectedElement().getCustomProperty("rawHtmlApi").makeEditable()}),a})}}class b extends r.Plugin{static get requires(){return[s,u,a.Widget]}static get pluginName(){return"HtmlEmbed"}}}]);
5
+ !function(e){const t=e.en=e.en||{};t.dictionary=Object.assign(t.dictionary||{},{"Edit source":"Edit source","Empty snippet content":"Empty snippet content","HTML snippet":"HTML snippet","Insert HTML":"Insert HTML","No preview available":"No preview available","Paste raw HTML here...":"Paste raw HTML here...","Save changes":"Save changes"})}(window.CKEDITOR_TRANSLATIONS||(window.CKEDITOR_TRANSLATIONS={})),window.CKEditor5=window.CKEditor5||{},window.CKEditor5.htmlEmbed=function(e){var t={};function n(r){if(t[r])return t[r].exports;var i=t[r]={i:r,l:!1,exports:{}};return e[r].call(i.exports,i,i.exports,n),i.l=!0,i.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var i in e)n.d(r,i,function(t){return e[t]}.bind(null,i));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=8)}([function(e,t,n){e.exports=n(3)("./src/core.js")},function(e,t,n){e.exports=n(3)("./src/utils.js")},function(e,t,n){e.exports=n(3)("./src/widget.js")},function(e,t){e.exports=CKEditor5.dll},function(e,t,n){e.exports=n(3)("./src/ui.js")},function(e,t,n){var r=n(6),i=n(7);"string"==typeof(i=i.__esModule?i.default:i)&&(i=[[e.i,i,""]]);var a={injectType:"singletonStyleTag",attributes:{"data-cke":!0},insert:"head",singleton:!0};r(i,a);e.exports=i.locals||{}},function(e,t,n){"use strict";var r,i=function(){return void 0===r&&(r=Boolean(window&&document&&document.all&&!window.atob)),r},a=function(){var e={};return function(t){if(void 0===e[t]){var n=document.querySelector(t);if(window.HTMLIFrameElement&&n instanceof window.HTMLIFrameElement)try{n=n.contentDocument.head}catch(e){n=null}e[t]=n}return e[t]}}(),o=[];function c(e){for(var t=-1,n=0;n<o.length;n++)if(o[n].identifier===e){t=n;break}return t}function l(e,t){for(var n={},r=[],i=0;i<e.length;i++){var a=e[i],l=t.base?a[0]+t.base:a[0],d=n[l]||0,s="".concat(l," ").concat(d);n[l]=d+1;var m=c(s),u={css:a[1],media:a[2],sourceMap:a[3]};-1!==m?(o[m].references++,o[m].updater(u)):o.push({identifier:s,updater:w(u,t),references:1}),r.push(s)}return r}function d(e){var t=document.createElement("style"),r=e.attributes||{};if(void 0===r.nonce){var i=n.nc;i&&(r.nonce=i)}if(Object.keys(r).forEach((function(e){t.setAttribute(e,r[e])})),"function"==typeof e.insert)e.insert(t);else{var o=a(e.insert||"head");if(!o)throw new Error("Couldn't find a style target. This probably means that the value for the 'insert' parameter is invalid.");o.appendChild(t)}return t}var s,m=(s=[],function(e,t){return s[e]=t,s.filter(Boolean).join("\n")});function u(e,t,n,r){var i=n?"":r.media?"@media ".concat(r.media," {").concat(r.css,"}"):r.css;if(e.styleSheet)e.styleSheet.cssText=m(t,i);else{var a=document.createTextNode(i),o=e.childNodes;o[t]&&e.removeChild(o[t]),o.length?e.insertBefore(a,o[t]):e.appendChild(a)}}function b(e,t,n){var r=n.css,i=n.media,a=n.sourceMap;if(i?e.setAttribute("media",i):e.removeAttribute("media"),a&&"undefined"!=typeof btoa&&(r+="\n/*# sourceMappingURL=data:application/json;base64,".concat(btoa(unescape(encodeURIComponent(JSON.stringify(a))))," */")),e.styleSheet)e.styleSheet.cssText=r;else{for(;e.firstChild;)e.removeChild(e.firstChild);e.appendChild(document.createTextNode(r))}}var p=null,h=0;function w(e,t){var n,r,i;if(t.singleton){var a=h++;n=p||(p=d(t)),r=u.bind(null,n,a,!1),i=u.bind(null,n,a,!0)}else n=d(t),r=b.bind(null,n,t),i=function(){!function(e){if(null===e.parentNode)return!1;e.parentNode.removeChild(e)}(n)};return r(e),function(t){if(t){if(t.css===e.css&&t.media===e.media&&t.sourceMap===e.sourceMap)return;r(e=t)}else i()}}e.exports=function(e,t){(t=t||{}).singleton||"boolean"==typeof t.singleton||(t.singleton=i());var n=l(e=e||[],t);return function(e){if(e=e||[],"[object Array]"===Object.prototype.toString.call(e)){for(var r=0;r<n.length;r++){var i=c(n[r]);o[i].references--}for(var a=l(e,t),d=0;d<n.length;d++){var s=c(n[d]);0===o[s].references&&(o[s].updater(),o.splice(s,1))}n=a}}}},function(e,t){e.exports=".ck-widget.raw-html-embed{margin:.9em auto;position:relative;display:flow-root;min-width:15em;font-style:normal}.ck-widget.raw-html-embed:before{position:absolute;z-index:1}.ck-widget.raw-html-embed .raw-html-embed__buttons-wrapper{position:absolute;display:flex;flex-direction:column}.ck-widget.raw-html-embed .raw-html-embed__preview{position:relative;overflow:hidden;display:flex}.ck-widget.raw-html-embed .raw-html-embed__preview-content{width:100%;position:relative;margin:auto;display:table;border-collapse:separate;border-spacing:7px}.ck-widget.raw-html-embed .raw-html-embed__preview-placeholder{position:absolute;left:0;top:0;right:0;bottom:0;display:flex;align-items:center;justify-content:center}:root{--ck-html-embed-content-width:calc(100% - var(--ck-icon-size)*1.5);--ck-html-embed-source-height:10em;--ck-html-embed-unfocused-outline-width:1px;--ck-html-embed-content-min-height:calc(var(--ck-icon-size) + var(--ck-spacing-standard));--ck-html-embed-source-disabled-background:var(--ck-color-base-foreground);--ck-html-embed-source-disabled-color:#737373}.ck-widget.raw-html-embed{font-size:var(--ck-font-size-base);background-color:var(--ck-color-base-foreground)}.ck-widget.raw-html-embed:not(.ck-widget_selected):not(:hover){outline:var(--ck-html-embed-unfocused-outline-width) dashed var(--ck-color-widget-blurred-border)}.ck-widget.raw-html-embed[dir=ltr]{text-align:left}.ck-widget.raw-html-embed[dir=rtl]{text-align:right}.ck-widget.raw-html-embed:before{content:attr(data-html-embed-label);top:calc(var(--ck-html-embed-unfocused-outline-width)*-1);left:var(--ck-spacing-standard);background:#999;transition:background var(--ck-widget-handler-animation-duration) var(--ck-widget-handler-animation-curve);padding:calc(var(--ck-spacing-tiny) + var(--ck-html-embed-unfocused-outline-width)) var(--ck-spacing-small) var(--ck-spacing-tiny);border-radius:0 0 var(--ck-border-radius) var(--ck-border-radius);color:var(--ck-color-base-background);font-size:var(--ck-font-size-tiny);font-family:var(--ck-font-face)}.ck-widget.raw-html-embed[dir=rtl]:before{left:auto;right:var(--ck-spacing-standard)}.ck-widget.raw-html-embed[dir=ltr] .ck-widget__type-around .ck-widget__type-around__button.ck-widget__type-around__button_before{margin-left:50px}.ck.ck-editor__editable.ck-blurred .ck-widget.raw-html-embed.ck-widget_selected:before{top:0;padding:var(--ck-spacing-tiny) var(--ck-spacing-small)}.ck.ck-editor__editable:not(.ck-blurred) .ck-widget.raw-html-embed.ck-widget_selected:before{top:0;padding:var(--ck-spacing-tiny) var(--ck-spacing-small);background:var(--ck-color-focus-border)}.ck.ck-editor__editable .ck-widget.raw-html-embed:not(.ck-widget_selected):hover:before{top:0;padding:var(--ck-spacing-tiny) var(--ck-spacing-small)}.ck-widget.raw-html-embed .raw-html-embed__content-wrapper{padding:var(--ck-spacing-standard)}.ck-widget.raw-html-embed .raw-html-embed__buttons-wrapper{top:var(--ck-spacing-standard);right:var(--ck-spacing-standard)}.ck-widget.raw-html-embed .raw-html-embed__buttons-wrapper .ck-button.raw-html-embed__save-button{color:var(--ck-color-button-save)}.ck-widget.raw-html-embed .raw-html-embed__buttons-wrapper .ck-button.raw-html-embed__cancel-button{color:var(--ck-color-button-cancel)}.ck-widget.raw-html-embed .raw-html-embed__buttons-wrapper .ck-button:not(:first-child){margin-top:var(--ck-spacing-small)}.ck-widget.raw-html-embed[dir=rtl] .raw-html-embed__buttons-wrapper{left:var(--ck-spacing-standard);right:auto}.ck-widget.raw-html-embed .raw-html-embed__source{box-sizing:border-box;height:var(--ck-html-embed-source-height);width:var(--ck-html-embed-content-width);resize:none;min-width:0;padding:var(--ck-spacing-standard);font-family:monospace;tab-size:4;white-space:pre-wrap;font-size:var(--ck-font-size-base);text-align:left;direction:ltr}.ck-widget.raw-html-embed .raw-html-embed__source[disabled]{background:var(--ck-html-embed-source-disabled-background);color:var(--ck-html-embed-source-disabled-color);-webkit-text-fill-color:var(--ck-html-embed-source-disabled-color);opacity:1}.ck-widget.raw-html-embed .raw-html-embed__preview{min-height:var(--ck-html-embed-content-min-height);width:var(--ck-html-embed-content-width)}.ck-editor__editable:not(.ck-read-only) .ck-widget.raw-html-embed .raw-html-embed__preview{pointer-events:none}.ck-widget.raw-html-embed .raw-html-embed__preview-content{box-sizing:border-box;background-color:var(--ck-color-base-foreground)}.ck-widget.raw-html-embed .raw-html-embed__preview-content>*{margin-left:auto;margin-right:auto}.ck-widget.raw-html-embed .raw-html-embed__preview-placeholder{color:var(--ck-html-embed-source-disabled-color)}"},function(e,t,n){"use strict";n.r(t),n.d(t,"HtmlEmbed",(function(){return u})),n.d(t,"HtmlEmbedEditing",(function(){return d})),n.d(t,"HtmlEmbedUI",(function(){return m}));var r=n(0),i=n(2),a=n(4),o=n(1);class c extends r.Command{refresh(){const e=this.editor.model,t=e.schema,n=e.document.selection,r=l(n);this.isEnabled=function(e,t,n){const r=function(e,t){const n=Object(i.findOptimalInsertionRange)(e,t).start.parent;if(n.isEmpty&&!n.is("element","$root"))return n.parent;return n}(e,n);return t.checkChild(r,"rawHtml")}(n,t,e),this.value=r?r.getAttribute("value")||"":null}execute(e){const t=this.editor.model,n=t.document.selection;t.change(r=>{let i;null!==this.value?i=l(n):(i=r.createElement("rawHtml"),t.insertContent(i),r.setSelection(i,"on")),r.setAttribute("value",e,i)})}}function l(e){const t=e.getSelectedElement();return t&&t.is("element","rawHtml")?t:null}n(5);class d extends r.Plugin{static get pluginName(){return"HtmlEmbedEditing"}constructor(e){super(e),e.config.define("htmlEmbed",{showPreviews:!1,sanitizeHtml:e=>(Object(o.logWarning)("html-embed-provide-sanitize-function"),{html:e,hasChanged:!1})}),this._widgetButtonViewReferences=new Set}init(){const e=this.editor;e.model.schema.register("rawHtml",{isObject:!0,allowWhere:"$block",allowAttributes:["value"]}),e.commands.add("htmlEmbed",new c(e)),this._setupConversion()}_setupConversion(){const e=this.editor,t=e.t,n=e.editing.view,r=this._widgetButtonViewReferences,a=e.config.get("htmlEmbed");function c({domElement:e,editor:n,state:i,props:a}){e.textContent="";const c=e.ownerDocument;let d;if(i.isEditable){const t={isDisabled:!1,placeholder:a.textareaPlaceholder};d=l({domDocument:c,state:i,props:t}),e.append(d)}else if(i.showPreviews){const r={sanitizeHtml:a.sanitizeHtml};e.append(function({domDocument:e,state:n,props:r,editor:i}){const a=r.sanitizeHtml(n.getRawHtmlValue()),c=n.getRawHtmlValue().length>0?t("No preview available"):t("Empty snippet content"),l=Object(o.createElement)(e,"div",{class:"ck ck-reset_all raw-html-embed__preview-placeholder"},c),d=Object(o.createElement)(e,"div",{class:"raw-html-embed__preview-content",dir:i.locale.contentLanguageDirection}),s=e.createRange().createContextualFragment(a.html);d.appendChild(s);return Object(o.createElement)(e,"div",{class:"raw-html-embed__preview"},[l,d])}({domDocument:c,state:i,props:r,editor:n}))}else{const t={isDisabled:!0,placeholder:a.textareaPlaceholder};e.append(l({domDocument:c,state:i,props:t}))}const m={onEditClick:a.onEditClick,onSaveClick:()=>{a.onSaveClick(d.value)},onCancelClick:a.onCancelClick};e.prepend(function({editor:e,domDocument:t,state:n,props:i}){const a=Object(o.createElement)(t,"div",{class:"raw-html-embed__buttons-wrapper"});if(n.isEditable){const t=s(e,"save",i.onSaveClick),n=s(e,"cancel",i.onCancelClick);a.append(t.element,n.element),r.add(t).add(n)}else{const t=s(e,"edit",i.onEditClick);a.append(t.element),r.add(t)}return a}({editor:n,domDocument:c,state:i,props:m}))}function l({domDocument:e,state:t,props:n}){const r=Object(o.createElement)(e,"textarea",{placeholder:n.placeholder,class:"ck ck-reset ck-input ck-input-text raw-html-embed__source"});return r.disabled=n.isDisabled,r.value=t.getRawHtmlValue(),r}this.editor.editing.view.on("render",()=>{for(const e of r){if(e.element.isConnected)return;e.destroy(),r.delete(e)}},{priority:"lowest"}),e.data.registerRawContentMatcher({name:"div",classes:"raw-html-embed"}),e.conversion.for("upcast").elementToElement({view:{name:"div",classes:"raw-html-embed"},model:(e,{writer:t})=>t.createElement("rawHtml",{value:e.getCustomProperty("$rawContent")})}),e.conversion.for("dataDowncast").elementToElement({model:"rawHtml",view:(e,{writer:t})=>t.createRawElement("div",{class:"raw-html-embed"},(function(t){t.innerHTML=e.getAttribute("value")||""}))}),e.conversion.for("editingDowncast").elementToElement({triggerBy:{attributes:["value"]},model:"rawHtml",view:(r,{writer:o})=>{let l,d,s;const m=o.createContainerElement("div",{class:"raw-html-embed","data-html-embed-label":t("HTML snippet"),dir:e.locale.uiLanguageDirection}),u=o.createRawElement("div",{class:"raw-html-embed__content-wrapper"},(function(t){l=t,c({domElement:t,editor:e,state:d,props:s}),l.addEventListener("mousedown",()=>{if(d.isEditable){const t=e.model;t.document.selection.getSelectedElement()!==r&&t.change(e=>e.setSelection(r,"on"))}},!0)})),b={makeEditable(){d=Object.assign({},d,{isEditable:!0}),c({domElement:l,editor:e,state:d,props:s}),n.change(e=>{e.setAttribute("data-cke-ignore-events","true",u)}),l.querySelector("textarea").focus()},save(t){t!==d.getRawHtmlValue()?(e.execute("htmlEmbed",t),e.editing.view.focus()):this.cancel()},cancel(){d=Object.assign({},d,{isEditable:!1}),c({domElement:l,editor:e,state:d,props:s}),e.editing.view.focus(),n.change(e=>{e.removeAttribute("data-cke-ignore-events",u)})}};return d={showPreviews:a.showPreviews,isEditable:!1,getRawHtmlValue:()=>r.getAttribute("value")||""},s={sanitizeHtml:a.sanitizeHtml,textareaPlaceholder:t("Paste raw HTML here..."),onEditClick(){b.makeEditable()},onSaveClick(e){b.save(e)},onCancelClick(){b.cancel()}},o.insert(o.createPositionAt(m,0),u),o.setCustomProperty("rawHtmlApi",b,m),o.setCustomProperty("rawHtml",!0,m),Object(i.toWidget)(m,o,{widgetLabel:t("HTML snippet"),hasSelectionHandle:!0})}})}}function s(e,t,n){const i=e.locale.t,o=new a.ButtonView(e.locale),c=e.commands.get("htmlEmbed");return o.set({class:`raw-html-embed__${t}-button`,icon:r.icons.pencil,tooltip:!0,tooltipPosition:"rtl"===e.locale.uiLanguageDirection?"e":"w"}),o.render(),"edit"===t?(o.set({icon:r.icons.pencil,label:i("Edit source")}),o.bind("isEnabled").to(c)):"save"===t?(o.set({icon:r.icons.check,label:i("Save changes")}),o.bind("isEnabled").to(c)):o.set({icon:r.icons.cancel,label:i("Cancel")}),o.on("execute",n),o}class m extends r.Plugin{static get pluginName(){return"HtmlEmbedUI"}init(){const e=this.editor,t=e.t;e.ui.componentFactory.add("htmlEmbed",n=>{const r=e.commands.get("htmlEmbed"),i=new a.ButtonView(n);return i.set({label:t("Insert HTML"),icon:'<svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M17 0a2 2 0 0 1 2 2v7a1 1 0 0 1 1 1v5a1 1 0 0 1-.883.993l-.118.006L19 17a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2l-.001-1.001-.116-.006A1 1 0 0 1 0 15v-5a1 1 0 0 1 .999-1L1 2a2 2 0 0 1 2-2h14zm.499 15.999h-15L2.5 17a.5.5 0 0 0 .5.5h14a.5.5 0 0 0 .5-.5l-.001-1.001zm-3.478-6.013-.014.014H14v.007l-1.525 1.525-1.46-1.46-.015.013V10h-1v5h1v-3.53l1.428 1.43.048.043.131-.129L14 11.421V15h1v-5h-.965l-.014-.014zM2 10H1v5h1v-2h2v2h1v-5H4v2H2v-2zm7 0H6v1h1v4h1v-4h1v-1zm8 0h-1v5h3v-1h-2v-4zm0-8.5H3a.5.5 0 0 0-.5.5l-.001 6.999h15L17.5 2a.5.5 0 0 0-.5-.5zM10 7v1H4V7h6zm3-2v1H4V5h9zm-3-2v1H4V3h6z"/></svg>',tooltip:!0}),i.bind("isEnabled").to(r,"isEnabled"),this.listenTo(i,"execute",()=>{e.execute("htmlEmbed"),e.editing.view.focus();e.editing.view.document.selection.getSelectedElement().getCustomProperty("rawHtmlApi").makeEditable()}),i})}}class u extends r.Plugin{static get requires(){return[d,m,i.Widget]}static get pluginName(){return"HtmlEmbed"}}}]);
@@ -1 +1 @@
1
- !function(t){const n=t.pl=t.pl||{};n.dictionary=Object.assign(n.dictionary||{},{"Edit source":"Edytuj źródło","Empty snippet content":"","HTML snippet":"","Insert HTML":"Wstaw kod HTML","No preview available":"Podgląd nie jest dostępny","Paste raw HTML here...":"Wstaw tutaj czysty kod HTML...","Save changes":"Zapisz zmiany"})}(window.CKEDITOR_TRANSLATIONS||(window.CKEDITOR_TRANSLATIONS={}));
1
+ !function(t){const n=t.pl=t.pl||{};n.dictionary=Object.assign(n.dictionary||{},{"Edit source":"Edytuj źródło","Empty snippet content":"Brak kodu","HTML snippet":"Kod HTML","Insert HTML":"Wstaw kod HTML","No preview available":"Podgląd nie jest dostępny","Paste raw HTML here...":"Wstaw tutaj czysty kod HTML...","Save changes":"Zapisz zmiany"})}(window.CKEDITOR_TRANSLATIONS||(window.CKEDITOR_TRANSLATIONS={}));
@@ -22,7 +22,7 @@ msgstr "Wstaw kod HTML"
22
22
 
23
23
  msgctxt "The HTML snippet."
24
24
  msgid "HTML snippet"
25
- msgstr ""
25
+ msgstr "Kod HTML"
26
26
 
27
27
  msgctxt "A placeholder that will be displayed in the raw HTML textarea field."
28
28
  msgid "Paste raw HTML here..."
@@ -42,4 +42,4 @@ msgstr "Podgląd nie jest dostępny"
42
42
 
43
43
  msgctxt "An information displayed in the HTML embed preview if the HTML snippet has no content."
44
44
  msgid "Empty snippet content"
45
- msgstr ""
45
+ msgstr "Brak kodu"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ckeditor/ckeditor5-html-embed",
3
- "version": "30.0.0",
3
+ "version": "31.0.0",
4
4
  "description": "HTML embed feature for CKEditor 5.",
5
5
  "keywords": [
6
6
  "ckeditor",
@@ -12,21 +12,21 @@
12
12
  ],
13
13
  "main": "src/index.js",
14
14
  "dependencies": {
15
- "ckeditor5": "^30.0.0"
15
+ "ckeditor5": "^31.0.0"
16
16
  },
17
17
  "devDependencies": {
18
- "@ckeditor/ckeditor5-basic-styles": "^30.0.0",
19
- "@ckeditor/ckeditor5-core": "^30.0.0",
18
+ "@ckeditor/ckeditor5-basic-styles": "^31.0.0",
19
+ "@ckeditor/ckeditor5-core": "^31.0.0",
20
20
  "@ckeditor/ckeditor5-dev-utils": "^25.4.0",
21
- "@ckeditor/ckeditor5-clipboard": "^30.0.0",
22
- "@ckeditor/ckeditor5-editor-classic": "^30.0.0",
23
- "@ckeditor/ckeditor5-engine": "^30.0.0",
24
- "@ckeditor/ckeditor5-media-embed": "^30.0.0",
25
- "@ckeditor/ckeditor5-paragraph": "^30.0.0",
26
- "@ckeditor/ckeditor5-table": "^30.0.0",
27
- "@ckeditor/ckeditor5-ui": "^30.0.0",
28
- "@ckeditor/ckeditor5-theme-lark": "^30.0.0",
29
- "@ckeditor/ckeditor5-widget": "^30.0.0",
21
+ "@ckeditor/ckeditor5-clipboard": "^31.0.0",
22
+ "@ckeditor/ckeditor5-editor-classic": "^31.0.0",
23
+ "@ckeditor/ckeditor5-engine": "^31.0.0",
24
+ "@ckeditor/ckeditor5-media-embed": "^31.0.0",
25
+ "@ckeditor/ckeditor5-paragraph": "^31.0.0",
26
+ "@ckeditor/ckeditor5-table": "^31.0.0",
27
+ "@ckeditor/ckeditor5-ui": "^31.0.0",
28
+ "@ckeditor/ckeditor5-theme-lark": "^31.0.0",
29
+ "@ckeditor/ckeditor5-widget": "^31.0.0",
30
30
  "lodash-es": "^4.17.15",
31
31
  "sanitize-html": "^2.1.0",
32
32
  "webpack": "^4.43.0",
@@ -0,0 +1,117 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved.
3
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
+ */
5
+
6
+ /**
7
+ * @module html-embed/htmlembedcommand
8
+ */
9
+
10
+ import { Command } from 'ckeditor5/src/core';
11
+ import { findOptimalInsertionRange } from 'ckeditor5/src/widget';
12
+
13
+ /**
14
+ * The insert HTML embed element command.
15
+ *
16
+ * The command is registered by {@link module:html-embed/htmlembedediting~HtmlEmbedEditing} as `'htmlEmbed'`.
17
+ *
18
+ * To insert an empty HTML embed element at the current selection, execute the command:
19
+ *
20
+ * editor.execute( 'htmlEmbed' );
21
+ *
22
+ * You can specify the initial content of a new HTML embed in the argument:
23
+ *
24
+ * editor.execute( 'htmlEmbed', '<b>Initial content.</b>' );
25
+ *
26
+ * To update the content of the HTML embed, select it in the model and pass the content in the argument:
27
+ *
28
+ * editor.execute( 'htmlEmbed', '<b>New content of an existing embed.</b>' );
29
+ *
30
+ * @extends module:core/command~Command
31
+ */
32
+ export default class HtmlEmbedCommand extends Command {
33
+ /**
34
+ * @inheritDoc
35
+ */
36
+ refresh() {
37
+ const model = this.editor.model;
38
+ const schema = model.schema;
39
+ const selection = model.document.selection;
40
+ const selectedRawHtmlElement = getSelectedRawHtmlModelWidget( selection );
41
+
42
+ this.isEnabled = isHtmlEmbedAllowedInParent( selection, schema, model );
43
+ this.value = selectedRawHtmlElement ? selectedRawHtmlElement.getAttribute( 'value' ) || '' : null;
44
+ }
45
+
46
+ /**
47
+ * Executes the command, which either:
48
+ *
49
+ * * creates and inserts a new HTML embed element if none was selected,
50
+ * * updates the content of the HTML embed if one was selected.
51
+ *
52
+ * @fires execute
53
+ * @param {String} [value] When passed, the value (content) will be set on a new embed or a selected one.
54
+ */
55
+ execute( value ) {
56
+ const model = this.editor.model;
57
+ const selection = model.document.selection;
58
+
59
+ model.change( writer => {
60
+ let htmlEmbedElement;
61
+
62
+ // If the command has a non-null value, there must be some HTML embed selected in the model.
63
+ if ( this.value !== null ) {
64
+ htmlEmbedElement = getSelectedRawHtmlModelWidget( selection );
65
+ } else {
66
+ htmlEmbedElement = writer.createElement( 'rawHtml' );
67
+
68
+ model.insertContent( htmlEmbedElement );
69
+ writer.setSelection( htmlEmbedElement, 'on' );
70
+ }
71
+
72
+ writer.setAttribute( 'value', value, htmlEmbedElement );
73
+ } );
74
+ }
75
+ }
76
+
77
+ // Checks if an HTML embed is allowed by the schema in the optimal insertion parent.
78
+ //
79
+ // @param {module:engine/model/selection~Selection|module:engine/model/documentselection~DocumentSelection} selection
80
+ // @param {module:engine/model/schema~Schema} schema
81
+ // @param {module:engine/model/model~Model} model
82
+ // @returns {Boolean}
83
+ function isHtmlEmbedAllowedInParent( selection, schema, model ) {
84
+ const parent = getInsertHtmlEmbedParent( selection, model );
85
+
86
+ return schema.checkChild( parent, 'rawHtml' );
87
+ }
88
+
89
+ // Returns a node that will be used to insert a html embed with `model.insertContent` to check if a html embed element can be placed there.
90
+ //
91
+ // @param {module:engine/model/selection~Selection|module:engine/model/documentselection~DocumentSelection} selection
92
+ // @param {module:engine/model/model~Model} model
93
+ // @returns {module:engine/model/element~Element}
94
+ function getInsertHtmlEmbedParent( selection, model ) {
95
+ const insertionRange = findOptimalInsertionRange( selection, model );
96
+ const parent = insertionRange.start.parent;
97
+
98
+ if ( parent.isEmpty && !parent.is( 'element', '$root' ) ) {
99
+ return parent.parent;
100
+ }
101
+
102
+ return parent;
103
+ }
104
+
105
+ // Returns the selected HTML embed element in the model, if any.
106
+ //
107
+ // @param {module:engine/model/selection~Selection} selection
108
+ // @returns {module:engine/model/element~Element|null}
109
+ function getSelectedRawHtmlModelWidget( selection ) {
110
+ const selectedElement = selection.getSelectedElement();
111
+
112
+ if ( selectedElement && selectedElement.is( 'element', 'rawHtml' ) ) {
113
+ return selectedElement;
114
+ }
115
+
116
+ return null;
117
+ }
@@ -12,8 +12,7 @@ import { ButtonView } from 'ckeditor5/src/ui';
12
12
  import { toWidget } from 'ckeditor5/src/widget';
13
13
  import { logWarning, createElement } from 'ckeditor5/src/utils';
14
14
 
15
- import InsertHtmlEmbedCommand from './inserthtmlembedcommand';
16
- import UpdateHtmlEmbedCommand from './updatehtmlembedcommand';
15
+ import HtmlEmbedCommand from './htmlembedcommand';
17
16
 
18
17
  import '../theme/htmlembed.css';
19
18
 
@@ -55,6 +54,15 @@ export default class HtmlEmbedEditing extends Plugin {
55
54
  };
56
55
  }
57
56
  } );
57
+
58
+ /**
59
+ * Keeps references to {@link module:ui/button/buttonview~ButtonView edit, save, and cancel} button instances created for
60
+ * each widget so they can be destroyed if they are no longer in DOM after the editing view was re-rendered.
61
+ *
62
+ * @private
63
+ * @member {Set.<module:ui/button/buttonview~ButtonView>} #_widgetButtonViewReferences
64
+ */
65
+ this._widgetButtonViewReferences = new Set();
58
66
  }
59
67
 
60
68
  /**
@@ -70,8 +78,7 @@ export default class HtmlEmbedEditing extends Plugin {
70
78
  allowAttributes: [ 'value' ]
71
79
  } );
72
80
 
73
- editor.commands.add( 'updateHtmlEmbed', new UpdateHtmlEmbedCommand( editor ) );
74
- editor.commands.add( 'insertHtmlEmbed', new InsertHtmlEmbedCommand( editor ) );
81
+ editor.commands.add( 'htmlEmbed', new HtmlEmbedCommand( editor ) );
75
82
 
76
83
  this._setupConversion();
77
84
  }
@@ -85,9 +92,23 @@ export default class HtmlEmbedEditing extends Plugin {
85
92
  const editor = this.editor;
86
93
  const t = editor.t;
87
94
  const view = editor.editing.view;
95
+ const widgetButtonViewReferences = this._widgetButtonViewReferences;
88
96
 
89
97
  const htmlEmbedConfig = editor.config.get( 'htmlEmbed' );
90
98
 
99
+ // Destroy UI buttons created for widgets that have been removed from the view document (e.g. in the previous conversion).
100
+ // This prevents unexpected memory leaks from UI views.
101
+ this.editor.editing.view.on( 'render', () => {
102
+ for ( const buttonView of widgetButtonViewReferences ) {
103
+ if ( buttonView.element.isConnected ) {
104
+ return;
105
+ }
106
+
107
+ buttonView.destroy();
108
+ widgetButtonViewReferences.delete( buttonView );
109
+ }
110
+ }, { priority: 'lowest' } );
111
+
91
112
  // Register div.raw-html-embed as a raw content element so all of it's content will be provided
92
113
  // as a view element's custom property while data upcasting.
93
114
  editor.data.registerRawContentMatcher( {
@@ -176,7 +197,7 @@ export default class HtmlEmbedEditing extends Plugin {
176
197
  // If the value didn't change, we just cancel. If it changed,
177
198
  // it's enough to update the model – the entire widget will be reconverted.
178
199
  if ( newValue !== state.getRawHtmlValue() ) {
179
- editor.execute( 'updateHtmlEmbed', newValue );
200
+ editor.execute( 'htmlEmbed', newValue );
180
201
  editor.editing.view.focus();
181
202
  } else {
182
203
  this.cancel();
@@ -275,36 +296,18 @@ export default class HtmlEmbedEditing extends Plugin {
275
296
  const domButtonsWrapper = createElement( domDocument, 'div', {
276
297
  class: 'raw-html-embed__buttons-wrapper'
277
298
  } );
278
- // TODO these should be cached and we should only clone here these cached nodes!
279
- const domEditButton = createDomButton( editor, 'edit' );
280
- const domSaveButton = createDomButton( editor, 'save' );
281
- const domCancelButton = createDomButton( editor, 'cancel' );
282
299
 
283
300
  if ( state.isEditable ) {
284
- const clonedDomSaveButton = domSaveButton.cloneNode( true );
285
- const clonedDomCancelButton = domCancelButton.cloneNode( true );
286
-
287
- clonedDomSaveButton.addEventListener( 'click', evt => {
288
- evt.preventDefault();
289
- props.onSaveClick( );
290
- } );
301
+ const saveButtonView = createUIButton( editor, 'save', props.onSaveClick );
302
+ const cancelButtonView = createUIButton( editor, 'cancel', props.onCancelClick );
291
303
 
292
- clonedDomCancelButton.addEventListener( 'click', evt => {
293
- evt.preventDefault();
294
- props.onCancelClick( );
295
- } );
296
-
297
- domButtonsWrapper.appendChild( clonedDomSaveButton );
298
- domButtonsWrapper.appendChild( clonedDomCancelButton );
304
+ domButtonsWrapper.append( saveButtonView.element, cancelButtonView.element );
305
+ widgetButtonViewReferences.add( saveButtonView ).add( cancelButtonView );
299
306
  } else {
300
- const clonedDomEditButton = domEditButton.cloneNode( true );
301
-
302
- clonedDomEditButton.addEventListener( 'click', evt => {
303
- evt.preventDefault();
304
- props.onEditClick();
305
- } );
307
+ const editButtonView = createUIButton( editor, 'edit', props.onEditClick );
306
308
 
307
- domButtonsWrapper.appendChild( clonedDomEditButton );
309
+ domButtonsWrapper.append( editButtonView.element );
310
+ widgetButtonViewReferences.add( editButtonView );
308
311
  }
309
312
 
310
313
  return domButtonsWrapper;
@@ -355,20 +358,22 @@ export default class HtmlEmbedEditing extends Plugin {
355
358
  }
356
359
  }
357
360
 
358
- // Returns a toggle mode button DOM element that can be cloned and used in conversion.
361
+ // Returns a UI button view that can be used in conversion.
359
362
  //
360
363
  // @param {module:utils/locale~Locale} locale Editor locale.
361
364
  // @param {'edit'|'save'|'cancel'} type Type of button to create.
362
- // @returns {HTMLElement}
363
- function createDomButton( editor, type ) {
365
+ // @param {Function} onClick The callback executed on button click.
366
+ // @returns {module:ui/button/buttonview~ButtonView}
367
+ function createUIButton( editor, type, onClick ) {
364
368
  const t = editor.locale.t;
365
369
  const buttonView = new ButtonView( editor.locale );
366
- const command = editor.commands.get( 'updateHtmlEmbed' );
370
+ const command = editor.commands.get( 'htmlEmbed' );
367
371
 
368
372
  buttonView.set( {
369
- tooltipPosition: editor.locale.uiLanguageDirection === 'rtl' ? 'e' : 'w',
373
+ class: `raw-html-embed__${ type }-button`,
370
374
  icon: icons.pencil,
371
- tooltip: true
375
+ tooltip: true,
376
+ tooltipPosition: editor.locale.uiLanguageDirection === 'rtl' ? 'e' : 'w'
372
377
  } );
373
378
 
374
379
  buttonView.render();
@@ -376,25 +381,25 @@ function createDomButton( editor, type ) {
376
381
  if ( type === 'edit' ) {
377
382
  buttonView.set( {
378
383
  icon: icons.pencil,
379
- label: t( 'Edit source' ),
380
- class: 'raw-html-embed__edit-button'
384
+ label: t( 'Edit source' )
381
385
  } );
386
+
387
+ buttonView.bind( 'isEnabled' ).to( command );
382
388
  } else if ( type === 'save' ) {
383
389
  buttonView.set( {
384
390
  icon: icons.check,
385
- label: t( 'Save changes' ),
386
- class: 'raw-html-embed__save-button'
391
+ label: t( 'Save changes' )
387
392
  } );
388
- buttonView.bind( 'isEnabled' ).to( command, 'isEnabled' );
393
+
394
+ buttonView.bind( 'isEnabled' ).to( command );
389
395
  } else {
390
396
  buttonView.set( {
391
397
  icon: icons.cancel,
392
- label: t( 'Cancel' ),
393
- class: 'raw-html-embed__cancel-button'
398
+ label: t( 'Cancel' )
394
399
  } );
395
400
  }
396
401
 
397
- buttonView.destroy();
402
+ buttonView.on( 'execute', onClick );
398
403
 
399
- return buttonView.element.cloneNode( true );
404
+ return buttonView;
400
405
  }
@@ -34,7 +34,7 @@ export default class HtmlEmbedUI extends Plugin {
34
34
 
35
35
  // Add the `htmlEmbed` button to feature components.
36
36
  editor.ui.componentFactory.add( 'htmlEmbed', locale => {
37
- const command = editor.commands.get( 'insertHtmlEmbed' );
37
+ const command = editor.commands.get( 'htmlEmbed' );
38
38
  const view = new ButtonView( locale );
39
39
 
40
40
  view.set( {
@@ -47,7 +47,7 @@ export default class HtmlEmbedUI extends Plugin {
47
47
 
48
48
  // Execute the command.
49
49
  this.listenTo( view, 'execute', () => {
50
- editor.execute( 'insertHtmlEmbed' );
50
+ editor.execute( 'htmlEmbed' );
51
51
  editor.editing.view.focus();
52
52
 
53
53
  const widgetWrapper = editor.editing.view.document.selection.getSelectedElement();
@@ -1,79 +0,0 @@
1
- /**
2
- * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved.
3
- * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
- */
5
-
6
- /**
7
- * @module html-embed/inserthtmlembedcommand
8
- */
9
-
10
- import { Command } from 'ckeditor5/src/core';
11
- import { findOptimalInsertionRange } from 'ckeditor5/src/widget';
12
-
13
- /**
14
- * The insert HTML embed element command.
15
- *
16
- * The command is registered by {@link module:html-embed/htmlembedediting~HtmlEmbedEditing} as `'insertHtmlEmbed'`.
17
- *
18
- * To insert the HTML embed element at the current selection, execute the command:
19
- *
20
- * editor.execute( 'insertHtmlEmbed' );
21
- *
22
- * @extends module:core/command~Command
23
- */
24
- export default class InsertHtmlEmbedCommand extends Command {
25
- /**
26
- * @inheritDoc
27
- */
28
- refresh() {
29
- const model = this.editor.model;
30
- const schema = model.schema;
31
- const selection = model.document.selection;
32
-
33
- this.isEnabled = isHtmlEmbedAllowedInParent( selection, schema, model );
34
- }
35
-
36
- /**
37
- * Executes the command, which creates and inserts a new HTML embed element.
38
- *
39
- * @fires execute
40
- */
41
- execute() {
42
- const model = this.editor.model;
43
-
44
- model.change( writer => {
45
- const rawHtmlElement = writer.createElement( 'rawHtml' );
46
-
47
- model.insertContent( rawHtmlElement );
48
- writer.setSelection( rawHtmlElement, 'on' );
49
- } );
50
- }
51
- }
52
-
53
- // Checks if an HTML embed is allowed by the schema in the optimal insertion parent.
54
- //
55
- // @param {module:engine/model/selection~Selection|module:engine/model/documentselection~DocumentSelection} selection
56
- // @param {module:engine/model/schema~Schema} schema
57
- // @param {module:engine/model/model~Model} model Model instance.
58
- // @returns {Boolean}
59
- function isHtmlEmbedAllowedInParent( selection, schema, model ) {
60
- const parent = getInsertHtmlEmbedParent( selection, model );
61
-
62
- return schema.checkChild( parent, 'rawHtml' );
63
- }
64
-
65
- // Returns a node that will be used to insert a html embed with `model.insertContent` to check if a html embed element can be placed there.
66
- //
67
- // @param {module:engine/model/selection~Selection|module:engine/model/documentselection~DocumentSelection} selection
68
- // @param {module:engine/model/model~Model} model Model instance.
69
- // @returns {module:engine/model/element~Element}
70
- function getInsertHtmlEmbedParent( selection, model ) {
71
- const insertionRange = findOptimalInsertionRange( selection, model );
72
- const parent = insertionRange.start.parent;
73
-
74
- if ( parent.isEmpty && !parent.is( 'element', '$root' ) ) {
75
- return parent.parent;
76
- }
77
-
78
- return parent;
79
- }
@@ -1,64 +0,0 @@
1
- /**
2
- * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved.
3
- * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
- */
5
-
6
- /**
7
- * @module html-embed/updatehtmlembedcommand
8
- */
9
-
10
- import { Command } from 'ckeditor5/src/core';
11
-
12
- /**
13
- * The update HTML embed value command.
14
- *
15
- * The command is registered by {@link module:html-embed/htmlembedediting~HtmlEmbedEditing} as `'updateHtmlEmbed'`.
16
- *
17
- * To update the value of the HTML embed element at the current selection, execute the command:
18
- *
19
- * editor.execute( 'updateHtmlEmbed', '<b>HTML.</b>' );
20
- *
21
- * @extends module:core/command~Command
22
- */
23
- export default class UpdateHtmlEmbedCommand extends Command {
24
- /**
25
- * @inheritDoc
26
- */
27
- refresh() {
28
- const model = this.editor.model;
29
- const selection = model.document.selection;
30
- const rawHtmlElement = getSelectedRawHtmlModelWidget( selection );
31
-
32
- this.isEnabled = !!rawHtmlElement;
33
- }
34
-
35
- /**
36
- * Executes the command, which updates the `value` attribute of the embedded HTML element:
37
- *
38
- * @fires execute
39
- * @param {String} value HTML as a string.
40
- */
41
- execute( value ) {
42
- const model = this.editor.model;
43
- const selection = model.document.selection;
44
- const selectedRawHtmlElement = getSelectedRawHtmlModelWidget( selection );
45
-
46
- model.change( writer => {
47
- writer.setAttribute( 'value', value, selectedRawHtmlElement );
48
- } );
49
- }
50
- }
51
-
52
- // Returns the selected HTML embed element in the model, if any.
53
- //
54
- // @param {module:engine/model/selection~Selection} selection
55
- // @returns {module:engine/model/element~Element|null}
56
- function getSelectedRawHtmlModelWidget( selection ) {
57
- const selectedElement = selection.getSelectedElement();
58
-
59
- if ( selectedElement && selectedElement.is( 'element', 'rawHtml' ) ) {
60
- return selectedElement;
61
- }
62
-
63
- return null;
64
- }