@ckeditor/ckeditor5-source-editing 36.0.0 → 37.0.0-alpha.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,4 +2,4 @@
2
2
  /*!
3
3
  * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
4
4
  * For licensing, see LICENSE.md.
5
- */(()=>{var e={821:(e,t,i)=>{"use strict";i.d(t,{Z:()=>r});var n=i(609),o=i.n(n)()((function(e){return e[1]}));o.push([e.id,'.ck-source-editing-area{overflow:hidden;position:relative}.ck-source-editing-area textarea,.ck-source-editing-area:after{border:1px solid transparent;font-family:monospace;font-size:var(--ck-font-size-normal);line-height:var(--ck-line-height-base);margin:0;padding:var(--ck-spacing-large);white-space:pre-wrap}.ck-source-editing-area:after{content:attr(data-value) " ";display:block;visibility:hidden}.ck-source-editing-area textarea{border-color:var(--ck-color-base-border);border-radius:0;box-sizing:border-box;height:100%;outline:none;overflow:hidden;position:absolute;resize:none;width:100%}.ck-rounded-corners .ck-source-editing-area textarea,.ck-source-editing-area textarea.ck-rounded-corners{border-radius:var(--ck-border-radius);border-top-left-radius:0;border-top-right-radius:0}.ck-source-editing-area textarea:not([readonly]):focus{border:var(--ck-focus-ring);box-shadow:var(--ck-inner-shadow),0 0;outline:none}',""]);const r=o},609:e=>{"use strict";e.exports=function(e){var t=[];return t.toString=function(){return this.map((function(t){var i=e(t);return t[2]?"@media ".concat(t[2]," {").concat(i,"}"):i})).join("")},t.i=function(e,i,n){"string"==typeof e&&(e=[[null,e,""]]);var o={};if(n)for(var r=0;r<this.length;r++){var a=this[r][0];null!=a&&(o[a]=!0)}for(var s=0;s<e.length;s++){var d=[].concat(e[s]);n&&o[d[0]]||(i&&(d[2]?d[2]="".concat(i," and ").concat(d[2]):d[2]=i),t.push(d))}},t}},62:(e,t,i)=>{"use strict";var n,o=function(){return void 0===n&&(n=Boolean(window&&document&&document.all&&!window.atob)),n},r=function(){var e={};return function(t){if(void 0===e[t]){var i=document.querySelector(t);if(window.HTMLIFrameElement&&i instanceof window.HTMLIFrameElement)try{i=i.contentDocument.head}catch(e){i=null}e[t]=i}return e[t]}}(),a=[];function s(e){for(var t=-1,i=0;i<a.length;i++)if(a[i].identifier===e){t=i;break}return t}function d(e,t){for(var i={},n=[],o=0;o<e.length;o++){var r=e[o],d=t.base?r[0]+t.base:r[0],c=i[d]||0,l="".concat(d," ").concat(c);i[d]=c+1;var u=s(l),h={css:r[1],media:r[2],sourceMap:r[3]};-1!==u?(a[u].references++,a[u].updater(h)):a.push({identifier:l,updater:p(h,t),references:1}),n.push(l)}return n}function c(e){var t=document.createElement("style"),n=e.attributes||{};if(void 0===n.nonce){var o=i.nc;o&&(n.nonce=o)}if(Object.keys(n).forEach((function(e){t.setAttribute(e,n[e])})),"function"==typeof e.insert)e.insert(t);else{var a=r(e.insert||"head");if(!a)throw new Error("Couldn't find a style target. This probably means that the value for the 'insert' parameter is invalid.");a.appendChild(t)}return t}var l,u=(l=[],function(e,t){return l[e]=t,l.filter(Boolean).join("\n")});function h(e,t,i,n){var o=i?"":n.media?"@media ".concat(n.media," {").concat(n.css,"}"):n.css;if(e.styleSheet)e.styleSheet.cssText=u(t,o);else{var r=document.createTextNode(o),a=e.childNodes;a[t]&&e.removeChild(a[t]),a.length?e.insertBefore(r,a[t]):e.appendChild(r)}}function m(e,t,i){var n=i.css,o=i.media,r=i.sourceMap;if(o?e.setAttribute("media",o):e.removeAttribute("media"),r&&"undefined"!=typeof btoa&&(n+="\n/*# sourceMappingURL=data:application/json;base64,".concat(btoa(unescape(encodeURIComponent(JSON.stringify(r))))," */")),e.styleSheet)e.styleSheet.cssText=n;else{for(;e.firstChild;)e.removeChild(e.firstChild);e.appendChild(document.createTextNode(n))}}var f=null,g=0;function p(e,t){var i,n,o;if(t.singleton){var r=g++;i=f||(f=c(t)),n=h.bind(null,i,r,!1),o=h.bind(null,i,r,!0)}else i=c(t),n=m.bind(null,i,t),o=function(){!function(e){if(null===e.parentNode)return!1;e.parentNode.removeChild(e)}(i)};return n(e),function(t){if(t){if(t.css===e.css&&t.media===e.media&&t.sourceMap===e.sourceMap)return;n(e=t)}else o()}}e.exports=function(e,t){(t=t||{}).singleton||"boolean"==typeof t.singleton||(t.singleton=o());var i=d(e=e||[],t);return function(e){if(e=e||[],"[object Array]"===Object.prototype.toString.call(e)){for(var n=0;n<i.length;n++){var o=s(i[n]);a[o].references--}for(var r=d(e,t),c=0;c<i.length;c++){var l=s(i[c]);0===a[l].references&&(a[l].updater(),a.splice(l,1))}i=r}}}},704:(e,t,i)=>{e.exports=i(79)("./src/core.js")},273:(e,t,i)=>{e.exports=i(79)("./src/ui.js")},209:(e,t,i)=>{e.exports=i(79)("./src/utils.js")},79:e=>{"use strict";e.exports=CKEditor5.dll}},t={};function i(n){var o=t[n];if(void 0!==o)return o.exports;var r=t[n]={id:n,exports:{}};return e[n](r,r.exports,i),r.exports}i.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return i.d(t,{a:t}),t},i.d=(e,t)=>{for(var n in t)i.o(t,n)&&!i.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},i.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),i.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.nc=void 0;var n={};(()=>{"use strict";i.r(n),i.d(n,{SourceEditing:()=>h});var e=i(704),t=i(273),o=i(209);function r(e){const t=[{name:"address",isVoid:!1},{name:"article",isVoid:!1},{name:"aside",isVoid:!1},{name:"blockquote",isVoid:!1},{name:"br",isVoid:!0},{name:"details",isVoid:!1},{name:"dialog",isVoid:!1},{name:"dd",isVoid:!1},{name:"div",isVoid:!1},{name:"dl",isVoid:!1},{name:"dt",isVoid:!1},{name:"fieldset",isVoid:!1},{name:"figcaption",isVoid:!1},{name:"figure",isVoid:!1},{name:"footer",isVoid:!1},{name:"form",isVoid:!1},{name:"h1",isVoid:!1},{name:"h2",isVoid:!1},{name:"h3",isVoid:!1},{name:"h4",isVoid:!1},{name:"h5",isVoid:!1},{name:"h6",isVoid:!1},{name:"header",isVoid:!1},{name:"hgroup",isVoid:!1},{name:"hr",isVoid:!0},{name:"input",isVoid:!0},{name:"li",isVoid:!1},{name:"main",isVoid:!1},{name:"nav",isVoid:!1},{name:"ol",isVoid:!1},{name:"p",isVoid:!1},{name:"section",isVoid:!1},{name:"table",isVoid:!1},{name:"tbody",isVoid:!1},{name:"td",isVoid:!1},{name:"textarea",isVoid:!1},{name:"th",isVoid:!1},{name:"thead",isVoid:!1},{name:"tr",isVoid:!1},{name:"ul",isVoid:!1}],i=t.map((e=>e.name)).join("|"),n=e.replace(new RegExp(`</?(${i})( .*?)?>`,"g"),"\n$&\n").split("\n");let o=0;return n.filter((e=>e.length)).map((e=>function(e,t){return t.some((t=>!t.isVoid&&!!new RegExp(`<${t.name}( .*?)?>`).test(e)))}(e,t)?a(e,o++):function(e,t){return t.some((t=>new RegExp(`</${t.name}>`).test(e)))}(e,t)?a(e,--o):a(e,o))).join("\n")}function a(e,t,i=" "){return`${i.repeat(Math.max(0,t))}${e}`}var s=i(62),d=i.n(s),c=i(821),l={injectType:"singletonStyleTag",attributes:{"data-cke":!0},insert:"head",singleton:!0};d()(c.Z,l);c.Z.locals;const u="SourceEditingMode";class h extends e.Plugin{static get pluginName(){return"SourceEditing"}static get requires(){return[e.PendingActions]}constructor(e){super(e),this.set("isSourceEditingMode",!1),this._elementReplacer=new o.ElementReplacer,this._replacedRoots=new Map,this._dataFromRoots=new Map}init(){const i=this.editor,n=i.t;i.ui.componentFactory.add("sourceEditing",(o=>{const r=new t.ButtonView(o);return r.set({label:n("Source"),icon:'<svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="m12.5 0 5 4.5v15.003h-16V0h11zM3 1.5v3.25l-1.497 1-.003 8 1.5 1v3.254L7.685 18l-.001 1.504H17.5V8.002L16 9.428l-.004-4.22-4.222-3.692L3 1.5z"/><path d="M4.06 6.64a.75.75 0 0 1 .958 1.15l-.085.07L2.29 9.75l2.646 1.89c.302.216.4.62.232.951l-.058.095a.75.75 0 0 1-.951.232l-.095-.058-3.5-2.5V9.14l3.496-2.5zm4.194 6.22a.75.75 0 0 1-.958-1.149l.085-.07 2.643-1.89-2.646-1.89a.75.75 0 0 1-.232-.952l.058-.095a.75.75 0 0 1 .95-.232l.096.058 3.5 2.5v1.22l-3.496 2.5zm7.644-.836 2.122 2.122-5.825 5.809-2.125-.005.003-2.116zm2.539-1.847 1.414 1.414a.5.5 0 0 1 0 .707l-1.06 1.06-2.122-2.12 1.061-1.061a.5.5 0 0 1 .707 0z"/></svg>',tooltip:!0,withText:!0,class:"ck-source-editing-button"}),r.bind("isOn").to(this,"isSourceEditingMode"),r.bind("isEnabled").to(this,"isEnabled",i,"isReadOnly",i.plugins.get(e.PendingActions),"hasAny",((e,t,i)=>!!e&&(!t&&!i))),this.listenTo(r,"execute",(()=>{this.isSourceEditingMode=!this.isSourceEditingMode})),r})),this._isAllowedToHandleSourceEditingMode()&&(this.on("change:isSourceEditingMode",((e,t,i)=>{i?(this._showSourceEditing(),this._disableCommands()):(this._hideSourceEditing(),this._enableCommands())})),this.on("change:isEnabled",((e,t,i)=>this._handleReadOnlyMode(!i))),this.listenTo(i,"change:isReadOnly",((e,t,i)=>this._handleReadOnlyMode(i)))),i.data.on("get",(()=>{this.isSourceEditingMode&&this._updateEditorData()}),{priority:"high"})}afterInit(){const e=this.editor;["RealTimeCollaborativeEditing","CommentsEditing","TrackChangesEditing","RevisionHistory"].some((t=>e.plugins.has(t)))&&console.warn("You initialized the editor with the source editing feature and at least one of the collaboration features. Please be advised that the source editing feature may not work, and be careful when editing document source that contains markers created by the collaboration features."),e.plugins.has("RestrictedEditingModeEditing")&&console.warn("You initialized the editor with the source editing feature and restricted editing feature. Please be advised that the source editing feature may not work, and be careful when editing document source that contains markers created by the restricted editing feature.")}_showSourceEditing(){const e=this.editor,t=e.editing.view,i=e.model;i.change((e=>{e.setSelection(null),e.removeSelectionAttribute(i.document.selection.getAttributeKeys())}));for(const[i,n]of t.domRoots){const r=m(e.data.get({rootName:i})),a=(0,o.createElement)(n.ownerDocument,"textarea",{rows:"1","aria-label":"Source code editing area"}),s=(0,o.createElement)(n.ownerDocument,"div",{class:"ck-source-editing-area","data-value":r},[a]);a.value=r,a.setSelectionRange(0,0),a.addEventListener("input",(()=>{s.dataset.value=a.value})),t.change((e=>{const n=t.document.getRoot(i);e.addClass("ck-hidden",n)})),e.ui.setEditableElement("sourceEditing:"+i,a),this._replacedRoots.set(i,s),this._elementReplacer.replace(n,s),this._dataFromRoots.set(i,r)}this._focusSourceEditing()}_hideSourceEditing(){const e=this.editor.editing.view;this._updateEditorData(),e.change((t=>{for(const[i]of this._replacedRoots)t.removeClass("ck-hidden",e.document.getRoot(i))})),this._elementReplacer.restore(),this._replacedRoots.clear(),this._dataFromRoots.clear(),e.focus()}_updateEditorData(){const e=this.editor,t={};for(const[e,i]of this._replacedRoots){const n=this._dataFromRoots.get(e),o=i.dataset.value;n!==o&&(t[e]=o)}Object.keys(t).length&&e.data.set(t,{batchType:{isUndoable:!0}})}_focusSourceEditing(){const e=this.editor,[t]=this._replacedRoots.values(),i=t.querySelector("textarea");e.editing.view.document.isFocused=!1,i.focus()}_disableCommands(){const e=this.editor;for(const t of e.commands.commands())t.forceDisabled(u)}_enableCommands(){const e=this.editor;for(const t of e.commands.commands())t.clearForceDisabled(u)}_handleReadOnlyMode(e){if(this.isSourceEditingMode)for(const[,t]of this._replacedRoots)t.querySelector("textarea").readOnly=e}_isAllowedToHandleSourceEditingMode(){const e=this.editor.ui.view.editable;return e&&!e._hasExternalElement}}function m(e){return function(e){return e.startsWith("<")}(e)?r(e):e}})(),(window.CKEditor5=window.CKEditor5||{}).sourceEditing=n})();
5
+ */(()=>{var e={821:(e,t,i)=>{"use strict";i.d(t,{Z:()=>r});var n=i(609),o=i.n(n)()((function(e){return e[1]}));o.push([e.id,'.ck-source-editing-area{overflow:hidden;position:relative}.ck-source-editing-area textarea,.ck-source-editing-area:after{border:1px solid transparent;font-family:monospace;font-size:var(--ck-font-size-normal);line-height:var(--ck-line-height-base);margin:0;padding:var(--ck-spacing-large);white-space:pre-wrap}.ck-source-editing-area:after{content:attr(data-value) " ";display:block;visibility:hidden}.ck-source-editing-area textarea{border-color:var(--ck-color-base-border);border-radius:0;box-sizing:border-box;height:100%;outline:none;overflow:hidden;position:absolute;resize:none;width:100%}.ck-rounded-corners .ck-source-editing-area textarea,.ck-source-editing-area textarea.ck-rounded-corners{border-radius:var(--ck-border-radius);border-top-left-radius:0;border-top-right-radius:0}.ck-source-editing-area textarea:not([readonly]):focus{border:var(--ck-focus-ring);box-shadow:var(--ck-inner-shadow),0 0;outline:none}',""]);const r=o},609:e=>{"use strict";e.exports=function(e){var t=[];return t.toString=function(){return this.map((function(t){var i=e(t);return t[2]?"@media ".concat(t[2]," {").concat(i,"}"):i})).join("")},t.i=function(e,i,n){"string"==typeof e&&(e=[[null,e,""]]);var o={};if(n)for(var r=0;r<this.length;r++){var a=this[r][0];null!=a&&(o[a]=!0)}for(var s=0;s<e.length;s++){var d=[].concat(e[s]);n&&o[d[0]]||(i&&(d[2]?d[2]="".concat(i," and ").concat(d[2]):d[2]=i),t.push(d))}},t}},62:(e,t,i)=>{"use strict";var n,o=function(){return void 0===n&&(n=Boolean(window&&document&&document.all&&!window.atob)),n},r=function(){var e={};return function(t){if(void 0===e[t]){var i=document.querySelector(t);if(window.HTMLIFrameElement&&i instanceof window.HTMLIFrameElement)try{i=i.contentDocument.head}catch(e){i=null}e[t]=i}return e[t]}}(),a=[];function s(e){for(var t=-1,i=0;i<a.length;i++)if(a[i].identifier===e){t=i;break}return t}function d(e,t){for(var i={},n=[],o=0;o<e.length;o++){var r=e[o],d=t.base?r[0]+t.base:r[0],c=i[d]||0,l="".concat(d," ").concat(c);i[d]=c+1;var u=s(l),h={css:r[1],media:r[2],sourceMap:r[3]};-1!==u?(a[u].references++,a[u].updater(h)):a.push({identifier:l,updater:p(h,t),references:1}),n.push(l)}return n}function c(e){var t=document.createElement("style"),n=e.attributes||{};if(void 0===n.nonce){var o=i.nc;o&&(n.nonce=o)}if(Object.keys(n).forEach((function(e){t.setAttribute(e,n[e])})),"function"==typeof e.insert)e.insert(t);else{var a=r(e.insert||"head");if(!a)throw new Error("Couldn't find a style target. This probably means that the value for the 'insert' parameter is invalid.");a.appendChild(t)}return t}var l,u=(l=[],function(e,t){return l[e]=t,l.filter(Boolean).join("\n")});function h(e,t,i,n){var o=i?"":n.media?"@media ".concat(n.media," {").concat(n.css,"}"):n.css;if(e.styleSheet)e.styleSheet.cssText=u(t,o);else{var r=document.createTextNode(o),a=e.childNodes;a[t]&&e.removeChild(a[t]),a.length?e.insertBefore(r,a[t]):e.appendChild(r)}}function m(e,t,i){var n=i.css,o=i.media,r=i.sourceMap;if(o?e.setAttribute("media",o):e.removeAttribute("media"),r&&"undefined"!=typeof btoa&&(n+="\n/*# sourceMappingURL=data:application/json;base64,".concat(btoa(unescape(encodeURIComponent(JSON.stringify(r))))," */")),e.styleSheet)e.styleSheet.cssText=n;else{for(;e.firstChild;)e.removeChild(e.firstChild);e.appendChild(document.createTextNode(n))}}var f=null,g=0;function p(e,t){var i,n,o;if(t.singleton){var r=g++;i=f||(f=c(t)),n=h.bind(null,i,r,!1),o=h.bind(null,i,r,!0)}else i=c(t),n=m.bind(null,i,t),o=function(){!function(e){if(null===e.parentNode)return!1;e.parentNode.removeChild(e)}(i)};return n(e),function(t){if(t){if(t.css===e.css&&t.media===e.media&&t.sourceMap===e.sourceMap)return;n(e=t)}else o()}}e.exports=function(e,t){(t=t||{}).singleton||"boolean"==typeof t.singleton||(t.singleton=o());var i=d(e=e||[],t);return function(e){if(e=e||[],"[object Array]"===Object.prototype.toString.call(e)){for(var n=0;n<i.length;n++){var o=s(i[n]);a[o].references--}for(var r=d(e,t),c=0;c<i.length;c++){var l=s(i[c]);0===a[l].references&&(a[l].updater(),a.splice(l,1))}i=r}}}},704:(e,t,i)=>{e.exports=i(79)("./src/core.js")},273:(e,t,i)=>{e.exports=i(79)("./src/ui.js")},209:(e,t,i)=>{e.exports=i(79)("./src/utils.js")},79:e=>{"use strict";e.exports=CKEditor5.dll}},t={};function i(n){var o=t[n];if(void 0!==o)return o.exports;var r=t[n]={id:n,exports:{}};return e[n](r,r.exports,i),r.exports}i.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return i.d(t,{a:t}),t},i.d=(e,t)=>{for(var n in t)i.o(t,n)&&!i.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},i.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),i.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.nc=void 0;var n={};(()=>{"use strict";i.r(n),i.d(n,{SourceEditing:()=>h});var e=i(704),t=i(273),o=i(209);function r(e){const t=[{name:"address",isVoid:!1},{name:"article",isVoid:!1},{name:"aside",isVoid:!1},{name:"blockquote",isVoid:!1},{name:"br",isVoid:!0},{name:"details",isVoid:!1},{name:"dialog",isVoid:!1},{name:"dd",isVoid:!1},{name:"div",isVoid:!1},{name:"dl",isVoid:!1},{name:"dt",isVoid:!1},{name:"fieldset",isVoid:!1},{name:"figcaption",isVoid:!1},{name:"figure",isVoid:!1},{name:"footer",isVoid:!1},{name:"form",isVoid:!1},{name:"h1",isVoid:!1},{name:"h2",isVoid:!1},{name:"h3",isVoid:!1},{name:"h4",isVoid:!1},{name:"h5",isVoid:!1},{name:"h6",isVoid:!1},{name:"header",isVoid:!1},{name:"hgroup",isVoid:!1},{name:"hr",isVoid:!0},{name:"input",isVoid:!0},{name:"li",isVoid:!1},{name:"main",isVoid:!1},{name:"nav",isVoid:!1},{name:"ol",isVoid:!1},{name:"p",isVoid:!1},{name:"section",isVoid:!1},{name:"table",isVoid:!1},{name:"tbody",isVoid:!1},{name:"td",isVoid:!1},{name:"textarea",isVoid:!1},{name:"th",isVoid:!1},{name:"thead",isVoid:!1},{name:"tr",isVoid:!1},{name:"ul",isVoid:!1}],i=t.map((e=>e.name)).join("|"),n=e.replace(new RegExp(`</?(${i})( .*?)?>`,"g"),"\n$&\n").split("\n");let o=0;return n.filter((e=>e.length)).map((e=>function(e,t){return t.some((t=>!t.isVoid&&!!new RegExp(`<${t.name}( .*?)?>`).test(e)))}(e,t)?a(e,o++):function(e,t){return t.some((t=>new RegExp(`</${t.name}>`).test(e)))}(e,t)?a(e,--o):a(e,o))).join("\n")}function a(e,t,i=" "){return`${i.repeat(Math.max(0,t))}${e}`}var s=i(62),d=i.n(s),c=i(821),l={injectType:"singletonStyleTag",attributes:{"data-cke":!0},insert:"head",singleton:!0};d()(c.Z,l);c.Z.locals;const u="SourceEditingMode";class h extends e.Plugin{static get pluginName(){return"SourceEditing"}static get requires(){return[e.PendingActions]}constructor(e){super(e),this.set("isSourceEditingMode",!1),this._elementReplacer=new o.ElementReplacer,this._replacedRoots=new Map,this._dataFromRoots=new Map}init(){const i=this.editor,n=i.t;i.ui.componentFactory.add("sourceEditing",(o=>{const r=new t.ButtonView(o);return r.set({label:n("Source"),icon:'<svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="m12.5 0 5 4.5v15.003h-16V0h11zM3 1.5v3.25l-1.497 1-.003 8 1.5 1v3.254L7.685 18l-.001 1.504H17.5V8.002L16 9.428l-.004-4.22-4.222-3.692L3 1.5z"/><path d="M4.06 6.64a.75.75 0 0 1 .958 1.15l-.085.07L2.29 9.75l2.646 1.89c.302.216.4.62.232.951l-.058.095a.75.75 0 0 1-.951.232l-.095-.058-3.5-2.5V9.14l3.496-2.5zm4.194 6.22a.75.75 0 0 1-.958-1.149l.085-.07 2.643-1.89-2.646-1.89a.75.75 0 0 1-.232-.952l.058-.095a.75.75 0 0 1 .95-.232l.096.058 3.5 2.5v1.22l-3.496 2.5zm7.644-.836 2.122 2.122-5.825 5.809-2.125-.005.003-2.116zm2.539-1.847 1.414 1.414a.5.5 0 0 1 0 .707l-1.06 1.06-2.122-2.12 1.061-1.061a.5.5 0 0 1 .707 0z"/></svg>',tooltip:!0,withText:!0,class:"ck-source-editing-button"}),r.bind("isOn").to(this,"isSourceEditingMode"),r.bind("isEnabled").to(this,"isEnabled",i,"isReadOnly",i.plugins.get(e.PendingActions),"hasAny",((e,t,i)=>!!e&&(!t&&!i))),this.listenTo(r,"execute",(()=>{this.isSourceEditingMode=!this.isSourceEditingMode})),r})),this._isAllowedToHandleSourceEditingMode()&&(this.on("change:isSourceEditingMode",((e,t,i)=>{i?(this._showSourceEditing(),this._disableCommands()):(this._hideSourceEditing(),this._enableCommands())})),this.on("change:isEnabled",((e,t,i)=>this._handleReadOnlyMode(!i))),this.listenTo(i,"change:isReadOnly",((e,t,i)=>this._handleReadOnlyMode(i)))),i.data.on("get",(()=>{this.isSourceEditingMode&&this._updateEditorData()}),{priority:"high"})}afterInit(){const e=this.editor;["RealTimeCollaborativeEditing","CommentsEditing","TrackChangesEditing","RevisionHistory"].some((t=>e.plugins.has(t)))&&console.warn("You initialized the editor with the source editing feature and at least one of the collaboration features. Please be advised that the source editing feature may not work, and be careful when editing document source that contains markers created by the collaboration features."),e.plugins.has("RestrictedEditingModeEditing")&&console.warn("You initialized the editor with the source editing feature and restricted editing feature. Please be advised that the source editing feature may not work, and be careful when editing document source that contains markers created by the restricted editing feature.")}_showSourceEditing(){const e=this.editor,t=e.editing.view,i=e.model;i.change((e=>{e.setSelection(null),e.removeSelectionAttribute(i.document.selection.getAttributeKeys())}));for(const[i,n]of t.domRoots){const r=m(e.data.get({rootName:i})),a=(0,o.createElement)(n.ownerDocument,"textarea",{rows:"1","aria-label":"Source code editing area"}),s=(0,o.createElement)(n.ownerDocument,"div",{class:"ck-source-editing-area","data-value":r},[a]);a.value=r,a.setSelectionRange(0,0),a.addEventListener("input",(()=>{s.dataset.value=a.value})),t.change((e=>{const n=t.document.getRoot(i);e.addClass("ck-hidden",n)})),e.ui.setEditableElement("sourceEditing:"+i,a),this._replacedRoots.set(i,s),this._elementReplacer.replace(n,s),this._dataFromRoots.set(i,r)}this._focusSourceEditing()}_hideSourceEditing(){const e=this.editor.editing.view;this._updateEditorData(),e.change((t=>{for(const[i]of this._replacedRoots)t.removeClass("ck-hidden",e.document.getRoot(i))})),this._elementReplacer.restore(),this._replacedRoots.clear(),this._dataFromRoots.clear(),e.focus()}_updateEditorData(){const e=this.editor,t={};for(const[e,i]of this._replacedRoots){const n=this._dataFromRoots.get(e),o=i.dataset.value;n!==o&&(t[e]=o)}Object.keys(t).length&&e.data.set(t,{batchType:{isUndoable:!0}})}_focusSourceEditing(){const e=this.editor,[t]=this._replacedRoots.values(),i=t.querySelector("textarea");e.editing.view.document.isFocused=!1,i.focus()}_disableCommands(){const e=this.editor;for(const t of e.commands.commands())t.forceDisabled(u)}_enableCommands(){const e=this.editor;for(const t of e.commands.commands())t.clearForceDisabled(u)}_handleReadOnlyMode(e){if(this.isSourceEditingMode)for(const[,t]of this._replacedRoots)t.querySelector("textarea").readOnly=e}_isAllowedToHandleSourceEditingMode(){const e=this.editor.ui.view.editable;return e&&!e.hasExternalElement}}function m(e){return function(e){return e.startsWith("<")}(e)?r(e):e}})(),(window.CKEditor5=window.CKEditor5||{}).sourceEditing=n})();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ckeditor/ckeditor5-source-editing",
3
- "version": "36.0.0",
3
+ "version": "37.0.0-alpha.0",
4
4
  "description": "Source editing feature for CKEditor 5.",
5
5
  "keywords": [
6
6
  "ckeditor",
@@ -12,21 +12,22 @@
12
12
  ],
13
13
  "main": "src/index.js",
14
14
  "dependencies": {
15
- "@ckeditor/ckeditor5-theme-lark": "^36.0.0",
16
- "ckeditor5": "^36.0.0"
15
+ "@ckeditor/ckeditor5-theme-lark": "^37.0.0-alpha.0",
16
+ "ckeditor5": "^37.0.0-alpha.0"
17
17
  },
18
18
  "devDependencies": {
19
- "@ckeditor/ckeditor5-core": "^36.0.0",
20
- "@ckeditor/ckeditor5-dev-utils": "^32.0.0",
21
- "@ckeditor/ckeditor5-editor-classic": "^36.0.0",
22
- "@ckeditor/ckeditor5-engine": "^36.0.0",
23
- "@ckeditor/ckeditor5-essentials": "^36.0.0",
24
- "@ckeditor/ckeditor5-heading": "^36.0.0",
25
- "@ckeditor/ckeditor5-markdown-gfm": "^36.0.0",
26
- "@ckeditor/ckeditor5-paragraph": "^36.0.0",
27
- "@ckeditor/ckeditor5-table": "^36.0.0",
28
- "@ckeditor/ckeditor5-ui": "^36.0.0",
29
- "@ckeditor/ckeditor5-utils": "^36.0.0",
19
+ "@ckeditor/ckeditor5-core": "^37.0.0-alpha.0",
20
+ "@ckeditor/ckeditor5-dev-utils": "^34.0.0",
21
+ "@ckeditor/ckeditor5-editor-classic": "^37.0.0-alpha.0",
22
+ "@ckeditor/ckeditor5-engine": "^37.0.0-alpha.0",
23
+ "@ckeditor/ckeditor5-essentials": "^37.0.0-alpha.0",
24
+ "@ckeditor/ckeditor5-heading": "^37.0.0-alpha.0",
25
+ "@ckeditor/ckeditor5-markdown-gfm": "^37.0.0-alpha.0",
26
+ "@ckeditor/ckeditor5-paragraph": "^37.0.0-alpha.0",
27
+ "@ckeditor/ckeditor5-table": "^37.0.0-alpha.0",
28
+ "@ckeditor/ckeditor5-ui": "^37.0.0-alpha.0",
29
+ "@ckeditor/ckeditor5-utils": "^37.0.0-alpha.0",
30
+ "typescript": "^4.8.4",
30
31
  "webpack": "^5.58.1",
31
32
  "webpack-cli": "^4.9.0"
32
33
  },
@@ -45,13 +46,17 @@
45
46
  },
46
47
  "files": [
47
48
  "lang",
48
- "src",
49
+ "src/**/*.js",
50
+ "src/**/*.d.ts",
49
51
  "theme",
50
52
  "build",
51
53
  "ckeditor5-metadata.json",
52
54
  "CHANGELOG.md"
53
55
  ],
54
56
  "scripts": {
57
+ "build": "tsc -p ./tsconfig.release.json",
58
+ "postversion": "npm run build",
55
59
  "dll:build": "webpack"
56
- }
60
+ },
61
+ "types": "src/index.d.ts"
57
62
  }
package/src/index.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
3
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
+ */
5
+ /**
6
+ * @module source-editing
7
+ */
8
+ export { default as SourceEditing } from './sourceediting';
package/src/index.js CHANGED
@@ -2,9 +2,7 @@
2
2
  * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
3
3
  * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
4
  */
5
-
6
5
  /**
7
6
  * @module source-editing
8
7
  */
9
-
10
8
  export { default as SourceEditing } from './sourceediting';
@@ -0,0 +1,107 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
3
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
+ */
5
+ /**
6
+ * @module source-editing/sourceediting
7
+ */
8
+ import { type Editor, Plugin, type PluginDependencies } from 'ckeditor5/src/core';
9
+ import '../theme/sourceediting.css';
10
+ /**
11
+ * The source editing feature.
12
+ *
13
+ * It provides the possibility to view and edit the source of the document.
14
+ *
15
+ * For a detailed overview, check the {@glink features/source-editing source editing feature documentation} and the
16
+ * {@glink api/source-editing package page}.
17
+ */
18
+ export default class SourceEditing extends Plugin {
19
+ /**
20
+ * @inheritDoc
21
+ */
22
+ static get pluginName(): 'SourceEditing';
23
+ /**
24
+ * @inheritDoc
25
+ */
26
+ static get requires(): PluginDependencies;
27
+ /**
28
+ * Flag indicating whether the document source mode is active.
29
+ *
30
+ * @observable
31
+ */
32
+ isSourceEditingMode: boolean;
33
+ /**
34
+ * The element replacer instance used to replace the editing roots with the wrapper elements containing the document source.
35
+ */
36
+ private _elementReplacer;
37
+ /**
38
+ * Maps all root names to wrapper elements containing the document source.
39
+ */
40
+ private _replacedRoots;
41
+ /**
42
+ * Maps all root names to their document data.
43
+ */
44
+ private _dataFromRoots;
45
+ /**
46
+ * @inheritDoc
47
+ */
48
+ constructor(editor: Editor);
49
+ /**
50
+ * @inheritDoc
51
+ */
52
+ init(): void;
53
+ /**
54
+ * @inheritDoc
55
+ */
56
+ afterInit(): void;
57
+ /**
58
+ * Creates source editing wrappers that replace each editing root. Each wrapper contains the document source from the corresponding
59
+ * root.
60
+ *
61
+ * The wrapper element contains a textarea and it solves the problem, that the textarea element cannot auto expand its height based on
62
+ * the content it contains. The solution is to make the textarea more like a plain div element, which expands in height as much as it
63
+ * needs to, in order to display the whole document source without scrolling. The wrapper element is a parent for the textarea and for
64
+ * the pseudo-element `::after`, that replicates the look, content, and position of the textarea. The pseudo-element replica is hidden,
65
+ * but it is styled to be an identical visual copy of the textarea with the same content. Then, the wrapper is a grid container and both
66
+ * of its children (the textarea and the `::after` pseudo-element) are positioned within a CSS grid to occupy the same grid cell. The
67
+ * content in the pseudo-element `::after` is set in CSS and it stretches the grid to the appropriate size based on the textarea value.
68
+ * Since both children occupy the same grid cell, both have always the same height.
69
+ */
70
+ private _showSourceEditing;
71
+ /**
72
+ * Restores all hidden editing roots and sets the source data in them.
73
+ */
74
+ private _hideSourceEditing;
75
+ /**
76
+ * Updates the source data in all hidden editing roots.
77
+ */
78
+ private _updateEditorData;
79
+ /**
80
+ * Focuses the textarea containing document source from the first editing root.
81
+ */
82
+ private _focusSourceEditing;
83
+ /**
84
+ * Disables all commands.
85
+ */
86
+ private _disableCommands;
87
+ /**
88
+ * Clears forced disable for all commands, that was previously set through {@link #_disableCommands}.
89
+ */
90
+ private _enableCommands;
91
+ /**
92
+ * Adds or removes the `readonly` attribute from the textarea from all roots, if document source mode is active.
93
+ *
94
+ * @param isReadOnly Indicates whether all textarea elements should be read-only.
95
+ */
96
+ private _handleReadOnlyMode;
97
+ /**
98
+ * Checks, if the plugin is allowed to handle the source editing mode by itself. Currently, the source editing mode is supported only
99
+ * for the {@link module:editor-classic/classiceditor~ClassicEditor classic editor}.
100
+ */
101
+ private _isAllowedToHandleSourceEditingMode;
102
+ }
103
+ declare module '@ckeditor/ckeditor5-core' {
104
+ interface PluginsMap {
105
+ [SourceEditing.pluginName]: SourceEditing;
106
+ }
107
+ }
@@ -2,24 +2,17 @@
2
2
  * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
3
3
  * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
4
  */
5
-
6
5
  /**
7
6
  * @module source-editing/sourceediting
8
7
  */
9
-
10
8
  /* global console */
11
-
12
9
  import { Plugin, PendingActions } from 'ckeditor5/src/core';
13
10
  import { ButtonView } from 'ckeditor5/src/ui';
14
11
  import { createElement, ElementReplacer } from 'ckeditor5/src/utils';
15
12
  import { formatHtml } from './utils/formathtml';
16
-
17
13
  import '../theme/sourceediting.css';
18
-
19
14
  import sourceEditingIcon from '../theme/icons/source-editing.svg';
20
-
21
15
  const COMMAND_FORCE_DISABLE_ID = 'SourceEditingMode';
22
-
23
16
  /**
24
17
  * The source editing feature.
25
18
  *
@@ -27,388 +20,278 @@ const COMMAND_FORCE_DISABLE_ID = 'SourceEditingMode';
27
20
  *
28
21
  * For a detailed overview, check the {@glink features/source-editing source editing feature documentation} and the
29
22
  * {@glink api/source-editing package page}.
30
- *
31
- * @extends module:core/plugin~Plugin
32
23
  */
33
24
  export default class SourceEditing extends Plugin {
34
- /**
35
- * @inheritDoc
36
- */
37
- static get pluginName() {
38
- return 'SourceEditing';
39
- }
40
-
41
- /**
42
- * @inheritDoc
43
- */
44
- static get requires() {
45
- return [ PendingActions ];
46
- }
47
-
48
- /**
49
- * @inheritDoc
50
- */
51
- constructor( editor ) {
52
- super( editor );
53
-
54
- /**
55
- * Flag indicating whether the document source mode is active.
56
- *
57
- * @observable
58
- * @member {Boolean}
59
- */
60
- this.set( 'isSourceEditingMode', false );
61
-
62
- /**
63
- * The element replacer instance used to replace the editing roots with the wrapper elements containing the document source.
64
- *
65
- * @private
66
- * @member {module:utils/elementreplacer~ElementReplacer}
67
- */
68
- this._elementReplacer = new ElementReplacer();
69
-
70
- /**
71
- * Maps all root names to wrapper elements containing the document source.
72
- *
73
- * @private
74
- * @member {Map.<String,HTMLElement>}
75
- */
76
- this._replacedRoots = new Map();
77
-
78
- /**
79
- * Maps all root names to their document data.
80
- *
81
- * @private
82
- * @member {Map.<String,String>}
83
- */
84
- this._dataFromRoots = new Map();
85
- }
86
-
87
- /**
88
- * @inheritDoc
89
- */
90
- init() {
91
- const editor = this.editor;
92
- const t = editor.t;
93
-
94
- editor.ui.componentFactory.add( 'sourceEditing', locale => {
95
- const buttonView = new ButtonView( locale );
96
-
97
- buttonView.set( {
98
- label: t( 'Source' ),
99
- icon: sourceEditingIcon,
100
- tooltip: true,
101
- withText: true,
102
- class: 'ck-source-editing-button'
103
- } );
104
-
105
- buttonView.bind( 'isOn' ).to( this, 'isSourceEditingMode' );
106
-
107
- // The button should be disabled if one of the following conditions is met:
108
- buttonView.bind( 'isEnabled' ).to(
109
- this, 'isEnabled',
110
- editor, 'isReadOnly',
111
- editor.plugins.get( PendingActions ), 'hasAny',
112
- ( isEnabled, isEditorReadOnly, hasAnyPendingActions ) => {
113
- // (1) The plugin itself is disabled.
114
- if ( !isEnabled ) {
115
- return false;
116
- }
117
-
118
- // (2) The editor is in read-only mode.
119
- if ( isEditorReadOnly ) {
120
- return false;
121
- }
122
-
123
- // (3) Any pending action is scheduled. It may change the model, so modifying the document source should be prevented
124
- // until the model is finally set.
125
- if ( hasAnyPendingActions ) {
126
- return false;
127
- }
128
-
129
- return true;
130
- }
131
- );
132
-
133
- this.listenTo( buttonView, 'execute', () => {
134
- this.isSourceEditingMode = !this.isSourceEditingMode;
135
- } );
136
-
137
- return buttonView;
138
- } );
139
-
140
- // Currently, the plugin handles the source editing mode by itself only for the classic editor. To use this plugin with other
141
- // integrations, listen to the `change:isSourceEditingMode` event and act accordingly.
142
- if ( this._isAllowedToHandleSourceEditingMode() ) {
143
- this.on( 'change:isSourceEditingMode', ( evt, name, isSourceEditingMode ) => {
144
- if ( isSourceEditingMode ) {
145
- this._showSourceEditing();
146
- this._disableCommands();
147
- } else {
148
- this._hideSourceEditing();
149
- this._enableCommands();
150
- }
151
- } );
152
-
153
- this.on( 'change:isEnabled', ( evt, name, isEnabled ) => this._handleReadOnlyMode( !isEnabled ) );
154
-
155
- this.listenTo( editor, 'change:isReadOnly', ( evt, name, isReadOnly ) => this._handleReadOnlyMode( isReadOnly ) );
156
- }
157
-
158
- // Update the editor data while calling editor.getData() in the source editing mode.
159
- editor.data.on( 'get', () => {
160
- if ( this.isSourceEditingMode ) {
161
- this._updateEditorData();
162
- }
163
- }, { priority: 'high' } );
164
- }
165
-
166
- /**
167
- * @inheritDoc
168
- */
169
- afterInit() {
170
- const editor = this.editor;
171
-
172
- const collaborationPluginNamesToWarn = [
173
- 'RealTimeCollaborativeEditing',
174
- 'CommentsEditing',
175
- 'TrackChangesEditing',
176
- 'RevisionHistory'
177
- ];
178
-
179
- // Currently, the basic integration with Collaboration Features is to display a warning in the console.
180
- if ( collaborationPluginNamesToWarn.some( pluginName => editor.plugins.has( pluginName ) ) ) {
181
- console.warn(
182
- 'You initialized the editor with the source editing feature and at least one of the collaboration features. ' +
183
- 'Please be advised that the source editing feature may not work, and be careful when editing document source ' +
184
- 'that contains markers created by the collaboration features.'
185
- );
186
- }
187
-
188
- // Restricted Editing integration can also lead to problems. Warn the user accordingly.
189
- if ( editor.plugins.has( 'RestrictedEditingModeEditing' ) ) {
190
- console.warn(
191
- 'You initialized the editor with the source editing feature and restricted editing feature. ' +
192
- 'Please be advised that the source editing feature may not work, and be careful when editing document source ' +
193
- 'that contains markers created by the restricted editing feature.'
194
- );
195
- }
196
- }
197
-
198
- /**
199
- * Creates source editing wrappers that replace each editing root. Each wrapper contains the document source from the corresponding
200
- * root.
201
- *
202
- * The wrapper element contains a textarea and it solves the problem, that the textarea element cannot auto expand its height based on
203
- * the content it contains. The solution is to make the textarea more like a plain div element, which expands in height as much as it
204
- * needs to, in order to display the whole document source without scrolling. The wrapper element is a parent for the textarea and for
205
- * the pseudo-element `::after`, that replicates the look, content, and position of the textarea. The pseudo-element replica is hidden,
206
- * but it is styled to be an identical visual copy of the textarea with the same content. Then, the wrapper is a grid container and both
207
- * of its children (the textarea and the `::after` pseudo-element) are positioned within a CSS grid to occupy the same grid cell. The
208
- * content in the pseudo-element `::after` is set in CSS and it stretches the grid to the appropriate size based on the textarea value.
209
- * Since both children occupy the same grid cell, both have always the same height.
210
- *
211
- * @private
212
- */
213
- _showSourceEditing() {
214
- const editor = this.editor;
215
- const editingView = editor.editing.view;
216
- const model = editor.model;
217
-
218
- model.change( writer => {
219
- writer.setSelection( null );
220
- writer.removeSelectionAttribute( model.document.selection.getAttributeKeys() );
221
- } );
222
-
223
- // It is not needed to iterate through all editing roots, as currently the plugin supports only the Classic Editor with a single
224
- // main root, but this code may help understand and use this feature in external integrations.
225
- for ( const [ rootName, domRootElement ] of editingView.domRoots ) {
226
- const data = formatSource( editor.data.get( { rootName } ) );
227
-
228
- const domSourceEditingElementTextarea = createElement( domRootElement.ownerDocument, 'textarea', {
229
- rows: '1',
230
- 'aria-label': 'Source code editing area'
231
- } );
232
-
233
- const domSourceEditingElementWrapper = createElement( domRootElement.ownerDocument, 'div', {
234
- class: 'ck-source-editing-area',
235
- 'data-value': data
236
- }, [ domSourceEditingElementTextarea ] );
237
-
238
- domSourceEditingElementTextarea.value = data;
239
-
240
- // Setting a value to textarea moves the input cursor to the end. We want the selection at the beginning.
241
- domSourceEditingElementTextarea.setSelectionRange( 0, 0 );
242
-
243
- // Bind the textarea's value to the wrapper's `data-value` property. Each change of the textarea's value updates the
244
- // wrapper's `data-value` property.
245
- domSourceEditingElementTextarea.addEventListener( 'input', () => {
246
- domSourceEditingElementWrapper.dataset.value = domSourceEditingElementTextarea.value;
247
- } );
248
-
249
- editingView.change( writer => {
250
- const viewRoot = editingView.document.getRoot( rootName );
251
-
252
- writer.addClass( 'ck-hidden', viewRoot );
253
- } );
254
-
255
- // Register the element so it becomes available for Alt+F10 and Esc navigation.
256
- editor.ui.setEditableElement( 'sourceEditing:' + rootName, domSourceEditingElementTextarea );
257
-
258
- this._replacedRoots.set( rootName, domSourceEditingElementWrapper );
259
-
260
- this._elementReplacer.replace( domRootElement, domSourceEditingElementWrapper );
261
-
262
- this._dataFromRoots.set( rootName, data );
263
- }
264
-
265
- this._focusSourceEditing();
266
- }
267
-
268
- /**
269
- * Restores all hidden editing roots and sets the source data in them.
270
- *
271
- * @private
272
- */
273
- _hideSourceEditing() {
274
- const editor = this.editor;
275
- const editingView = editor.editing.view;
276
-
277
- this._updateEditorData();
278
-
279
- editingView.change( writer => {
280
- for ( const [ rootName ] of this._replacedRoots ) {
281
- writer.removeClass( 'ck-hidden', editingView.document.getRoot( rootName ) );
282
- }
283
- } );
284
-
285
- this._elementReplacer.restore();
286
-
287
- this._replacedRoots.clear();
288
- this._dataFromRoots.clear();
289
-
290
- editingView.focus();
291
- }
292
-
293
- /**
294
- * Updates the source data in all hidden editing roots.
295
- *
296
- * @private
297
- */
298
- _updateEditorData() {
299
- const editor = this.editor;
300
- const data = {};
301
-
302
- for ( const [ rootName, domSourceEditingElementWrapper ] of this._replacedRoots ) {
303
- const oldData = this._dataFromRoots.get( rootName );
304
- const newData = domSourceEditingElementWrapper.dataset.value;
305
-
306
- // Do not set the data unless some changes have been made in the meantime.
307
- // This prevents empty undo steps after switching to the normal editor.
308
- if ( oldData !== newData ) {
309
- data[ rootName ] = newData;
310
- }
311
- }
312
-
313
- if ( Object.keys( data ).length ) {
314
- editor.data.set( data, { batchType: { isUndoable: true } } );
315
- }
316
- }
317
-
318
- /**
319
- * Focuses the textarea containing document source from the first editing root.
320
- *
321
- * @private
322
- */
323
- _focusSourceEditing() {
324
- const editor = this.editor;
325
- const [ domSourceEditingElementWrapper ] = this._replacedRoots.values();
326
- const textarea = domSourceEditingElementWrapper.querySelector( 'textarea' );
327
-
328
- // The FocusObserver was disabled by View.render() while the DOM root was getting hidden and the replacer
329
- // revealed the textarea. So it couldn't notice that the DOM root got blurred in the process.
330
- // Let's sync this state manually here because otherwise Renderer will attempt to render selection
331
- // in an invisible DOM root.
332
- editor.editing.view.document.isFocused = false;
333
-
334
- textarea.focus();
335
- }
336
-
337
- /**
338
- * Disables all commands.
339
- *
340
- * @private
341
- */
342
- _disableCommands() {
343
- const editor = this.editor;
344
-
345
- for ( const command of editor.commands.commands() ) {
346
- command.forceDisabled( COMMAND_FORCE_DISABLE_ID );
347
- }
348
- }
349
-
350
- /**
351
- * Clears forced disable for all commands, that was previously set through {@link #_disableCommands}.
352
- *
353
- * @private
354
- */
355
- _enableCommands() {
356
- const editor = this.editor;
357
-
358
- for ( const command of editor.commands.commands() ) {
359
- command.clearForceDisabled( COMMAND_FORCE_DISABLE_ID );
360
- }
361
- }
362
-
363
- /**
364
- * Adds or removes the `readonly` attribute from the textarea from all roots, if document source mode is active.
365
- *
366
- * @param {Boolean} isReadOnly Indicates whether all textarea elements should be read-only.
367
- */
368
- _handleReadOnlyMode( isReadOnly ) {
369
- if ( !this.isSourceEditingMode ) {
370
- return;
371
- }
372
-
373
- for ( const [ , domSourceEditingElementWrapper ] of this._replacedRoots ) {
374
- domSourceEditingElementWrapper.querySelector( 'textarea' ).readOnly = isReadOnly;
375
- }
376
- }
377
-
378
- /**
379
- * Checks, if the plugin is allowed to handle the source editing mode by itself. Currently, the source editing mode is supported only
380
- * for the {@link module:editor-classic/classiceditor~ClassicEditor classic editor}.
381
- *
382
- * @private
383
- * @returns {Boolean}
384
- */
385
- _isAllowedToHandleSourceEditingMode() {
386
- const editor = this.editor;
387
- const editable = editor.ui.view.editable;
388
-
389
- // Checks, if the editor's editable belongs to the editor's DOM tree.
390
- return editable && !editable._hasExternalElement;
391
- }
25
+ /**
26
+ * @inheritDoc
27
+ */
28
+ static get pluginName() {
29
+ return 'SourceEditing';
30
+ }
31
+ /**
32
+ * @inheritDoc
33
+ */
34
+ static get requires() {
35
+ return [PendingActions];
36
+ }
37
+ /**
38
+ * @inheritDoc
39
+ */
40
+ constructor(editor) {
41
+ super(editor);
42
+ this.set('isSourceEditingMode', false);
43
+ this._elementReplacer = new ElementReplacer();
44
+ this._replacedRoots = new Map();
45
+ this._dataFromRoots = new Map();
46
+ }
47
+ /**
48
+ * @inheritDoc
49
+ */
50
+ init() {
51
+ const editor = this.editor;
52
+ const t = editor.t;
53
+ editor.ui.componentFactory.add('sourceEditing', locale => {
54
+ const buttonView = new ButtonView(locale);
55
+ buttonView.set({
56
+ label: t('Source'),
57
+ icon: sourceEditingIcon,
58
+ tooltip: true,
59
+ withText: true,
60
+ class: 'ck-source-editing-button'
61
+ });
62
+ buttonView.bind('isOn').to(this, 'isSourceEditingMode');
63
+ // The button should be disabled if one of the following conditions is met:
64
+ buttonView.bind('isEnabled').to(this, 'isEnabled', editor, 'isReadOnly', editor.plugins.get(PendingActions), 'hasAny', (isEnabled, isEditorReadOnly, hasAnyPendingActions) => {
65
+ // (1) The plugin itself is disabled.
66
+ if (!isEnabled) {
67
+ return false;
68
+ }
69
+ // (2) The editor is in read-only mode.
70
+ if (isEditorReadOnly) {
71
+ return false;
72
+ }
73
+ // (3) Any pending action is scheduled. It may change the model, so modifying the document source should be prevented
74
+ // until the model is finally set.
75
+ if (hasAnyPendingActions) {
76
+ return false;
77
+ }
78
+ return true;
79
+ });
80
+ this.listenTo(buttonView, 'execute', () => {
81
+ this.isSourceEditingMode = !this.isSourceEditingMode;
82
+ });
83
+ return buttonView;
84
+ });
85
+ // Currently, the plugin handles the source editing mode by itself only for the classic editor. To use this plugin with other
86
+ // integrations, listen to the `change:isSourceEditingMode` event and act accordingly.
87
+ if (this._isAllowedToHandleSourceEditingMode()) {
88
+ this.on('change:isSourceEditingMode', (evt, name, isSourceEditingMode) => {
89
+ if (isSourceEditingMode) {
90
+ this._showSourceEditing();
91
+ this._disableCommands();
92
+ }
93
+ else {
94
+ this._hideSourceEditing();
95
+ this._enableCommands();
96
+ }
97
+ });
98
+ this.on('change:isEnabled', (evt, name, isEnabled) => this._handleReadOnlyMode(!isEnabled));
99
+ this.listenTo(editor, 'change:isReadOnly', (evt, name, isReadOnly) => this._handleReadOnlyMode(isReadOnly));
100
+ }
101
+ // Update the editor data while calling editor.getData() in the source editing mode.
102
+ editor.data.on('get', () => {
103
+ if (this.isSourceEditingMode) {
104
+ this._updateEditorData();
105
+ }
106
+ }, { priority: 'high' });
107
+ }
108
+ /**
109
+ * @inheritDoc
110
+ */
111
+ afterInit() {
112
+ const editor = this.editor;
113
+ const collaborationPluginNamesToWarn = [
114
+ 'RealTimeCollaborativeEditing',
115
+ 'CommentsEditing',
116
+ 'TrackChangesEditing',
117
+ 'RevisionHistory'
118
+ ];
119
+ // Currently, the basic integration with Collaboration Features is to display a warning in the console.
120
+ if (collaborationPluginNamesToWarn.some(pluginName => editor.plugins.has(pluginName))) {
121
+ console.warn('You initialized the editor with the source editing feature and at least one of the collaboration features. ' +
122
+ 'Please be advised that the source editing feature may not work, and be careful when editing document source ' +
123
+ 'that contains markers created by the collaboration features.');
124
+ }
125
+ // Restricted Editing integration can also lead to problems. Warn the user accordingly.
126
+ if (editor.plugins.has('RestrictedEditingModeEditing')) {
127
+ console.warn('You initialized the editor with the source editing feature and restricted editing feature. ' +
128
+ 'Please be advised that the source editing feature may not work, and be careful when editing document source ' +
129
+ 'that contains markers created by the restricted editing feature.');
130
+ }
131
+ }
132
+ /**
133
+ * Creates source editing wrappers that replace each editing root. Each wrapper contains the document source from the corresponding
134
+ * root.
135
+ *
136
+ * The wrapper element contains a textarea and it solves the problem, that the textarea element cannot auto expand its height based on
137
+ * the content it contains. The solution is to make the textarea more like a plain div element, which expands in height as much as it
138
+ * needs to, in order to display the whole document source without scrolling. The wrapper element is a parent for the textarea and for
139
+ * the pseudo-element `::after`, that replicates the look, content, and position of the textarea. The pseudo-element replica is hidden,
140
+ * but it is styled to be an identical visual copy of the textarea with the same content. Then, the wrapper is a grid container and both
141
+ * of its children (the textarea and the `::after` pseudo-element) are positioned within a CSS grid to occupy the same grid cell. The
142
+ * content in the pseudo-element `::after` is set in CSS and it stretches the grid to the appropriate size based on the textarea value.
143
+ * Since both children occupy the same grid cell, both have always the same height.
144
+ */
145
+ _showSourceEditing() {
146
+ const editor = this.editor;
147
+ const editingView = editor.editing.view;
148
+ const model = editor.model;
149
+ model.change(writer => {
150
+ writer.setSelection(null);
151
+ writer.removeSelectionAttribute(model.document.selection.getAttributeKeys());
152
+ });
153
+ // It is not needed to iterate through all editing roots, as currently the plugin supports only the Classic Editor with a single
154
+ // main root, but this code may help understand and use this feature in external integrations.
155
+ for (const [rootName, domRootElement] of editingView.domRoots) {
156
+ const data = formatSource(editor.data.get({ rootName }));
157
+ const domSourceEditingElementTextarea = createElement(domRootElement.ownerDocument, 'textarea', {
158
+ rows: '1',
159
+ 'aria-label': 'Source code editing area'
160
+ });
161
+ const domSourceEditingElementWrapper = createElement(domRootElement.ownerDocument, 'div', {
162
+ class: 'ck-source-editing-area',
163
+ 'data-value': data
164
+ }, [domSourceEditingElementTextarea]);
165
+ domSourceEditingElementTextarea.value = data;
166
+ // Setting a value to textarea moves the input cursor to the end. We want the selection at the beginning.
167
+ domSourceEditingElementTextarea.setSelectionRange(0, 0);
168
+ // Bind the textarea's value to the wrapper's `data-value` property. Each change of the textarea's value updates the
169
+ // wrapper's `data-value` property.
170
+ domSourceEditingElementTextarea.addEventListener('input', () => {
171
+ domSourceEditingElementWrapper.dataset.value = domSourceEditingElementTextarea.value;
172
+ });
173
+ editingView.change(writer => {
174
+ const viewRoot = editingView.document.getRoot(rootName);
175
+ writer.addClass('ck-hidden', viewRoot);
176
+ });
177
+ // Register the element so it becomes available for Alt+F10 and Esc navigation.
178
+ editor.ui.setEditableElement('sourceEditing:' + rootName, domSourceEditingElementTextarea);
179
+ this._replacedRoots.set(rootName, domSourceEditingElementWrapper);
180
+ this._elementReplacer.replace(domRootElement, domSourceEditingElementWrapper);
181
+ this._dataFromRoots.set(rootName, data);
182
+ }
183
+ this._focusSourceEditing();
184
+ }
185
+ /**
186
+ * Restores all hidden editing roots and sets the source data in them.
187
+ */
188
+ _hideSourceEditing() {
189
+ const editor = this.editor;
190
+ const editingView = editor.editing.view;
191
+ this._updateEditorData();
192
+ editingView.change(writer => {
193
+ for (const [rootName] of this._replacedRoots) {
194
+ writer.removeClass('ck-hidden', editingView.document.getRoot(rootName));
195
+ }
196
+ });
197
+ this._elementReplacer.restore();
198
+ this._replacedRoots.clear();
199
+ this._dataFromRoots.clear();
200
+ editingView.focus();
201
+ }
202
+ /**
203
+ * Updates the source data in all hidden editing roots.
204
+ */
205
+ _updateEditorData() {
206
+ const editor = this.editor;
207
+ const data = {};
208
+ for (const [rootName, domSourceEditingElementWrapper] of this._replacedRoots) {
209
+ const oldData = this._dataFromRoots.get(rootName);
210
+ const newData = domSourceEditingElementWrapper.dataset.value;
211
+ // Do not set the data unless some changes have been made in the meantime.
212
+ // This prevents empty undo steps after switching to the normal editor.
213
+ if (oldData !== newData) {
214
+ data[rootName] = newData;
215
+ }
216
+ }
217
+ if (Object.keys(data).length) {
218
+ editor.data.set(data, { batchType: { isUndoable: true } });
219
+ }
220
+ }
221
+ /**
222
+ * Focuses the textarea containing document source from the first editing root.
223
+ */
224
+ _focusSourceEditing() {
225
+ const editor = this.editor;
226
+ const [domSourceEditingElementWrapper] = this._replacedRoots.values();
227
+ const textarea = domSourceEditingElementWrapper.querySelector('textarea');
228
+ // The FocusObserver was disabled by View.render() while the DOM root was getting hidden and the replacer
229
+ // revealed the textarea. So it couldn't notice that the DOM root got blurred in the process.
230
+ // Let's sync this state manually here because otherwise Renderer will attempt to render selection
231
+ // in an invisible DOM root.
232
+ editor.editing.view.document.isFocused = false;
233
+ textarea.focus();
234
+ }
235
+ /**
236
+ * Disables all commands.
237
+ */
238
+ _disableCommands() {
239
+ const editor = this.editor;
240
+ for (const command of editor.commands.commands()) {
241
+ command.forceDisabled(COMMAND_FORCE_DISABLE_ID);
242
+ }
243
+ }
244
+ /**
245
+ * Clears forced disable for all commands, that was previously set through {@link #_disableCommands}.
246
+ */
247
+ _enableCommands() {
248
+ const editor = this.editor;
249
+ for (const command of editor.commands.commands()) {
250
+ command.clearForceDisabled(COMMAND_FORCE_DISABLE_ID);
251
+ }
252
+ }
253
+ /**
254
+ * Adds or removes the `readonly` attribute from the textarea from all roots, if document source mode is active.
255
+ *
256
+ * @param isReadOnly Indicates whether all textarea elements should be read-only.
257
+ */
258
+ _handleReadOnlyMode(isReadOnly) {
259
+ if (!this.isSourceEditingMode) {
260
+ return;
261
+ }
262
+ for (const [, domSourceEditingElementWrapper] of this._replacedRoots) {
263
+ domSourceEditingElementWrapper.querySelector('textarea').readOnly = isReadOnly;
264
+ }
265
+ }
266
+ /**
267
+ * Checks, if the plugin is allowed to handle the source editing mode by itself. Currently, the source editing mode is supported only
268
+ * for the {@link module:editor-classic/classiceditor~ClassicEditor classic editor}.
269
+ */
270
+ _isAllowedToHandleSourceEditingMode() {
271
+ const editor = this.editor;
272
+ const editable = editor.ui.view.editable;
273
+ // Checks, if the editor's editable belongs to the editor's DOM tree.
274
+ return editable && !editable.hasExternalElement;
275
+ }
392
276
  }
393
-
394
- // Formats the content for a better readability.
395
- //
396
- // For a non-HTML source the unchanged input string is returned.
397
- //
398
- // @param {String} input Input string to check.
399
- // @returns {Boolean}
400
- function formatSource( input ) {
401
- if ( !isHtml( input ) ) {
402
- return input;
403
- }
404
-
405
- return formatHtml( input );
277
+ /**
278
+ * Formats the content for a better readability.
279
+ *
280
+ * For a non-HTML source the unchanged input string is returned.
281
+ *
282
+ * @param input Input string to check.
283
+ */
284
+ function formatSource(input) {
285
+ if (!isHtml(input)) {
286
+ return input;
287
+ }
288
+ return formatHtml(input);
406
289
  }
407
-
408
- // Checks, if the document source is HTML. It is sufficient to just check the first character from the document data.
409
- //
410
- // @param {String} input Input string to check.
411
- // @returns {Boolean}
412
- function isHtml( input ) {
413
- return input.startsWith( '<' );
290
+ /**
291
+ * Checks, if the document source is HTML. It is sufficient to just check the first character from the document data.
292
+ *
293
+ * @param input Input string to check.
294
+ */
295
+ function isHtml(input) {
296
+ return input.startsWith('<');
414
297
  }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
3
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
+ */
5
+ /**
6
+ * @module source-editing/utils/formathtml
7
+ */
8
+ /**
9
+ * A simple (and naive) HTML code formatter that returns a formatted HTML markup that can be easily
10
+ * parsed by human eyes. It beautifies the HTML code by adding new lines between elements that behave like block elements
11
+ * (https://developer.mozilla.org/en-US/docs/Web/HTML/Block-level_elements
12
+ * and a few more like `tr`, `td`, and similar ones) and inserting indents for nested content.
13
+ *
14
+ * WARNING: This function works only on a text that does not contain any indentations or new lines.
15
+ * Calling this function on the already formatted text will damage the formatting.
16
+ *
17
+ * @param input An HTML string to format.
18
+ */
19
+ export declare function formatHtml(input: string): string;
@@ -2,11 +2,9 @@
2
2
  * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
3
3
  * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
4
  */
5
-
6
5
  /**
7
6
  * @module source-editing/utils/formathtml
8
7
  */
9
-
10
8
  /**
11
9
  * A simple (and naive) HTML code formatter that returns a formatted HTML markup that can be easily
12
10
  * parsed by human eyes. It beautifies the HTML code by adding new lines between elements that behave like block elements
@@ -16,128 +14,115 @@
16
14
  * WARNING: This function works only on a text that does not contain any indentations or new lines.
17
15
  * Calling this function on the already formatted text will damage the formatting.
18
16
  *
19
- * @param {String} input An HTML string to format.
20
- * @returns {String}
17
+ * @param input An HTML string to format.
21
18
  */
22
- export function formatHtml( input ) {
23
- // A list of block-like elements around which the new lines should be inserted, and within which
24
- // the indentation of their children should be increased.
25
- // The list is partially based on https://developer.mozilla.org/en-US/docs/Web/HTML/Block-level_elements that contains
26
- // a full list of HTML block-level elements.
27
- // A void element is an element that cannot have any child - https://html.spec.whatwg.org/multipage/syntax.html#void-elements.
28
- // Note that <pre> element is not listed on this list to avoid breaking whitespace formatting.
29
- const elementsToFormat = [
30
- { name: 'address', isVoid: false },
31
- { name: 'article', isVoid: false },
32
- { name: 'aside', isVoid: false },
33
- { name: 'blockquote', isVoid: false },
34
- { name: 'br', isVoid: true },
35
- { name: 'details', isVoid: false },
36
- { name: 'dialog', isVoid: false },
37
- { name: 'dd', isVoid: false },
38
- { name: 'div', isVoid: false },
39
- { name: 'dl', isVoid: false },
40
- { name: 'dt', isVoid: false },
41
- { name: 'fieldset', isVoid: false },
42
- { name: 'figcaption', isVoid: false },
43
- { name: 'figure', isVoid: false },
44
- { name: 'footer', isVoid: false },
45
- { name: 'form', isVoid: false },
46
- { name: 'h1', isVoid: false },
47
- { name: 'h2', isVoid: false },
48
- { name: 'h3', isVoid: false },
49
- { name: 'h4', isVoid: false },
50
- { name: 'h5', isVoid: false },
51
- { name: 'h6', isVoid: false },
52
- { name: 'header', isVoid: false },
53
- { name: 'hgroup', isVoid: false },
54
- { name: 'hr', isVoid: true },
55
- { name: 'input', isVoid: true },
56
- { name: 'li', isVoid: false },
57
- { name: 'main', isVoid: false },
58
- { name: 'nav', isVoid: false },
59
- { name: 'ol', isVoid: false },
60
- { name: 'p', isVoid: false },
61
- { name: 'section', isVoid: false },
62
- { name: 'table', isVoid: false },
63
- { name: 'tbody', isVoid: false },
64
- { name: 'td', isVoid: false },
65
- { name: 'textarea', isVoid: false },
66
- { name: 'th', isVoid: false },
67
- { name: 'thead', isVoid: false },
68
- { name: 'tr', isVoid: false },
69
- { name: 'ul', isVoid: false }
70
- ];
71
-
72
- const elementNamesToFormat = elementsToFormat.map( element => element.name ).join( '|' );
73
-
74
- // It is not the fastest way to format the HTML markup but the performance should be good enough.
75
- const lines = input
76
- // Add new line before and after `<tag>` and `</tag>`.
77
- // It may separate individual elements with two new lines, but this will be fixed below.
78
- .replace( new RegExp( `</?(${ elementNamesToFormat })( .*?)?>`, 'g' ), '\n$&\n' )
79
- // Divide input string into lines, which start with either an opening tag, a closing tag, or just a text.
80
- .split( '\n' );
81
-
82
- let indentCount = 0;
83
-
84
- return lines
85
- .filter( line => line.length )
86
- .map( line => {
87
- if ( isNonVoidOpeningTag( line, elementsToFormat ) ) {
88
- return indentLine( line, indentCount++ );
89
- }
90
-
91
- if ( isClosingTag( line, elementsToFormat ) ) {
92
- return indentLine( line, --indentCount );
93
- }
94
-
95
- return indentLine( line, indentCount );
96
- } )
97
- .join( '\n' );
19
+ export function formatHtml(input) {
20
+ // A list of block-like elements around which the new lines should be inserted, and within which
21
+ // the indentation of their children should be increased.
22
+ // The list is partially based on https://developer.mozilla.org/en-US/docs/Web/HTML/Block-level_elements that contains
23
+ // a full list of HTML block-level elements.
24
+ // A void element is an element that cannot have any child - https://html.spec.whatwg.org/multipage/syntax.html#void-elements.
25
+ // Note that <pre> element is not listed on this list to avoid breaking whitespace formatting.
26
+ const elementsToFormat = [
27
+ { name: 'address', isVoid: false },
28
+ { name: 'article', isVoid: false },
29
+ { name: 'aside', isVoid: false },
30
+ { name: 'blockquote', isVoid: false },
31
+ { name: 'br', isVoid: true },
32
+ { name: 'details', isVoid: false },
33
+ { name: 'dialog', isVoid: false },
34
+ { name: 'dd', isVoid: false },
35
+ { name: 'div', isVoid: false },
36
+ { name: 'dl', isVoid: false },
37
+ { name: 'dt', isVoid: false },
38
+ { name: 'fieldset', isVoid: false },
39
+ { name: 'figcaption', isVoid: false },
40
+ { name: 'figure', isVoid: false },
41
+ { name: 'footer', isVoid: false },
42
+ { name: 'form', isVoid: false },
43
+ { name: 'h1', isVoid: false },
44
+ { name: 'h2', isVoid: false },
45
+ { name: 'h3', isVoid: false },
46
+ { name: 'h4', isVoid: false },
47
+ { name: 'h5', isVoid: false },
48
+ { name: 'h6', isVoid: false },
49
+ { name: 'header', isVoid: false },
50
+ { name: 'hgroup', isVoid: false },
51
+ { name: 'hr', isVoid: true },
52
+ { name: 'input', isVoid: true },
53
+ { name: 'li', isVoid: false },
54
+ { name: 'main', isVoid: false },
55
+ { name: 'nav', isVoid: false },
56
+ { name: 'ol', isVoid: false },
57
+ { name: 'p', isVoid: false },
58
+ { name: 'section', isVoid: false },
59
+ { name: 'table', isVoid: false },
60
+ { name: 'tbody', isVoid: false },
61
+ { name: 'td', isVoid: false },
62
+ { name: 'textarea', isVoid: false },
63
+ { name: 'th', isVoid: false },
64
+ { name: 'thead', isVoid: false },
65
+ { name: 'tr', isVoid: false },
66
+ { name: 'ul', isVoid: false }
67
+ ];
68
+ const elementNamesToFormat = elementsToFormat.map(element => element.name).join('|');
69
+ // It is not the fastest way to format the HTML markup but the performance should be good enough.
70
+ const lines = input
71
+ // Add new line before and after `<tag>` and `</tag>`.
72
+ // It may separate individual elements with two new lines, but this will be fixed below.
73
+ .replace(new RegExp(`</?(${elementNamesToFormat})( .*?)?>`, 'g'), '\n$&\n')
74
+ // Divide input string into lines, which start with either an opening tag, a closing tag, or just a text.
75
+ .split('\n');
76
+ let indentCount = 0;
77
+ return lines
78
+ .filter(line => line.length)
79
+ .map(line => {
80
+ if (isNonVoidOpeningTag(line, elementsToFormat)) {
81
+ return indentLine(line, indentCount++);
82
+ }
83
+ if (isClosingTag(line, elementsToFormat)) {
84
+ return indentLine(line, --indentCount);
85
+ }
86
+ return indentLine(line, indentCount);
87
+ })
88
+ .join('\n');
98
89
  }
99
-
100
- // Checks, if an argument is an opening tag of a non-void element to be formatted.
101
- //
102
- // @param {String} line String to check.
103
- // @param {Array} elementsToFormat Elements to be formatted.
104
- // @param {String} elementsToFormat.name Element name.
105
- // @param {Boolean} elementsToFormat.isVoid Flag indicating whether element is a void one.
106
- // @returns {Boolean}
107
- function isNonVoidOpeningTag( line, elementsToFormat ) {
108
- return elementsToFormat.some( element => {
109
- if ( element.isVoid ) {
110
- return false;
111
- }
112
-
113
- if ( !new RegExp( `<${ element.name }( .*?)?>` ).test( line ) ) {
114
- return false;
115
- }
116
-
117
- return true;
118
- } );
90
+ /**
91
+ * Checks, if an argument is an opening tag of a non-void element to be formatted.
92
+ *
93
+ * @param line String to check.
94
+ * @param elementsToFormat Elements to be formatted.
95
+ */
96
+ function isNonVoidOpeningTag(line, elementsToFormat) {
97
+ return elementsToFormat.some(element => {
98
+ if (element.isVoid) {
99
+ return false;
100
+ }
101
+ if (!new RegExp(`<${element.name}( .*?)?>`).test(line)) {
102
+ return false;
103
+ }
104
+ return true;
105
+ });
119
106
  }
120
-
121
- // Checks, if an argument is a closing tag.
122
- //
123
- // @param {String} line String to check.
124
- // @param {Array} elementsToFormat Elements to be formatted.
125
- // @param {String} elementsToFormat.name Element name.
126
- // @param {Boolean} elementsToFormat.isVoid Flag indicating whether element is a void one.
127
- // @returns {Boolean}
128
- function isClosingTag( line, elementsToFormat ) {
129
- return elementsToFormat.some( element => {
130
- return new RegExp( `</${ element.name }>` ).test( line );
131
- } );
107
+ /**
108
+ * Checks, if an argument is a closing tag.
109
+ *
110
+ * @param line String to check.
111
+ * @param elementsToFormat Elements to be formatted.
112
+ */
113
+ function isClosingTag(line, elementsToFormat) {
114
+ return elementsToFormat.some(element => {
115
+ return new RegExp(`</${element.name}>`).test(line);
116
+ });
132
117
  }
133
-
134
- // Indents a line by a specified number of characters.
135
- //
136
- // @param {String} line Line to indent.
137
- // @param {Number} indentCount Number of characters to use for indentation.
138
- // @param {String} [indentChar] Indentation character(s). 4 spaces by default.
139
- // @returns {String}
140
- function indentLine( line, indentCount, indentChar = ' ' ) {
141
- // More about Math.max() here in https://github.com/ckeditor/ckeditor5/issues/10698.
142
- return `${ indentChar.repeat( Math.max( 0, indentCount ) ) }${ line }`;
118
+ /**
119
+ * Indents a line by a specified number of characters.
120
+ *
121
+ * @param line Line to indent.
122
+ * @param indentCount Number of characters to use for indentation.
123
+ * @param indentChar Indentation character(s). 4 spaces by default.
124
+ */
125
+ function indentLine(line, indentCount, indentChar = ' ') {
126
+ // More about Math.max() here in https://github.com/ckeditor/ckeditor5/issues/10698.
127
+ return `${indentChar.repeat(Math.max(0, indentCount))}${line}`;
143
128
  }