diff --git a/js/ckeditor/build/ckeditor.js b/js/ckeditor/build/ckeditor.js
index 6cc5c0c89..38175e029 100644
--- a/js/ckeditor/build/ckeditor.js
+++ b/js/ckeditor/build/ckeditor.js
@@ -1,154799 +1,7 @@
-(function(d){ const l = d['en'] = d['en'] || {}; l.dictionary=Object.assign( l.dictionary||{}, {"(may require Fn)":"(may require Fn)","%0 of %1":"%0 of %1",Accept:"Accept",Accessibility:"Accessibility","Accessibility help":"Accessibility help","Align cell text to the bottom":"Align cell text to the bottom","Align cell text to the center":"Align cell text to the center","Align cell text to the left":"Align cell text to the left","Align cell text to the middle":"Align cell text to the middle","Align cell text to the right":"Align cell text to the right","Align cell text to the top":"Align cell text to the top","Align center":"Align center","Align left":"Align left","Align right":"Align right","Align table to the left":"Align table to the left","Align table to the right":"Align table to the right",Alignment:"Alignment",Aquamarine:"Aquamarine",Background:"Background","Below, you can find a list of keyboard shortcuts that can be used in the editor.":"Below, you can find a list of keyboard shortcuts that can be used in the editor.",Big:"Big",Black:"Black","Block quote":"Block quote",Blue:"Blue","Blue marker":"Blue marker",Bold:"Bold","Bold text":"Bold text",Border:"Border","Break text":"Break text","Bulleted List":"Bulleted List","Bulleted list styles toolbar":"Bulleted list styles toolbar",Cancel:"Cancel","Caption for image: %0":"Caption for image: %0","Caption for the image":"Caption for the image","Cell properties":"Cell properties","Center table":"Center table","Centered image":"Centered image","Change image text alternative":"Change image text alternative","Choose heading":"Choose heading",Circle:"Circle",Clear:"Clear","Click to edit block":"Click to edit block",Close:"Close","Close contextual balloons, dropdowns, and dialogs":"Close contextual balloons, dropdowns, and dialogs",Code:"Code","Code block":"Code block",Color:"Color","Color picker":"Color picker",Column:"Column","Content editing keystrokes":"Content editing keystrokes","Copy selected content":"Copy selected content","Create link":"Create link",Custom:"Custom","Custom image size":"Custom image size",Dashed:"Dashed",Decimal:"Decimal","Decimal with leading zero":"Decimal with leading zero","Decrease indent":"Decrease indent","Decrease list item indent":"Decrease list item indent",Default:"Default","Delete column":"Delete column","Delete row":"Delete row","Dim grey":"Dim grey",Dimensions:"Dimensions",Disc:"Disc","Document colors":"Document colors",Dotted:"Dotted",Double:"Double",Downloadable:"Downloadable","Drag to move":"Drag to move","Dropdown toolbar":"Dropdown toolbar","Edit block":"Edit block","Edit link":"Edit link","Editor block content toolbar":"Editor block content toolbar","Editor contextual toolbar":"Editor contextual toolbar","Editor dialog":"Editor dialog","Editor editing area: %0":"Editor editing area: %0","Editor menu bar":"Editor menu bar","Editor toolbar":"Editor toolbar","Enter image caption":"Enter image caption","Enter table caption":"Enter table caption","Entering %0 code snippet":"Entering %0 code snippet","Entering a to-do list":"Entering a to-do list","Entering code snippet":"Entering code snippet","Error during image upload":"Error during image upload","Execute the currently focused button. Executing buttons that interact with the editor content moves the focus back to the content.":"Execute the currently focused button. Executing buttons that interact with the editor content moves the focus back to the content.","Font Background Color":"Font Background Color","Font Color":"Font Color","Font Family":"Font Family","Font Size":"Font Size","Full size image":"Full size image",Green:"Green","Green marker":"Green marker","Green pen":"Green pen",Grey:"Grey",Groove:"Groove","Header column":"Header column","Header row":"Header row",Heading:"Heading","Heading 1":"Heading 1","Heading 2":"Heading 2","Heading 3":"Heading 3","Heading 4":"Heading 4","Heading 5":"Heading 5","Heading 6":"Heading 6",Height:"Height","Help Contents. To close this dialog press ESC.":"Help Contents. To close this dialog press ESC.",HEX:"HEX",Highlight:"Highlight","Horizontal line":"Horizontal line","Horizontal text alignment toolbar":"Horizontal text alignment toolbar","HTML object":"HTML object",Huge:"Huge","Image from computer":"Image from computer","Image resize list":"Image resize list","Image toolbar":"Image toolbar","Image upload complete":"Image upload complete","image widget":"image widget","In line":"In line","Increase indent":"Increase indent","Increase list item indent":"Increase list item indent",Insert:"Insert","Insert a hard break (a new paragraph)":"Insert a hard break (a new paragraph)","Insert a new paragraph directly after a widget":"Insert a new paragraph directly after a widget","Insert a new paragraph directly before a widget":"Insert a new paragraph directly before a widget","Insert a new table row (when in the last cell of a table)":"Insert a new table row (when in the last cell of a table)","Insert a soft break (a <br> element)":"Insert a soft break (a <br> element)","Insert code block":"Insert code block","Insert column left":"Insert column left","Insert column right":"Insert column right","Insert image":"Insert image","Insert image via URL":"Insert image via URL","Insert paragraph after block":"Insert paragraph after block","Insert paragraph before block":"Insert paragraph before block","Insert row above":"Insert row above","Insert row below":"Insert row below","Insert table":"Insert table",Inset:"Inset","Invalid start index value.":"Invalid start index value.",Italic:"Italic","Italic text":"Italic text",Justify:"Justify","Justify cell text":"Justify cell text","Keystrokes that can be used in a list":"Keystrokes that can be used in a list","Keystrokes that can be used in a table cell":"Keystrokes that can be used in a table cell","Keystrokes that can be used when a widget is selected (for example: image, table, etc.)":"Keystrokes that can be used when a widget is selected (for example: image, table, etc.)","Leaving %0 code snippet":"Leaving %0 code snippet","Leaving a to-do list":"Leaving a to-do list","Leaving code snippet":"Leaving code snippet","Left aligned image":"Left aligned image","Light blue":"Light blue","Light green":"Light green","Light grey":"Light grey",Link:"Link","Link image":"Link image","Link URL":"Link URL","Link URL must not be empty.":"Link URL must not be empty.","List properties":"List properties","Lower-latin":"Lower-latin","Lower–roman":"Lower–roman",MENU_BAR_MENU_EDIT:"Edit",MENU_BAR_MENU_FILE:"File",MENU_BAR_MENU_FONT:"Font",MENU_BAR_MENU_FORMAT:"Format",MENU_BAR_MENU_HELP:"Help",MENU_BAR_MENU_INSERT:"Insert",MENU_BAR_MENU_TEXT:"Text",MENU_BAR_MENU_TOOLS:"Tools",MENU_BAR_MENU_VIEW:"View","Merge cell down":"Merge cell down","Merge cell left":"Merge cell left","Merge cell right":"Merge cell right","Merge cell up":"Merge cell up","Merge cells":"Merge cells","Move focus between form fields (inputs, buttons, etc.)":"Move focus between form fields (inputs, buttons, etc.)","Move focus in and out of an active dialog window":"Move focus in and out of an active dialog window","Move focus to the menu bar, navigate between menu bars":"Move focus to the menu bar, navigate between menu bars","Move focus to the toolbar, navigate between toolbars":"Move focus to the toolbar, navigate between toolbars","Move out of a link":"Move out of a link","Move out of an inline code style":"Move out of an inline code style","Move the caret to allow typing directly after a widget":"Move the caret to allow typing directly after a widget","Move the caret to allow typing directly before a widget":"Move the caret to allow typing directly before a widget","Move the selection to the next cell":"Move the selection to the next cell","Move the selection to the previous cell":"Move the selection to the previous cell","Navigate through the table":"Navigate through the table","Navigate through the toolbar or menu bar":"Navigate through the toolbar or menu bar",Next:"Next","No results found":"No results found","No searchable items":"No searchable items",None:"None","Numbered List":"Numbered List","Numbered list styles toolbar":"Numbered list styles toolbar","Open in a new tab":"Open in a new tab","Open link in new tab":"Open link in new tab","Open the accessibility help dialog":"Open the accessibility help dialog",Orange:"Orange",Original:"Original",Outset:"Outset",Padding:"Padding",Paragraph:"Paragraph","Paste content":"Paste content","Paste content as plain text":"Paste content as plain text","Pink marker":"Pink marker","Plain text":"Plain text","Please enter a valid color (e.g. \"ff0000\").":"Please enter a valid color (e.g. \"ff0000\").","Press %0 for help.":"Press %0 for help.","Press Enter to type after or press Shift + Enter to type before the widget":"Press Enter to type after or press Shift + Enter to type before the widget",Previous:"Previous",Purple:"Purple",Red:"Red","Red pen":"Red pen",Redo:"Redo","Remove color":"Remove color","Remove Format":"Remove Format","Remove highlight":"Remove highlight","Replace from computer":"Replace from computer","Replace image":"Replace image","Replace image from computer":"Replace image from computer","Resize image":"Resize image","Resize image (in %0)":"Resize image (in %0)","Resize image to %0":"Resize image to %0","Resize image to the original size":"Resize image to the original size","Restore default":"Restore default","Reversed order":"Reversed order","Rich Text Editor":"Rich Text Editor",Ridge:"Ridge","Right aligned image":"Right aligned image",Row:"Row",Save:"Save","Select all":"Select all","Select column":"Select column","Select row":"Select row","Show more items":"Show more items","Show source":"Show source","Side image":"Side image",Small:"Small",Solid:"Solid",Source:"Source","Split cell horizontally":"Split cell horizontally","Split cell vertically":"Split cell vertically",Square:"Square","Start at":"Start at","Start index must be greater than 0.":"Start index must be greater than 0.",Strikethrough:"Strikethrough","Strikethrough text":"Strikethrough text",Style:"Style",Subscript:"Subscript",Superscript:"Superscript",Table:"Table","Table alignment toolbar":"Table alignment toolbar","Table cell text alignment":"Table cell text alignment","Table properties":"Table properties","Table toolbar":"Table toolbar","Text alignment":"Text alignment","Text alignment toolbar":"Text alignment toolbar","Text alternative":"Text alternative","Text highlight toolbar":"Text highlight toolbar","The color is invalid. Try \"#FF0000\" or \"rgb(255,0,0)\" or \"red\".":"The color is invalid. Try \"#FF0000\" or \"rgb(255,0,0)\" or \"red\".","The value is invalid. Try \"10px\" or \"2em\" or simply \"2\".":"The value is invalid. Try \"10px\" or \"2em\" or simply \"2\".","The value must not be empty.":"The value must not be empty.","The value should be a plain number.":"The value should be a plain number.","These keyboard shortcuts allow for quick access to content editing features.":"These keyboard shortcuts allow for quick access to content editing features.","This link has no URL":"This link has no URL",Tiny:"Tiny","To-do List":"To-do List","Toggle caption off":"Toggle caption off","Toggle caption on":"Toggle caption on","Toggle the circle list style":"Toggle the circle list style","Toggle the decimal list style":"Toggle the decimal list style","Toggle the decimal with leading zero list style":"Toggle the decimal with leading zero list style","Toggle the disc list style":"Toggle the disc list style","Toggle the lower–latin list style":"Toggle the lower–latin list style","Toggle the lower–roman list style":"Toggle the lower–roman list style","Toggle the square list style":"Toggle the square list style","Toggle the upper–latin list style":"Toggle the upper–latin list style","Toggle the upper–roman list style":"Toggle the upper–roman list style",Turquoise:"Turquoise","Type or paste your content here.":"Type or paste your content here.","Type your title":"Type your title",Underline:"Underline","Underline text":"Underline text",Undo:"Undo",Unlink:"Unlink",Update:"Update","Update image URL":"Update image URL","Upload failed":"Upload failed","Upload from computer":"Upload from computer","Upload image from computer":"Upload image from computer","Upload in progress":"Upload in progress","Uploading image":"Uploading image","Upper-latin":"Upper-latin","Upper-roman":"Upper-roman","Use the following keystrokes for more efficient navigation in the CKEditor 5 user interface.":"Use the following keystrokes for more efficient navigation in the CKEditor 5 user interface.","User interface and content navigation keystrokes":"User interface and content navigation keystrokes","Vertical text alignment toolbar":"Vertical text alignment toolbar",White:"White","Widget toolbar":"Widget toolbar",Width:"Width","Wrap text":"Wrap text",Yellow:"Yellow","Yellow marker":"Yellow marker"} );})(window.CKEDITOR_TRANSLATIONS||(window.CKEDITOR_TRANSLATIONS={}));
+!function(t){const e=t.en=t.en||{};e.dictionary=Object.assign(e.dictionary||{},{"(may require Fn)":"(may require Fn)","%0 of %1":"%0 of %1",Accept:"Accept",Accessibility:"Accessibility","Accessibility help":"Accessibility help","Align cell text to the bottom":"Align cell text to the bottom","Align cell text to the center":"Align cell text to the center","Align cell text to the left":"Align cell text to the left","Align cell text to the middle":"Align cell text to the middle","Align cell text to the right":"Align cell text to the right","Align cell text to the top":"Align cell text to the top","Align center":"Align center","Align left":"Align left","Align right":"Align right","Align table to the left":"Align table to the left","Align table to the right":"Align table to the right",Alignment:"Alignment",Aquamarine:"Aquamarine",Background:"Background","Below, you can find a list of keyboard shortcuts that can be used in the editor.":"Below, you can find a list of keyboard shortcuts that can be used in the editor.",Big:"Big",Black:"Black","Block quote":"Block quote",Blue:"Blue","Blue marker":"Blue marker",Bold:"Bold","Bold text":"Bold text",Border:"Border","Break text":"Break text","Bulleted List":"Bulleted List","Bulleted list styles toolbar":"Bulleted list styles toolbar",Cancel:"Cancel","Caption for image: %0":"Caption for image: %0","Caption for the image":"Caption for the image","Cell properties":"Cell properties","Center table":"Center table","Centered image":"Centered image","Change image text alternative":"Change image text alternative","Choose heading":"Choose heading",Circle:"Circle",Clear:"Clear","Click to edit block":"Click to edit block",Close:"Close","Close contextual balloons, dropdowns, and dialogs":"Close contextual balloons, dropdowns, and dialogs",Code:"Code","Code block":"Code block",Color:"Color","Color picker":"Color picker",Column:"Column","Content editing keystrokes":"Content editing keystrokes","Copy selected content":"Copy selected content","Create link":"Create link",Custom:"Custom","Custom image size":"Custom image size",Dashed:"Dashed",Decimal:"Decimal","Decimal with leading zero":"Decimal with leading zero","Decrease indent":"Decrease indent","Decrease list item indent":"Decrease list item indent",Default:"Default","Delete column":"Delete column","Delete row":"Delete row","Dim grey":"Dim grey",Dimensions:"Dimensions",Disc:"Disc","Document colors":"Document colors",Dotted:"Dotted",Double:"Double",Downloadable:"Downloadable","Drag to move":"Drag to move","Dropdown toolbar":"Dropdown toolbar","Edit block":"Edit block","Edit link":"Edit link","Editor block content toolbar":"Editor block content toolbar","Editor contextual toolbar":"Editor contextual toolbar","Editor dialog":"Editor dialog","Editor editing area: %0":"Editor editing area: %0","Editor menu bar":"Editor menu bar","Editor toolbar":"Editor toolbar","Enter image caption":"Enter image caption","Enter table caption":"Enter table caption","Entering %0 code snippet":"Entering %0 code snippet","Entering a to-do list":"Entering a to-do list","Entering code snippet":"Entering code snippet","Error during image upload":"Error during image upload","Execute the currently focused button. Executing buttons that interact with the editor content moves the focus back to the content.":"Execute the currently focused button. Executing buttons that interact with the editor content moves the focus back to the content.","Font Background Color":"Font Background Color","Font Color":"Font Color","Font Family":"Font Family","Font Size":"Font Size","Full size image":"Full size image",Green:"Green","Green marker":"Green marker","Green pen":"Green pen",Grey:"Grey",Groove:"Groove","Header column":"Header column","Header row":"Header row",Heading:"Heading","Heading 1":"Heading 1","Heading 2":"Heading 2","Heading 3":"Heading 3","Heading 4":"Heading 4","Heading 5":"Heading 5","Heading 6":"Heading 6",Height:"Height","Help Contents. To close this dialog press ESC.":"Help Contents. To close this dialog press ESC.",HEX:"HEX",Highlight:"Highlight","Horizontal line":"Horizontal line","Horizontal text alignment toolbar":"Horizontal text alignment toolbar","HTML object":"HTML object",Huge:"Huge","Image from computer":"Image from computer","Image resize list":"Image resize list","Image toolbar":"Image toolbar","Image upload complete":"Image upload complete","image widget":"image widget","In line":"In line","Increase indent":"Increase indent","Increase list item indent":"Increase list item indent",Insert:"Insert","Insert a hard break (a new paragraph)":"Insert a hard break (a new paragraph)","Insert a new paragraph directly after a widget":"Insert a new paragraph directly after a widget","Insert a new paragraph directly before a widget":"Insert a new paragraph directly before a widget","Insert a new table row (when in the last cell of a table)":"Insert a new table row (when in the last cell of a table)","Insert a soft break (a <br> element)":"Insert a soft break (a <br> element)","Insert code block":"Insert code block","Insert column left":"Insert column left","Insert column right":"Insert column right","Insert image":"Insert image","Insert image via URL":"Insert image via URL","Insert paragraph after block":"Insert paragraph after block","Insert paragraph before block":"Insert paragraph before block","Insert row above":"Insert row above","Insert row below":"Insert row below","Insert table":"Insert table",Inset:"Inset","Invalid start index value.":"Invalid start index value.",Italic:"Italic","Italic text":"Italic text",Justify:"Justify","Justify cell text":"Justify cell text","Keystrokes that can be used in a list":"Keystrokes that can be used in a list","Keystrokes that can be used in a table cell":"Keystrokes that can be used in a table cell","Keystrokes that can be used when a widget is selected (for example: image, table, etc.)":"Keystrokes that can be used when a widget is selected (for example: image, table, etc.)","Leaving %0 code snippet":"Leaving %0 code snippet","Leaving a to-do list":"Leaving a to-do list","Leaving code snippet":"Leaving code snippet","Left aligned image":"Left aligned image","Light blue":"Light blue","Light green":"Light green","Light grey":"Light grey",Link:"Link","Link image":"Link image","Link URL":"Link URL","Link URL must not be empty.":"Link URL must not be empty.","List properties":"List properties","Lower-latin":"Lower-latin","Lower–roman":"Lower–roman",MENU_BAR_MENU_EDIT:"Edit",MENU_BAR_MENU_FILE:"File",MENU_BAR_MENU_FONT:"Font",MENU_BAR_MENU_FORMAT:"Format",MENU_BAR_MENU_HELP:"Help",MENU_BAR_MENU_INSERT:"Insert",MENU_BAR_MENU_TEXT:"Text",MENU_BAR_MENU_TOOLS:"Tools",MENU_BAR_MENU_VIEW:"View","Merge cell down":"Merge cell down","Merge cell left":"Merge cell left","Merge cell right":"Merge cell right","Merge cell up":"Merge cell up","Merge cells":"Merge cells","Move focus between form fields (inputs, buttons, etc.)":"Move focus between form fields (inputs, buttons, etc.)","Move focus in and out of an active dialog window":"Move focus in and out of an active dialog window","Move focus to the menu bar, navigate between menu bars":"Move focus to the menu bar, navigate between menu bars","Move focus to the toolbar, navigate between toolbars":"Move focus to the toolbar, navigate between toolbars","Move out of a link":"Move out of a link","Move out of an inline code style":"Move out of an inline code style","Move the caret to allow typing directly after a widget":"Move the caret to allow typing directly after a widget","Move the caret to allow typing directly before a widget":"Move the caret to allow typing directly before a widget","Move the selection to the next cell":"Move the selection to the next cell","Move the selection to the previous cell":"Move the selection to the previous cell","Navigate through the table":"Navigate through the table","Navigate through the toolbar or menu bar":"Navigate through the toolbar or menu bar",Next:"Next","No results found":"No results found","No searchable items":"No searchable items",None:"None","Numbered List":"Numbered List","Numbered list styles toolbar":"Numbered list styles toolbar","Open in a new tab":"Open in a new tab","Open link in new tab":"Open link in new tab","Open the accessibility help dialog":"Open the accessibility help dialog",Orange:"Orange",Original:"Original",Outset:"Outset",Padding:"Padding",Paragraph:"Paragraph","Paste content":"Paste content","Paste content as plain text":"Paste content as plain text","Pink marker":"Pink marker","Plain text":"Plain text",'Please enter a valid color (e.g. "ff0000").':'Please enter a valid color (e.g. "ff0000").',"Press %0 for help.":"Press %0 for help.","Press Enter to type after or press Shift + Enter to type before the widget":"Press Enter to type after or press Shift + Enter to type before the widget",Previous:"Previous",Purple:"Purple",Red:"Red","Red pen":"Red pen",Redo:"Redo","Remove color":"Remove color","Remove Format":"Remove Format","Remove highlight":"Remove highlight","Replace from computer":"Replace from computer","Replace image":"Replace image","Replace image from computer":"Replace image from computer","Resize image":"Resize image","Resize image (in %0)":"Resize image (in %0)","Resize image to %0":"Resize image to %0","Resize image to the original size":"Resize image to the original size","Restore default":"Restore default","Reversed order":"Reversed order","Rich Text Editor":"Rich Text Editor",Ridge:"Ridge","Right aligned image":"Right aligned image",Row:"Row",Save:"Save","Select all":"Select all","Select column":"Select column","Select row":"Select row","Show more items":"Show more items","Show source":"Show source","Side image":"Side image",Small:"Small",Solid:"Solid",Source:"Source","Split cell horizontally":"Split cell horizontally","Split cell vertically":"Split cell vertically",Square:"Square","Start at":"Start at","Start index must be greater than 0.":"Start index must be greater than 0.",Strikethrough:"Strikethrough","Strikethrough text":"Strikethrough text",Style:"Style",Subscript:"Subscript",Superscript:"Superscript",Table:"Table","Table alignment toolbar":"Table alignment toolbar","Table cell text alignment":"Table cell text alignment","Table properties":"Table properties","Table toolbar":"Table toolbar","Text alignment":"Text alignment","Text alignment toolbar":"Text alignment toolbar","Text alternative":"Text alternative","Text highlight toolbar":"Text highlight toolbar",'The color is invalid. Try "#FF0000" or "rgb(255,0,0)" or "red".':'The color is invalid. Try "#FF0000" or "rgb(255,0,0)" or "red".','The value is invalid. Try "10px" or "2em" or simply "2".':'The value is invalid. Try "10px" or "2em" or simply "2".',"The value must not be empty.":"The value must not be empty.","The value should be a plain number.":"The value should be a plain number.","These keyboard shortcuts allow for quick access to content editing features.":"These keyboard shortcuts allow for quick access to content editing features.","This link has no URL":"This link has no URL",Tiny:"Tiny","To-do List":"To-do List","Toggle caption off":"Toggle caption off","Toggle caption on":"Toggle caption on","Toggle the circle list style":"Toggle the circle list style","Toggle the decimal list style":"Toggle the decimal list style","Toggle the decimal with leading zero list style":"Toggle the decimal with leading zero list style","Toggle the disc list style":"Toggle the disc list style","Toggle the lower–latin list style":"Toggle the lower–latin list style","Toggle the lower–roman list style":"Toggle the lower–roman list style","Toggle the square list style":"Toggle the square list style","Toggle the upper–latin list style":"Toggle the upper–latin list style","Toggle the upper–roman list style":"Toggle the upper–roman list style",Turquoise:"Turquoise","Type or paste your content here.":"Type or paste your content here.","Type your title":"Type your title",Underline:"Underline","Underline text":"Underline text",Undo:"Undo",Unlink:"Unlink",Update:"Update","Update image URL":"Update image URL","Upload failed":"Upload failed","Upload from computer":"Upload from computer","Upload image from computer":"Upload image from computer","Upload in progress":"Upload in progress","Uploading image":"Uploading image","Upper-latin":"Upper-latin","Upper-roman":"Upper-roman","Use the following keystrokes for more efficient navigation in the CKEditor 5 user interface.":"Use the following keystrokes for more efficient navigation in the CKEditor 5 user interface.","User interface and content navigation keystrokes":"User interface and content navigation keystrokes","Vertical text alignment toolbar":"Vertical text alignment toolbar",White:"White","Widget toolbar":"Widget toolbar",Width:"Width","Wrap text":"Wrap text",Yellow:"Yellow","Yellow marker":"Yellow marker"})}(window.CKEDITOR_TRANSLATIONS||(window.CKEDITOR_TRANSLATIONS={})),
/*!
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md.
*/
-(function webpackUniversalModuleDefinition(root, factory) {
- if(typeof exports === 'object' && typeof module === 'object')
- module.exports = factory();
- else if(typeof define === 'function' && define.amd)
- define([], factory);
- else if(typeof exports === 'object')
- exports["ClassicEditor"] = factory();
- else
- root["ClassicEditor"] = factory();
-})(self, () => {
-return /******/ (() => { // webpackBootstrap
-/******/ var __webpack_modules__ = ({
-
-/***/ "./node_modules/color-convert/conversions.js":
-/*!***************************************************!*\
- !*** ./node_modules/color-convert/conversions.js ***!
- \***************************************************/
-/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
-
-/* MIT license */
-/* eslint-disable no-mixed-operators */
-const cssKeywords = __webpack_require__(/*! color-name */ "./node_modules/color-name/index.js");
-
-// NOTE: conversions should only return primitive values (i.e. arrays, or
-// values that give correct `typeof` results).
-// do not use box values types (i.e. Number(), String(), etc.)
-
-const reverseKeywords = {};
-for (const key of Object.keys(cssKeywords)) {
- reverseKeywords[cssKeywords[key]] = key;
-}
-
-const convert = {
- rgb: {channels: 3, labels: 'rgb'},
- hsl: {channels: 3, labels: 'hsl'},
- hsv: {channels: 3, labels: 'hsv'},
- hwb: {channels: 3, labels: 'hwb'},
- cmyk: {channels: 4, labels: 'cmyk'},
- xyz: {channels: 3, labels: 'xyz'},
- lab: {channels: 3, labels: 'lab'},
- lch: {channels: 3, labels: 'lch'},
- hex: {channels: 1, labels: ['hex']},
- keyword: {channels: 1, labels: ['keyword']},
- ansi16: {channels: 1, labels: ['ansi16']},
- ansi256: {channels: 1, labels: ['ansi256']},
- hcg: {channels: 3, labels: ['h', 'c', 'g']},
- apple: {channels: 3, labels: ['r16', 'g16', 'b16']},
- gray: {channels: 1, labels: ['gray']}
-};
-
-module.exports = convert;
-
-// Hide .channels and .labels properties
-for (const model of Object.keys(convert)) {
- if (!('channels' in convert[model])) {
- throw new Error('missing channels property: ' + model);
- }
-
- if (!('labels' in convert[model])) {
- throw new Error('missing channel labels property: ' + model);
- }
-
- if (convert[model].labels.length !== convert[model].channels) {
- throw new Error('channel and label counts mismatch: ' + model);
- }
-
- const {channels, labels} = convert[model];
- delete convert[model].channels;
- delete convert[model].labels;
- Object.defineProperty(convert[model], 'channels', {value: channels});
- Object.defineProperty(convert[model], 'labels', {value: labels});
-}
-
-convert.rgb.hsl = function (rgb) {
- const r = rgb[0] / 255;
- const g = rgb[1] / 255;
- const b = rgb[2] / 255;
- const min = Math.min(r, g, b);
- const max = Math.max(r, g, b);
- const delta = max - min;
- let h;
- let s;
-
- if (max === min) {
- h = 0;
- } else if (r === max) {
- h = (g - b) / delta;
- } else if (g === max) {
- h = 2 + (b - r) / delta;
- } else if (b === max) {
- h = 4 + (r - g) / delta;
- }
-
- h = Math.min(h * 60, 360);
-
- if (h < 0) {
- h += 360;
- }
-
- const l = (min + max) / 2;
-
- if (max === min) {
- s = 0;
- } else if (l <= 0.5) {
- s = delta / (max + min);
- } else {
- s = delta / (2 - max - min);
- }
-
- return [h, s * 100, l * 100];
-};
-
-convert.rgb.hsv = function (rgb) {
- let rdif;
- let gdif;
- let bdif;
- let h;
- let s;
-
- const r = rgb[0] / 255;
- const g = rgb[1] / 255;
- const b = rgb[2] / 255;
- const v = Math.max(r, g, b);
- const diff = v - Math.min(r, g, b);
- const diffc = function (c) {
- return (v - c) / 6 / diff + 1 / 2;
- };
-
- if (diff === 0) {
- h = 0;
- s = 0;
- } else {
- s = diff / v;
- rdif = diffc(r);
- gdif = diffc(g);
- bdif = diffc(b);
-
- if (r === v) {
- h = bdif - gdif;
- } else if (g === v) {
- h = (1 / 3) + rdif - bdif;
- } else if (b === v) {
- h = (2 / 3) + gdif - rdif;
- }
-
- if (h < 0) {
- h += 1;
- } else if (h > 1) {
- h -= 1;
- }
- }
-
- return [
- h * 360,
- s * 100,
- v * 100
- ];
-};
-
-convert.rgb.hwb = function (rgb) {
- const r = rgb[0];
- const g = rgb[1];
- let b = rgb[2];
- const h = convert.rgb.hsl(rgb)[0];
- const w = 1 / 255 * Math.min(r, Math.min(g, b));
-
- b = 1 - 1 / 255 * Math.max(r, Math.max(g, b));
-
- return [h, w * 100, b * 100];
-};
-
-convert.rgb.cmyk = function (rgb) {
- const r = rgb[0] / 255;
- const g = rgb[1] / 255;
- const b = rgb[2] / 255;
-
- const k = Math.min(1 - r, 1 - g, 1 - b);
- const c = (1 - r - k) / (1 - k) || 0;
- const m = (1 - g - k) / (1 - k) || 0;
- const y = (1 - b - k) / (1 - k) || 0;
-
- return [c * 100, m * 100, y * 100, k * 100];
-};
-
-function comparativeDistance(x, y) {
- /*
- See https://en.m.wikipedia.org/wiki/Euclidean_distance#Squared_Euclidean_distance
- */
- return (
- ((x[0] - y[0]) ** 2) +
- ((x[1] - y[1]) ** 2) +
- ((x[2] - y[2]) ** 2)
- );
-}
-
-convert.rgb.keyword = function (rgb) {
- const reversed = reverseKeywords[rgb];
- if (reversed) {
- return reversed;
- }
-
- let currentClosestDistance = Infinity;
- let currentClosestKeyword;
-
- for (const keyword of Object.keys(cssKeywords)) {
- const value = cssKeywords[keyword];
-
- // Compute comparative distance
- const distance = comparativeDistance(rgb, value);
-
- // Check if its less, if so set as closest
- if (distance < currentClosestDistance) {
- currentClosestDistance = distance;
- currentClosestKeyword = keyword;
- }
- }
-
- return currentClosestKeyword;
-};
-
-convert.keyword.rgb = function (keyword) {
- return cssKeywords[keyword];
-};
-
-convert.rgb.xyz = function (rgb) {
- let r = rgb[0] / 255;
- let g = rgb[1] / 255;
- let b = rgb[2] / 255;
-
- // Assume sRGB
- r = r > 0.04045 ? (((r + 0.055) / 1.055) ** 2.4) : (r / 12.92);
- g = g > 0.04045 ? (((g + 0.055) / 1.055) ** 2.4) : (g / 12.92);
- b = b > 0.04045 ? (((b + 0.055) / 1.055) ** 2.4) : (b / 12.92);
-
- const x = (r * 0.4124) + (g * 0.3576) + (b * 0.1805);
- const y = (r * 0.2126) + (g * 0.7152) + (b * 0.0722);
- const z = (r * 0.0193) + (g * 0.1192) + (b * 0.9505);
-
- return [x * 100, y * 100, z * 100];
-};
-
-convert.rgb.lab = function (rgb) {
- const xyz = convert.rgb.xyz(rgb);
- let x = xyz[0];
- let y = xyz[1];
- let z = xyz[2];
-
- x /= 95.047;
- y /= 100;
- z /= 108.883;
-
- x = x > 0.008856 ? (x ** (1 / 3)) : (7.787 * x) + (16 / 116);
- y = y > 0.008856 ? (y ** (1 / 3)) : (7.787 * y) + (16 / 116);
- z = z > 0.008856 ? (z ** (1 / 3)) : (7.787 * z) + (16 / 116);
-
- const l = (116 * y) - 16;
- const a = 500 * (x - y);
- const b = 200 * (y - z);
-
- return [l, a, b];
-};
-
-convert.hsl.rgb = function (hsl) {
- const h = hsl[0] / 360;
- const s = hsl[1] / 100;
- const l = hsl[2] / 100;
- let t2;
- let t3;
- let val;
-
- if (s === 0) {
- val = l * 255;
- return [val, val, val];
- }
-
- if (l < 0.5) {
- t2 = l * (1 + s);
- } else {
- t2 = l + s - l * s;
- }
-
- const t1 = 2 * l - t2;
-
- const rgb = [0, 0, 0];
- for (let i = 0; i < 3; i++) {
- t3 = h + 1 / 3 * -(i - 1);
- if (t3 < 0) {
- t3++;
- }
-
- if (t3 > 1) {
- t3--;
- }
-
- if (6 * t3 < 1) {
- val = t1 + (t2 - t1) * 6 * t3;
- } else if (2 * t3 < 1) {
- val = t2;
- } else if (3 * t3 < 2) {
- val = t1 + (t2 - t1) * (2 / 3 - t3) * 6;
- } else {
- val = t1;
- }
-
- rgb[i] = val * 255;
- }
-
- return rgb;
-};
-
-convert.hsl.hsv = function (hsl) {
- const h = hsl[0];
- let s = hsl[1] / 100;
- let l = hsl[2] / 100;
- let smin = s;
- const lmin = Math.max(l, 0.01);
-
- l *= 2;
- s *= (l <= 1) ? l : 2 - l;
- smin *= lmin <= 1 ? lmin : 2 - lmin;
- const v = (l + s) / 2;
- const sv = l === 0 ? (2 * smin) / (lmin + smin) : (2 * s) / (l + s);
-
- return [h, sv * 100, v * 100];
-};
-
-convert.hsv.rgb = function (hsv) {
- const h = hsv[0] / 60;
- const s = hsv[1] / 100;
- let v = hsv[2] / 100;
- const hi = Math.floor(h) % 6;
-
- const f = h - Math.floor(h);
- const p = 255 * v * (1 - s);
- const q = 255 * v * (1 - (s * f));
- const t = 255 * v * (1 - (s * (1 - f)));
- v *= 255;
-
- switch (hi) {
- case 0:
- return [v, t, p];
- case 1:
- return [q, v, p];
- case 2:
- return [p, v, t];
- case 3:
- return [p, q, v];
- case 4:
- return [t, p, v];
- case 5:
- return [v, p, q];
- }
-};
-
-convert.hsv.hsl = function (hsv) {
- const h = hsv[0];
- const s = hsv[1] / 100;
- const v = hsv[2] / 100;
- const vmin = Math.max(v, 0.01);
- let sl;
- let l;
-
- l = (2 - s) * v;
- const lmin = (2 - s) * vmin;
- sl = s * vmin;
- sl /= (lmin <= 1) ? lmin : 2 - lmin;
- sl = sl || 0;
- l /= 2;
-
- return [h, sl * 100, l * 100];
-};
-
-// http://dev.w3.org/csswg/css-color/#hwb-to-rgb
-convert.hwb.rgb = function (hwb) {
- const h = hwb[0] / 360;
- let wh = hwb[1] / 100;
- let bl = hwb[2] / 100;
- const ratio = wh + bl;
- let f;
-
- // Wh + bl cant be > 1
- if (ratio > 1) {
- wh /= ratio;
- bl /= ratio;
- }
-
- const i = Math.floor(6 * h);
- const v = 1 - bl;
- f = 6 * h - i;
-
- if ((i & 0x01) !== 0) {
- f = 1 - f;
- }
-
- const n = wh + f * (v - wh); // Linear interpolation
-
- let r;
- let g;
- let b;
- /* eslint-disable max-statements-per-line,no-multi-spaces */
- switch (i) {
- default:
- case 6:
- case 0: r = v; g = n; b = wh; break;
- case 1: r = n; g = v; b = wh; break;
- case 2: r = wh; g = v; b = n; break;
- case 3: r = wh; g = n; b = v; break;
- case 4: r = n; g = wh; b = v; break;
- case 5: r = v; g = wh; b = n; break;
- }
- /* eslint-enable max-statements-per-line,no-multi-spaces */
-
- return [r * 255, g * 255, b * 255];
-};
-
-convert.cmyk.rgb = function (cmyk) {
- const c = cmyk[0] / 100;
- const m = cmyk[1] / 100;
- const y = cmyk[2] / 100;
- const k = cmyk[3] / 100;
-
- const r = 1 - Math.min(1, c * (1 - k) + k);
- const g = 1 - Math.min(1, m * (1 - k) + k);
- const b = 1 - Math.min(1, y * (1 - k) + k);
-
- return [r * 255, g * 255, b * 255];
-};
-
-convert.xyz.rgb = function (xyz) {
- const x = xyz[0] / 100;
- const y = xyz[1] / 100;
- const z = xyz[2] / 100;
- let r;
- let g;
- let b;
-
- r = (x * 3.2406) + (y * -1.5372) + (z * -0.4986);
- g = (x * -0.9689) + (y * 1.8758) + (z * 0.0415);
- b = (x * 0.0557) + (y * -0.2040) + (z * 1.0570);
-
- // Assume sRGB
- r = r > 0.0031308
- ? ((1.055 * (r ** (1.0 / 2.4))) - 0.055)
- : r * 12.92;
-
- g = g > 0.0031308
- ? ((1.055 * (g ** (1.0 / 2.4))) - 0.055)
- : g * 12.92;
-
- b = b > 0.0031308
- ? ((1.055 * (b ** (1.0 / 2.4))) - 0.055)
- : b * 12.92;
-
- r = Math.min(Math.max(0, r), 1);
- g = Math.min(Math.max(0, g), 1);
- b = Math.min(Math.max(0, b), 1);
-
- return [r * 255, g * 255, b * 255];
-};
-
-convert.xyz.lab = function (xyz) {
- let x = xyz[0];
- let y = xyz[1];
- let z = xyz[2];
-
- x /= 95.047;
- y /= 100;
- z /= 108.883;
-
- x = x > 0.008856 ? (x ** (1 / 3)) : (7.787 * x) + (16 / 116);
- y = y > 0.008856 ? (y ** (1 / 3)) : (7.787 * y) + (16 / 116);
- z = z > 0.008856 ? (z ** (1 / 3)) : (7.787 * z) + (16 / 116);
-
- const l = (116 * y) - 16;
- const a = 500 * (x - y);
- const b = 200 * (y - z);
-
- return [l, a, b];
-};
-
-convert.lab.xyz = function (lab) {
- const l = lab[0];
- const a = lab[1];
- const b = lab[2];
- let x;
- let y;
- let z;
-
- y = (l + 16) / 116;
- x = a / 500 + y;
- z = y - b / 200;
-
- const y2 = y ** 3;
- const x2 = x ** 3;
- const z2 = z ** 3;
- y = y2 > 0.008856 ? y2 : (y - 16 / 116) / 7.787;
- x = x2 > 0.008856 ? x2 : (x - 16 / 116) / 7.787;
- z = z2 > 0.008856 ? z2 : (z - 16 / 116) / 7.787;
-
- x *= 95.047;
- y *= 100;
- z *= 108.883;
-
- return [x, y, z];
-};
-
-convert.lab.lch = function (lab) {
- const l = lab[0];
- const a = lab[1];
- const b = lab[2];
- let h;
-
- const hr = Math.atan2(b, a);
- h = hr * 360 / 2 / Math.PI;
-
- if (h < 0) {
- h += 360;
- }
-
- const c = Math.sqrt(a * a + b * b);
-
- return [l, c, h];
-};
-
-convert.lch.lab = function (lch) {
- const l = lch[0];
- const c = lch[1];
- const h = lch[2];
-
- const hr = h / 360 * 2 * Math.PI;
- const a = c * Math.cos(hr);
- const b = c * Math.sin(hr);
-
- return [l, a, b];
-};
-
-convert.rgb.ansi16 = function (args, saturation = null) {
- const [r, g, b] = args;
- let value = saturation === null ? convert.rgb.hsv(args)[2] : saturation; // Hsv -> ansi16 optimization
-
- value = Math.round(value / 50);
-
- if (value === 0) {
- return 30;
- }
-
- let ansi = 30
- + ((Math.round(b / 255) << 2)
- | (Math.round(g / 255) << 1)
- | Math.round(r / 255));
-
- if (value === 2) {
- ansi += 60;
- }
-
- return ansi;
-};
-
-convert.hsv.ansi16 = function (args) {
- // Optimization here; we already know the value and don't need to get
- // it converted for us.
- return convert.rgb.ansi16(convert.hsv.rgb(args), args[2]);
-};
-
-convert.rgb.ansi256 = function (args) {
- const r = args[0];
- const g = args[1];
- const b = args[2];
-
- // We use the extended greyscale palette here, with the exception of
- // black and white. normal palette only has 4 greyscale shades.
- if (r === g && g === b) {
- if (r < 8) {
- return 16;
- }
-
- if (r > 248) {
- return 231;
- }
-
- return Math.round(((r - 8) / 247) * 24) + 232;
- }
-
- const ansi = 16
- + (36 * Math.round(r / 255 * 5))
- + (6 * Math.round(g / 255 * 5))
- + Math.round(b / 255 * 5);
-
- return ansi;
-};
-
-convert.ansi16.rgb = function (args) {
- let color = args % 10;
-
- // Handle greyscale
- if (color === 0 || color === 7) {
- if (args > 50) {
- color += 3.5;
- }
-
- color = color / 10.5 * 255;
-
- return [color, color, color];
- }
-
- const mult = (~~(args > 50) + 1) * 0.5;
- const r = ((color & 1) * mult) * 255;
- const g = (((color >> 1) & 1) * mult) * 255;
- const b = (((color >> 2) & 1) * mult) * 255;
-
- return [r, g, b];
-};
-
-convert.ansi256.rgb = function (args) {
- // Handle greyscale
- if (args >= 232) {
- const c = (args - 232) * 10 + 8;
- return [c, c, c];
- }
-
- args -= 16;
-
- let rem;
- const r = Math.floor(args / 36) / 5 * 255;
- const g = Math.floor((rem = args % 36) / 6) / 5 * 255;
- const b = (rem % 6) / 5 * 255;
-
- return [r, g, b];
-};
-
-convert.rgb.hex = function (args) {
- const integer = ((Math.round(args[0]) & 0xFF) << 16)
- + ((Math.round(args[1]) & 0xFF) << 8)
- + (Math.round(args[2]) & 0xFF);
-
- const string = integer.toString(16).toUpperCase();
- return '000000'.substring(string.length) + string;
-};
-
-convert.hex.rgb = function (args) {
- const match = args.toString(16).match(/[a-f0-9]{6}|[a-f0-9]{3}/i);
- if (!match) {
- return [0, 0, 0];
- }
-
- let colorString = match[0];
-
- if (match[0].length === 3) {
- colorString = colorString.split('').map(char => {
- return char + char;
- }).join('');
- }
-
- const integer = parseInt(colorString, 16);
- const r = (integer >> 16) & 0xFF;
- const g = (integer >> 8) & 0xFF;
- const b = integer & 0xFF;
-
- return [r, g, b];
-};
-
-convert.rgb.hcg = function (rgb) {
- const r = rgb[0] / 255;
- const g = rgb[1] / 255;
- const b = rgb[2] / 255;
- const max = Math.max(Math.max(r, g), b);
- const min = Math.min(Math.min(r, g), b);
- const chroma = (max - min);
- let grayscale;
- let hue;
-
- if (chroma < 1) {
- grayscale = min / (1 - chroma);
- } else {
- grayscale = 0;
- }
-
- if (chroma <= 0) {
- hue = 0;
- } else
- if (max === r) {
- hue = ((g - b) / chroma) % 6;
- } else
- if (max === g) {
- hue = 2 + (b - r) / chroma;
- } else {
- hue = 4 + (r - g) / chroma;
- }
-
- hue /= 6;
- hue %= 1;
-
- return [hue * 360, chroma * 100, grayscale * 100];
-};
-
-convert.hsl.hcg = function (hsl) {
- const s = hsl[1] / 100;
- const l = hsl[2] / 100;
-
- const c = l < 0.5 ? (2.0 * s * l) : (2.0 * s * (1.0 - l));
-
- let f = 0;
- if (c < 1.0) {
- f = (l - 0.5 * c) / (1.0 - c);
- }
-
- return [hsl[0], c * 100, f * 100];
-};
-
-convert.hsv.hcg = function (hsv) {
- const s = hsv[1] / 100;
- const v = hsv[2] / 100;
-
- const c = s * v;
- let f = 0;
-
- if (c < 1.0) {
- f = (v - c) / (1 - c);
- }
-
- return [hsv[0], c * 100, f * 100];
-};
-
-convert.hcg.rgb = function (hcg) {
- const h = hcg[0] / 360;
- const c = hcg[1] / 100;
- const g = hcg[2] / 100;
-
- if (c === 0.0) {
- return [g * 255, g * 255, g * 255];
- }
-
- const pure = [0, 0, 0];
- const hi = (h % 1) * 6;
- const v = hi % 1;
- const w = 1 - v;
- let mg = 0;
-
- /* eslint-disable max-statements-per-line */
- switch (Math.floor(hi)) {
- case 0:
- pure[0] = 1; pure[1] = v; pure[2] = 0; break;
- case 1:
- pure[0] = w; pure[1] = 1; pure[2] = 0; break;
- case 2:
- pure[0] = 0; pure[1] = 1; pure[2] = v; break;
- case 3:
- pure[0] = 0; pure[1] = w; pure[2] = 1; break;
- case 4:
- pure[0] = v; pure[1] = 0; pure[2] = 1; break;
- default:
- pure[0] = 1; pure[1] = 0; pure[2] = w;
- }
- /* eslint-enable max-statements-per-line */
-
- mg = (1.0 - c) * g;
-
- return [
- (c * pure[0] + mg) * 255,
- (c * pure[1] + mg) * 255,
- (c * pure[2] + mg) * 255
- ];
-};
-
-convert.hcg.hsv = function (hcg) {
- const c = hcg[1] / 100;
- const g = hcg[2] / 100;
-
- const v = c + g * (1.0 - c);
- let f = 0;
-
- if (v > 0.0) {
- f = c / v;
- }
-
- return [hcg[0], f * 100, v * 100];
-};
-
-convert.hcg.hsl = function (hcg) {
- const c = hcg[1] / 100;
- const g = hcg[2] / 100;
-
- const l = g * (1.0 - c) + 0.5 * c;
- let s = 0;
-
- if (l > 0.0 && l < 0.5) {
- s = c / (2 * l);
- } else
- if (l >= 0.5 && l < 1.0) {
- s = c / (2 * (1 - l));
- }
-
- return [hcg[0], s * 100, l * 100];
-};
-
-convert.hcg.hwb = function (hcg) {
- const c = hcg[1] / 100;
- const g = hcg[2] / 100;
- const v = c + g * (1.0 - c);
- return [hcg[0], (v - c) * 100, (1 - v) * 100];
-};
-
-convert.hwb.hcg = function (hwb) {
- const w = hwb[1] / 100;
- const b = hwb[2] / 100;
- const v = 1 - b;
- const c = v - w;
- let g = 0;
-
- if (c < 1) {
- g = (v - c) / (1 - c);
- }
-
- return [hwb[0], c * 100, g * 100];
-};
-
-convert.apple.rgb = function (apple) {
- return [(apple[0] / 65535) * 255, (apple[1] / 65535) * 255, (apple[2] / 65535) * 255];
-};
-
-convert.rgb.apple = function (rgb) {
- return [(rgb[0] / 255) * 65535, (rgb[1] / 255) * 65535, (rgb[2] / 255) * 65535];
-};
-
-convert.gray.rgb = function (args) {
- return [args[0] / 100 * 255, args[0] / 100 * 255, args[0] / 100 * 255];
-};
-
-convert.gray.hsl = function (args) {
- return [0, 0, args[0]];
-};
-
-convert.gray.hsv = convert.gray.hsl;
-
-convert.gray.hwb = function (gray) {
- return [0, 100, gray[0]];
-};
-
-convert.gray.cmyk = function (gray) {
- return [0, 0, 0, gray[0]];
-};
-
-convert.gray.lab = function (gray) {
- return [gray[0], 0, 0];
-};
-
-convert.gray.hex = function (gray) {
- const val = Math.round(gray[0] / 100 * 255) & 0xFF;
- const integer = (val << 16) + (val << 8) + val;
-
- const string = integer.toString(16).toUpperCase();
- return '000000'.substring(string.length) + string;
-};
-
-convert.rgb.gray = function (rgb) {
- const val = (rgb[0] + rgb[1] + rgb[2]) / 3;
- return [val / 255 * 100];
-};
-
-
-/***/ }),
-
-/***/ "./node_modules/color-convert/index.js":
-/*!*********************************************!*\
- !*** ./node_modules/color-convert/index.js ***!
- \*********************************************/
-/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
-
-const conversions = __webpack_require__(/*! ./conversions */ "./node_modules/color-convert/conversions.js");
-const route = __webpack_require__(/*! ./route */ "./node_modules/color-convert/route.js");
-
-const convert = {};
-
-const models = Object.keys(conversions);
-
-function wrapRaw(fn) {
- const wrappedFn = function (...args) {
- const arg0 = args[0];
- if (arg0 === undefined || arg0 === null) {
- return arg0;
- }
-
- if (arg0.length > 1) {
- args = arg0;
- }
-
- return fn(args);
- };
-
- // Preserve .conversion property if there is one
- if ('conversion' in fn) {
- wrappedFn.conversion = fn.conversion;
- }
-
- return wrappedFn;
-}
-
-function wrapRounded(fn) {
- const wrappedFn = function (...args) {
- const arg0 = args[0];
-
- if (arg0 === undefined || arg0 === null) {
- return arg0;
- }
-
- if (arg0.length > 1) {
- args = arg0;
- }
-
- const result = fn(args);
-
- // We're assuming the result is an array here.
- // see notice in conversions.js; don't use box types
- // in conversion functions.
- if (typeof result === 'object') {
- for (let len = result.length, i = 0; i < len; i++) {
- result[i] = Math.round(result[i]);
- }
- }
-
- return result;
- };
-
- // Preserve .conversion property if there is one
- if ('conversion' in fn) {
- wrappedFn.conversion = fn.conversion;
- }
-
- return wrappedFn;
-}
-
-models.forEach(fromModel => {
- convert[fromModel] = {};
-
- Object.defineProperty(convert[fromModel], 'channels', {value: conversions[fromModel].channels});
- Object.defineProperty(convert[fromModel], 'labels', {value: conversions[fromModel].labels});
-
- const routes = route(fromModel);
- const routeModels = Object.keys(routes);
-
- routeModels.forEach(toModel => {
- const fn = routes[toModel];
-
- convert[fromModel][toModel] = wrapRounded(fn);
- convert[fromModel][toModel].raw = wrapRaw(fn);
- });
-});
-
-module.exports = convert;
-
-
-/***/ }),
-
-/***/ "./node_modules/color-convert/route.js":
-/*!*********************************************!*\
- !*** ./node_modules/color-convert/route.js ***!
- \*********************************************/
-/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
-
-const conversions = __webpack_require__(/*! ./conversions */ "./node_modules/color-convert/conversions.js");
-
-/*
- This function routes a model to all other models.
-
- all functions that are routed have a property `.conversion` attached
- to the returned synthetic function. This property is an array
- of strings, each with the steps in between the 'from' and 'to'
- color models (inclusive).
-
- conversions that are not possible simply are not included.
-*/
-
-function buildGraph() {
- const graph = {};
- // https://jsperf.com/object-keys-vs-for-in-with-closure/3
- const models = Object.keys(conversions);
-
- for (let len = models.length, i = 0; i < len; i++) {
- graph[models[i]] = {
- // http://jsperf.com/1-vs-infinity
- // micro-opt, but this is simple.
- distance: -1,
- parent: null
- };
- }
-
- return graph;
-}
-
-// https://en.wikipedia.org/wiki/Breadth-first_search
-function deriveBFS(fromModel) {
- const graph = buildGraph();
- const queue = [fromModel]; // Unshift -> queue -> pop
-
- graph[fromModel].distance = 0;
-
- while (queue.length) {
- const current = queue.pop();
- const adjacents = Object.keys(conversions[current]);
-
- for (let len = adjacents.length, i = 0; i < len; i++) {
- const adjacent = adjacents[i];
- const node = graph[adjacent];
-
- if (node.distance === -1) {
- node.distance = graph[current].distance + 1;
- node.parent = current;
- queue.unshift(adjacent);
- }
- }
- }
-
- return graph;
-}
-
-function link(from, to) {
- return function (args) {
- return to(from(args));
- };
-}
-
-function wrapConversion(toModel, graph) {
- const path = [graph[toModel].parent, toModel];
- let fn = conversions[graph[toModel].parent][toModel];
-
- let cur = graph[toModel].parent;
- while (graph[cur].parent) {
- path.unshift(graph[cur].parent);
- fn = link(conversions[graph[cur].parent][cur], fn);
- cur = graph[cur].parent;
- }
-
- fn.conversion = path;
- return fn;
-}
-
-module.exports = function (fromModel) {
- const graph = deriveBFS(fromModel);
- const conversion = {};
-
- const models = Object.keys(graph);
- for (let len = models.length, i = 0; i < len; i++) {
- const toModel = models[i];
- const node = graph[toModel];
-
- if (node.parent === null) {
- // No possible conversion, or this node is the source model.
- continue;
- }
-
- conversion[toModel] = wrapConversion(toModel, graph);
- }
-
- return conversion;
-};
-
-
-
-/***/ }),
-
-/***/ "./node_modules/color-name/index.js":
-/*!******************************************!*\
- !*** ./node_modules/color-name/index.js ***!
- \******************************************/
-/***/ ((module) => {
-
-"use strict";
-
-
-module.exports = {
- "aliceblue": [240, 248, 255],
- "antiquewhite": [250, 235, 215],
- "aqua": [0, 255, 255],
- "aquamarine": [127, 255, 212],
- "azure": [240, 255, 255],
- "beige": [245, 245, 220],
- "bisque": [255, 228, 196],
- "black": [0, 0, 0],
- "blanchedalmond": [255, 235, 205],
- "blue": [0, 0, 255],
- "blueviolet": [138, 43, 226],
- "brown": [165, 42, 42],
- "burlywood": [222, 184, 135],
- "cadetblue": [95, 158, 160],
- "chartreuse": [127, 255, 0],
- "chocolate": [210, 105, 30],
- "coral": [255, 127, 80],
- "cornflowerblue": [100, 149, 237],
- "cornsilk": [255, 248, 220],
- "crimson": [220, 20, 60],
- "cyan": [0, 255, 255],
- "darkblue": [0, 0, 139],
- "darkcyan": [0, 139, 139],
- "darkgoldenrod": [184, 134, 11],
- "darkgray": [169, 169, 169],
- "darkgreen": [0, 100, 0],
- "darkgrey": [169, 169, 169],
- "darkkhaki": [189, 183, 107],
- "darkmagenta": [139, 0, 139],
- "darkolivegreen": [85, 107, 47],
- "darkorange": [255, 140, 0],
- "darkorchid": [153, 50, 204],
- "darkred": [139, 0, 0],
- "darksalmon": [233, 150, 122],
- "darkseagreen": [143, 188, 143],
- "darkslateblue": [72, 61, 139],
- "darkslategray": [47, 79, 79],
- "darkslategrey": [47, 79, 79],
- "darkturquoise": [0, 206, 209],
- "darkviolet": [148, 0, 211],
- "deeppink": [255, 20, 147],
- "deepskyblue": [0, 191, 255],
- "dimgray": [105, 105, 105],
- "dimgrey": [105, 105, 105],
- "dodgerblue": [30, 144, 255],
- "firebrick": [178, 34, 34],
- "floralwhite": [255, 250, 240],
- "forestgreen": [34, 139, 34],
- "fuchsia": [255, 0, 255],
- "gainsboro": [220, 220, 220],
- "ghostwhite": [248, 248, 255],
- "gold": [255, 215, 0],
- "goldenrod": [218, 165, 32],
- "gray": [128, 128, 128],
- "green": [0, 128, 0],
- "greenyellow": [173, 255, 47],
- "grey": [128, 128, 128],
- "honeydew": [240, 255, 240],
- "hotpink": [255, 105, 180],
- "indianred": [205, 92, 92],
- "indigo": [75, 0, 130],
- "ivory": [255, 255, 240],
- "khaki": [240, 230, 140],
- "lavender": [230, 230, 250],
- "lavenderblush": [255, 240, 245],
- "lawngreen": [124, 252, 0],
- "lemonchiffon": [255, 250, 205],
- "lightblue": [173, 216, 230],
- "lightcoral": [240, 128, 128],
- "lightcyan": [224, 255, 255],
- "lightgoldenrodyellow": [250, 250, 210],
- "lightgray": [211, 211, 211],
- "lightgreen": [144, 238, 144],
- "lightgrey": [211, 211, 211],
- "lightpink": [255, 182, 193],
- "lightsalmon": [255, 160, 122],
- "lightseagreen": [32, 178, 170],
- "lightskyblue": [135, 206, 250],
- "lightslategray": [119, 136, 153],
- "lightslategrey": [119, 136, 153],
- "lightsteelblue": [176, 196, 222],
- "lightyellow": [255, 255, 224],
- "lime": [0, 255, 0],
- "limegreen": [50, 205, 50],
- "linen": [250, 240, 230],
- "magenta": [255, 0, 255],
- "maroon": [128, 0, 0],
- "mediumaquamarine": [102, 205, 170],
- "mediumblue": [0, 0, 205],
- "mediumorchid": [186, 85, 211],
- "mediumpurple": [147, 112, 219],
- "mediumseagreen": [60, 179, 113],
- "mediumslateblue": [123, 104, 238],
- "mediumspringgreen": [0, 250, 154],
- "mediumturquoise": [72, 209, 204],
- "mediumvioletred": [199, 21, 133],
- "midnightblue": [25, 25, 112],
- "mintcream": [245, 255, 250],
- "mistyrose": [255, 228, 225],
- "moccasin": [255, 228, 181],
- "navajowhite": [255, 222, 173],
- "navy": [0, 0, 128],
- "oldlace": [253, 245, 230],
- "olive": [128, 128, 0],
- "olivedrab": [107, 142, 35],
- "orange": [255, 165, 0],
- "orangered": [255, 69, 0],
- "orchid": [218, 112, 214],
- "palegoldenrod": [238, 232, 170],
- "palegreen": [152, 251, 152],
- "paleturquoise": [175, 238, 238],
- "palevioletred": [219, 112, 147],
- "papayawhip": [255, 239, 213],
- "peachpuff": [255, 218, 185],
- "peru": [205, 133, 63],
- "pink": [255, 192, 203],
- "plum": [221, 160, 221],
- "powderblue": [176, 224, 230],
- "purple": [128, 0, 128],
- "rebeccapurple": [102, 51, 153],
- "red": [255, 0, 0],
- "rosybrown": [188, 143, 143],
- "royalblue": [65, 105, 225],
- "saddlebrown": [139, 69, 19],
- "salmon": [250, 128, 114],
- "sandybrown": [244, 164, 96],
- "seagreen": [46, 139, 87],
- "seashell": [255, 245, 238],
- "sienna": [160, 82, 45],
- "silver": [192, 192, 192],
- "skyblue": [135, 206, 235],
- "slateblue": [106, 90, 205],
- "slategray": [112, 128, 144],
- "slategrey": [112, 128, 144],
- "snow": [255, 250, 250],
- "springgreen": [0, 255, 127],
- "steelblue": [70, 130, 180],
- "tan": [210, 180, 140],
- "teal": [0, 128, 128],
- "thistle": [216, 191, 216],
- "tomato": [255, 99, 71],
- "turquoise": [64, 224, 208],
- "violet": [238, 130, 238],
- "wheat": [245, 222, 179],
- "white": [255, 255, 255],
- "whitesmoke": [245, 245, 245],
- "yellow": [255, 255, 0],
- "yellowgreen": [154, 205, 50]
-};
-
-
-/***/ }),
-
-/***/ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[2].use[2]!./node_modules/@ckeditor/ckeditor5-basic-styles/theme/code.css":
-/*!**********************************************************************************************************************************************************************************!*\
- !*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[2].use[2]!./node_modules/@ckeditor/ckeditor5-basic-styles/theme/code.css ***!
- \**********************************************************************************************************************************************************************************/
-/***/ ((module, __webpack_exports__, __webpack_require__) => {
-
-"use strict";
-__webpack_require__.r(__webpack_exports__);
-/* harmony export */ __webpack_require__.d(__webpack_exports__, {
-/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
-/* harmony export */ });
-/* harmony import */ var _css_loader_dist_runtime_cssWithMappingToString_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../../../css-loader/dist/runtime/cssWithMappingToString.js */ "./node_modules/css-loader/dist/runtime/cssWithMappingToString.js");
-/* harmony import */ var _css_loader_dist_runtime_cssWithMappingToString_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_css_loader_dist_runtime_cssWithMappingToString_js__WEBPACK_IMPORTED_MODULE_0__);
-/* harmony import */ var _css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../../../css-loader/dist/runtime/api.js */ "./node_modules/css-loader/dist/runtime/api.js");
-/* harmony import */ var _css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__);
-// Imports
-
-
-var ___CSS_LOADER_EXPORT___ = _css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default()((_css_loader_dist_runtime_cssWithMappingToString_js__WEBPACK_IMPORTED_MODULE_0___default()));
-// Module
-___CSS_LOADER_EXPORT___.push([module.id, ".ck-content code{background-color:hsla(0,0%,78%,.3);border-radius:2px;padding:.15em}.ck.ck-editor__editable .ck-code_selected{background-color:hsla(0,0%,78%,.5)}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-basic-styles/theme/code.css"],"names":[],"mappings":"AAKA,iBACC,kCAAuC,CAEvC,iBAAkB,CADlB,aAED,CAEA,0CACC,kCACD","sourcesContent":["/*\n * Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n\n.ck-content code {\n\tbackground-color: hsla(0, 0%, 78%, 0.3);\n\tpadding: .15em;\n\tborder-radius: 2px;\n}\n\n.ck.ck-editor__editable .ck-code_selected {\n\tbackground-color: hsla(0, 0%, 78%, 0.5);\n}\n"],"sourceRoot":""}]);
-// Exports
-/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (___CSS_LOADER_EXPORT___);
-
-
-/***/ }),
-
-/***/ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[2].use[2]!./node_modules/@ckeditor/ckeditor5-block-quote/theme/blockquote.css":
-/*!***************************************************************************************************************************************************************************************!*\
- !*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[2].use[2]!./node_modules/@ckeditor/ckeditor5-block-quote/theme/blockquote.css ***!
- \***************************************************************************************************************************************************************************************/
-/***/ ((module, __webpack_exports__, __webpack_require__) => {
-
-"use strict";
-__webpack_require__.r(__webpack_exports__);
-/* harmony export */ __webpack_require__.d(__webpack_exports__, {
-/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
-/* harmony export */ });
-/* harmony import */ var _css_loader_dist_runtime_cssWithMappingToString_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../../../css-loader/dist/runtime/cssWithMappingToString.js */ "./node_modules/css-loader/dist/runtime/cssWithMappingToString.js");
-/* harmony import */ var _css_loader_dist_runtime_cssWithMappingToString_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_css_loader_dist_runtime_cssWithMappingToString_js__WEBPACK_IMPORTED_MODULE_0__);
-/* harmony import */ var _css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../../../css-loader/dist/runtime/api.js */ "./node_modules/css-loader/dist/runtime/api.js");
-/* harmony import */ var _css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__);
-// Imports
-
-
-var ___CSS_LOADER_EXPORT___ = _css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default()((_css_loader_dist_runtime_cssWithMappingToString_js__WEBPACK_IMPORTED_MODULE_0___default()));
-// Module
-___CSS_LOADER_EXPORT___.push([module.id, ".ck-content blockquote{border-left:5px solid #ccc;font-style:italic;margin-left:0;margin-right:0;overflow:hidden;padding-left:1.5em;padding-right:1.5em}.ck-content[dir=rtl] blockquote{border-left:0;border-right:5px solid #ccc}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-block-quote/theme/blockquote.css"],"names":[],"mappings":"AAKA,uBAWC,0BAAsC,CADtC,iBAAkB,CAFlB,aAAc,CACd,cAAe,CAPf,eAAgB,CAIhB,kBAAmB,CADnB,mBAOD,CAEA,gCACC,aAAc,CACd,2BACD","sourcesContent":["/*\n * Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n\n.ck-content blockquote {\n\t/* See #12 */\n\toverflow: hidden;\n\n\t/* https://github.com/ckeditor/ckeditor5-block-quote/issues/15 */\n\tpadding-right: 1.5em;\n\tpadding-left: 1.5em;\n\n\tmargin-left: 0;\n\tmargin-right: 0;\n\tfont-style: italic;\n\tborder-left: solid 5px hsl(0, 0%, 80%);\n}\n\n.ck-content[dir=\"rtl\"] blockquote {\n\tborder-left: 0;\n\tborder-right: solid 5px hsl(0, 0%, 80%);\n}\n"],"sourceRoot":""}]);
-// Exports
-/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (___CSS_LOADER_EXPORT___);
-
-
-/***/ }),
-
-/***/ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[2].use[2]!./node_modules/@ckeditor/ckeditor5-clipboard/theme/clipboard.css":
-/*!************************************************************************************************************************************************************************************!*\
- !*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[2].use[2]!./node_modules/@ckeditor/ckeditor5-clipboard/theme/clipboard.css ***!
- \************************************************************************************************************************************************************************************/
-/***/ ((module, __webpack_exports__, __webpack_require__) => {
-
-"use strict";
-__webpack_require__.r(__webpack_exports__);
-/* harmony export */ __webpack_require__.d(__webpack_exports__, {
-/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
-/* harmony export */ });
-/* harmony import */ var _css_loader_dist_runtime_cssWithMappingToString_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../../../css-loader/dist/runtime/cssWithMappingToString.js */ "./node_modules/css-loader/dist/runtime/cssWithMappingToString.js");
-/* harmony import */ var _css_loader_dist_runtime_cssWithMappingToString_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_css_loader_dist_runtime_cssWithMappingToString_js__WEBPACK_IMPORTED_MODULE_0__);
-/* harmony import */ var _css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../../../css-loader/dist/runtime/api.js */ "./node_modules/css-loader/dist/runtime/api.js");
-/* harmony import */ var _css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__);
-// Imports
-
-
-var ___CSS_LOADER_EXPORT___ = _css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default()((_css_loader_dist_runtime_cssWithMappingToString_js__WEBPACK_IMPORTED_MODULE_0___default()));
-// Module
-___CSS_LOADER_EXPORT___.push([module.id, ".ck.ck-editor__editable .ck.ck-clipboard-drop-target-position{display:inline;pointer-events:none;position:relative}.ck.ck-editor__editable .ck.ck-clipboard-drop-target-position span{position:absolute;width:0}.ck.ck-editor__editable .ck-widget:-webkit-drag>.ck-widget__selection-handle,.ck.ck-editor__editable .ck-widget:-webkit-drag>.ck-widget__type-around{display:none}.ck.ck-clipboard-drop-target-line{pointer-events:none;position:absolute}:root{--ck-clipboard-drop-target-dot-width:12px;--ck-clipboard-drop-target-dot-height:8px;--ck-clipboard-drop-target-color:var(--ck-color-focus-border)}.ck.ck-editor__editable .ck.ck-clipboard-drop-target-position span{background:var(--ck-clipboard-drop-target-color);border:1px solid var(--ck-clipboard-drop-target-color);bottom:calc(var(--ck-clipboard-drop-target-dot-height)*-.5);margin-left:-1px;top:calc(var(--ck-clipboard-drop-target-dot-height)*-.5)}.ck.ck-editor__editable .ck.ck-clipboard-drop-target-position span:after{border-color:var(--ck-clipboard-drop-target-color) transparent transparent transparent;border-style:solid;border-width:calc(var(--ck-clipboard-drop-target-dot-height)) calc(var(--ck-clipboard-drop-target-dot-width)*.5) 0 calc(var(--ck-clipboard-drop-target-dot-width)*.5);content:\"\";display:block;height:0;left:50%;position:absolute;top:calc(var(--ck-clipboard-drop-target-dot-height)*-.5);transform:translateX(-50%);width:0}.ck.ck-editor__editable .ck-widget.ck-clipboard-drop-target-range{outline:var(--ck-widget-outline-thickness) solid var(--ck-clipboard-drop-target-color)!important}.ck.ck-editor__editable .ck-widget:-webkit-drag{zoom:.6;outline:none!important}.ck.ck-clipboard-drop-target-line{background:var(--ck-clipboard-drop-target-color);border:1px solid var(--ck-clipboard-drop-target-color);height:0;margin-top:-1px}.ck.ck-clipboard-drop-target-line:before{border-style:solid;content:\"\";height:0;position:absolute;top:calc(var(--ck-clipboard-drop-target-dot-width)*-.5);width:0}[dir=ltr] .ck.ck-clipboard-drop-target-line:before{border-color:transparent transparent transparent var(--ck-clipboard-drop-target-color);border-width:calc(var(--ck-clipboard-drop-target-dot-width)*.5) 0 calc(var(--ck-clipboard-drop-target-dot-width)*.5) var(--ck-clipboard-drop-target-dot-height);left:-1px}[dir=rtl] .ck.ck-clipboard-drop-target-line:before{border-color:transparent var(--ck-clipboard-drop-target-color) transparent transparent;border-width:calc(var(--ck-clipboard-drop-target-dot-width)*.5) var(--ck-clipboard-drop-target-dot-height) calc(var(--ck-clipboard-drop-target-dot-width)*.5) 0;right:-1px}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-clipboard/theme/clipboard.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/ckeditor5-clipboard/clipboard.css"],"names":[],"mappings":"AASC,8DACC,cAAe,CAEf,mBAAoB,CADpB,iBAOD,CAJC,mEACC,iBAAkB,CAClB,OACD,CAWA,qJACC,YACD,CAIF,kCAEC,mBAAoB,CADpB,iBAED,CC9BA,MACC,yCAA0C,CAC1C,yCAA0C,CAC1C,6DACD,CAOE,mEAIC,gDAAiD,CADjD,sDAAuD,CAFvD,2DAA8D,CAI9D,gBAAiB,CAHjB,wDAqBD,CAfC,yEAWC,sFAAuF,CAEvF,kBAAmB,CADnB,qKAA0K,CAX1K,UAAW,CAIX,aAAc,CAFd,QAAS,CAIT,QAAS,CADT,iBAAkB,CAElB,wDAA2D,CAE3D,0BAA2B,CAR3B,OAYD,CAOF,kEACC,gGACD,CAKA,gDACC,OAAS,CACT,sBACD,CAGD,kCAGC,gDAAiD,CADjD,sDAAuD,CADvD,QAAS,CAGT,eAwBD,CAtBC,yCAMC,kBAAmB,CALnB,UAAW,CAIX,QAAS,CAHT,iBAAkB,CAClB,uDAA0D,CAC1D,OAiBD,CArBA,mDAYE,sFAAuF,CADvF,+JAAoK,CAFpK,SAYF,CArBA,mDAmBE,sFAAuF,CADvF,+JAAmK,CAFnK,UAKF","sourcesContent":["/*\n * Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n\n.ck.ck-editor__editable {\n\t/*\n\t * Vertical drop target (in text).\n\t */\n\t& .ck.ck-clipboard-drop-target-position {\n\t\tdisplay: inline;\n\t\tposition: relative;\n\t\tpointer-events: none;\n\n\t\t& span {\n\t\t\tposition: absolute;\n\t\t\twidth: 0;\n\t\t}\n\t}\n\n\t/*\n\t * Styles of the widget being dragged (its preview).\n\t */\n\t& .ck-widget:-webkit-drag {\n\t\t& > .ck-widget__selection-handle {\n\t\t\tdisplay: none;\n\t\t}\n\n\t\t& > .ck-widget__type-around {\n\t\t\tdisplay: none;\n\t\t}\n\t}\n}\n\n.ck.ck-clipboard-drop-target-line {\n\tposition: absolute;\n\tpointer-events: none;\n}\n","/*\n * Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n\n@import \"@ckeditor/ckeditor5-ui/theme/mixins/_dir.css\";\n\n:root {\n\t--ck-clipboard-drop-target-dot-width: 12px;\n\t--ck-clipboard-drop-target-dot-height: 8px;\n\t--ck-clipboard-drop-target-color: var(--ck-color-focus-border);\n}\n\n.ck.ck-editor__editable {\n\t/*\n\t * Vertical drop target (in text).\n\t */\n\t& .ck.ck-clipboard-drop-target-position {\n\t\t& span {\n\t\t\tbottom: calc(-.5 * var(--ck-clipboard-drop-target-dot-height));\n\t\t\ttop: calc(-.5 * var(--ck-clipboard-drop-target-dot-height));\n\t\t\tborder: 1px solid var(--ck-clipboard-drop-target-color);\n\t\t\tbackground: var(--ck-clipboard-drop-target-color);\n\t\t\tmargin-left: -1px;\n\n\t\t\t/* The triangle above the marker */\n\t\t\t&::after {\n\t\t\t\tcontent: '';\n\t\t\t\twidth: 0;\n\t\t\t\theight: 0;\n\n\t\t\t\tdisplay: block;\n\t\t\t\tposition: absolute;\n\t\t\t\tleft: 50%;\n\t\t\t\ttop: calc(-.5 * var(--ck-clipboard-drop-target-dot-height));\n\n\t\t\t\ttransform: translateX(-50%);\n\t\t\t\tborder-color: var(--ck-clipboard-drop-target-color) transparent transparent transparent;\n\t\t\t\tborder-width: calc(var(--ck-clipboard-drop-target-dot-height)) calc(.5 * var(--ck-clipboard-drop-target-dot-width)) 0 calc(.5 * var(--ck-clipboard-drop-target-dot-width));\n\t\t\t\tborder-style: solid;\n\t\t\t}\n\t\t}\n\t}\n\n\t/*\n\t * Styles of the widget that it a drop target.\n\t */\n\t& .ck-widget.ck-clipboard-drop-target-range {\n\t\toutline: var(--ck-widget-outline-thickness) solid var(--ck-clipboard-drop-target-color) !important;\n\t}\n\n\t/*\n\t * Styles of the widget being dragged (its preview).\n\t */\n\t& .ck-widget:-webkit-drag {\n\t\tzoom: 0.6;\n\t\toutline: none !important;\n\t}\n}\n\n.ck.ck-clipboard-drop-target-line {\n\theight: 0;\n\tborder: 1px solid var(--ck-clipboard-drop-target-color);\n\tbackground: var(--ck-clipboard-drop-target-color);\n\tmargin-top: -1px;\n\n\t&::before {\n\t\tcontent: '';\n\t\tposition: absolute;\n\t\ttop: calc(-.5 * var(--ck-clipboard-drop-target-dot-width));\n\t\twidth: 0;\n\t\theight: 0;\n\t\tborder-style: solid;\n\n\t\t@mixin ck-dir ltr {\n\t\t\tleft: -1px;\n\n\t\t\tborder-width: calc(.5 * var(--ck-clipboard-drop-target-dot-width)) 0 calc(.5 * var(--ck-clipboard-drop-target-dot-width)) var(--ck-clipboard-drop-target-dot-height);\n\t\t\tborder-color: transparent transparent transparent var(--ck-clipboard-drop-target-color);\n\t\t}\n\n\t\t@mixin ck-dir rtl {\n\t\t\tright: -1px;\n\n\t\t\tborder-width:calc(.5 * var(--ck-clipboard-drop-target-dot-width)) var(--ck-clipboard-drop-target-dot-height) calc(.5 * var(--ck-clipboard-drop-target-dot-width)) 0;\n\t\t\tborder-color: transparent var(--ck-clipboard-drop-target-color) transparent transparent;\n\t\t}\n\t}\n}\n"],"sourceRoot":""}]);
-// Exports
-/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (___CSS_LOADER_EXPORT___);
-
-
-/***/ }),
-
-/***/ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[2].use[2]!./node_modules/@ckeditor/ckeditor5-code-block/theme/codeblock.css":
-/*!*************************************************************************************************************************************************************************************!*\
- !*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[2].use[2]!./node_modules/@ckeditor/ckeditor5-code-block/theme/codeblock.css ***!
- \*************************************************************************************************************************************************************************************/
-/***/ ((module, __webpack_exports__, __webpack_require__) => {
-
-"use strict";
-__webpack_require__.r(__webpack_exports__);
-/* harmony export */ __webpack_require__.d(__webpack_exports__, {
-/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
-/* harmony export */ });
-/* harmony import */ var _css_loader_dist_runtime_cssWithMappingToString_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../../../css-loader/dist/runtime/cssWithMappingToString.js */ "./node_modules/css-loader/dist/runtime/cssWithMappingToString.js");
-/* harmony import */ var _css_loader_dist_runtime_cssWithMappingToString_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_css_loader_dist_runtime_cssWithMappingToString_js__WEBPACK_IMPORTED_MODULE_0__);
-/* harmony import */ var _css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../../../css-loader/dist/runtime/api.js */ "./node_modules/css-loader/dist/runtime/api.js");
-/* harmony import */ var _css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__);
-// Imports
-
-
-var ___CSS_LOADER_EXPORT___ = _css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default()((_css_loader_dist_runtime_cssWithMappingToString_js__WEBPACK_IMPORTED_MODULE_0___default()));
-// Module
-___CSS_LOADER_EXPORT___.push([module.id, ".ck-content pre{background:hsla(0,0%,78%,.3);border:1px solid #c4c4c4;border-radius:2px;color:#353535;direction:ltr;font-style:normal;min-width:200px;padding:1em;tab-size:4;text-align:left;white-space:pre-wrap}.ck-content pre code{background:unset;border-radius:0;padding:0}.ck.ck-editor__editable pre{position:relative}.ck.ck-editor__editable pre[data-language]:after{content:attr(data-language);position:absolute}:root{--ck-color-code-block-label-background:#757575}.ck.ck-editor__editable pre[data-language]:after{background:var(--ck-color-code-block-label-background);color:#fff;font-family:var(--ck-font-face);font-size:10px;line-height:16px;padding:var(--ck-spacing-tiny) var(--ck-spacing-medium);right:10px;top:-1px;white-space:nowrap}.ck.ck-code-block-dropdown .ck-dropdown__panel{max-height:250px;overflow-x:hidden;overflow-y:auto}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-code-block/theme/codeblock.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/ckeditor5-code-block/codeblock.css"],"names":[],"mappings":"AAKA,gBAGC,4BAAiC,CACjC,wBAAiC,CACjC,iBAAkB,CAHlB,aAAwB,CAOxB,aAAc,CAMd,iBAAkB,CAGlB,eAAgB,CAjBhB,WAAY,CAUZ,UAAW,CAHX,eAAgB,CAIhB,oBAaD,CALC,qBACC,gBAAiB,CAEjB,eAAgB,CADhB,SAED,CAGD,4BACC,iBAMD,CAJC,iDACC,2BAA4B,CAC5B,iBACD,CCjCD,MACC,8CACD,CAEA,iDAGC,sDAAuD,CAMvD,UAAuB,CAHvB,+BAAgC,CADhC,cAAe,CAEf,gBAAiB,CACjB,uDAAwD,CANxD,UAAW,CADX,QAAS,CAST,kBACD,CAEA,+CAEC,gBAAiB,CAEjB,iBAAkB,CADlB,eAED","sourcesContent":["/*\n * Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n\n.ck-content pre {\n\tpadding: 1em;\n\tcolor: hsl(0, 0%, 20.8%);\n\tbackground: hsla(0, 0%, 78%, 0.3);\n\tborder: 1px solid hsl(0, 0%, 77%);\n\tborder-radius: 2px;\n\n\t/* Code block are language direction–agnostic. */\n\ttext-align: left;\n\tdirection: ltr;\n\n\ttab-size: 4;\n\twhite-space: pre-wrap;\n\n\t/* Don't inherit the style, e.g. when in a block quote. */\n\tfont-style: normal;\n\n\t/* Don't let the code be squashed e.g. when in a table cell. */\n\tmin-width: 200px;\n\n\t& code {\n\t\tbackground: unset;\n\t\tpadding: 0;\n\t\tborder-radius: 0;\n\t}\n}\n\n.ck.ck-editor__editable pre {\n\tposition: relative;\n\n\t&[data-language]::after {\n\t\tcontent: attr(data-language);\n\t\tposition: absolute;\n\t}\n}\n","/*\n * Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n\n:root {\n\t--ck-color-code-block-label-background: hsl(0, 0%, 46%);\n}\n\n.ck.ck-editor__editable pre[data-language]::after {\n\ttop: -1px;\n\tright: 10px;\n\tbackground: var(--ck-color-code-block-label-background);\n\n\tfont-size: 10px;\n\tfont-family: var(--ck-font-face);\n\tline-height: 16px;\n\tpadding: var(--ck-spacing-tiny) var(--ck-spacing-medium);\n\tcolor: hsl(0, 0%, 100%);\n\twhite-space: nowrap;\n}\n\n.ck.ck-code-block-dropdown .ck-dropdown__panel {\n\t/* There could be dozens of languages available. Use scroll to prevent a 10e6px dropdown. */\n\tmax-height: 250px;\n\toverflow-y: auto;\n\toverflow-x: hidden;\n}\n"],"sourceRoot":""}]);
-// Exports
-/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (___CSS_LOADER_EXPORT___);
-
-
-/***/ }),
-
-/***/ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[2].use[2]!./node_modules/@ckeditor/ckeditor5-editor-classic/theme/classiceditor.css":
-/*!*********************************************************************************************************************************************************************************************!*\
- !*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[2].use[2]!./node_modules/@ckeditor/ckeditor5-editor-classic/theme/classiceditor.css ***!
- \*********************************************************************************************************************************************************************************************/
-/***/ ((module, __webpack_exports__, __webpack_require__) => {
-
-"use strict";
-__webpack_require__.r(__webpack_exports__);
-/* harmony export */ __webpack_require__.d(__webpack_exports__, {
-/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
-/* harmony export */ });
-/* harmony import */ var _css_loader_dist_runtime_cssWithMappingToString_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../../../css-loader/dist/runtime/cssWithMappingToString.js */ "./node_modules/css-loader/dist/runtime/cssWithMappingToString.js");
-/* harmony import */ var _css_loader_dist_runtime_cssWithMappingToString_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_css_loader_dist_runtime_cssWithMappingToString_js__WEBPACK_IMPORTED_MODULE_0__);
-/* harmony import */ var _css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../../../css-loader/dist/runtime/api.js */ "./node_modules/css-loader/dist/runtime/api.js");
-/* harmony import */ var _css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__);
-// Imports
-
-
-var ___CSS_LOADER_EXPORT___ = _css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default()((_css_loader_dist_runtime_cssWithMappingToString_js__WEBPACK_IMPORTED_MODULE_0___default()));
-// Module
-___CSS_LOADER_EXPORT___.push([module.id, ".ck.ck-editor{position:relative}.ck.ck-editor .ck-editor__top .ck-sticky-panel .ck-toolbar{z-index:var(--ck-z-panel)}.ck.ck-editor__top .ck-sticky-panel .ck-sticky-panel__content{border-radius:0}.ck-rounded-corners .ck.ck-editor__top .ck-sticky-panel .ck-sticky-panel__content,.ck.ck-editor__top .ck-sticky-panel .ck-sticky-panel__content.ck-rounded-corners{border-radius:var(--ck-border-radius);border-bottom-left-radius:0;border-bottom-right-radius:0}.ck.ck-editor__top .ck-sticky-panel .ck-sticky-panel__content{border:solid var(--ck-color-base-border);border-width:1px 1px 0}.ck.ck-editor__top .ck-sticky-panel .ck-sticky-panel__content.ck-sticky-panel__content_sticky{border-bottom-width:1px}.ck.ck-editor__top .ck-sticky-panel .ck-sticky-panel__content .ck-menu-bar,.ck.ck-editor__top .ck-sticky-panel .ck-sticky-panel__content .ck-toolbar{border:0}.ck.ck-editor__main>.ck-editor__editable{background:var(--ck-color-base-background);border-radius:0}.ck-rounded-corners .ck.ck-editor__main>.ck-editor__editable,.ck.ck-editor__main>.ck-editor__editable.ck-rounded-corners{border-radius:var(--ck-border-radius);border-top-left-radius:0;border-top-right-radius:0}.ck.ck-editor__main>.ck-editor__editable:not(.ck-focused){border-color:var(--ck-color-base-border)}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-editor-classic/theme/classiceditor.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/ckeditor5-editor-classic/classiceditor.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/mixins/_rounded.css"],"names":[],"mappings":"AAKA,cAIC,iBAMD,CAJC,2DAEC,yBACD,CCLC,8DCED,eDeC,CAjBA,mKCMA,qCAAsC,CDJpC,2BAA4B,CAC5B,4BAcF,CAjBA,8DAOC,wCAAsB,CAAtB,sBAUD,CARC,8FACC,uBACD,CAEA,qJAEC,QACD,CAMH,yCAEC,0CAA2C,CCtB3C,eDgCD,CAZA,yHChBE,qCAAsC,CDqBtC,wBAAyB,CACzB,yBAMF,CAHC,0DACC,wCACD","sourcesContent":["/*\n * Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n\n.ck.ck-editor {\n\t/* All the elements within `.ck-editor` are positioned relatively to it.\n\t If any element needs to be positioned with respect to the
<br> element)"),keystroke:"Shift+Enter"}]})}}class Rw extends Rr{refresh(){this.value=this._getValue(),this.isEnabled=this._checkEnabled()}execute(t={}){const e=this.editor.model,n=e.schema,o=e.document.selection,i=Array.from(o.getSelectedBlocks()),r=void 0===t.forceValue?!this.value:t.forceValue;e.change((t=>{if(r){const e=i.filter((t=>Fw(t)||Mw(n,t)));this._applyQuote(t,e)}else this._removeQuote(t,i.filter(Fw))}))}_getValue(){const t=$i(this.editor.model.document.selection.getSelectedBlocks());return!(!t||!Fw(t))}_checkEnabled(){if(this.value)return!0;const t=this.editor.model.document.selection,e=this.editor.model.schema,n=$i(t.getSelectedBlocks());return!!n&&Mw(e,n)}_removeQuote(t,e){zw(t,e).reverse().forEach((e=>{if(e.start.isAtStart&&e.end.isAtEnd)return void t.unwrap(e.start.parent);if(e.start.isAtStart){const n=t.createPositionBefore(e.start.parent);return void t.move(e,n)}e.end.isAtEnd||t.split(e.end);const n=t.createPositionAfter(e.end.parent);t.move(e,n)}))}_applyQuote(t,e){const n=[];zw(t,e).reverse().forEach((e=>{let o=Fw(e.start);o||(o=t.createElement("blockQuote"),t.wrap(e,o)),n.push(o)})),n.reverse().reduce(((e,n)=>e.nextSibling==n?(t.merge(t.createPositionAfter(e)),e):n))}}function Fw(t){return"blockQuote"==t.parent.name?t.parent:null}function zw(t,e){let n,o=0;const i=[];for(;o").replace(/\r?\n/g,"
").replace(/\t/g," ").replace(/^\s/," ").replace(/\s$/," ").replace(/\s\s/g," ")).includes("
")||r.includes("
"))&&(r=`
${r}
`),t=r),i=this.editor.data.htmlProcessor.toView(t)}var r;const s=new m(this,"inputTransformation");this.fire(s,{content:i,dataTransfer:o,targetRanges:e.targetRanges,method:e.method}),s.stop.called&&t.stop(),n.scrollToTheSelection()}),{priority:"low"}),this.listenTo(this,"inputTransformation",((t,n)=>{if(n.content.isEmpty)return;const o=this.editor.data.toModel(n.content,"$clipboardHolder");0!=o.childCount&&(t.stop(),e.change((()=>{this.fire("contentInsertion",{content:o,method:n.method,dataTransfer:n.dataTransfer,targetRanges:n.targetRanges})})))}),{priority:"low"}),this.listenTo(this,"contentInsertion",((t,e)=>{e.resultRange=i._pasteFragmentWithMarkers(e.content)}),{priority:"low"})}_setupCopyCut(){const t=this.editor,e=t.model.document,n=t.editing.view.document,o=(t,n)=>{const o=n.dataTransfer;n.preventDefault(),this._fireOutputTransformationEvent(o,e.selection,t.name)};this.listenTo(n,"copy",o,{priority:"low"}),this.listenTo(n,"cut",((e,n)=>{t.model.canEditAt(t.model.document.selection)?o(e,n):n.preventDefault()}),{priority:"low"}),this.listenTo(this,"outputTransformation",((e,o)=>{const i=t.data.toView(o.content);n.fire("clipboardOutput",{dataTransfer:o.dataTransfer,content:i,method:o.method})}),{priority:"low"}),this.listenTo(n,"clipboardOutput",((n,o)=>{o.content.isEmpty||(o.dataTransfer.setData("text/html",this.editor.data.htmlProcessor.toData(o.content)),o.dataTransfer.setData("text/plain",dA(o.content))),"cut"==o.method&&t.model.deleteContent(e.selection)}),{priority:"low"})}}class SA extends(S()){constructor(){super(...arguments),this._stack=[]}add(t,e){const n=this._stack,o=n[0];this._insertDescriptor(t);const i=n[0];o===i||TA(o,i)||this.fire("change:top",{oldDescriptor:o,newDescriptor:i,writer:e})}remove(t,e){const n=this._stack,o=n[0];this._removeDescriptor(t);const i=n[0];o===i||TA(o,i)||this.fire("change:top",{oldDescriptor:o,newDescriptor:i,writer:e})}_insertDescriptor(t){const e=this._stack,n=e.findIndex((e=>e.id===t.id));if(TA(t,e[n]))return;n>-1&&e.splice(n,1);let o=0;for(;e[o]&&IA(e[o],t);)o++;e.splice(o,0,t)}_removeDescriptor(t){const e=this._stack,n=e.findIndex((e=>e.id===t));n>-1&&e.splice(n,1)}}function TA(t,e){return t&&e&&t.priority==e.priority&&PA(t.classes)==PA(e.classes)}function IA(t,e){return t.priority>e.priority||!(t.priority` element.\n */\nexport default class CodeEditing extends Plugin {\n /**\n * @inheritDoc\n */\n static get pluginName() {\n return 'CodeEditing';\n }\n /**\n * @inheritDoc\n */\n static get requires() {\n return [TwoStepCaretMovement];\n }\n /**\n * @inheritDoc\n */\n init() {\n const editor = this.editor;\n const t = this.editor.t;\n // Allow code attribute on text nodes.\n editor.model.schema.extend('$text', { allowAttributes: CODE });\n editor.model.schema.setAttributeProperties(CODE, {\n isFormatting: true,\n copyOnEnter: false\n });\n editor.conversion.attributeToElement({\n model: CODE,\n view: 'code',\n upcastAlso: {\n styles: {\n 'word-wrap': 'break-word'\n }\n }\n });\n // Create code command.\n editor.commands.add(CODE, new AttributeCommand(editor, CODE));\n // Enable two-step caret movement for `code` attribute.\n editor.plugins.get(TwoStepCaretMovement).registerAttribute(CODE);\n // Setup highlight over selected element.\n inlineHighlight(editor, CODE, 'code', HIGHLIGHT_CLASS);\n // Add the information about the keystroke to the accessibility database.\n editor.accessibility.addKeystrokeInfos({\n keystrokes: [\n {\n label: t('Move out of an inline code style'),\n keystroke: [\n ['arrowleft', 'arrowleft'],\n ['arrowright', 'arrowright']\n ]\n }\n ]\n });\n }\n}\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n/**\n * @module basic-styles/code/codeui\n */\nimport { Plugin } from 'ckeditor5/src/core.js';\nimport { ButtonView, MenuBarMenuListItemButtonView } from 'ckeditor5/src/ui.js';\nimport { getButtonCreator } from '../utils.js';\nimport codeIcon from '../../theme/icons/code.svg';\nimport '../../theme/code.css';\nconst CODE = 'code';\n/**\n * The code UI feature. It introduces the Code button.\n */\nexport default class CodeUI extends Plugin {\n /**\n * @inheritDoc\n */\n static get pluginName() {\n return 'CodeUI';\n }\n /**\n * @inheritDoc\n */\n init() {\n const editor = this.editor;\n const t = editor.locale.t;\n const createButton = getButtonCreator({\n editor,\n commandName: CODE,\n plugin: this,\n icon: codeIcon,\n label: t('Code')\n });\n // Add code button to feature components.\n editor.ui.componentFactory.add(CODE, () => {\n const buttonView = createButton(ButtonView);\n const command = editor.commands.get(CODE);\n buttonView.set({\n tooltip: true\n });\n // Bind button model to command.\n buttonView.bind('isOn').to(command, 'value');\n return buttonView;\n });\n editor.ui.componentFactory.add('menuBar:' + CODE, () => {\n return createButton(MenuBarMenuListItemButtonView);\n });\n }\n}\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n/**\n * @module basic-styles\n */\nexport { default as Bold } from './bold.js';\nexport { default as BoldEditing } from './bold/boldediting.js';\nexport { default as BoldUI } from './bold/boldui.js';\nexport { default as Code } from './code.js';\nexport { default as CodeEditing } from './code/codeediting.js';\nexport { default as CodeUI } from './code/codeui.js';\nexport { default as Italic } from './italic.js';\nexport { default as ItalicEditing } from './italic/italicediting.js';\nexport { default as ItalicUI } from './italic/italicui.js';\nexport { default as Strikethrough } from './strikethrough.js';\nexport { default as StrikethroughEditing } from './strikethrough/strikethroughediting.js';\nexport { default as StrikethroughUI } from './strikethrough/strikethroughui.js';\nexport { default as Subscript } from './subscript.js';\nexport { default as SubscriptEditing } from './subscript/subscriptediting.js';\nexport { default as SubscriptUI } from './subscript/subscriptui.js';\nexport { default as Superscript } from './superscript.js';\nexport { default as SuperscriptEditing } from './superscript/superscriptediting.js';\nexport { default as SuperscriptUI } from './superscript/superscriptui.js';\nexport { default as Underline } from './underline.js';\nexport { default as UnderlineEditing } from './underline/underlineediting.js';\nexport { default as UnderlineUI } from './underline/underlineui.js';\nimport './augmentation.js';\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n/**\n * @module basic-styles/italic\n */\nimport { Plugin } from 'ckeditor5/src/core.js';\nimport ItalicEditing from './italic/italicediting.js';\nimport ItalicUI from './italic/italicui.js';\n/**\n * The italic feature.\n *\n * For a detailed overview check the {@glink features/basic-styles Basic styles feature} guide\n * and the {@glink api/basic-styles package page}.\n *\n * This is a \"glue\" plugin which loads the {@link module:basic-styles/italic/italicediting~ItalicEditing} and\n * {@link module:basic-styles/italic/italicui~ItalicUI} plugins.\n */\nexport default class Italic extends Plugin {\n /**\n * @inheritDoc\n */\n static get requires() {\n return [ItalicEditing, ItalicUI];\n }\n /**\n * @inheritDoc\n */\n static get pluginName() {\n return 'Italic';\n }\n}\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n/**\n * @module basic-styles/italic/italicediting\n */\nimport { Plugin } from 'ckeditor5/src/core.js';\nimport AttributeCommand from '../attributecommand.js';\nconst ITALIC = 'italic';\n/**\n * The italic editing feature.\n *\n * It registers the `'italic'` command, the Ctrl+I keystroke and introduces the `italic` attribute in the model\n * which renders to the view as an `` element.\n */\nexport default class ItalicEditing extends Plugin {\n /**\n * @inheritDoc\n */\n static get pluginName() {\n return 'ItalicEditing';\n }\n /**\n * @inheritDoc\n */\n init() {\n const editor = this.editor;\n const t = this.editor.t;\n // Allow italic attribute on text nodes.\n editor.model.schema.extend('$text', { allowAttributes: ITALIC });\n editor.model.schema.setAttributeProperties(ITALIC, {\n isFormatting: true,\n copyOnEnter: true\n });\n editor.conversion.attributeToElement({\n model: ITALIC,\n view: 'i',\n upcastAlso: [\n 'em',\n {\n styles: {\n 'font-style': 'italic'\n }\n }\n ]\n });\n // Create italic command.\n editor.commands.add(ITALIC, new AttributeCommand(editor, ITALIC));\n // Set the Ctrl+I keystroke.\n editor.keystrokes.set('CTRL+I', ITALIC);\n // Add the information about the keystroke to the accessibility database.\n editor.accessibility.addKeystrokeInfos({\n keystrokes: [\n {\n label: t('Italic text'),\n keystroke: 'CTRL+I'\n }\n ]\n });\n }\n}\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n/**\n * @module basic-styles/italic/italicui\n */\nimport { Plugin } from 'ckeditor5/src/core.js';\nimport { MenuBarMenuListItemButtonView, ButtonView } from 'ckeditor5/src/ui.js';\nimport { getButtonCreator } from '../utils.js';\nimport italicIcon from '../../theme/icons/italic.svg';\nconst ITALIC = 'italic';\n/**\n * The italic UI feature. It introduces the Italic button.\n */\nexport default class ItalicUI extends Plugin {\n /**\n * @inheritDoc\n */\n static get pluginName() {\n return 'ItalicUI';\n }\n /**\n * @inheritDoc\n */\n init() {\n const editor = this.editor;\n const command = editor.commands.get(ITALIC);\n const t = editor.locale.t;\n const createButton = getButtonCreator({\n editor,\n commandName: ITALIC,\n plugin: this,\n icon: italicIcon,\n keystroke: 'CTRL+I',\n label: t('Italic')\n });\n // Add bold button to feature components.\n editor.ui.componentFactory.add(ITALIC, () => {\n const buttonView = createButton(ButtonView);\n buttonView.set({\n tooltip: true\n });\n buttonView.bind('isOn').to(command, 'value');\n return buttonView;\n });\n editor.ui.componentFactory.add('menuBar:' + ITALIC, () => {\n return createButton(MenuBarMenuListItemButtonView);\n });\n }\n}\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n/**\n * @module basic-styles/strikethrough\n */\nimport { Plugin } from 'ckeditor5/src/core.js';\nimport StrikethroughEditing from './strikethrough/strikethroughediting.js';\nimport StrikethroughUI from './strikethrough/strikethroughui.js';\n/**\n * The strikethrough feature.\n *\n * For a detailed overview check the {@glink features/basic-styles Basic styles feature} guide\n * and the {@glink api/basic-styles package page}.\n *\n * This is a \"glue\" plugin which loads the {@link module:basic-styles/strikethrough/strikethroughediting~StrikethroughEditing} and\n * {@link module:basic-styles/strikethrough/strikethroughui~StrikethroughUI} plugins.\n */\nexport default class Strikethrough extends Plugin {\n /**\n * @inheritDoc\n */\n static get requires() {\n return [StrikethroughEditing, StrikethroughUI];\n }\n /**\n * @inheritDoc\n */\n static get pluginName() {\n return 'Strikethrough';\n }\n}\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n/**\n * @module basic-styles/strikethrough/strikethroughediting\n */\nimport { Plugin } from 'ckeditor5/src/core.js';\nimport AttributeCommand from '../attributecommand.js';\nconst STRIKETHROUGH = 'strikethrough';\n/**\n * The strikethrough editing feature.\n *\n * It registers the `'strikethrough'` command, the Ctrl+Shift+X keystroke and introduces the\n * `strikethroughsthrough` attribute in the model which renders to the view\n * as a `` element.\n */\nexport default class StrikethroughEditing extends Plugin {\n /**\n * @inheritDoc\n */\n static get pluginName() {\n return 'StrikethroughEditing';\n }\n /**\n * @inheritDoc\n */\n init() {\n const editor = this.editor;\n const t = this.editor.t;\n // Allow strikethrough attribute on text nodes.\n editor.model.schema.extend('$text', { allowAttributes: STRIKETHROUGH });\n editor.model.schema.setAttributeProperties(STRIKETHROUGH, {\n isFormatting: true,\n copyOnEnter: true\n });\n editor.conversion.attributeToElement({\n model: STRIKETHROUGH,\n view: 's',\n upcastAlso: [\n 'del',\n 'strike',\n {\n styles: {\n 'text-decoration': 'line-through'\n }\n }\n ]\n });\n // Create strikethrough command.\n editor.commands.add(STRIKETHROUGH, new AttributeCommand(editor, STRIKETHROUGH));\n // Set the Ctrl+Shift+X keystroke.\n editor.keystrokes.set('CTRL+SHIFT+X', 'strikethrough');\n // Add the information about the keystroke to the accessibility database.\n editor.accessibility.addKeystrokeInfos({\n keystrokes: [\n {\n label: t('Strikethrough text'),\n keystroke: 'CTRL+SHIFT+X'\n }\n ]\n });\n }\n}\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n/**\n * @module basic-styles/strikethrough/strikethroughui\n */\nimport { Plugin } from 'ckeditor5/src/core.js';\nimport { ButtonView, MenuBarMenuListItemButtonView } from 'ckeditor5/src/ui.js';\nimport { getButtonCreator } from '../utils.js';\nimport strikethroughIcon from '../../theme/icons/strikethrough.svg';\nconst STRIKETHROUGH = 'strikethrough';\n/**\n * The strikethrough UI feature. It introduces the Strikethrough button.\n */\nexport default class StrikethroughUI extends Plugin {\n /**\n * @inheritDoc\n */\n static get pluginName() {\n return 'StrikethroughUI';\n }\n /**\n * @inheritDoc\n */\n init() {\n const editor = this.editor;\n const t = editor.locale.t;\n const createButton = getButtonCreator({\n editor,\n commandName: STRIKETHROUGH,\n plugin: this,\n icon: strikethroughIcon,\n keystroke: 'CTRL+SHIFT+X',\n label: t('Strikethrough')\n });\n // Add strikethrough button to feature components.\n editor.ui.componentFactory.add(STRIKETHROUGH, () => {\n const buttonView = createButton(ButtonView);\n const command = editor.commands.get(STRIKETHROUGH);\n buttonView.set({\n tooltip: true\n });\n // Bind button model to command.\n buttonView.bind('isOn').to(command, 'value');\n return buttonView;\n });\n editor.ui.componentFactory.add('menuBar:' + STRIKETHROUGH, () => {\n return createButton(MenuBarMenuListItemButtonView);\n });\n }\n}\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n/**\n * @module basic-styles/subscript\n */\nimport { Plugin } from 'ckeditor5/src/core.js';\nimport SubscriptEditing from './subscript/subscriptediting.js';\nimport SubscriptUI from './subscript/subscriptui.js';\n/**\n * The subscript feature.\n *\n * It loads the {@link module:basic-styles/subscript/subscriptediting~SubscriptEditing} and\n * {@link module:basic-styles/subscript/subscriptui~SubscriptUI} plugins.\n */\nexport default class Subscript extends Plugin {\n /**\n * @inheritDoc\n */\n static get requires() {\n return [SubscriptEditing, SubscriptUI];\n }\n /**\n * @inheritDoc\n */\n static get pluginName() {\n return 'Subscript';\n }\n}\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n/**\n * @module basic-styles/subscript/subscriptediting\n */\nimport { Plugin } from 'ckeditor5/src/core.js';\nimport AttributeCommand from '../attributecommand.js';\nconst SUBSCRIPT = 'subscript';\n/**\n * The subscript editing feature.\n *\n * It registers the `sub` command and introduces the `sub` attribute in the model which renders to the view\n * as a `` element.\n */\nexport default class SubscriptEditing extends Plugin {\n /**\n * @inheritDoc\n */\n static get pluginName() {\n return 'SubscriptEditing';\n }\n /**\n * @inheritDoc\n */\n init() {\n const editor = this.editor;\n // Allow sub attribute on text nodes.\n editor.model.schema.extend('$text', { allowAttributes: SUBSCRIPT });\n editor.model.schema.setAttributeProperties(SUBSCRIPT, {\n isFormatting: true,\n copyOnEnter: true\n });\n // Build converter from model to view for data and editing pipelines.\n editor.conversion.attributeToElement({\n model: SUBSCRIPT,\n view: 'sub',\n upcastAlso: [\n {\n styles: {\n 'vertical-align': 'sub'\n }\n }\n ]\n });\n // Create sub command.\n editor.commands.add(SUBSCRIPT, new AttributeCommand(editor, SUBSCRIPT));\n }\n}\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n/**\n * @module basic-styles/subscript/subscriptui\n */\nimport { Plugin } from 'ckeditor5/src/core.js';\nimport { ButtonView, MenuBarMenuListItemButtonView } from 'ckeditor5/src/ui.js';\nimport { getButtonCreator } from '../utils.js';\nimport subscriptIcon from '../../theme/icons/subscript.svg';\nconst SUBSCRIPT = 'subscript';\n/**\n * The subscript UI feature. It introduces the Subscript button.\n */\nexport default class SubscriptUI extends Plugin {\n /**\n * @inheritDoc\n */\n static get pluginName() {\n return 'SubscriptUI';\n }\n /**\n * @inheritDoc\n */\n init() {\n const editor = this.editor;\n const t = editor.locale.t;\n const createButton = getButtonCreator({\n editor,\n commandName: SUBSCRIPT,\n plugin: this,\n icon: subscriptIcon,\n label: t('Subscript')\n });\n // Add subscript button to feature components.\n editor.ui.componentFactory.add(SUBSCRIPT, () => {\n const buttonView = createButton(ButtonView);\n const command = editor.commands.get(SUBSCRIPT);\n buttonView.set({\n tooltip: true\n });\n // Bind button model to command.\n buttonView.bind('isOn').to(command, 'value');\n return buttonView;\n });\n editor.ui.componentFactory.add('menuBar:' + SUBSCRIPT, () => {\n return createButton(MenuBarMenuListItemButtonView);\n });\n }\n}\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n/**\n * @module basic-styles/superscript\n */\nimport { Plugin } from 'ckeditor5/src/core.js';\nimport SuperscriptEditing from './superscript/superscriptediting.js';\nimport SuperscriptUI from './superscript/superscriptui.js';\n/**\n * The superscript feature.\n *\n * It loads the {@link module:basic-styles/superscript/superscriptediting~SuperscriptEditing} and\n * {@link module:basic-styles/superscript/superscriptui~SuperscriptUI} plugins.\n */\nexport default class Superscript extends Plugin {\n /**\n * @inheritDoc\n */\n static get requires() {\n return [SuperscriptEditing, SuperscriptUI];\n }\n /**\n * @inheritDoc\n */\n static get pluginName() {\n return 'Superscript';\n }\n}\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n/**\n * @module basic-styles/superscript/superscriptediting\n */\nimport { Plugin } from 'ckeditor5/src/core.js';\nimport AttributeCommand from '../attributecommand.js';\nconst SUPERSCRIPT = 'superscript';\n/**\n * The superscript editing feature.\n *\n * It registers the `super` command and introduces the `super` attribute in the model which renders to the view\n * as a `` element.\n */\nexport default class SuperscriptEditing extends Plugin {\n /**\n * @inheritDoc\n */\n static get pluginName() {\n return 'SuperscriptEditing';\n }\n /**\n * @inheritDoc\n */\n init() {\n const editor = this.editor;\n // Allow super attribute on text nodes.\n editor.model.schema.extend('$text', { allowAttributes: SUPERSCRIPT });\n editor.model.schema.setAttributeProperties(SUPERSCRIPT, {\n isFormatting: true,\n copyOnEnter: true\n });\n // Build converter from model to view for data and editing pipelines.\n editor.conversion.attributeToElement({\n model: SUPERSCRIPT,\n view: 'sup',\n upcastAlso: [\n {\n styles: {\n 'vertical-align': 'super'\n }\n }\n ]\n });\n // Create super command.\n editor.commands.add(SUPERSCRIPT, new AttributeCommand(editor, SUPERSCRIPT));\n }\n}\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n/**\n * @module basic-styles/superscript/superscriptui\n */\nimport { Plugin } from 'ckeditor5/src/core.js';\nimport { ButtonView, MenuBarMenuListItemButtonView } from 'ckeditor5/src/ui.js';\nimport { getButtonCreator } from '../utils.js';\nimport superscriptIcon from '../../theme/icons/superscript.svg';\nconst SUPERSCRIPT = 'superscript';\n/**\n * The superscript UI feature. It introduces the Superscript button.\n */\nexport default class SuperscriptUI extends Plugin {\n /**\n * @inheritDoc\n */\n static get pluginName() {\n return 'SuperscriptUI';\n }\n /**\n * @inheritDoc\n */\n init() {\n const editor = this.editor;\n const t = editor.locale.t;\n const createButton = getButtonCreator({\n editor,\n commandName: SUPERSCRIPT,\n plugin: this,\n icon: superscriptIcon,\n label: t('Superscript')\n });\n // Add superscript button to feature components.\n editor.ui.componentFactory.add(SUPERSCRIPT, () => {\n const buttonView = createButton(ButtonView);\n const command = editor.commands.get(SUPERSCRIPT);\n buttonView.set({\n tooltip: true\n });\n // Bind button model to command.\n buttonView.bind('isOn').to(command, 'value');\n return buttonView;\n });\n editor.ui.componentFactory.add('menuBar:' + SUPERSCRIPT, () => {\n return createButton(MenuBarMenuListItemButtonView);\n });\n }\n}\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n/**\n * @module basic-styles/underline\n */\nimport { Plugin } from 'ckeditor5/src/core.js';\nimport UnderlineEditing from './underline/underlineediting.js';\nimport UnderlineUI from './underline/underlineui.js';\n/**\n * The underline feature.\n *\n * For a detailed overview check the {@glink features/basic-styles Basic styles feature} guide\n * and the {@glink api/basic-styles package page}.\n *\n * This is a \"glue\" plugin which loads the {@link module:basic-styles/underline/underlineediting~UnderlineEditing} and\n * {@link module:basic-styles/underline/underlineui~UnderlineUI} plugins.\n */\nexport default class Underline extends Plugin {\n /**\n * @inheritDoc\n */\n static get requires() {\n return [UnderlineEditing, UnderlineUI];\n }\n /**\n * @inheritDoc\n */\n static get pluginName() {\n return 'Underline';\n }\n}\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n/**\n * @module basic-styles/underline/underlineediting\n */\nimport { Plugin } from 'ckeditor5/src/core.js';\nimport AttributeCommand from '../attributecommand.js';\nconst UNDERLINE = 'underline';\n/**\n * The underline editing feature.\n *\n * It registers the `'underline'` command, the Ctrl+U keystroke\n * and introduces the `underline` attribute in the model which renders to the view as an `` element.\n */\nexport default class UnderlineEditing extends Plugin {\n /**\n * @inheritDoc\n */\n static get pluginName() {\n return 'UnderlineEditing';\n }\n /**\n * @inheritDoc\n */\n init() {\n const editor = this.editor;\n const t = this.editor.t;\n // Allow strikethrough attribute on text nodes.\n editor.model.schema.extend('$text', { allowAttributes: UNDERLINE });\n editor.model.schema.setAttributeProperties(UNDERLINE, {\n isFormatting: true,\n copyOnEnter: true\n });\n editor.conversion.attributeToElement({\n model: UNDERLINE,\n view: 'u',\n upcastAlso: {\n styles: {\n 'text-decoration': 'underline'\n }\n }\n });\n // Create underline command.\n editor.commands.add(UNDERLINE, new AttributeCommand(editor, UNDERLINE));\n // Set the Ctrl+U keystroke.\n editor.keystrokes.set('CTRL+U', 'underline');\n // Add the information about the keystroke to the accessibility database.\n editor.accessibility.addKeystrokeInfos({\n keystrokes: [\n {\n label: t('Underline text'),\n keystroke: 'CTRL+U'\n }\n ]\n });\n }\n}\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n/**\n * @module basic-styles/underline/underlineui\n */\nimport { Plugin } from 'ckeditor5/src/core.js';\nimport { ButtonView, MenuBarMenuListItemButtonView } from 'ckeditor5/src/ui.js';\nimport { getButtonCreator } from '../utils.js';\nimport underlineIcon from '../../theme/icons/underline.svg';\nconst UNDERLINE = 'underline';\n/**\n * The underline UI feature. It introduces the Underline button.\n */\nexport default class UnderlineUI extends Plugin {\n /**\n * @inheritDoc\n */\n static get pluginName() {\n return 'UnderlineUI';\n }\n /**\n * @inheritDoc\n */\n init() {\n const editor = this.editor;\n const command = editor.commands.get(UNDERLINE);\n const t = editor.locale.t;\n const createButton = getButtonCreator({\n editor,\n commandName: UNDERLINE,\n plugin: this,\n icon: underlineIcon,\n label: t('Underline'),\n keystroke: 'CTRL+U'\n });\n // Add bold button to feature components.\n editor.ui.componentFactory.add(UNDERLINE, () => {\n const buttonView = createButton(ButtonView);\n buttonView.set({\n tooltip: true\n });\n buttonView.bind('isOn').to(command, 'value');\n return buttonView;\n });\n editor.ui.componentFactory.add('menuBar:' + UNDERLINE, () => {\n return createButton(MenuBarMenuListItemButtonView);\n });\n }\n}\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n/**\n * Returns a function that creates a (toolbar or menu bar) button for a basic style feature.\n */\nexport function getButtonCreator({ editor, commandName, plugin, icon, label, keystroke }) {\n return (ButtonClass) => {\n const command = editor.commands.get(commandName);\n const view = new ButtonClass(editor.locale);\n view.set({\n label,\n icon,\n keystroke,\n isToggleable: true\n });\n view.bind('isEnabled').to(command, 'isEnabled');\n // Execute the command.\n plugin.listenTo(view, 'execute', () => {\n editor.execute(commandName);\n editor.editing.view.focus();\n });\n return view;\n };\n}\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\nexport {};\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n/**\n * @module block-quote/blockquote\n */\nimport { Plugin } from 'ckeditor5/src/core.js';\nimport BlockQuoteEditing from './blockquoteediting.js';\nimport BlockQuoteUI from './blockquoteui.js';\n/**\n * The block quote plugin.\n *\n * For more information about this feature check the {@glink api/block-quote package page}.\n *\n * This is a \"glue\" plugin which loads the {@link module:block-quote/blockquoteediting~BlockQuoteEditing block quote editing feature}\n * and {@link module:block-quote/blockquoteui~BlockQuoteUI block quote UI feature}.\n *\n * @extends module:core/plugin~Plugin\n */\nexport default class BlockQuote extends Plugin {\n /**\n * @inheritDoc\n */\n static get requires() {\n return [BlockQuoteEditing, BlockQuoteUI];\n }\n /**\n * @inheritDoc\n */\n static get pluginName() {\n return 'BlockQuote';\n }\n}\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n/**\n * @module block-quote/blockquotecommand\n */\nimport { Command } from 'ckeditor5/src/core.js';\nimport { first } from 'ckeditor5/src/utils.js';\n/**\n * The block quote command plugin.\n *\n * @extends module:core/command~Command\n */\nexport default class BlockQuoteCommand extends Command {\n /**\n * @inheritDoc\n */\n refresh() {\n this.value = this._getValue();\n this.isEnabled = this._checkEnabled();\n }\n /**\n * Executes the command. When the command {@link #value is on}, all top-most block quotes within\n * the selection will be removed. If it is off, all selected blocks will be wrapped with\n * a block quote.\n *\n * @fires execute\n * @param options Command options.\n * @param options.forceValue If set, it will force the command behavior. If `true`, the command will apply a block quote,\n * otherwise the command will remove the block quote. If not set, the command will act basing on its current value.\n */\n execute(options = {}) {\n const model = this.editor.model;\n const schema = model.schema;\n const selection = model.document.selection;\n const blocks = Array.from(selection.getSelectedBlocks());\n const value = (options.forceValue === undefined) ? !this.value : options.forceValue;\n model.change(writer => {\n if (!value) {\n this._removeQuote(writer, blocks.filter(findQuote));\n }\n else {\n const blocksToQuote = blocks.filter(block => {\n // Already quoted blocks needs to be considered while quoting too\n // in order to reuse their elements.\n return findQuote(block) || checkCanBeQuoted(schema, block);\n });\n this._applyQuote(writer, blocksToQuote);\n }\n });\n }\n /**\n * Checks the command's {@link #value}.\n */\n _getValue() {\n const selection = this.editor.model.document.selection;\n const firstBlock = first(selection.getSelectedBlocks());\n // In the current implementation, the block quote must be an immediate parent of a block element.\n return !!(firstBlock && findQuote(firstBlock));\n }\n /**\n * Checks whether the command can be enabled in the current context.\n *\n * @returns Whether the command should be enabled.\n */\n _checkEnabled() {\n if (this.value) {\n return true;\n }\n const selection = this.editor.model.document.selection;\n const schema = this.editor.model.schema;\n const firstBlock = first(selection.getSelectedBlocks());\n if (!firstBlock) {\n return false;\n }\n return checkCanBeQuoted(schema, firstBlock);\n }\n /**\n * Removes the quote from given blocks.\n *\n * If blocks which are supposed to be \"unquoted\" are in the middle of a quote,\n * start it or end it, then the quote will be split (if needed) and the blocks\n * will be moved out of it, so other quoted blocks remained quoted.\n */\n _removeQuote(writer, blocks) {\n // Unquote all groups of block. Iterate in the reverse order to not break following ranges.\n getRangesOfBlockGroups(writer, blocks).reverse().forEach(groupRange => {\n if (groupRange.start.isAtStart && groupRange.end.isAtEnd) {\n writer.unwrap(groupRange.start.parent);\n return;\n }\n // The group of blocks are at the beginning of an so let's move them left (out of the ).\n if (groupRange.start.isAtStart) {\n const positionBefore = writer.createPositionBefore(groupRange.start.parent);\n writer.move(groupRange, positionBefore);\n return;\n }\n // The blocks are in the middle of an so we need to split the after the last block\n // so we move the items there.\n if (!groupRange.end.isAtEnd) {\n writer.split(groupRange.end);\n }\n // Now we are sure that groupRange.end.isAtEnd is true, so let's move the blocks right.\n const positionAfter = writer.createPositionAfter(groupRange.end.parent);\n writer.move(groupRange, positionAfter);\n });\n }\n /**\n * Applies the quote to given blocks.\n */\n _applyQuote(writer, blocks) {\n const quotesToMerge = [];\n // Quote all groups of block. Iterate in the reverse order to not break following ranges.\n getRangesOfBlockGroups(writer, blocks).reverse().forEach(groupRange => {\n let quote = findQuote(groupRange.start);\n if (!quote) {\n quote = writer.createElement('blockQuote');\n writer.wrap(groupRange, quote);\n }\n quotesToMerge.push(quote);\n });\n // Merge subsequent elements. Reverse the order again because this time we want to go through\n // the elements in the source order (due to how merge works – it moves the right element's content\n // to the first element and removes the right one. Since we may need to merge a couple of subsequent `` elements\n // we want to keep the reference to the first (furthest left) one.\n quotesToMerge.reverse().reduce((currentQuote, nextQuote) => {\n if (currentQuote.nextSibling == nextQuote) {\n writer.merge(writer.createPositionAfter(currentQuote));\n return currentQuote;\n }\n return nextQuote;\n });\n }\n}\nfunction findQuote(elementOrPosition) {\n return elementOrPosition.parent.name == 'blockQuote' ? elementOrPosition.parent : null;\n}\n/**\n * Returns a minimal array of ranges containing groups of subsequent blocks.\n *\n * content: abcdefgh\n * blocks: [ a, b, d, f, g, h ]\n * output ranges: [ab]c[d]e[fgh]\n */\nfunction getRangesOfBlockGroups(writer, blocks) {\n let startPosition;\n let i = 0;\n const ranges = [];\n while (i < blocks.length) {\n const block = blocks[i];\n const nextBlock = blocks[i + 1];\n if (!startPosition) {\n startPosition = writer.createPositionBefore(block);\n }\n if (!nextBlock || block.nextSibling != nextBlock) {\n ranges.push(writer.createRange(startPosition, writer.createPositionAfter(block)));\n startPosition = null;\n }\n i++;\n }\n return ranges;\n}\n/**\n * Checks whether can wrap the block.\n */\nfunction checkCanBeQuoted(schema, block) {\n // TMP will be replaced with schema.checkWrap().\n const isBQAllowed = schema.checkChild(block.parent, 'blockQuote');\n const isBlockAllowedInBQ = schema.checkChild(['$root', 'blockQuote'], block);\n return isBQAllowed && isBlockAllowedInBQ;\n}\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n/**\n * @module block-quote/blockquoteediting\n */\nimport { Plugin } from 'ckeditor5/src/core.js';\nimport { Enter } from 'ckeditor5/src/enter.js';\nimport { Delete } from 'ckeditor5/src/typing.js';\nimport BlockQuoteCommand from './blockquotecommand.js';\n/**\n * The block quote editing.\n *\n * Introduces the `'blockQuote'` command and the `'blockQuote'` model element.\n *\n * @extends module:core/plugin~Plugin\n */\nexport default class BlockQuoteEditing extends Plugin {\n /**\n * @inheritDoc\n */\n static get pluginName() {\n return 'BlockQuoteEditing';\n }\n /**\n * @inheritDoc\n */\n static get requires() {\n return [Enter, Delete];\n }\n /**\n * @inheritDoc\n */\n init() {\n const editor = this.editor;\n const schema = editor.model.schema;\n editor.commands.add('blockQuote', new BlockQuoteCommand(editor));\n schema.register('blockQuote', {\n inheritAllFrom: '$container'\n });\n editor.conversion.elementToElement({ model: 'blockQuote', view: 'blockquote' });\n // Postfixer which cleans incorrect model states connected with block quotes.\n editor.model.document.registerPostFixer(writer => {\n const changes = editor.model.document.differ.getChanges();\n for (const entry of changes) {\n if (entry.type == 'insert') {\n const element = entry.position.nodeAfter;\n if (!element) {\n // We are inside a text node.\n continue;\n }\n if (element.is('element', 'blockQuote') && element.isEmpty) {\n // Added an empty blockQuote - remove it.\n writer.remove(element);\n return true;\n }\n else if (element.is('element', 'blockQuote') && !schema.checkChild(entry.position, element)) {\n // Added a blockQuote in incorrect place. Unwrap it so the content inside is not lost.\n writer.unwrap(element);\n return true;\n }\n else if (element.is('element')) {\n // Just added an element. Check that all children meet the scheme rules.\n const range = writer.createRangeIn(element);\n for (const child of range.getItems()) {\n if (child.is('element', 'blockQuote') &&\n !schema.checkChild(writer.createPositionBefore(child), child)) {\n writer.unwrap(child);\n return true;\n }\n }\n }\n }\n else if (entry.type == 'remove') {\n const parent = entry.position.parent;\n if (parent.is('element', 'blockQuote') && parent.isEmpty) {\n // Something got removed and now blockQuote is empty. Remove the blockQuote as well.\n writer.remove(parent);\n return true;\n }\n }\n }\n return false;\n });\n const viewDocument = this.editor.editing.view.document;\n const selection = editor.model.document.selection;\n const blockQuoteCommand = editor.commands.get('blockQuote');\n // Overwrite default Enter key behavior.\n // If Enter key is pressed with selection collapsed in empty block inside a quote, break the quote.\n this.listenTo(viewDocument, 'enter', (evt, data) => {\n if (!selection.isCollapsed || !blockQuoteCommand.value) {\n return;\n }\n const positionParent = selection.getLastPosition().parent;\n if (positionParent.isEmpty) {\n editor.execute('blockQuote');\n editor.editing.view.scrollToTheSelection();\n data.preventDefault();\n evt.stop();\n }\n }, { context: 'blockquote' });\n // Overwrite default Backspace key behavior.\n // If Backspace key is pressed with selection collapsed in first empty block inside a quote, break the quote.\n this.listenTo(viewDocument, 'delete', (evt, data) => {\n if (data.direction != 'backward' || !selection.isCollapsed || !blockQuoteCommand.value) {\n return;\n }\n const positionParent = selection.getLastPosition().parent;\n if (positionParent.isEmpty && !positionParent.previousSibling) {\n editor.execute('blockQuote');\n editor.editing.view.scrollToTheSelection();\n data.preventDefault();\n evt.stop();\n }\n }, { context: 'blockquote' });\n }\n}\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n/**\n * @module block-quote/blockquoteui\n */\nimport { Plugin, icons } from 'ckeditor5/src/core.js';\nimport { ButtonView, MenuBarMenuListItemButtonView } from 'ckeditor5/src/ui.js';\nimport '../theme/blockquote.css';\n/**\n * The block quote UI plugin.\n *\n * It introduces the `'blockQuote'` button.\n *\n * @extends module:core/plugin~Plugin\n */\nexport default class BlockQuoteUI extends Plugin {\n /**\n * @inheritDoc\n */\n static get pluginName() {\n return 'BlockQuoteUI';\n }\n /**\n * @inheritDoc\n */\n init() {\n const editor = this.editor;\n const command = editor.commands.get('blockQuote');\n editor.ui.componentFactory.add('blockQuote', () => {\n const buttonView = this._createButton(ButtonView);\n buttonView.set({\n tooltip: true\n });\n // Bind button model to command.\n buttonView.bind('isOn').to(command, 'value');\n return buttonView;\n });\n editor.ui.componentFactory.add('menuBar:blockQuote', () => this._createButton(MenuBarMenuListItemButtonView));\n }\n /**\n * Creates a button for block quote command to use either in toolbar or in menu bar.\n */\n _createButton(ButtonClass) {\n const editor = this.editor;\n const locale = editor.locale;\n const command = editor.commands.get('blockQuote');\n const view = new ButtonClass(editor.locale);\n const t = locale.t;\n view.set({\n label: t('Block quote'),\n icon: icons.quote,\n isToggleable: true\n });\n view.bind('isEnabled').to(command, 'isEnabled');\n // Execute the command.\n this.listenTo(view, 'execute', () => {\n editor.execute('blockQuote');\n editor.editing.view.focus();\n });\n return view;\n }\n}\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n/**\n * @module block-quote\n */\nexport { default as BlockQuote } from './blockquote.js';\nexport { default as BlockQuoteEditing } from './blockquoteediting.js';\nexport { default as BlockQuoteUI } from './blockquoteui.js';\nimport './augmentation.js';\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\nexport {};\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n/**\n * @module clipboard/clipboard\n */\nimport { Plugin } from '@ckeditor/ckeditor5-core';\nimport ClipboardPipeline from './clipboardpipeline.js';\nimport DragDrop from './dragdrop.js';\nimport PastePlainText from './pasteplaintext.js';\nimport ClipboardMarkersUtils from './clipboardmarkersutils.js';\n/**\n * The clipboard feature.\n *\n * Read more about the clipboard integration in the {@glink framework/deep-dive/clipboard clipboard deep-dive} guide.\n *\n * This is a \"glue\" plugin which loads the following plugins:\n * * {@link module:clipboard/clipboardpipeline~ClipboardPipeline}\n * * {@link module:clipboard/dragdrop~DragDrop}\n * * {@link module:clipboard/pasteplaintext~PastePlainText}\n */\nexport default class Clipboard extends Plugin {\n /**\n * @inheritDoc\n */\n static get pluginName() {\n return 'Clipboard';\n }\n /**\n * @inheritDoc\n */\n static get requires() {\n return [ClipboardMarkersUtils, ClipboardPipeline, DragDrop, PastePlainText];\n }\n /**\n * @inheritDoc\n */\n init() {\n const editor = this.editor;\n const t = this.editor.t;\n // Add the information about the keystrokes to the accessibility database.\n editor.accessibility.addKeystrokeInfos({\n keystrokes: [\n {\n label: t('Copy selected content'),\n keystroke: 'CTRL+C'\n },\n {\n label: t('Paste content'),\n keystroke: 'CTRL+V'\n },\n {\n label: t('Paste content as plain text'),\n keystroke: 'CTRL+SHIFT+V'\n }\n ]\n });\n }\n}\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n/**\n * @module clipboard/clipboardmarkersutils\n */\nimport { mapValues } from 'lodash-es';\nimport { uid } from '@ckeditor/ckeditor5-utils';\nimport { Plugin } from '@ckeditor/ckeditor5-core';\nimport { Range } from '@ckeditor/ckeditor5-engine';\n/**\n * Part of the clipboard logic. Responsible for collecting markers from selected fragments\n * and restoring them with proper positions in pasted elements.\n *\n * @internal\n */\nexport default class ClipboardMarkersUtils extends Plugin {\n constructor() {\n super(...arguments);\n /**\n * Map of marker names that can be copied.\n *\n * @internal\n */\n this._markersToCopy = new Map();\n }\n /**\n * @inheritDoc\n */\n static get pluginName() {\n return 'ClipboardMarkersUtils';\n }\n /**\n * Registers marker name as copyable in clipboard pipeline.\n *\n * @param markerName Name of marker that can be copied.\n * @param config Configuration that describes what can be performed on specified marker.\n * @internal\n */\n _registerMarkerToCopy(markerName, config) {\n this._markersToCopy.set(markerName, config);\n }\n /**\n * Performs copy markers on provided selection and paste it to fragment returned from `getCopiedFragment`.\n *\n * \t1. Picks all markers in provided selection.\n * \t2. Inserts fake markers to document.\n * \t3. Gets copied selection fragment from document.\n * \t4. Removes fake elements from fragment and document.\n * \t5. Inserts markers in the place of removed fake markers.\n *\n * Due to selection modification, when inserting items, `getCopiedFragment` must *always* operate on `writer.model.document.selection'.\n * Do not use any other custom selection object within callback, as this will lead to out-of-bounds exceptions in rare scenarios.\n *\n * @param action Type of clipboard action.\n * @param writer An instance of the model writer.\n * @param selection Selection to be checked.\n * @param getCopiedFragment\tCallback that performs copy of selection and returns it as fragment.\n * @internal\n */\n _copySelectedFragmentWithMarkers(action, selection, getCopiedFragment = writer => writer.model.getSelectedContent(writer.model.document.selection)) {\n return this.editor.model.change(writer => {\n const oldSelection = writer.model.document.selection;\n // In some scenarios, such like in drag & drop, passed `selection` parameter is not actually\n // the same `selection` as the `writer.model.document.selection` which means that `_insertFakeMarkersToSelection`\n // is not affecting passed `selection` `start` and `end` positions but rather modifies `writer.model.document.selection`.\n //\n // It is critical due to fact that when we have selection that starts [ 0, 0 ] and ends at [ 1, 0 ]\n // and after inserting fake marker it will point to such marker instead of new widget position at start: [ 1, 0 ] end: [2, 0 ].\n // `writer.insert` modifies only original `writer.model.document.selection`.\n writer.setSelection(selection);\n const sourceSelectionInsertedMarkers = this._insertFakeMarkersIntoSelection(writer, writer.model.document.selection, action);\n const fragment = getCopiedFragment(writer);\n const fakeMarkersRangesInsideRange = this._removeFakeMarkersInsideElement(writer, fragment);\n // [Foo] Bar \n // ^ ^\n // In `_insertFakeMarkersIntoSelection` call we inserted fake marker just before first element.\n // The problem is that the first element can be start position of selection so insertion fake-marker\n // before such element shifts selection (so selection that was at [0, 0] now is at [0, 1]).\n // It means that inserted fake-marker is no longer present inside such selection and is orphaned.\n // This function checks special case of such problem. Markers that are orphaned at the start position\n // and end position in the same time. Basically it means that they overlaps whole element.\n for (const [markerName, elements] of Object.entries(sourceSelectionInsertedMarkers)) {\n fakeMarkersRangesInsideRange[markerName] || (fakeMarkersRangesInsideRange[markerName] = writer.createRangeIn(fragment));\n for (const element of elements) {\n writer.remove(element);\n }\n }\n fragment.markers.clear();\n for (const [markerName, range] of Object.entries(fakeMarkersRangesInsideRange)) {\n fragment.markers.set(markerName, range);\n }\n // Revert back selection to previous one.\n writer.setSelection(oldSelection);\n return fragment;\n });\n }\n /**\n * Performs paste of markers on already pasted element.\n *\n * \t1. Inserts fake markers that are present in fragment element (such fragment will be processed in `getPastedDocumentElement`).\n * \t2. Calls `getPastedDocumentElement` and gets element that is inserted into root model.\n * \t3. Removes all fake markers present in transformed element.\n * \t4. Inserts new markers with removed fake markers ranges into pasted fragment.\n *\n * There are multiple edge cases that have to be considered before calling this function:\n *\n * \t* `markers` are inserted into the same element that must be later transformed inside `getPastedDocumentElement`.\n * \t* Fake marker elements inside `getPastedDocumentElement` can be cloned, but their ranges cannot overlap.\n * \t* If `duplicateOnPaste` is `true` in marker config then associated marker ID is regenerated before pasting.\n *\n * @param action Type of clipboard action.\n * @param markers Object that maps marker name to corresponding range.\n * @param getPastedDocumentElement Getter used to get target markers element.\n * @internal\n */\n _pasteMarkersIntoTransformedElement(markers, getPastedDocumentElement) {\n const pasteMarkers = this._getPasteMarkersFromRangeMap(markers);\n return this.editor.model.change(writer => {\n // Inserts fake markers into source fragment / element that is later transformed inside `getPastedDocumentElement`.\n const sourceFragmentFakeMarkers = this._insertFakeMarkersElements(writer, pasteMarkers);\n // Modifies document fragment (for example, cloning table cells) and then inserts it into the document.\n const transformedElement = getPastedDocumentElement(writer);\n // Removes markers in pasted and transformed fragment in root document.\n const removedFakeMarkers = this._removeFakeMarkersInsideElement(writer, transformedElement);\n // Cleans up fake markers inserted into source fragment (that one before transformation which is not pasted).\n for (const element of Object.values(sourceFragmentFakeMarkers).flat()) {\n writer.remove(element);\n }\n // Inserts to root document fake markers.\n for (const [markerName, range] of Object.entries(removedFakeMarkers)) {\n if (!writer.model.markers.has(markerName)) {\n writer.addMarker(markerName, {\n usingOperation: true,\n affectsData: true,\n range\n });\n }\n }\n return transformedElement;\n });\n }\n /**\n * Pastes document fragment with markers to document.\n * If `duplicateOnPaste` is `true` in marker config then associated markers IDs\n * are regenerated before pasting to avoid markers duplications in content.\n *\n * @param fragment Document fragment that should contain already processed by pipeline markers.\n * @internal\n */\n _pasteFragmentWithMarkers(fragment) {\n const pasteMarkers = this._getPasteMarkersFromRangeMap(fragment.markers);\n fragment.markers.clear();\n for (const copyableMarker of pasteMarkers) {\n fragment.markers.set(copyableMarker.name, copyableMarker.range);\n }\n return this.editor.model.insertContent(fragment);\n }\n /**\n * In some situations we have to perform copy on selected fragment with certain markers. This function allows to temporarily bypass\n * restrictions on markers that we want to copy.\n *\n * This function executes `executor()` callback. For the duration of the callback, if the clipboard pipeline is used to copy\n * content, markers with the specified name will be copied to the clipboard as well.\n *\n * @param markerName Which markers should be copied.\n * @param executor Callback executed.\n * @param config Optional configuration flags used to copy (such like partial copy flag).\n * @internal\n */\n _forceMarkersCopy(markerName, executor, config = {\n allowedActions: 'all',\n copyPartiallySelected: true,\n duplicateOnPaste: true\n }) {\n const before = this._markersToCopy.get(markerName);\n this._markersToCopy.set(markerName, config);\n executor();\n if (before) {\n this._markersToCopy.set(markerName, before);\n }\n else {\n this._markersToCopy.delete(markerName);\n }\n }\n /**\n * Checks if marker can be copied.\n *\n * @param markerName Name of checked marker.\n * @param action Type of clipboard action. If null then checks only if marker is registered as copyable.\n * @internal\n */\n _isMarkerCopyable(markerName, action) {\n const config = this._getMarkerClipboardConfig(markerName);\n if (!config) {\n return false;\n }\n // If there is no action provided then only presence of marker is checked.\n if (!action) {\n return true;\n }\n const { allowedActions } = config;\n return allowedActions === 'all' || allowedActions.includes(action);\n }\n /**\n * Checks if marker has any clipboard copy behavior configuration.\n *\n * @param markerName Name of checked marker.\n */\n _hasMarkerConfiguration(markerName) {\n return !!this._getMarkerClipboardConfig(markerName);\n }\n /**\n * Returns marker's configuration flags passed during registration.\n *\n * @param markerName Name of marker that should be returned.\n * @internal\n */\n _getMarkerClipboardConfig(markerName) {\n const [markerNamePrefix] = markerName.split(':');\n return this._markersToCopy.get(markerNamePrefix) || null;\n }\n /**\n * First step of copying markers. It looks for markers intersecting with given selection and inserts `$marker` elements\n * at positions where document markers start or end. This way `$marker` elements can be easily copied together with\n * the rest of the content of the selection.\n *\n * @param writer An instance of the model writer.\n * @param selection Selection to be checked.\n * @param action Type of clipboard action.\n */\n _insertFakeMarkersIntoSelection(writer, selection, action) {\n const copyableMarkers = this._getCopyableMarkersFromSelection(writer, selection, action);\n return this._insertFakeMarkersElements(writer, copyableMarkers);\n }\n /**\n * Returns array of markers that can be copied in specified selection.\n *\n * If marker cannot be copied partially (according to `copyPartiallySelected` configuration flag) and\n * is not present entirely in any selection range then it will be skipped.\n *\n * @param writer An instance of the model writer.\n * @param selection Selection which will be checked.\n * @param action Type of clipboard action. If null then checks only if marker is registered as copyable.\n */\n _getCopyableMarkersFromSelection(writer, selection, action) {\n const selectionRanges = Array.from(selection.getRanges());\n // Picks all markers in provided ranges. Ensures that there are no duplications if\n // there are multiple ranges that intersects with the same marker.\n const markersInRanges = new Set(selectionRanges.flatMap(selectionRange => Array.from(writer.model.markers.getMarkersIntersectingRange(selectionRange))));\n const isSelectionMarkerCopyable = (marker) => {\n // Check if marker exists in configuration and provided action can be performed on it.\n const isCopyable = this._isMarkerCopyable(marker.name, action);\n if (!isCopyable) {\n return false;\n }\n // Checks if configuration disallows to copy marker only if part of its content is selected.\n //\n // Example:\n // \t Hello [ World ] \n //\t\t\t\t\t\t^ selection\n //\n // In this scenario `marker-a` won't be copied because selection doesn't overlap its content entirely.\n const { copyPartiallySelected } = this._getMarkerClipboardConfig(marker.name);\n if (!copyPartiallySelected) {\n const markerRange = marker.getRange();\n return selectionRanges.some(selectionRange => selectionRange.containsRange(markerRange, true));\n }\n return true;\n };\n return Array\n .from(markersInRanges)\n .filter(isSelectionMarkerCopyable)\n .map((copyableMarker) => {\n // During `dragstart` event original marker is still present in tree.\n // It is removed after the clipboard drop event, so none of the copied markers are inserted at the end.\n // It happens because there already markers with specified `marker.name` when clipboard is trying to insert data\n // and it aborts inserting.\n const name = action === 'dragstart' ? this._getUniqueMarkerName(copyableMarker.name) : copyableMarker.name;\n return {\n name,\n range: copyableMarker.getRange()\n };\n });\n }\n /**\n * Picks all markers from markers map that can be pasted.\n * If `duplicateOnPaste` is `true`, it regenerates their IDs to ensure uniqueness.\n * If marker is not registered, it will be kept in the array anyway.\n *\n * @param markers Object that maps marker name to corresponding range.\n * @param action Type of clipboard action. If null then checks only if marker is registered as copyable.\n */\n _getPasteMarkersFromRangeMap(markers, action = null) {\n const { model } = this.editor;\n const entries = markers instanceof Map ? Array.from(markers.entries()) : Object.entries(markers);\n return entries.flatMap(([markerName, range]) => {\n if (!this._hasMarkerConfiguration(markerName)) {\n return [\n {\n name: markerName,\n range\n }\n ];\n }\n if (this._isMarkerCopyable(markerName, action)) {\n const copyMarkerConfig = this._getMarkerClipboardConfig(markerName);\n const isInGraveyard = model.markers.has(markerName) &&\n model.markers.get(markerName).getRange().root.rootName === '$graveyard';\n if (copyMarkerConfig.duplicateOnPaste || isInGraveyard) {\n markerName = this._getUniqueMarkerName(markerName);\n }\n return [\n {\n name: markerName,\n range\n }\n ];\n }\n return [];\n });\n }\n /**\n * Inserts specified array of fake markers elements to document and assigns them `type` and `name` attributes.\n * Fake markers elements are used to calculate position of markers on pasted fragment that were transformed during\n * steps between copy and paste.\n *\n * @param writer An instance of the model writer.\n * @param markers Array of markers that will be inserted.\n */\n _insertFakeMarkersElements(writer, markers) {\n const mappedMarkers = {};\n const sortedMarkers = markers\n .flatMap(marker => {\n const { start, end } = marker.range;\n return [\n { position: start, marker, type: 'start' },\n { position: end, marker, type: 'end' }\n ];\n })\n // Markers position is sorted backwards to ensure that the insertion of fake markers will not change\n // the position of the next markers.\n .sort(({ position: posA }, { position: posB }) => posA.isBefore(posB) ? 1 : -1);\n for (const { position, marker, type } of sortedMarkers) {\n const fakeMarker = writer.createElement('$marker', {\n 'data-name': marker.name,\n 'data-type': type\n });\n if (!mappedMarkers[marker.name]) {\n mappedMarkers[marker.name] = [];\n }\n mappedMarkers[marker.name].push(fakeMarker);\n writer.insert(fakeMarker, position);\n }\n return mappedMarkers;\n }\n /**\n * Removes all `$marker` elements from the given document fragment.\n *\n * Returns an object where keys are marker names, and values are ranges corresponding to positions\n * where `$marker` elements were inserted.\n *\n * If the document fragment had only one `$marker` element for given marker (start or end) the other boundary is set automatically\n * (to the end or start of the document fragment, respectively).\n *\n * @param writer An instance of the model writer.\n * @param rootElement The element to be checked.\n */\n _removeFakeMarkersInsideElement(writer, rootElement) {\n const fakeMarkersElements = this._getAllFakeMarkersFromElement(writer, rootElement);\n const fakeMarkersRanges = fakeMarkersElements.reduce((acc, fakeMarker) => {\n const position = fakeMarker.markerElement && writer.createPositionBefore(fakeMarker.markerElement);\n let prevFakeMarker = acc[fakeMarker.name];\n // Handle scenario when tables clone cells with the same fake node. Example:\n //\n // | | | \n // ^ cloned ^ cloned\n //\n // The easiest way to bypass this issue is to rename already existing in map nodes and\n // set them new unique name.\n let skipAssign = false;\n if (prevFakeMarker && prevFakeMarker.start && prevFakeMarker.end) {\n const config = this._getMarkerClipboardConfig(fakeMarker.name);\n if (config.duplicateOnPaste) {\n acc[this._getUniqueMarkerName(fakeMarker.name)] = acc[fakeMarker.name];\n }\n else {\n skipAssign = true;\n }\n prevFakeMarker = null;\n }\n if (!skipAssign) {\n acc[fakeMarker.name] = {\n ...prevFakeMarker,\n [fakeMarker.type]: position\n };\n }\n if (fakeMarker.markerElement) {\n writer.remove(fakeMarker.markerElement);\n }\n return acc;\n }, {});\n // We cannot construct ranges directly in previous reduce because element ranges can overlap.\n // In other words lets assume we have such scenario:\n // \n //\n // We have to remove `fake-marker-start` firstly and then remove `fake-marker-2-start`.\n // Removal of `fake-marker-2-start` affects `fake-marker-end` position so we cannot create\n // connection between `fake-marker-start` and `fake-marker-end` without iterating whole set firstly.\n return mapValues(fakeMarkersRanges, range => new Range(range.start || writer.createPositionFromPath(rootElement, [0]), range.end || writer.createPositionAt(rootElement, 'end')));\n }\n /**\n * Returns array that contains list of fake markers with corresponding `$marker` elements.\n *\n * For each marker, there can be two `$marker` elements or only one (if the document fragment contained\n * only the beginning or only the end of a marker).\n *\n * @param writer An instance of the model writer.\n * @param rootElement The element to be checked.\n */\n _getAllFakeMarkersFromElement(writer, rootElement) {\n const foundFakeMarkers = Array\n .from(writer.createRangeIn(rootElement))\n .flatMap(({ item }) => {\n if (!item.is('element', '$marker')) {\n return [];\n }\n const name = item.getAttribute('data-name');\n const type = item.getAttribute('data-type');\n return [\n {\n markerElement: item,\n name,\n type\n }\n ];\n });\n const prependFakeMarkers = [];\n const appendFakeMarkers = [];\n for (const fakeMarker of foundFakeMarkers) {\n if (fakeMarker.type === 'end') {\n // [ phrase phrase ]\n // ^\n // Handle case when marker is just before start of selection.\n // Only end marker is inside selection.\n const hasMatchingStartMarker = foundFakeMarkers.some(otherFakeMarker => otherFakeMarker.name === fakeMarker.name && otherFakeMarker.type === 'start');\n if (!hasMatchingStartMarker) {\n prependFakeMarkers.push({\n markerElement: null,\n name: fakeMarker.name,\n type: 'start'\n });\n }\n }\n if (fakeMarker.type === 'start') {\n // [phrase] \n // ^\n // Handle case when fake marker is after selection.\n // Only start marker is inside selection.\n const hasMatchingEndMarker = foundFakeMarkers.some(otherFakeMarker => otherFakeMarker.name === fakeMarker.name && otherFakeMarker.type === 'end');\n if (!hasMatchingEndMarker) {\n appendFakeMarkers.unshift({\n markerElement: null,\n name: fakeMarker.name,\n type: 'end'\n });\n }\n }\n }\n return [\n ...prependFakeMarkers,\n ...foundFakeMarkers,\n ...appendFakeMarkers\n ];\n }\n /**\n * When copy of markers occurs we have to make sure that pasted markers have different names\n * than source markers. This functions helps with assigning unique part to marker name to\n * prevent duplicated markers error.\n *\n * @param name Name of marker\n */\n _getUniqueMarkerName(name) {\n const parts = name.split(':');\n const newId = uid().substring(1, 6);\n // It looks like the marker already is UID marker so in this scenario just swap\n // last part of marker name and assign new UID.\n //\n // example: comment:{ threadId }:{ id } => comment:{ threadId }:{ newId }\n if (parts.length === 3) {\n return `${parts.slice(0, 2).join(':')}:${newId}`;\n }\n // Assign new segment to marker name with id.\n //\n // example: comment => comment:{ newId }\n return `${parts.join(':')}:${newId}`;\n }\n}\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n/**\n * @module clipboard/clipboardobserver\n */\nimport { EventInfo } from '@ckeditor/ckeditor5-utils';\nimport { DataTransfer, DomEventObserver } from '@ckeditor/ckeditor5-engine';\n/**\n * Clipboard events observer.\n *\n * Fires the following events:\n *\n * * {@link module:engine/view/document~Document#event:clipboardInput},\n * * {@link module:engine/view/document~Document#event:paste},\n * * {@link module:engine/view/document~Document#event:copy},\n * * {@link module:engine/view/document~Document#event:cut},\n * * {@link module:engine/view/document~Document#event:drop},\n * * {@link module:engine/view/document~Document#event:dragover},\n * * {@link module:engine/view/document~Document#event:dragging},\n * * {@link module:engine/view/document~Document#event:dragstart},\n * * {@link module:engine/view/document~Document#event:dragend},\n * * {@link module:engine/view/document~Document#event:dragenter},\n * * {@link module:engine/view/document~Document#event:dragleave}.\n *\n * **Note**: This observer is not available by default (ckeditor5-engine does not add it on its own).\n * To make it available, it needs to be added to {@link module:engine/view/document~Document} by using\n * the {@link module:engine/view/view~View#addObserver `View#addObserver()`} method. Alternatively, you can load the\n * {@link module:clipboard/clipboard~Clipboard} plugin which adds this observer automatically (because it uses it).\n */\nexport default class ClipboardObserver extends DomEventObserver {\n constructor(view) {\n super(view);\n this.domEventType = [\n 'paste', 'copy', 'cut', 'drop', 'dragover', 'dragstart', 'dragend', 'dragenter', 'dragleave'\n ];\n const viewDocument = this.document;\n this.listenTo(viewDocument, 'paste', handleInput('clipboardInput'), { priority: 'low' });\n this.listenTo(viewDocument, 'drop', handleInput('clipboardInput'), { priority: 'low' });\n this.listenTo(viewDocument, 'dragover', handleInput('dragging'), { priority: 'low' });\n function handleInput(type) {\n return (evt, data) => {\n data.preventDefault();\n const targetRanges = data.dropRange ? [data.dropRange] : null;\n const eventInfo = new EventInfo(viewDocument, type);\n viewDocument.fire(eventInfo, {\n dataTransfer: data.dataTransfer,\n method: evt.name,\n targetRanges,\n target: data.target,\n domEvent: data.domEvent\n });\n // If CKEditor handled the input, do not bubble the original event any further.\n // This helps external integrations recognize that fact and act accordingly.\n // https://github.com/ckeditor/ckeditor5-upload/issues/92\n if (eventInfo.stop.called) {\n data.stopPropagation();\n }\n };\n }\n }\n onDomEvent(domEvent) {\n const nativeDataTransfer = 'clipboardData' in domEvent ? domEvent.clipboardData : domEvent.dataTransfer;\n const cacheFiles = domEvent.type == 'drop' || domEvent.type == 'paste';\n const evtData = {\n dataTransfer: new DataTransfer(nativeDataTransfer, { cacheFiles })\n };\n if (domEvent.type == 'drop' || domEvent.type == 'dragover') {\n evtData.dropRange = getDropViewRange(this.view, domEvent);\n }\n this.fire(domEvent.type, domEvent, evtData);\n }\n}\nfunction getDropViewRange(view, domEvent) {\n const domDoc = domEvent.target.ownerDocument;\n const x = domEvent.clientX;\n const y = domEvent.clientY;\n let domRange;\n // Webkit & Blink.\n if (domDoc.caretRangeFromPoint && domDoc.caretRangeFromPoint(x, y)) {\n domRange = domDoc.caretRangeFromPoint(x, y);\n }\n // FF.\n else if (domEvent.rangeParent) {\n domRange = domDoc.createRange();\n domRange.setStart(domEvent.rangeParent, domEvent.rangeOffset);\n domRange.collapse(true);\n }\n if (domRange) {\n return view.domConverter.domRangeToView(domRange);\n }\n return null;\n}\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n/**\n * @module clipboard/clipboardpipeline\n */\nimport { Plugin } from '@ckeditor/ckeditor5-core';\nimport { EventInfo } from '@ckeditor/ckeditor5-utils';\nimport ClipboardObserver from './clipboardobserver.js';\nimport plainTextToHtml from './utils/plaintexttohtml.js';\nimport normalizeClipboardHtml from './utils/normalizeclipboarddata.js';\nimport viewToPlainText from './utils/viewtoplaintext.js';\nimport ClipboardMarkersUtils from './clipboardmarkersutils.js';\n// Input pipeline events overview:\n//\n// ┌──────────────────────┐ ┌──────────────────────┐\n// │ view.Document │ │ view.Document │\n// │ paste │ │ drop │\n// └───────────┬──────────┘ └───────────┬──────────┘\n// │ │\n// └────────────────┌────────────────┘\n// │\n// ┌─────────V────────┐\n// │ view.Document │ Retrieves text/html or text/plain from data.dataTransfer\n// │ clipboardInput │ and processes it to view.DocumentFragment.\n// └─────────┬────────┘\n// │\n// ┌───────────V───────────┐\n// │ ClipboardPipeline │ Converts view.DocumentFragment to model.DocumentFragment.\n// │ inputTransformation │\n// └───────────┬───────────┘\n// │\n// ┌──────────V──────────┐\n// │ ClipboardPipeline │ Calls model.insertContent().\n// │ contentInsertion │\n// └─────────────────────┘\n//\n//\n// Output pipeline events overview:\n//\n// ┌──────────────────────┐ ┌──────────────────────┐\n// │ view.Document │ │ view.Document │ Retrieves the selected model.DocumentFragment\n// │ copy │ │ cut │ and fires the `outputTransformation` event.\n// └───────────┬──────────┘ └───────────┬──────────┘\n// │ │\n// └────────────────┌────────────────┘\n// │\n// ┌───────────V───────────┐\n// │ ClipboardPipeline │ Processes model.DocumentFragment and converts it to\n// │ outputTransformation │ view.DocumentFragment.\n// └───────────┬───────────┘\n// │\n// ┌─────────V────────┐\n// │ view.Document │ Processes view.DocumentFragment to text/html and text/plain\n// │ clipboardOutput │ and stores the results in data.dataTransfer.\n// └──────────────────┘\n//\n/**\n * The clipboard pipeline feature. It is responsible for intercepting the `paste` and `drop` events and\n * passing the pasted content through a series of events in order to insert it into the editor's content.\n * It also handles the `cut` and `copy` events to fill the native clipboard with the serialized editor's data.\n *\n * # Input pipeline\n *\n * The behavior of the default handlers (all at a `low` priority):\n *\n * ## Event: `paste` or `drop`\n *\n * 1. Translates the event data.\n * 2. Fires the {@link module:engine/view/document~Document#event:clipboardInput `view.Document#clipboardInput`} event.\n *\n * ## Event: `view.Document#clipboardInput`\n *\n * 1. If the `data.content` event field is already set (by some listener on a higher priority), it takes this content and fires the event\n * from the last point.\n * 2. Otherwise, it retrieves `text/html` or `text/plain` from `data.dataTransfer`.\n * 3. Normalizes the raw data by applying simple filters on string data.\n * 4. Processes the raw data to {@link module:engine/view/documentfragment~DocumentFragment `view.DocumentFragment`} with the\n * {@link module:engine/controller/datacontroller~DataController#htmlProcessor `DataController#htmlProcessor`}.\n * 5. Fires the {@link module:clipboard/clipboardpipeline~ClipboardPipeline#event:inputTransformation\n * `ClipboardPipeline#inputTransformation`} event with the view document fragment in the `data.content` event field.\n *\n * ## Event: `ClipboardPipeline#inputTransformation`\n *\n * 1. Converts {@link module:engine/view/documentfragment~DocumentFragment `view.DocumentFragment`} from the `data.content` field to\n * {@link module:engine/model/documentfragment~DocumentFragment `model.DocumentFragment`}.\n * 2. Fires the {@link module:clipboard/clipboardpipeline~ClipboardPipeline#event:contentInsertion `ClipboardPipeline#contentInsertion`}\n * event with the model document fragment in the `data.content` event field.\n * **Note**: The `ClipboardPipeline#contentInsertion` event is fired within a model change block to allow other handlers\n * to run in the same block without post-fixers called in between (i.e., the selection post-fixer).\n *\n * ## Event: `ClipboardPipeline#contentInsertion`\n *\n * 1. Calls {@link module:engine/model/model~Model#insertContent `model.insertContent()`} to insert `data.content`\n * at the current selection position.\n *\n * # Output pipeline\n *\n * The behavior of the default handlers (all at a `low` priority):\n *\n * ## Event: `copy`, `cut` or `dragstart`\n *\n * 1. Retrieves the selected {@link module:engine/model/documentfragment~DocumentFragment `model.DocumentFragment`} by calling\n * {@link module:engine/model/model~Model#getSelectedContent `model#getSelectedContent()`}.\n * 2. Converts the model document fragment to {@link module:engine/view/documentfragment~DocumentFragment `view.DocumentFragment`}.\n * 3. Fires the {@link module:engine/view/document~Document#event:clipboardOutput `view.Document#clipboardOutput`} event\n * with the view document fragment in the `data.content` event field.\n *\n * ## Event: `view.Document#clipboardOutput`\n *\n * 1. Processes `data.content` to HTML and plain text with the\n * {@link module:engine/controller/datacontroller~DataController#htmlProcessor `DataController#htmlProcessor`}.\n * 2. Updates the `data.dataTransfer` data for `text/html` and `text/plain` with the processed data.\n * 3. For the `cut` method, calls {@link module:engine/model/model~Model#deleteContent `model.deleteContent()`}\n * on the current selection.\n *\n * Read more about the clipboard integration in the {@glink framework/deep-dive/clipboard clipboard deep-dive} guide.\n */\nexport default class ClipboardPipeline extends Plugin {\n /**\n * @inheritDoc\n */\n static get pluginName() {\n return 'ClipboardPipeline';\n }\n /**\n * @inheritDoc\n */\n static get requires() {\n return [ClipboardMarkersUtils];\n }\n /**\n * @inheritDoc\n */\n init() {\n const editor = this.editor;\n const view = editor.editing.view;\n view.addObserver(ClipboardObserver);\n this._setupPasteDrop();\n this._setupCopyCut();\n }\n /**\n * Fires Clipboard `'outputTransformation'` event for given parameters.\n *\n * @internal\n */\n _fireOutputTransformationEvent(dataTransfer, selection, method) {\n const clipboardMarkersUtils = this.editor.plugins.get('ClipboardMarkersUtils');\n this.editor.model.enqueueChange({ isUndoable: method === 'cut' }, () => {\n const documentFragment = clipboardMarkersUtils._copySelectedFragmentWithMarkers(method, selection);\n this.fire('outputTransformation', {\n dataTransfer,\n content: documentFragment,\n method\n });\n });\n }\n /**\n * The clipboard paste pipeline.\n */\n _setupPasteDrop() {\n const editor = this.editor;\n const model = editor.model;\n const view = editor.editing.view;\n const viewDocument = view.document;\n const clipboardMarkersUtils = this.editor.plugins.get('ClipboardMarkersUtils');\n // Pasting is disabled when selection is in non-editable place.\n // Dropping is disabled in drag and drop handler.\n this.listenTo(viewDocument, 'clipboardInput', (evt, data) => {\n if (data.method == 'paste' && !editor.model.canEditAt(editor.model.document.selection)) {\n evt.stop();\n }\n }, { priority: 'highest' });\n this.listenTo(viewDocument, 'clipboardInput', (evt, data) => {\n const dataTransfer = data.dataTransfer;\n let content;\n // Some feature could already inject content in the higher priority event handler (i.e., codeBlock).\n if (data.content) {\n content = data.content;\n }\n else {\n let contentData = '';\n if (dataTransfer.getData('text/html')) {\n contentData = normalizeClipboardHtml(dataTransfer.getData('text/html'));\n }\n else if (dataTransfer.getData('text/plain')) {\n contentData = plainTextToHtml(dataTransfer.getData('text/plain'));\n }\n content = this.editor.data.htmlProcessor.toView(contentData);\n }\n const eventInfo = new EventInfo(this, 'inputTransformation');\n this.fire(eventInfo, {\n content,\n dataTransfer,\n targetRanges: data.targetRanges,\n method: data.method\n });\n // If CKEditor handled the input, do not bubble the original event any further.\n // This helps external integrations recognize this fact and act accordingly.\n // https://github.com/ckeditor/ckeditor5-upload/issues/92\n if (eventInfo.stop.called) {\n evt.stop();\n }\n view.scrollToTheSelection();\n }, { priority: 'low' });\n this.listenTo(this, 'inputTransformation', (evt, data) => {\n if (data.content.isEmpty) {\n return;\n }\n const dataController = this.editor.data;\n // Convert the pasted content into a model document fragment.\n // The conversion is contextual, but in this case an \"all allowed\" context is needed\n // and for that we use the $clipboardHolder item.\n const modelFragment = dataController.toModel(data.content, '$clipboardHolder');\n if (modelFragment.childCount == 0) {\n return;\n }\n evt.stop();\n // Fire content insertion event in a single change block to allow other handlers to run in the same block\n // without post-fixers called in between (i.e., the selection post-fixer).\n model.change(() => {\n this.fire('contentInsertion', {\n content: modelFragment,\n method: data.method,\n dataTransfer: data.dataTransfer,\n targetRanges: data.targetRanges\n });\n });\n }, { priority: 'low' });\n this.listenTo(this, 'contentInsertion', (evt, data) => {\n data.resultRange = clipboardMarkersUtils._pasteFragmentWithMarkers(data.content);\n }, { priority: 'low' });\n }\n /**\n * The clipboard copy/cut pipeline.\n */\n _setupCopyCut() {\n const editor = this.editor;\n const modelDocument = editor.model.document;\n const view = editor.editing.view;\n const viewDocument = view.document;\n const onCopyCut = (evt, data) => {\n const dataTransfer = data.dataTransfer;\n data.preventDefault();\n this._fireOutputTransformationEvent(dataTransfer, modelDocument.selection, evt.name);\n };\n this.listenTo(viewDocument, 'copy', onCopyCut, { priority: 'low' });\n this.listenTo(viewDocument, 'cut', (evt, data) => {\n // Cutting is disabled when selection is in non-editable place.\n // See: https://github.com/ckeditor/ckeditor5-clipboard/issues/26.\n if (!editor.model.canEditAt(editor.model.document.selection)) {\n data.preventDefault();\n }\n else {\n onCopyCut(evt, data);\n }\n }, { priority: 'low' });\n this.listenTo(this, 'outputTransformation', (evt, data) => {\n const content = editor.data.toView(data.content);\n viewDocument.fire('clipboardOutput', {\n dataTransfer: data.dataTransfer,\n content,\n method: data.method\n });\n }, { priority: 'low' });\n this.listenTo(viewDocument, 'clipboardOutput', (evt, data) => {\n if (!data.content.isEmpty) {\n data.dataTransfer.setData('text/html', this.editor.data.htmlProcessor.toData(data.content));\n data.dataTransfer.setData('text/plain', viewToPlainText(data.content));\n }\n if (data.method == 'cut') {\n editor.model.deleteContent(modelDocument.selection);\n }\n }, { priority: 'low' });\n }\n}\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n/**\n * @module clipboard/dragdrop\n */\nimport { Plugin } from '@ckeditor/ckeditor5-core';\nimport { LiveRange, MouseObserver } from '@ckeditor/ckeditor5-engine';\nimport { Widget, isWidget } from '@ckeditor/ckeditor5-widget';\nimport { env, uid, global, createElement, DomEmitterMixin, delay, Rect } from '@ckeditor/ckeditor5-utils';\nimport ClipboardPipeline from './clipboardpipeline.js';\nimport ClipboardObserver from './clipboardobserver.js';\nimport DragDropTarget from './dragdroptarget.js';\nimport DragDropBlockToolbar from './dragdropblocktoolbar.js';\nimport '../theme/clipboard.css';\n// Drag and drop events overview:\n//\n// ┌──────────────────┐\n// │ mousedown │ Sets the draggable attribute.\n// └─────────┬────────┘\n// │\n// └─────────────────────┐\n// │ │\n// │ ┌─────────V────────┐\n// │ │ mouseup │ Dragging did not start, removes the draggable attribute.\n// │ └──────────────────┘\n// │\n// ┌─────────V────────┐ Retrieves the selected model.DocumentFragment\n// │ dragstart │ and converts it to view.DocumentFragment.\n// └─────────┬────────┘\n// │\n// ┌─────────V────────┐ Processes view.DocumentFragment to text/html and text/plain\n// │ clipboardOutput │ and stores the results in data.dataTransfer.\n// └─────────┬────────┘\n// │\n// │ DOM dragover\n// ┌────────────┐\n// │ │\n// ┌─────────V────────┐ │\n// │ dragging │ │ Updates the drop target marker.\n// └─────────┬────────┘ │\n// │ │\n// ┌─────────────└────────────┘\n// │ │ │\n// │ ┌─────────V────────┐ │\n// │ │ dragleave │ │ Removes the drop target marker.\n// │ └─────────┬────────┘ │\n// │ │ │\n// ┌───│─────────────┘ │\n// │ │ │ │\n// │ │ ┌─────────V────────┐ │\n// │ │ │ dragenter │ │ Focuses the editor view.\n// │ │ └─────────┬────────┘ │\n// │ │ │ │\n// │ │ └────────────┘\n// │ │\n// │ └─────────────┐\n// │ │ │\n// │ │ ┌─────────V────────┐\n// └───┐ │ drop │ (The default handler of the clipboard pipeline).\n// │ └─────────┬────────┘\n// │ │\n// │ ┌─────────V────────┐ Resolves the final data.targetRanges.\n// │ │ clipboardInput │ Aborts if dropping on dragged content.\n// │ └─────────┬────────┘\n// │ │\n// │ ┌─────────V────────┐\n// │ │ clipboardInput │ (The default handler of the clipboard pipeline).\n// │ └─────────┬────────┘\n// │ │\n// │ ┌───────────V───────────┐\n// │ │ inputTransformation │ (The default handler of the clipboard pipeline).\n// │ └───────────┬───────────┘\n// │ │\n// │ ┌──────────V──────────┐\n// │ │ contentInsertion │ Updates the document selection to drop range.\n// │ └──────────┬──────────┘\n// │ │\n// │ ┌──────────V──────────┐\n// │ │ contentInsertion │ (The default handler of the clipboard pipeline).\n// │ └──────────┬──────────┘\n// │ │\n// │ ┌──────────V──────────┐\n// │ │ contentInsertion │ Removes the content from the original range if the insertion was successful.\n// │ └──────────┬──────────┘\n// │ │\n// └─────────────┐\n// │\n// ┌─────────V────────┐\n// │ dragend │ Removes the drop marker and cleans the state.\n// └──────────────────┘\n//\n/**\n * The drag and drop feature. It works on top of the {@link module:clipboard/clipboardpipeline~ClipboardPipeline}.\n *\n * Read more about the clipboard integration in the {@glink framework/deep-dive/clipboard clipboard deep-dive} guide.\n *\n * @internal\n */\nexport default class DragDrop extends Plugin {\n constructor() {\n super(...arguments);\n /**\n * A delayed callback removing draggable attributes.\n */\n this._clearDraggableAttributesDelayed = delay(() => this._clearDraggableAttributes(), 40);\n /**\n * Whether the dragged content can be dropped only in block context.\n */\n // TODO handle drag from other editor instance\n // TODO configure to use block, inline or both\n this._blockMode = false;\n /**\n * DOM Emitter.\n */\n this._domEmitter = new (DomEmitterMixin())();\n }\n /**\n * @inheritDoc\n */\n static get pluginName() {\n return 'DragDrop';\n }\n /**\n * @inheritDoc\n */\n static get requires() {\n return [ClipboardPipeline, Widget, DragDropTarget, DragDropBlockToolbar];\n }\n /**\n * @inheritDoc\n */\n init() {\n const editor = this.editor;\n const view = editor.editing.view;\n this._draggedRange = null;\n this._draggingUid = '';\n this._draggableElement = null;\n view.addObserver(ClipboardObserver);\n view.addObserver(MouseObserver);\n this._setupDragging();\n this._setupContentInsertionIntegration();\n this._setupClipboardInputIntegration();\n this._setupDraggableAttributeHandling();\n this.listenTo(editor, 'change:isReadOnly', (evt, name, isReadOnly) => {\n if (isReadOnly) {\n this.forceDisabled('readOnlyMode');\n }\n else {\n this.clearForceDisabled('readOnlyMode');\n }\n });\n this.on('change:isEnabled', (evt, name, isEnabled) => {\n if (!isEnabled) {\n this._finalizeDragging(false);\n }\n });\n if (env.isAndroid) {\n this.forceDisabled('noAndroidSupport');\n }\n }\n /**\n * @inheritDoc\n */\n destroy() {\n if (this._draggedRange) {\n this._draggedRange.detach();\n this._draggedRange = null;\n }\n if (this._previewContainer) {\n this._previewContainer.remove();\n }\n this._domEmitter.stopListening();\n this._clearDraggableAttributesDelayed.cancel();\n return super.destroy();\n }\n /**\n * Drag and drop events handling.\n */\n _setupDragging() {\n const editor = this.editor;\n const model = editor.model;\n const view = editor.editing.view;\n const viewDocument = view.document;\n const dragDropTarget = editor.plugins.get(DragDropTarget);\n // The handler for the drag start; it is responsible for setting data transfer object.\n this.listenTo(viewDocument, 'dragstart', (evt, data) => {\n // Don't drag the editable element itself.\n if (data.target && data.target.is('editableElement')) {\n data.preventDefault();\n return;\n }\n this._prepareDraggedRange(data.target);\n if (!this._draggedRange) {\n data.preventDefault();\n return;\n }\n this._draggingUid = uid();\n data.dataTransfer.effectAllowed = this.isEnabled ? 'copyMove' : 'copy';\n data.dataTransfer.setData('application/ckeditor5-dragging-uid', this._draggingUid);\n const draggedSelection = model.createSelection(this._draggedRange.toRange());\n const clipboardPipeline = this.editor.plugins.get('ClipboardPipeline');\n clipboardPipeline._fireOutputTransformationEvent(data.dataTransfer, draggedSelection, 'dragstart');\n const { dataTransfer, domTarget, domEvent } = data;\n const { clientX } = domEvent;\n this._updatePreview({ dataTransfer, domTarget, clientX });\n data.stopPropagation();\n if (!this.isEnabled) {\n this._draggedRange.detach();\n this._draggedRange = null;\n this._draggingUid = '';\n }\n }, { priority: 'low' });\n // The handler for finalizing drag and drop. It should always be triggered after dragging completes\n // even if it was completed in a different application.\n // Note: This is not fired if source text node got removed while downcasting a marker.\n this.listenTo(viewDocument, 'dragend', (evt, data) => {\n this._finalizeDragging(!data.dataTransfer.isCanceled && data.dataTransfer.dropEffect == 'move');\n }, { priority: 'low' });\n // Reset block dragging mode even if dropped outside the editable.\n this._domEmitter.listenTo(global.document, 'dragend', () => {\n this._blockMode = false;\n }, { useCapture: true });\n // Dragging over the editable.\n this.listenTo(viewDocument, 'dragenter', () => {\n if (!this.isEnabled) {\n return;\n }\n view.focus();\n });\n // Dragging out of the editable.\n this.listenTo(viewDocument, 'dragleave', () => {\n // We do not know if the mouse left the editor or just some element in it, so let us wait a few milliseconds\n // to check if 'dragover' is not fired.\n dragDropTarget.removeDropMarkerDelayed();\n });\n // Handler for moving dragged content over the target area.\n this.listenTo(viewDocument, 'dragging', (evt, data) => {\n if (!this.isEnabled) {\n data.dataTransfer.dropEffect = 'none';\n return;\n }\n const { clientX, clientY } = data.domEvent;\n dragDropTarget.updateDropMarker(data.target, data.targetRanges, clientX, clientY, this._blockMode, this._draggedRange);\n // If this is content being dragged from another editor, moving out of current editor instance\n // is not possible until 'dragend' event case will be fixed.\n if (!this._draggedRange) {\n data.dataTransfer.dropEffect = 'copy';\n }\n // In Firefox it is already set and effect allowed remains the same as originally set.\n if (!env.isGecko) {\n if (data.dataTransfer.effectAllowed == 'copy') {\n data.dataTransfer.dropEffect = 'copy';\n }\n else if (['all', 'copyMove'].includes(data.dataTransfer.effectAllowed)) {\n data.dataTransfer.dropEffect = 'move';\n }\n }\n evt.stop();\n }, { priority: 'low' });\n }\n /**\n * Integration with the `clipboardInput` event.\n */\n _setupClipboardInputIntegration() {\n const editor = this.editor;\n const view = editor.editing.view;\n const viewDocument = view.document;\n const dragDropTarget = editor.plugins.get(DragDropTarget);\n // Update the event target ranges and abort dropping if dropping over itself.\n this.listenTo(viewDocument, 'clipboardInput', (evt, data) => {\n if (data.method != 'drop') {\n return;\n }\n const { clientX, clientY } = data.domEvent;\n const targetRange = dragDropTarget.getFinalDropRange(data.target, data.targetRanges, clientX, clientY, this._blockMode, this._draggedRange);\n if (!targetRange) {\n this._finalizeDragging(false);\n evt.stop();\n return;\n }\n // Since we cannot rely on the drag end event, we must check if the local drag range is from the current drag and drop\n // or it is from some previous not cleared one.\n if (this._draggedRange && this._draggingUid != data.dataTransfer.getData('application/ckeditor5-dragging-uid')) {\n this._draggedRange.detach();\n this._draggedRange = null;\n this._draggingUid = '';\n }\n // Do not do anything if some content was dragged within the same document to the same position.\n const isMove = getFinalDropEffect(data.dataTransfer) == 'move';\n if (isMove && this._draggedRange && this._draggedRange.containsRange(targetRange, true)) {\n this._finalizeDragging(false);\n evt.stop();\n return;\n }\n // Override the target ranges with the one adjusted to the best one for a drop.\n data.targetRanges = [editor.editing.mapper.toViewRange(targetRange)];\n }, { priority: 'high' });\n }\n /**\n * Integration with the `contentInsertion` event of the clipboard pipeline.\n */\n _setupContentInsertionIntegration() {\n const clipboardPipeline = this.editor.plugins.get(ClipboardPipeline);\n clipboardPipeline.on('contentInsertion', (evt, data) => {\n if (!this.isEnabled || data.method !== 'drop') {\n return;\n }\n // Update the selection to the target range in the same change block to avoid selection post-fixing\n // and to be able to clone text attributes for plain text dropping.\n const ranges = data.targetRanges.map(viewRange => this.editor.editing.mapper.toModelRange(viewRange));\n this.editor.model.change(writer => writer.setSelection(ranges));\n }, { priority: 'high' });\n clipboardPipeline.on('contentInsertion', (evt, data) => {\n if (!this.isEnabled || data.method !== 'drop') {\n return;\n }\n // Remove dragged range content, remove markers, clean after dragging.\n const isMove = getFinalDropEffect(data.dataTransfer) == 'move';\n // Whether any content was inserted (insertion might fail if the schema is disallowing some elements\n // (for example an image caption allows only the content of a block but not blocks themselves.\n // Some integrations might not return valid range (i.e., table pasting).\n const isSuccess = !data.resultRange || !data.resultRange.isCollapsed;\n this._finalizeDragging(isSuccess && isMove);\n }, { priority: 'lowest' });\n }\n /**\n * Adds listeners that add the `draggable` attribute to the elements while the mouse button is down so the dragging could start.\n */\n _setupDraggableAttributeHandling() {\n const editor = this.editor;\n const view = editor.editing.view;\n const viewDocument = view.document;\n // Add the 'draggable' attribute to the widget while pressing the selection handle.\n // This is required for widgets to be draggable. In Chrome it will enable dragging text nodes.\n this.listenTo(viewDocument, 'mousedown', (evt, data) => {\n // The lack of data can be caused by editor tests firing fake mouse events. This should not occur\n // in real-life scenarios but this greatly simplifies editor tests that would otherwise fail a lot.\n if (env.isAndroid || !data) {\n return;\n }\n this._clearDraggableAttributesDelayed.cancel();\n // Check if this is a mousedown over the widget (but not a nested editable).\n let draggableElement = findDraggableWidget(data.target);\n // Note: There is a limitation that if more than a widget is selected (a widget and some text)\n // and dragging starts on the widget, then only the widget is dragged.\n // If this was not a widget then we should check if we need to drag some text content.\n // In Chrome set a 'draggable' attribute on closest editable to allow immediate dragging of the selected text range.\n // In Firefox this is not needed. In Safari it makes the whole editable draggable (not just textual content).\n // Disabled in read-only mode because draggable=\"true\" + contenteditable=\"false\" results\n // in not firing selectionchange event ever, which makes the selection stuck in read-only mode.\n if (env.isBlink && !editor.isReadOnly && !draggableElement && !viewDocument.selection.isCollapsed) {\n const selectedElement = viewDocument.selection.getSelectedElement();\n if (!selectedElement || !isWidget(selectedElement)) {\n draggableElement = viewDocument.selection.editableElement;\n }\n }\n if (draggableElement) {\n view.change(writer => {\n writer.setAttribute('draggable', 'true', draggableElement);\n });\n // Keep the reference to the model element in case the view element gets removed while dragging.\n this._draggableElement = editor.editing.mapper.toModelElement(draggableElement);\n }\n });\n // Remove the draggable attribute in case no dragging started (only mousedown + mouseup).\n this.listenTo(viewDocument, 'mouseup', () => {\n if (!env.isAndroid) {\n this._clearDraggableAttributesDelayed();\n }\n });\n }\n /**\n * Removes the `draggable` attribute from the element that was used for dragging.\n */\n _clearDraggableAttributes() {\n const editing = this.editor.editing;\n editing.view.change(writer => {\n // Remove 'draggable' attribute.\n if (this._draggableElement && this._draggableElement.root.rootName != '$graveyard') {\n writer.removeAttribute('draggable', editing.mapper.toViewElement(this._draggableElement));\n }\n this._draggableElement = null;\n });\n }\n /**\n * Deletes the dragged content from its original range and clears the dragging state.\n *\n * @param moved Whether the move succeeded.\n */\n _finalizeDragging(moved) {\n const editor = this.editor;\n const model = editor.model;\n const dragDropTarget = editor.plugins.get(DragDropTarget);\n dragDropTarget.removeDropMarker();\n this._clearDraggableAttributes();\n if (editor.plugins.has('WidgetToolbarRepository')) {\n const widgetToolbarRepository = editor.plugins.get('WidgetToolbarRepository');\n widgetToolbarRepository.clearForceDisabled('dragDrop');\n }\n this._draggingUid = '';\n if (this._previewContainer) {\n this._previewContainer.remove();\n this._previewContainer = undefined;\n }\n if (!this._draggedRange) {\n return;\n }\n // Delete moved content.\n if (moved && this.isEnabled) {\n model.change(writer => {\n const selection = model.createSelection(this._draggedRange);\n model.deleteContent(selection, { doNotAutoparagraph: true });\n // Check result selection if it does not require auto-paragraphing of empty container.\n const selectionParent = selection.getFirstPosition().parent;\n if (selectionParent.isEmpty &&\n !model.schema.checkChild(selectionParent, '$text') &&\n model.schema.checkChild(selectionParent, 'paragraph')) {\n writer.insertElement('paragraph', selectionParent, 0);\n }\n });\n }\n this._draggedRange.detach();\n this._draggedRange = null;\n }\n /**\n * Sets the dragged source range based on event target and document selection.\n */\n _prepareDraggedRange(target) {\n const editor = this.editor;\n const model = editor.model;\n const selection = model.document.selection;\n // Check if this is dragstart over the widget (but not a nested editable).\n const draggableWidget = target ? findDraggableWidget(target) : null;\n if (draggableWidget) {\n const modelElement = editor.editing.mapper.toModelElement(draggableWidget);\n this._draggedRange = LiveRange.fromRange(model.createRangeOn(modelElement));\n this._blockMode = model.schema.isBlock(modelElement);\n // Disable toolbars so they won't obscure the drop area.\n if (editor.plugins.has('WidgetToolbarRepository')) {\n const widgetToolbarRepository = editor.plugins.get('WidgetToolbarRepository');\n widgetToolbarRepository.forceDisabled('dragDrop');\n }\n return;\n }\n // If this was not a widget we should check if we need to drag some text content.\n if (selection.isCollapsed && !selection.getFirstPosition().parent.isEmpty) {\n return;\n }\n const blocks = Array.from(selection.getSelectedBlocks());\n const draggedRange = selection.getFirstRange();\n if (blocks.length == 0) {\n this._draggedRange = LiveRange.fromRange(draggedRange);\n return;\n }\n const blockRange = getRangeIncludingFullySelectedParents(model, blocks);\n if (blocks.length > 1) {\n this._draggedRange = LiveRange.fromRange(blockRange);\n this._blockMode = true;\n // TODO block mode for dragging from outside editor? or inline? or both?\n }\n else if (blocks.length == 1) {\n const touchesBlockEdges = draggedRange.start.isTouching(blockRange.start) &&\n draggedRange.end.isTouching(blockRange.end);\n this._draggedRange = LiveRange.fromRange(touchesBlockEdges ? blockRange : draggedRange);\n this._blockMode = touchesBlockEdges;\n }\n model.change(writer => writer.setSelection(this._draggedRange.toRange()));\n }\n /**\n * Updates the dragged preview image.\n */\n _updatePreview({ dataTransfer, domTarget, clientX }) {\n const view = this.editor.editing.view;\n const editable = view.document.selection.editableElement;\n const domEditable = view.domConverter.mapViewToDom(editable);\n const computedStyle = global.window.getComputedStyle(domEditable);\n if (!this._previewContainer) {\n this._previewContainer = createElement(global.document, 'div', {\n style: 'position: fixed; left: -999999px;'\n });\n global.document.body.appendChild(this._previewContainer);\n }\n else if (this._previewContainer.firstElementChild) {\n this._previewContainer.removeChild(this._previewContainer.firstElementChild);\n }\n const domRect = new Rect(domEditable);\n // If domTarget is inside the editable root, browsers will display the preview correctly by themselves.\n if (domEditable.contains(domTarget)) {\n return;\n }\n const domEditablePaddingLeft = parseFloat(computedStyle.paddingLeft);\n const preview = createElement(global.document, 'div');\n preview.className = 'ck ck-content';\n preview.style.width = computedStyle.width;\n preview.style.paddingLeft = `${domRect.left - clientX + domEditablePaddingLeft}px`;\n /**\n * Set white background in drag and drop preview if iOS.\n * Check: https://github.com/ckeditor/ckeditor5/issues/15085\n */\n if (env.isiOS) {\n preview.style.backgroundColor = 'white';\n }\n preview.innerHTML = dataTransfer.getData('text/html');\n dataTransfer.setDragImage(preview, 0, 0);\n this._previewContainer.appendChild(preview);\n }\n}\n/**\n * Returns the drop effect that should be a result of dragging the content.\n * This function is handling a quirk when checking the effect in the 'drop' DOM event.\n */\nfunction getFinalDropEffect(dataTransfer) {\n if (env.isGecko) {\n return dataTransfer.dropEffect;\n }\n return ['all', 'copyMove'].includes(dataTransfer.effectAllowed) ? 'move' : 'copy';\n}\n/**\n * Returns a widget element that should be dragged.\n */\nfunction findDraggableWidget(target) {\n // This is directly an editable so not a widget for sure.\n if (target.is('editableElement')) {\n return null;\n }\n // TODO: Let's have a isWidgetSelectionHandleDomElement() helper in ckeditor5-widget utils.\n if (target.hasClass('ck-widget__selection-handle')) {\n return target.findAncestor(isWidget);\n }\n // Direct hit on a widget.\n if (isWidget(target)) {\n return target;\n }\n // Find closest ancestor that is either a widget or an editable element...\n const ancestor = target.findAncestor(node => isWidget(node) || node.is('editableElement'));\n // ...and if closer was the widget then enable dragging it.\n if (isWidget(ancestor)) {\n return ancestor;\n }\n return null;\n}\n/**\n * Recursively checks if common parent of provided elements doesn't have any other children. If that's the case,\n * it returns range including this parent. Otherwise, it returns only the range from first to last element.\n *\n * Example:\n *\n * \n * [Test 1 \n * Test 2 \n * Test 3] \n * \n *\n * Because all elements inside the `blockQuote` are selected, the range is extended to include the `blockQuote` too.\n * If only first and second paragraphs would be selected, the range would not include it.\n */\nfunction getRangeIncludingFullySelectedParents(model, elements) {\n const firstElement = elements[0];\n const lastElement = elements[elements.length - 1];\n const parent = firstElement.getCommonAncestor(lastElement);\n const startPosition = model.createPositionBefore(firstElement);\n const endPosition = model.createPositionAfter(lastElement);\n if (parent &&\n parent.is('element') &&\n !model.schema.isLimit(parent)) {\n const parentRange = model.createRangeOn(parent);\n const touchesStart = startPosition.isTouching(parentRange.start);\n const touchesEnd = endPosition.isTouching(parentRange.end);\n if (touchesStart && touchesEnd) {\n // Selection includes all elements in the parent.\n return getRangeIncludingFullySelectedParents(model, [parent]);\n }\n }\n return model.createRange(startPosition, endPosition);\n}\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n/**\n * @module clipboard/dragdropblocktoolbar\n */\nimport { Plugin } from '@ckeditor/ckeditor5-core';\nimport { env, global, DomEmitterMixin } from '@ckeditor/ckeditor5-utils';\nimport ClipboardObserver from './clipboardobserver.js';\n/**\n * Integration of a block Drag and Drop support with the block toolbar.\n *\n * @internal\n */\nexport default class DragDropBlockToolbar extends Plugin {\n constructor() {\n super(...arguments);\n /**\n * Whether current dragging is started by block toolbar button dragging.\n */\n this._isBlockDragging = false;\n /**\n * DOM Emitter.\n */\n this._domEmitter = new (DomEmitterMixin())();\n }\n /**\n * @inheritDoc\n */\n static get pluginName() {\n return 'DragDropBlockToolbar';\n }\n /**\n * @inheritDoc\n */\n init() {\n const editor = this.editor;\n this.listenTo(editor, 'change:isReadOnly', (evt, name, isReadOnly) => {\n if (isReadOnly) {\n this.forceDisabled('readOnlyMode');\n this._isBlockDragging = false;\n }\n else {\n this.clearForceDisabled('readOnlyMode');\n }\n });\n if (env.isAndroid) {\n this.forceDisabled('noAndroidSupport');\n }\n if (editor.plugins.has('BlockToolbar')) {\n const blockToolbar = editor.plugins.get('BlockToolbar');\n const element = blockToolbar.buttonView.element;\n this._domEmitter.listenTo(element, 'dragstart', (evt, data) => this._handleBlockDragStart(data));\n this._domEmitter.listenTo(global.document, 'dragover', (evt, data) => this._handleBlockDragging(data));\n this._domEmitter.listenTo(global.document, 'drop', (evt, data) => this._handleBlockDragging(data));\n this._domEmitter.listenTo(global.document, 'dragend', () => this._handleBlockDragEnd(), { useCapture: true });\n if (this.isEnabled) {\n element.setAttribute('draggable', 'true');\n }\n this.on('change:isEnabled', (evt, name, isEnabled) => {\n element.setAttribute('draggable', isEnabled ? 'true' : 'false');\n });\n }\n }\n /**\n * @inheritDoc\n */\n destroy() {\n this._domEmitter.stopListening();\n return super.destroy();\n }\n /**\n * The `dragstart` event handler.\n */\n _handleBlockDragStart(domEvent) {\n if (!this.isEnabled) {\n return;\n }\n const model = this.editor.model;\n const selection = model.document.selection;\n const view = this.editor.editing.view;\n const blocks = Array.from(selection.getSelectedBlocks());\n const draggedRange = model.createRange(model.createPositionBefore(blocks[0]), model.createPositionAfter(blocks[blocks.length - 1]));\n model.change(writer => writer.setSelection(draggedRange));\n this._isBlockDragging = true;\n view.focus();\n view.getObserver(ClipboardObserver).onDomEvent(domEvent);\n }\n /**\n * The `dragover` and `drop` event handler.\n */\n _handleBlockDragging(domEvent) {\n if (!this.isEnabled || !this._isBlockDragging) {\n return;\n }\n const clientX = domEvent.clientX + (this.editor.locale.contentLanguageDirection == 'ltr' ? 100 : -100);\n const clientY = domEvent.clientY;\n const target = document.elementFromPoint(clientX, clientY);\n const view = this.editor.editing.view;\n if (!target || !target.closest('.ck-editor__editable')) {\n return;\n }\n view.getObserver(ClipboardObserver).onDomEvent({\n ...domEvent,\n type: domEvent.type,\n dataTransfer: domEvent.dataTransfer,\n target,\n clientX,\n clientY,\n preventDefault: () => domEvent.preventDefault(),\n stopPropagation: () => domEvent.stopPropagation()\n });\n }\n /**\n * The `dragend` event handler.\n */\n _handleBlockDragEnd() {\n this._isBlockDragging = false;\n }\n}\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n/**\n * @module clipboard/dragdroptarget\n */\nimport { Plugin } from '@ckeditor/ckeditor5-core';\nimport { global, Rect, DomEmitterMixin, delay, ResizeObserver } from '@ckeditor/ckeditor5-utils';\nimport LineView from './lineview.js';\nimport { throttle } from 'lodash-es';\n/**\n * Part of the Drag and Drop handling. Responsible for finding and displaying the drop target.\n *\n * @internal\n */\nexport default class DragDropTarget extends Plugin {\n constructor() {\n super(...arguments);\n /**\n * A delayed callback removing the drop marker.\n *\n * @internal\n */\n this.removeDropMarkerDelayed = delay(() => this.removeDropMarker(), 40);\n /**\n * A throttled callback updating the drop marker.\n */\n this._updateDropMarkerThrottled = throttle(targetRange => this._updateDropMarker(targetRange), 40);\n /**\n * A throttled callback reconverting the drop parker.\n */\n this._reconvertMarkerThrottled = throttle(() => {\n if (this.editor.model.markers.has('drop-target')) {\n this.editor.editing.reconvertMarker('drop-target');\n }\n }, 0);\n /**\n * The horizontal drop target line view.\n */\n this._dropTargetLineView = new LineView();\n /**\n * DOM Emitter.\n */\n this._domEmitter = new (DomEmitterMixin())();\n /**\n * Map of document scrollable elements.\n */\n this._scrollables = new Map();\n }\n /**\n * @inheritDoc\n */\n static get pluginName() {\n return 'DragDropTarget';\n }\n /**\n * @inheritDoc\n */\n init() {\n this._setupDropMarker();\n }\n /**\n * @inheritDoc\n */\n destroy() {\n this._domEmitter.stopListening();\n for (const { resizeObserver } of this._scrollables.values()) {\n resizeObserver.destroy();\n }\n this._updateDropMarkerThrottled.cancel();\n this.removeDropMarkerDelayed.cancel();\n this._reconvertMarkerThrottled.cancel();\n return super.destroy();\n }\n /**\n * Finds the drop target range and updates the drop marker.\n *\n * @internal\n */\n updateDropMarker(targetViewElement, targetViewRanges, clientX, clientY, blockMode, draggedRange) {\n this.removeDropMarkerDelayed.cancel();\n const targetRange = findDropTargetRange(this.editor, targetViewElement, targetViewRanges, clientX, clientY, blockMode, draggedRange);\n /* istanbul ignore next -- @preserve */\n if (!targetRange) {\n return;\n }\n if (draggedRange && draggedRange.containsRange(targetRange)) {\n // Target range is inside the dragged range.\n return this.removeDropMarker();\n }\n this._updateDropMarkerThrottled(targetRange);\n }\n /**\n * Finds the final drop target range.\n *\n * @internal\n */\n getFinalDropRange(targetViewElement, targetViewRanges, clientX, clientY, blockMode, draggedRange) {\n const targetRange = findDropTargetRange(this.editor, targetViewElement, targetViewRanges, clientX, clientY, blockMode, draggedRange);\n // The dragging markers must be removed after searching for the target range because sometimes\n // the target lands on the marker itself.\n this.removeDropMarker();\n return targetRange;\n }\n /**\n * Removes the drop target marker.\n *\n * @internal\n */\n removeDropMarker() {\n const model = this.editor.model;\n this.removeDropMarkerDelayed.cancel();\n this._updateDropMarkerThrottled.cancel();\n this._dropTargetLineView.isVisible = false;\n if (model.markers.has('drop-target')) {\n model.change(writer => {\n writer.removeMarker('drop-target');\n });\n }\n }\n /**\n * Creates downcast conversion for the drop target marker.\n */\n _setupDropMarker() {\n const editor = this.editor;\n editor.ui.view.body.add(this._dropTargetLineView);\n // Drop marker conversion for hovering over widgets.\n editor.conversion.for('editingDowncast').markerToHighlight({\n model: 'drop-target',\n view: {\n classes: ['ck-clipboard-drop-target-range']\n }\n });\n // Drop marker conversion for in text and block drop target.\n editor.conversion.for('editingDowncast').markerToElement({\n model: 'drop-target',\n view: (data, { writer }) => {\n // Inline drop.\n if (editor.model.schema.checkChild(data.markerRange.start, '$text')) {\n this._dropTargetLineView.isVisible = false;\n return this._createDropTargetPosition(writer);\n }\n // Block drop.\n else {\n if (data.markerRange.isCollapsed) {\n this._updateDropTargetLine(data.markerRange);\n }\n else {\n this._dropTargetLineView.isVisible = false;\n }\n }\n }\n });\n }\n /**\n * Updates the drop target marker to the provided range.\n *\n * @param targetRange The range to set the marker to.\n */\n _updateDropMarker(targetRange) {\n const editor = this.editor;\n const markers = editor.model.markers;\n editor.model.change(writer => {\n if (markers.has('drop-target')) {\n if (!markers.get('drop-target').getRange().isEqual(targetRange)) {\n writer.updateMarker('drop-target', { range: targetRange });\n }\n }\n else {\n writer.addMarker('drop-target', {\n range: targetRange,\n usingOperation: false,\n affectsData: false\n });\n }\n });\n }\n /**\n * Creates the UI element for vertical (in-line) drop target.\n */\n _createDropTargetPosition(writer) {\n return writer.createUIElement('span', { class: 'ck ck-clipboard-drop-target-position' }, function (domDocument) {\n const domElement = this.toDomElement(domDocument);\n // Using word joiner to make this marker as high as text and also making text not break on marker.\n domElement.append('\\u2060', domDocument.createElement('span'), '\\u2060');\n return domElement;\n });\n }\n /**\n * Updates the horizontal drop target line.\n */\n _updateDropTargetLine(range) {\n const editing = this.editor.editing;\n const nodeBefore = range.start.nodeBefore;\n const nodeAfter = range.start.nodeAfter;\n const nodeParent = range.start.parent;\n const viewElementBefore = nodeBefore ? editing.mapper.toViewElement(nodeBefore) : null;\n const domElementBefore = viewElementBefore ? editing.view.domConverter.mapViewToDom(viewElementBefore) : null;\n const viewElementAfter = nodeAfter ? editing.mapper.toViewElement(nodeAfter) : null;\n const domElementAfter = viewElementAfter ? editing.view.domConverter.mapViewToDom(viewElementAfter) : null;\n const viewElementParent = editing.mapper.toViewElement(nodeParent);\n if (!viewElementParent) {\n return;\n }\n const domElementParent = editing.view.domConverter.mapViewToDom(viewElementParent);\n const domScrollableRect = this._getScrollableRect(viewElementParent);\n const { scrollX, scrollY } = global.window;\n const rectBefore = domElementBefore ? new Rect(domElementBefore) : null;\n const rectAfter = domElementAfter ? new Rect(domElementAfter) : null;\n const rectParent = new Rect(domElementParent).excludeScrollbarsAndBorders();\n const above = rectBefore ? rectBefore.bottom : rectParent.top;\n const below = rectAfter ? rectAfter.top : rectParent.bottom;\n const parentStyle = global.window.getComputedStyle(domElementParent);\n const top = (above <= below ? (above + below) / 2 : below);\n if (domScrollableRect.top < top && top < domScrollableRect.bottom) {\n const left = rectParent.left + parseFloat(parentStyle.paddingLeft);\n const right = rectParent.right - parseFloat(parentStyle.paddingRight);\n const leftClamped = Math.max(left + scrollX, domScrollableRect.left);\n const rightClamped = Math.min(right + scrollX, domScrollableRect.right);\n this._dropTargetLineView.set({\n isVisible: true,\n left: leftClamped,\n top: top + scrollY,\n width: rightClamped - leftClamped\n });\n }\n else {\n this._dropTargetLineView.isVisible = false;\n }\n }\n /**\n * Finds the closest scrollable element rect for the given view element.\n */\n _getScrollableRect(viewElement) {\n const rootName = viewElement.root.rootName;\n let domScrollable;\n if (this._scrollables.has(rootName)) {\n domScrollable = this._scrollables.get(rootName).domElement;\n }\n else {\n const domElement = this.editor.editing.view.domConverter.mapViewToDom(viewElement);\n domScrollable = findScrollableElement(domElement);\n this._domEmitter.listenTo(domScrollable, 'scroll', this._reconvertMarkerThrottled, { usePassive: true });\n const resizeObserver = new ResizeObserver(domScrollable, this._reconvertMarkerThrottled);\n this._scrollables.set(rootName, {\n domElement: domScrollable,\n resizeObserver\n });\n }\n return new Rect(domScrollable).excludeScrollbarsAndBorders();\n }\n}\n/**\n * Returns fixed selection range for given position and target element.\n */\nfunction findDropTargetRange(editor, targetViewElement, targetViewRanges, clientX, clientY, blockMode, draggedRange) {\n const model = editor.model;\n const mapper = editor.editing.mapper;\n const targetModelElement = getClosestMappedModelElement(editor, targetViewElement);\n let modelElement = targetModelElement;\n while (modelElement) {\n if (!blockMode) {\n if (model.schema.checkChild(modelElement, '$text')) {\n if (targetViewRanges) {\n const targetViewPosition = targetViewRanges[0].start;\n const targetModelPosition = mapper.toModelPosition(targetViewPosition);\n const canDropOnPosition = !draggedRange || Array\n .from(draggedRange.getItems())\n .every(item => model.schema.checkChild(targetModelPosition, item));\n if (canDropOnPosition) {\n if (model.schema.checkChild(targetModelPosition, '$text')) {\n return model.createRange(targetModelPosition);\n }\n else if (targetViewPosition) {\n // This is the case of dropping inside a span wrapper of an inline image.\n return findDropTargetRangeForElement(editor, getClosestMappedModelElement(editor, targetViewPosition.parent), clientX, clientY);\n }\n }\n }\n }\n else if (model.schema.isInline(modelElement)) {\n return findDropTargetRangeForElement(editor, modelElement, clientX, clientY);\n }\n }\n if (model.schema.isBlock(modelElement)) {\n return findDropTargetRangeForElement(editor, modelElement, clientX, clientY);\n }\n else if (model.schema.checkChild(modelElement, '$block')) {\n const childNodes = Array.from(modelElement.getChildren())\n .filter((node) => node.is('element') && !shouldIgnoreElement(editor, node));\n let startIndex = 0;\n let endIndex = childNodes.length;\n if (endIndex == 0) {\n return model.createRange(model.createPositionAt(modelElement, 'end'));\n }\n while (startIndex < endIndex - 1) {\n const middleIndex = Math.floor((startIndex + endIndex) / 2);\n const side = findElementSide(editor, childNodes[middleIndex], clientX, clientY);\n if (side == 'before') {\n endIndex = middleIndex;\n }\n else {\n startIndex = middleIndex;\n }\n }\n return findDropTargetRangeForElement(editor, childNodes[startIndex], clientX, clientY);\n }\n modelElement = modelElement.parent;\n }\n return null;\n}\n/**\n * Returns true for elements which should be ignored.\n */\nfunction shouldIgnoreElement(editor, modelElement) {\n const mapper = editor.editing.mapper;\n const domConverter = editor.editing.view.domConverter;\n const viewElement = mapper.toViewElement(modelElement);\n if (!viewElement) {\n return true;\n }\n const domElement = domConverter.mapViewToDom(viewElement);\n return global.window.getComputedStyle(domElement).float != 'none';\n}\n/**\n * Returns target range relative to the given element.\n */\nfunction findDropTargetRangeForElement(editor, modelElement, clientX, clientY) {\n const model = editor.model;\n return model.createRange(model.createPositionAt(modelElement, findElementSide(editor, modelElement, clientX, clientY)));\n}\n/**\n * Resolves whether drop marker should be before or after the given element.\n */\nfunction findElementSide(editor, modelElement, clientX, clientY) {\n const mapper = editor.editing.mapper;\n const domConverter = editor.editing.view.domConverter;\n const viewElement = mapper.toViewElement(modelElement);\n const domElement = domConverter.mapViewToDom(viewElement);\n const rect = new Rect(domElement);\n if (editor.model.schema.isInline(modelElement)) {\n return clientX < (rect.left + rect.right) / 2 ? 'before' : 'after';\n }\n else {\n return clientY < (rect.top + rect.bottom) / 2 ? 'before' : 'after';\n }\n}\n/**\n * Returns the closest model element for the specified view element.\n */\nfunction getClosestMappedModelElement(editor, element) {\n const mapper = editor.editing.mapper;\n const view = editor.editing.view;\n const targetModelElement = mapper.toModelElement(element);\n if (targetModelElement) {\n return targetModelElement;\n }\n // Find mapped ancestor if the target is inside not mapped element (for example inline code element).\n const viewPosition = view.createPositionBefore(element);\n const viewElement = mapper.findMappedViewAncestor(viewPosition);\n return mapper.toModelElement(viewElement);\n}\n/**\n * Returns the closest scrollable ancestor DOM element.\n *\n * It is assumed that `domNode` is attached to the document.\n */\nfunction findScrollableElement(domNode) {\n let domElement = domNode;\n do {\n domElement = domElement.parentElement;\n const overflow = global.window.getComputedStyle(domElement).overflowY;\n if (overflow == 'auto' || overflow == 'scroll') {\n break;\n }\n } while (domElement.tagName != 'BODY');\n return domElement;\n}\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n/**\n * @module clipboard\n */\nexport { default as Clipboard } from './clipboard.js';\nexport { default as ClipboardPipeline } from './clipboardpipeline.js';\nexport { default as ClipboardMarkersUtils } from './clipboardmarkersutils.js';\nexport { default as DragDrop } from './dragdrop.js';\nexport { default as PastePlainText } from './pasteplaintext.js';\nexport { default as DragDropTarget } from './dragdroptarget.js';\nexport { default as DragDropBlockToolbar } from './dragdropblocktoolbar.js';\nimport './augmentation.js';\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n/**\n * @module clipboard/lineview\n */\n/* istanbul ignore file -- @preserve */\nimport { View } from '@ckeditor/ckeditor5-ui';\nimport { toUnit } from '@ckeditor/ckeditor5-utils';\nconst toPx = toUnit('px');\n/**\n * The horizontal drop target line view.\n */\nexport default class LineView extends View {\n /**\n * @inheritDoc\n */\n constructor() {\n super();\n const bind = this.bindTemplate;\n this.set({\n isVisible: false,\n left: null,\n top: null,\n width: null\n });\n this.setTemplate({\n tag: 'div',\n attributes: {\n class: [\n 'ck',\n 'ck-clipboard-drop-target-line',\n bind.if('isVisible', 'ck-hidden', value => !value)\n ],\n style: {\n left: bind.to('left', left => toPx(left)),\n top: bind.to('top', top => toPx(top)),\n width: bind.to('width', width => toPx(width))\n }\n }\n });\n }\n}\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n/**\n * @module clipboard/pasteplaintext\n */\nimport { Plugin } from '@ckeditor/ckeditor5-core';\nimport ClipboardObserver from './clipboardobserver.js';\nimport ClipboardPipeline from './clipboardpipeline.js';\n/**\n * The plugin detects the user's intention to paste plain text.\n *\n * For example, it detects the Ctrl/Cmd + Shift + V keystroke.\n */\nexport default class PastePlainText extends Plugin {\n /**\n * @inheritDoc\n */\n static get pluginName() {\n return 'PastePlainText';\n }\n /**\n * @inheritDoc\n */\n static get requires() {\n return [ClipboardPipeline];\n }\n /**\n * @inheritDoc\n */\n init() {\n const editor = this.editor;\n const model = editor.model;\n const view = editor.editing.view;\n const viewDocument = view.document;\n const selection = model.document.selection;\n let shiftPressed = false;\n view.addObserver(ClipboardObserver);\n this.listenTo(viewDocument, 'keydown', (evt, data) => {\n shiftPressed = data.shiftKey;\n });\n editor.plugins.get(ClipboardPipeline).on('contentInsertion', (evt, data) => {\n // Plain text can be determined based on the event flag (#7799) or auto-detection (#1006). If detected,\n // preserve selection attributes on pasted items.\n if (!shiftPressed && !isPlainTextFragment(data.content, model.schema)) {\n return;\n }\n model.change(writer => {\n // Formatting attributes should be preserved.\n const textAttributes = Array.from(selection.getAttributes())\n .filter(([key]) => model.schema.getAttributeProperties(key).isFormatting);\n if (!selection.isCollapsed) {\n model.deleteContent(selection, { doNotAutoparagraph: true });\n }\n // Also preserve other attributes if they survived the content deletion (because they were not fully selected).\n // For example linkHref is not a formatting attribute but it should be preserved if pasted text was in the middle\n // of a link.\n textAttributes.push(...selection.getAttributes());\n const range = writer.createRangeIn(data.content);\n for (const item of range.getItems()) {\n if (item.is('$textProxy')) {\n writer.setAttributes(textAttributes, item);\n }\n }\n });\n });\n }\n}\n/**\n * Returns true if specified `documentFragment` represents a plain text.\n */\nfunction isPlainTextFragment(documentFragment, schema) {\n if (documentFragment.childCount > 1) {\n return false;\n }\n const child = documentFragment.getChild(0);\n if (schema.isObject(child)) {\n return false;\n }\n return Array.from(child.getAttributeKeys()).length == 0;\n}\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n/**\n * @module clipboard/utils/normalizeclipboarddata\n */\n/**\n * Removes some popular browser quirks out of the clipboard data (HTML).\n * Removes all HTML comments. These are considered an internal thing and it makes little sense if they leak into the editor data.\n *\n * @param data The HTML data to normalize.\n * @returns Normalized HTML.\n */\nexport default function normalizeClipboardData(data) {\n return data\n .replace(/(\\s+)<\\/span>/g, (fullMatch, spaces) => {\n // Handle the most popular and problematic case when even a single space becomes an nbsp;.\n // Decode those to normal spaces. Read more in https://github.com/ckeditor/ckeditor5-clipboard/issues/2.\n if (spaces.length == 1) {\n return ' ';\n }\n return spaces;\n })\n // Remove all HTML comments.\n .replace(//g, '');\n}\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n/**\n * @module clipboard/utils/plaintexttohtml\n */\n/**\n * Converts plain text to its HTML-ized version.\n *\n * @param text The plain text to convert.\n * @returns HTML generated from the plain text.\n */\nexport default function plainTextToHtml(text) {\n text = text\n // Encode &.\n .replace(/&/g, '&')\n // Encode <>.\n .replace(//g, '>')\n // Creates a paragraph for each double line break.\n .replace(/\\r?\\n\\r?\\n/g, '')\n // Creates a line break for each single line break.\n .replace(/\\r?\\n/g, '
')\n // Replace tabs with four spaces.\n .replace(/\\t/g, ' ')\n // Preserve trailing spaces (only the first and last one – the rest is handled below).\n .replace(/^\\s/, ' ')\n .replace(/\\s$/, ' ')\n // Preserve other subsequent spaces now.\n .replace(/\\s\\s/g, ' ');\n if (text.includes('
') || text.includes('
')) {\n // If we created paragraphs above, add the trailing ones.\n text = `
${text}
`;\n }\n // TODO:\n // * What about '\\nfoo' vs ' foo'?\n return text;\n}\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n// Elements which should not have empty-line padding.\n// Most `view.ContainerElement` want to be separate by new-line, but some are creating one structure\n// together (like ``) so it is better to separate them by only one \"\\n\".\nconst smallPaddingElements = ['figcaption', 'li'];\nconst listElements = ['ol', 'ul'];\n/**\n * Converts {@link module:engine/view/item~Item view item} and all of its children to plain text.\n *\n * @param viewItem View item to convert.\n * @returns Plain text representation of `viewItem`.\n */\nexport default function viewToPlainText(viewItem) {\n if (viewItem.is('$text') || viewItem.is('$textProxy')) {\n return viewItem.data;\n }\n if (viewItem.is('element', 'img') && viewItem.hasAttribute('alt')) {\n return viewItem.getAttribute('alt');\n }\n if (viewItem.is('element', 'br')) {\n return '\\n'; // Convert soft breaks to single line break (#8045).\n }\n /**\n * Item is a document fragment, attribute element or container element. It doesn't\n * have it's own text value, so we need to convert its children elements.\n */\n let text = '';\n let prev = null;\n for (const child of viewItem.getChildren()) {\n text += newLinePadding(child, prev) + viewToPlainText(child);\n prev = child;\n }\n return text;\n}\n/**\n * Returns new line padding to prefix the given elements with.\n */\nfunction newLinePadding(element, previous) {\n if (!previous) {\n // Don't add padding to first elements in a level.\n return '';\n }\n if (element.is('element', 'li') && !element.isEmpty && element.getChild(0).is('containerElement')) {\n // Separate document list items with empty lines.\n return '\\n\\n';\n }\n if (listElements.includes(element.name) && listElements.includes(previous.name)) {\n /**\n * Because `` and `` are AttributeElements, two consecutive lists will not have any padding between\n * them (see the `if` statement below). To fix this, we need to make an exception for this case.\n */\n return '\\n\\n';\n }\n if (!element.is('containerElement') && !previous.is('containerElement')) {\n // Don't add padding between non-container elements.\n return '';\n }\n if (smallPaddingElements.includes(element.name) || smallPaddingElements.includes(previous.name)) {\n // Add small padding between selected container elements.\n return '\\n';\n }\n // Add empty lines between container elements.\n return '\\n\\n';\n}\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\nexport {};\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n/**\n * @module code-block/codeblock\n */\nimport { Plugin } from 'ckeditor5/src/core.js';\nimport CodeBlockEditing from './codeblockediting.js';\nimport CodeBlockUI from './codeblockui.js';\n/**\n * The code block plugin.\n *\n * For more information about this feature check the {@glink api/code-block package page} and the\n * {@glink features/code-blocks code block} feature guide.\n *\n * This is a \"glue\" plugin that loads the {@link module:code-block/codeblockediting~CodeBlockEditing code block editing feature}\n * and the {@link module:code-block/codeblockui~CodeBlockUI code block UI feature}.\n */\nexport default class CodeBlock extends Plugin {\n /**\n * @inheritDoc\n */\n static get requires() {\n return [CodeBlockEditing, CodeBlockUI];\n }\n /**\n * @inheritDoc\n */\n static get pluginName() {\n return 'CodeBlock';\n }\n}\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\nimport { Command } from 'ckeditor5/src/core.js';\nimport { first } from 'ckeditor5/src/utils.js';\nimport { getNormalizedAndLocalizedLanguageDefinitions, canBeCodeBlock } from './utils.js';\n/**\n * The code block command plugin.\n */\nexport default class CodeBlockCommand extends Command {\n /**\n * @inheritDoc\n */\n constructor(editor) {\n super(editor);\n this._lastLanguage = null;\n }\n /**\n * @inheritDoc\n */\n refresh() {\n this.value = this._getValue();\n this.isEnabled = this._checkEnabled();\n }\n /**\n * Executes the command. When the command {@link #value is on}, all topmost code blocks within\n * the selection will be removed. If it is off, all selected blocks will be flattened and\n * wrapped by a code block.\n *\n * @fires execute\n * @param options Command options.\n * @param options.language The code block language.\n * @param options.forceValue If set, it will force the command behavior. If `true`, the command will apply a code block,\n * otherwise the command will remove the code block. If not set, the command will act basing on its current value.\n * @param options.usePreviousLanguageChoice If set on `true` and the `options.language` is not specified, the command\n * will apply the previous language (if the command was already executed) when inserting the `codeBlock` element.\n */\n execute(options = {}) {\n const editor = this.editor;\n const model = editor.model;\n const selection = model.document.selection;\n const normalizedLanguagesDefs = getNormalizedAndLocalizedLanguageDefinitions(editor);\n const firstLanguageInConfig = normalizedLanguagesDefs[0];\n const blocks = Array.from(selection.getSelectedBlocks());\n const value = options.forceValue == undefined ? !this.value : options.forceValue;\n const language = getLanguage(options, this._lastLanguage, firstLanguageInConfig.language);\n model.change(writer => {\n if (value) {\n this._applyCodeBlock(writer, blocks, language);\n }\n else {\n this._removeCodeBlock(writer, blocks);\n }\n });\n }\n /**\n * Checks the command's {@link #value}.\n *\n * @returns The current value.\n */\n _getValue() {\n const selection = this.editor.model.document.selection;\n const firstBlock = first(selection.getSelectedBlocks());\n const isCodeBlock = !!(firstBlock && firstBlock.is('element', 'codeBlock'));\n return isCodeBlock ? firstBlock.getAttribute('language') : false;\n }\n /**\n * Checks whether the command can be enabled in the current context.\n *\n * @returns Whether the command should be enabled.\n */\n _checkEnabled() {\n if (this.value) {\n return true;\n }\n const selection = this.editor.model.document.selection;\n const schema = this.editor.model.schema;\n const firstBlock = first(selection.getSelectedBlocks());\n if (!firstBlock) {\n return false;\n }\n return canBeCodeBlock(schema, firstBlock);\n }\n _applyCodeBlock(writer, blocks, language) {\n this._lastLanguage = language;\n const schema = this.editor.model.schema;\n const allowedBlocks = blocks.filter(block => canBeCodeBlock(schema, block));\n for (const block of allowedBlocks) {\n writer.rename(block, 'codeBlock');\n writer.setAttribute('language', language, block);\n schema.removeDisallowedAttributes([block], writer);\n // Remove children of the `codeBlock` element that are not allowed. See #9567.\n Array.from(block.getChildren())\n .filter(child => !schema.checkChild(block, child))\n .forEach(child => writer.remove(child));\n }\n allowedBlocks.reverse().forEach((currentBlock, i) => {\n const nextBlock = allowedBlocks[i + 1];\n if (currentBlock.previousSibling === nextBlock) {\n writer.appendElement('softBreak', nextBlock);\n writer.merge(writer.createPositionBefore(currentBlock));\n }\n });\n }\n _removeCodeBlock(writer, blocks) {\n const codeBlocks = blocks.filter(block => block.is('element', 'codeBlock'));\n for (const block of codeBlocks) {\n const range = writer.createRangeOn(block);\n for (const item of Array.from(range.getItems()).reverse()) {\n if (item.is('element', 'softBreak') && item.parent.is('element', 'codeBlock')) {\n const { position } = writer.split(writer.createPositionBefore(item));\n const elementAfter = position.nodeAfter;\n writer.rename(elementAfter, 'paragraph');\n writer.removeAttribute('language', elementAfter);\n writer.remove(item);\n }\n }\n writer.rename(block, 'paragraph');\n writer.removeAttribute('language', block);\n }\n }\n}\n/**\n * Picks the language for the new code block. If any language is passed as an option,\n * it will be returned. Else, if option usePreviousLanguageChoice is true and some\n * code block was already created (lastLanguage is not null) then previously used\n * language will be returned. If not, it will return default language.\n */\nfunction getLanguage(options, lastLanguage, defaultLanguage) {\n if (options.language) {\n return options.language;\n }\n if (options.usePreviousLanguageChoice && lastLanguage) {\n return lastLanguage;\n }\n return defaultLanguage;\n}\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\nimport { Plugin } from 'ckeditor5/src/core.js';\nimport { ShiftEnter } from 'ckeditor5/src/enter.js';\nimport { UpcastWriter } from 'ckeditor5/src/engine.js';\nimport CodeBlockCommand from './codeblockcommand.js';\nimport IndentCodeBlockCommand from './indentcodeblockcommand.js';\nimport OutdentCodeBlockCommand from './outdentcodeblockcommand.js';\nimport { getNormalizedAndLocalizedLanguageDefinitions, getLeadingWhiteSpaces, rawSnippetTextToViewDocumentFragment, getCodeBlockAriaAnnouncement } from './utils.js';\nimport { modelToViewCodeBlockInsertion, modelToDataViewSoftBreakInsertion, dataViewToModelCodeBlockInsertion, dataViewToModelTextNewlinesInsertion, dataViewToModelOrphanNodeConsumer } from './converters.js';\nconst DEFAULT_ELEMENT = 'paragraph';\n/**\n * The editing part of the code block feature.\n *\n * Introduces the `'codeBlock'` command and the `'codeBlock'` model element.\n */\nexport default class CodeBlockEditing extends Plugin {\n /**\n * @inheritDoc\n */\n static get pluginName() {\n return 'CodeBlockEditing';\n }\n /**\n * @inheritDoc\n */\n static get requires() {\n return [ShiftEnter];\n }\n /**\n * @inheritDoc\n */\n constructor(editor) {\n super(editor);\n editor.config.define('codeBlock', {\n languages: [\n { language: 'plaintext', label: 'Plain text' },\n { language: 'c', label: 'C' },\n { language: 'cs', label: 'C#' },\n { language: 'cpp', label: 'C++' },\n { language: 'css', label: 'CSS' },\n { language: 'diff', label: 'Diff' },\n { language: 'html', label: 'HTML' },\n { language: 'java', label: 'Java' },\n { language: 'javascript', label: 'JavaScript' },\n { language: 'php', label: 'PHP' },\n { language: 'python', label: 'Python' },\n { language: 'ruby', label: 'Ruby' },\n { language: 'typescript', label: 'TypeScript' },\n { language: 'xml', label: 'XML' }\n ],\n // A single tab.\n indentSequence: '\\t'\n });\n }\n /**\n * @inheritDoc\n */\n init() {\n const editor = this.editor;\n const schema = editor.model.schema;\n const model = editor.model;\n const view = editor.editing.view;\n const listEditing = editor.plugins.has('ListEditing') ?\n editor.plugins.get('ListEditing') : null;\n const normalizedLanguagesDefs = getNormalizedAndLocalizedLanguageDefinitions(editor);\n // The main command.\n editor.commands.add('codeBlock', new CodeBlockCommand(editor));\n // Commands that change the indentation.\n editor.commands.add('indentCodeBlock', new IndentCodeBlockCommand(editor));\n editor.commands.add('outdentCodeBlock', new OutdentCodeBlockCommand(editor));\n this.listenTo(view.document, 'tab', (evt, data) => {\n const commandName = data.shiftKey ? 'outdentCodeBlock' : 'indentCodeBlock';\n const command = editor.commands.get(commandName);\n if (!command.isEnabled) {\n return;\n }\n editor.execute(commandName);\n data.stopPropagation();\n data.preventDefault();\n evt.stop();\n }, { context: 'pre' });\n schema.register('codeBlock', {\n allowWhere: '$block',\n allowChildren: '$text',\n isBlock: true,\n allowAttributes: ['language']\n });\n // Allow all list* attributes on `codeBlock` (integration with DocumentList).\n // Disallow all attributes on $text inside `codeBlock`.\n schema.addAttributeCheck((context, attributeName) => {\n if (context.endsWith('codeBlock') &&\n listEditing && listEditing.getListAttributeNames().includes(attributeName)) {\n return true;\n }\n if (context.endsWith('codeBlock $text')) {\n return false;\n }\n });\n // Disallow object elements inside `codeBlock`. See #9567.\n editor.model.schema.addChildCheck((context, childDefinition) => {\n if (context.endsWith('codeBlock') && childDefinition.isObject) {\n return false;\n }\n });\n // Conversion.\n editor.editing.downcastDispatcher.on('insert:codeBlock', modelToViewCodeBlockInsertion(model, normalizedLanguagesDefs, true));\n editor.data.downcastDispatcher.on('insert:codeBlock', modelToViewCodeBlockInsertion(model, normalizedLanguagesDefs));\n editor.data.downcastDispatcher.on('insert:softBreak', modelToDataViewSoftBreakInsertion(model), { priority: 'high' });\n editor.data.upcastDispatcher.on('element:code', dataViewToModelCodeBlockInsertion(view, normalizedLanguagesDefs));\n editor.data.upcastDispatcher.on('text', dataViewToModelTextNewlinesInsertion());\n editor.data.upcastDispatcher.on('element:pre', dataViewToModelOrphanNodeConsumer(), { priority: 'high' });\n // Intercept the clipboard input (paste) when the selection is anchored in the code block and force the clipboard\n // data to be pasted as a single plain text. Otherwise, the code lines will split the code block and\n // \"spill out\" as separate paragraphs.\n this.listenTo(editor.editing.view.document, 'clipboardInput', (evt, data) => {\n let insertionRange = model.createRange(model.document.selection.anchor);\n // Use target ranges in case this is a drop.\n if (data.targetRanges) {\n insertionRange = editor.editing.mapper.toModelRange(data.targetRanges[0]);\n }\n if (!insertionRange.start.parent.is('element', 'codeBlock')) {\n return;\n }\n const text = data.dataTransfer.getData('text/plain');\n const writer = new UpcastWriter(editor.editing.view.document);\n // Pass the view fragment to the default clipboardInput handler.\n data.content = rawSnippetTextToViewDocumentFragment(writer, text);\n });\n // Make sure multi–line selection is always wrapped in a code block when `getSelectedContent()`\n // is used (e.g. clipboard copy). Otherwise, only the raw text will be copied to the clipboard and,\n // upon next paste, this bare text will not be inserted as a code block, which is not the best UX.\n // Similarly, when the selection in a single line, the selected content should be an inline code\n // so it can be pasted later on and retain it's preformatted nature.\n this.listenTo(model, 'getSelectedContent', (evt, [selection]) => {\n const anchor = selection.anchor;\n if (selection.isCollapsed || !anchor.parent.is('element', 'codeBlock') || !anchor.hasSameParentAs(selection.focus)) {\n return;\n }\n model.change(writer => {\n const docFragment = evt.return;\n // fo[o b]ar -> [o b]\n if (anchor.parent.is('element') &&\n (docFragment.childCount > 1 || selection.containsEntireContent(anchor.parent))) {\n const codeBlock = writer.createElement('codeBlock', anchor.parent.getAttributes());\n writer.append(docFragment, codeBlock);\n const newDocumentFragment = writer.createDocumentFragment();\n writer.append(codeBlock, newDocumentFragment);\n evt.return = newDocumentFragment;\n return;\n }\n // \"f[oo]\" -> <$text code=\"true\">oo\n const textNode = docFragment.getChild(0);\n if (schema.checkAttribute(textNode, 'code')) {\n writer.setAttribute('code', true, textNode);\n }\n });\n });\n }\n /**\n * @inheritDoc\n */\n afterInit() {\n const editor = this.editor;\n const commands = editor.commands;\n const indent = commands.get('indent');\n const outdent = commands.get('outdent');\n if (indent) {\n // Priority is highest due to integration with `IndentList` command of `List` plugin.\n // If selection is in a code block we give priority to it. This way list item cannot be indented\n // but if we would give priority to indenting list item then user would have to indent list item\n // as much as possible and only then he could indent code block.\n indent.registerChildCommand(commands.get('indentCodeBlock'), { priority: 'highest' });\n }\n if (outdent) {\n outdent.registerChildCommand(commands.get('outdentCodeBlock'));\n }\n // Customize the response to the Enter and Shift+Enter\n // key press when the selection is in the code block. Upon enter key press we can either\n // leave the block if it's \"two or three enters\" in a row or create a new code block line, preserving\n // previous line's indentation.\n this.listenTo(editor.editing.view.document, 'enter', (evt, data) => {\n const positionParent = editor.model.document.selection.getLastPosition().parent;\n if (!positionParent.is('element', 'codeBlock')) {\n return;\n }\n if (!leaveBlockStartOnEnter(editor, data.isSoft) && !leaveBlockEndOnEnter(editor, data.isSoft)) {\n breakLineOnEnter(editor);\n }\n data.preventDefault();\n evt.stop();\n }, { context: 'pre' });\n this._initAriaAnnouncements();\n }\n /**\n * Observe when user enters or leaves code block and set proper aria value in global live announcer.\n * This allows screen readers to indicate when the user has entered and left the specified code block.\n *\n * @internal\n */\n _initAriaAnnouncements() {\n const { model, ui, t } = this.editor;\n const languageDefs = getNormalizedAndLocalizedLanguageDefinitions(this.editor);\n let lastFocusedCodeBlock = null;\n model.document.selection.on('change:range', () => {\n const focusParent = model.document.selection.focus.parent;\n if (!ui || lastFocusedCodeBlock === focusParent || !focusParent.is('element')) {\n return;\n }\n if (lastFocusedCodeBlock && lastFocusedCodeBlock.is('element', 'codeBlock')) {\n ui.ariaLiveAnnouncer.announce(getCodeBlockAriaAnnouncement(t, languageDefs, lastFocusedCodeBlock, 'leave'));\n }\n if (focusParent.is('element', 'codeBlock')) {\n ui.ariaLiveAnnouncer.announce(getCodeBlockAriaAnnouncement(t, languageDefs, focusParent, 'enter'));\n }\n lastFocusedCodeBlock = focusParent;\n });\n }\n}\n/**\n * Normally, when the Enter (or Shift+Enter) key is pressed, a soft line break is to be added to the\n * code block. Let's try to follow the indentation of the previous line when possible, for instance:\n *\n * ```html\n * // Before pressing enter (or shift enter)\n * \n * \" foo()\"[] // Indent of 4 spaces.\n * \n *\n * // After pressing:\n * \n * \" foo()\" // Indent of 4 spaces.\n * // A new soft break created by pressing enter.\n * \" \"[] // Retain the indent of 4 spaces.\n * \n * ```\n */\nfunction breakLineOnEnter(editor) {\n const model = editor.model;\n const modelDoc = model.document;\n const lastSelectionPosition = modelDoc.selection.getLastPosition();\n const node = lastSelectionPosition.nodeBefore || lastSelectionPosition.textNode;\n let leadingWhiteSpaces;\n // Figure out the indentation (white space chars) at the beginning of the line.\n if (node && node.is('$text')) {\n leadingWhiteSpaces = getLeadingWhiteSpaces(node);\n }\n // Keeping everything in a change block for a single undo step.\n editor.model.change(writer => {\n editor.execute('shiftEnter');\n // If the line before being broken in two had some indentation, let's retain it\n // in the new line.\n if (leadingWhiteSpaces) {\n writer.insertText(leadingWhiteSpaces, modelDoc.selection.anchor);\n }\n });\n}\n/**\n * Leave the code block when Enter (but NOT Shift+Enter) has been pressed twice at the beginning\n * of the code block:\n *\n * ```html\n * // Before:\n * [] foo \n *\n * // After pressing:\n * [] foo \n * ```\n *\n * @param isSoftEnter When `true`, enter was pressed along with Shift.\n * @returns `true` when selection left the block. `false` if stayed.\n */\nfunction leaveBlockStartOnEnter(editor, isSoftEnter) {\n const model = editor.model;\n const modelDoc = model.document;\n const view = editor.editing.view;\n const lastSelectionPosition = modelDoc.selection.getLastPosition();\n const nodeAfter = lastSelectionPosition.nodeAfter;\n if (isSoftEnter || !modelDoc.selection.isCollapsed || !lastSelectionPosition.isAtStart) {\n return false;\n }\n if (!isSoftBreakNode(nodeAfter)) {\n return false;\n }\n // We're doing everything in a single change block to have a single undo step.\n editor.model.change(writer => {\n // \"Clone\" the in the standard way.\n editor.execute('enter');\n // The cloned block exists now before the original code block.\n const newBlock = modelDoc.selection.anchor.parent.previousSibling;\n // Make the cloned a regular (with clean attributes, so no language).\n writer.rename(newBlock, DEFAULT_ELEMENT);\n writer.setSelection(newBlock, 'in');\n editor.model.schema.removeDisallowedAttributes([newBlock], writer);\n // Remove the that originally followed the selection position.\n writer.remove(nodeAfter);\n });\n // Eye candy.\n view.scrollToTheSelection();\n return true;\n}\n/**\n * Leave the code block when Enter (but NOT Shift+Enter) has been pressed twice at the end\n * of the code block:\n *\n * ```html\n * // Before:\n * foo[] \n *\n * // After first press:\n * foo [] \n *\n * // After second press:\n * foo [] \n * ```\n *\n * @param isSoftEnter When `true`, enter was pressed along with Shift.\n * @returns `true` when selection left the block. `false` if stayed.\n */\nfunction leaveBlockEndOnEnter(editor, isSoftEnter) {\n const model = editor.model;\n const modelDoc = model.document;\n const view = editor.editing.view;\n const lastSelectionPosition = modelDoc.selection.getLastPosition();\n const nodeBefore = lastSelectionPosition.nodeBefore;\n let emptyLineRangeToRemoveOnEnter;\n if (isSoftEnter || !modelDoc.selection.isCollapsed || !lastSelectionPosition.isAtEnd || !nodeBefore || !nodeBefore.previousSibling) {\n return false;\n }\n // When the position is directly preceded by two soft breaks\n //\n //\t\tfoo [] \n //\n // it creates the following range that will be cleaned up before leaving:\n //\n //\t\tfoo[ ] \n //\n if (isSoftBreakNode(nodeBefore) && isSoftBreakNode(nodeBefore.previousSibling)) {\n emptyLineRangeToRemoveOnEnter = model.createRange(model.createPositionBefore(nodeBefore.previousSibling), model.createPositionAfter(nodeBefore));\n }\n // When there's some text before the position that is\n // preceded by two soft breaks and made purely of white–space characters\n //\n //\t\tfoo [] \n //\n // it creates the following range to clean up before leaving:\n //\n //\t\tfoo[ ] \n //\n else if (isEmptyishTextNode(nodeBefore) &&\n isSoftBreakNode(nodeBefore.previousSibling) &&\n isSoftBreakNode(nodeBefore.previousSibling.previousSibling)) {\n emptyLineRangeToRemoveOnEnter = model.createRange(model.createPositionBefore(nodeBefore.previousSibling.previousSibling), model.createPositionAfter(nodeBefore));\n }\n // When there's some text before the position that is made purely of white–space characters\n // and is preceded by some other text made purely of white–space characters\n //\n //\t\tfoo [] \n //\n // it creates the following range to clean up before leaving:\n //\n //\t\tfoo[ ] \n //\n else if (isEmptyishTextNode(nodeBefore) &&\n isSoftBreakNode(nodeBefore.previousSibling) &&\n isEmptyishTextNode(nodeBefore.previousSibling.previousSibling) &&\n nodeBefore.previousSibling.previousSibling &&\n isSoftBreakNode(nodeBefore.previousSibling.previousSibling.previousSibling)) {\n emptyLineRangeToRemoveOnEnter = model.createRange(model.createPositionBefore(nodeBefore.previousSibling.previousSibling.previousSibling), model.createPositionAfter(nodeBefore));\n }\n // Not leaving the block in the following cases:\n //\n //\t\t [] \n //\t\t a [] \n //\t\tfoo [] \n //\t\tfoo bar[] \n //\t\tfoo a [] \n //\n else {\n return false;\n }\n // We're doing everything in a single change block to have a single undo step.\n editor.model.change(writer => {\n // Remove the last s and all white space characters that followed them.\n writer.remove(emptyLineRangeToRemoveOnEnter);\n // \"Clone\" the in the standard way.\n editor.execute('enter');\n const newBlock = modelDoc.selection.anchor.parent;\n // Make the cloned a regular (with clean attributes, so no language).\n writer.rename(newBlock, DEFAULT_ELEMENT);\n editor.model.schema.removeDisallowedAttributes([newBlock], writer);\n });\n // Eye candy.\n view.scrollToTheSelection();\n return true;\n}\nfunction isEmptyishTextNode(node) {\n return node && node.is('$text') && !node.data.match(/\\S/);\n}\nfunction isSoftBreakNode(node) {\n return node && node.is('element', 'softBreak');\n}\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n/**\n * @module code-block/codeblockui\n */\nimport { icons, Plugin } from 'ckeditor5/src/core.js';\nimport { Collection } from 'ckeditor5/src/utils.js';\nimport { ViewModel, SplitButtonView, createDropdown, addListToDropdown, MenuBarMenuListItemButtonView, MenuBarMenuListView, MenuBarMenuView, MenuBarMenuListItemView } from 'ckeditor5/src/ui.js';\nimport { getNormalizedAndLocalizedLanguageDefinitions } from './utils.js';\nimport '../theme/codeblock.css';\n/**\n * The code block UI plugin.\n *\n * Introduces the `'codeBlock'` dropdown.\n */\nexport default class CodeBlockUI extends Plugin {\n /**\n * @inheritDoc\n */\n static get pluginName() {\n return 'CodeBlockUI';\n }\n /**\n * @inheritDoc\n */\n init() {\n const editor = this.editor;\n const t = editor.t;\n const componentFactory = editor.ui.componentFactory;\n const normalizedLanguageDefs = getNormalizedAndLocalizedLanguageDefinitions(editor);\n const itemDefinitions = this._getLanguageListItemDefinitions(normalizedLanguageDefs);\n const command = editor.commands.get('codeBlock');\n componentFactory.add('codeBlock', locale => {\n const dropdownView = createDropdown(locale, SplitButtonView);\n const splitButtonView = dropdownView.buttonView;\n const accessibleLabel = t('Insert code block');\n splitButtonView.set({\n label: accessibleLabel,\n tooltip: true,\n icon: icons.codeBlock,\n isToggleable: true\n });\n splitButtonView.bind('isOn').to(command, 'value', value => !!value);\n splitButtonView.on('execute', () => {\n editor.execute('codeBlock', {\n usePreviousLanguageChoice: true\n });\n editor.editing.view.focus();\n });\n dropdownView.on('execute', evt => {\n editor.execute('codeBlock', {\n language: evt.source._codeBlockLanguage,\n forceValue: true\n });\n editor.editing.view.focus();\n });\n dropdownView.class = 'ck-code-block-dropdown';\n dropdownView.bind('isEnabled').to(command);\n addListToDropdown(dropdownView, itemDefinitions, {\n role: 'menu',\n ariaLabel: accessibleLabel\n });\n return dropdownView;\n });\n componentFactory.add('menuBar:codeBlock', locale => {\n const menuView = new MenuBarMenuView(locale);\n menuView.buttonView.set({\n label: t('Code block'),\n icon: icons.codeBlock\n });\n menuView.bind('isEnabled').to(command);\n const listView = new MenuBarMenuListView(locale);\n listView.set({\n ariaLabel: t('Insert code block')\n });\n for (const definition of itemDefinitions) {\n const listItemView = new MenuBarMenuListItemView(locale, menuView);\n const buttonView = new MenuBarMenuListItemButtonView(locale);\n buttonView.bind(...Object.keys(definition.model)).to(definition.model);\n buttonView.bind('ariaChecked').to(buttonView, 'isOn');\n buttonView.delegate('execute').to(menuView);\n buttonView.on('execute', () => {\n editor.execute('codeBlock', {\n language: definition.model._codeBlockLanguage,\n forceValue: command.value == definition.model._codeBlockLanguage ? false : true\n });\n editor.editing.view.focus();\n });\n listItemView.children.add(buttonView);\n listView.items.add(listItemView);\n }\n menuView.panelView.children.add(listView);\n return menuView;\n });\n }\n /**\n * A helper returning a collection of the `codeBlock` dropdown items representing languages\n * available for the user to choose from.\n */\n _getLanguageListItemDefinitions(normalizedLanguageDefs) {\n const editor = this.editor;\n const command = editor.commands.get('codeBlock');\n const itemDefinitions = new Collection();\n for (const languageDef of normalizedLanguageDefs) {\n const definition = {\n type: 'button',\n model: new ViewModel({\n _codeBlockLanguage: languageDef.language,\n label: languageDef.label,\n role: 'menuitemradio',\n withText: true\n })\n };\n definition.model.bind('isOn').to(command, 'value', value => {\n return value === definition.model._codeBlockLanguage;\n });\n itemDefinitions.add(definition);\n }\n return itemDefinitions;\n }\n}\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\nimport { getPropertyAssociation } from './utils.js';\n/**\n * A model-to-view (both editing and data) converter for the `codeBlock` element.\n *\n * Sample input:\n *\n * ```html\n * foo(); bar(); \n * ```\n *\n * Sample output (editing):\n *\n * ```html\n * foo();
bar();
\n * ```\n *\n * Sample output (data, see {@link module:code-block/converters~modelToDataViewSoftBreakInsertion}):\n *\n * ```html\n * foo();\\nbar();
\n * ```\n *\n * @param languageDefs The normalized language configuration passed to the feature.\n * @param useLabels When `true`, the `` element will get a `data-language` attribute with a\n * human–readable label of the language. Used only in the editing.\n * @returns Returns a conversion callback.\n */\nexport function modelToViewCodeBlockInsertion(model, languageDefs, useLabels = false) {\n // Language CSS classes:\n //\n //\t\t{\n //\t\t\tphp: 'language-php',\n //\t\t\tpython: 'language-python',\n //\t\t\tjavascript: 'js',\n //\t\t\t...\n //\t\t}\n const languagesToClasses = getPropertyAssociation(languageDefs, 'language', 'class');\n // Language labels:\n //\n //\t\t{\n //\t\t\tphp: 'PHP',\n //\t\t\tpython: 'Python',\n //\t\t\tjavascript: 'JavaScript',\n //\t\t\t...\n //\t\t}\n const languagesToLabels = getPropertyAssociation(languageDefs, 'language', 'label');\n return (evt, data, conversionApi) => {\n const { writer, mapper, consumable } = conversionApi;\n if (!consumable.consume(data.item, 'insert')) {\n return;\n }\n const codeBlockLanguage = data.item.getAttribute('language');\n const targetViewPosition = mapper.toViewPosition(model.createPositionBefore(data.item));\n const preAttributes = {};\n // Attributes added only in the editing view.\n if (useLabels) {\n preAttributes['data-language'] = languagesToLabels[codeBlockLanguage];\n preAttributes.spellcheck = 'false';\n }\n const codeAttributes = languagesToClasses[codeBlockLanguage] ? {\n class: languagesToClasses[codeBlockLanguage]\n } : undefined;\n const code = writer.createContainerElement('code', codeAttributes);\n const pre = writer.createContainerElement('pre', preAttributes, code);\n writer.insert(targetViewPosition, pre);\n mapper.bindElements(data.item, code);\n };\n}\n/**\n * A model-to-data view converter for the new line (`softBreak`) separator.\n *\n * Sample input:\n *\n * ```html\n * foo(); bar(); \n * ```\n *\n * Sample output:\n *\n * ```html\n * foo();\\nbar();
\n * ```\n *\n * @returns Returns a conversion callback.\n */\nexport function modelToDataViewSoftBreakInsertion(model) {\n return (evt, data, conversionApi) => {\n if (data.item.parent.name !== 'codeBlock') {\n return;\n }\n const { writer, mapper, consumable } = conversionApi;\n if (!consumable.consume(data.item, 'insert')) {\n return;\n }\n const position = mapper.toViewPosition(model.createPositionBefore(data.item));\n writer.insert(position, writer.createText('\\n'));\n };\n}\n/**\n * A view-to-model converter for `` with the `` HTML.\n *\n * Sample input:\n *\n * ```html\n * foo();bar();
\n * ```\n *\n * Sample output:\n *\n * ```html\n * foo();bar(); \n * ```\n *\n * @param languageDefs The normalized language configuration passed to the feature.\n * @returns Returns a conversion callback.\n */\nexport function dataViewToModelCodeBlockInsertion(editingView, languageDefs) {\n // Language names associated with CSS classes:\n //\n //\t\t{\n //\t\t\t'language-php': 'php',\n //\t\t\t'language-python': 'python',\n //\t\t\tjs: 'javascript',\n //\t\t\t...\n //\t\t}\n const classesToLanguages = getPropertyAssociation(languageDefs, 'class', 'language');\n const defaultLanguageName = languageDefs[0].language;\n return (evt, data, conversionApi) => {\n const viewCodeElement = data.viewItem;\n const viewPreElement = viewCodeElement.parent;\n if (!viewPreElement || !viewPreElement.is('element', 'pre')) {\n return;\n }\n // In case of nested code blocks we don't want to convert to another code block.\n if (data.modelCursor.findAncestor('codeBlock')) {\n return;\n }\n const { consumable, writer } = conversionApi;\n if (!consumable.test(viewCodeElement, { name: true })) {\n return;\n }\n const codeBlock = writer.createElement('codeBlock');\n const viewChildClasses = [...viewCodeElement.getClassNames()];\n // As we're to associate each class with a model language, a lack of class (empty class) can be\n // also associated with a language if the language definition was configured so. Pushing an empty\n // string to make sure the association will work.\n if (!viewChildClasses.length) {\n viewChildClasses.push('');\n }\n // Figure out if any of the element's class names is a valid programming\n // language class. If so, use it on the model element (becomes the language of the entire block).\n for (const className of viewChildClasses) {\n const language = classesToLanguages[className];\n if (language) {\n writer.setAttribute('language', language, codeBlock);\n break;\n }\n }\n // If no language value was set, use the default language from the config.\n if (!codeBlock.hasAttribute('language')) {\n writer.setAttribute('language', defaultLanguageName, codeBlock);\n }\n conversionApi.convertChildren(viewCodeElement, codeBlock);\n // Let's try to insert code block.\n if (!conversionApi.safeInsert(codeBlock, data.modelCursor)) {\n return;\n }\n consumable.consume(viewCodeElement, { name: true });\n conversionApi.updateConversionResult(codeBlock, data);\n };\n}\n/**\n * A view-to-model converter for new line characters in ``.\n *\n * Sample input:\n *\n * ```html\n * foo();\\nbar();
\n * ```\n *\n * Sample output:\n *\n * ```html\n * foo(); bar(); \n * ```\n *\n * @returns {Function} Returns a conversion callback.\n */\nexport function dataViewToModelTextNewlinesInsertion() {\n return (evt, data, { consumable, writer }) => {\n let position = data.modelCursor;\n // When node is already converted then do nothing.\n if (!consumable.test(data.viewItem)) {\n return;\n }\n // When not inside `codeBlock` then do nothing.\n if (!position.findAncestor('codeBlock')) {\n return;\n }\n consumable.consume(data.viewItem);\n const text = data.viewItem.data;\n const textLines = text.split('\\n').map(data => writer.createText(data));\n const lastLine = textLines[textLines.length - 1];\n for (const node of textLines) {\n writer.insert(node, position);\n position = position.getShiftedBy(node.offsetSize);\n if (node !== lastLine) {\n const softBreak = writer.createElement('softBreak');\n writer.insert(softBreak, position);\n position = writer.createPositionAfter(softBreak);\n }\n }\n data.modelRange = writer.createRange(data.modelCursor, position);\n data.modelCursor = position;\n };\n}\n/**\n * A view-to-model converter that handles orphan text nodes (white spaces, new lines, etc.)\n * that surround `` inside ``.\n *\n * Sample input:\n *\n * ```html\n * // White spaces\n * foo()
\n *\n * // White spaces\n * foo()
\n *\n * // White spaces\n * \t\t\tfoo()\t\t\t
\n *\n * // New lines\n * \n * \tfoo()\n *
\n *\n * // Redundant text\n * ABCfoo()DEF
\n * ```\n *\n * Unified output for each case:\n *\n * ```html\n * foo() \n * ```\n *\n * @returns Returns a conversion callback.\n */\nexport function dataViewToModelOrphanNodeConsumer() {\n return (evt, data, { consumable }) => {\n const preElement = data.viewItem;\n // Don't clean up nested pre elements. Their content should stay as it is, they are not upcasted\n // to code blocks.\n if (preElement.findAncestor('pre')) {\n return;\n }\n const preChildren = Array.from(preElement.getChildren());\n const childCodeElement = preChildren.find(node => node.is('element', 'code'));\n // -less . It will not upcast to code block in the model, skipping.\n if (!childCodeElement) {\n return;\n }\n for (const child of preChildren) {\n if (child === childCodeElement || !child.is('$text')) {\n continue;\n }\n // Consuming the orphan to remove it from the input data.\n // Second argument in `consumable.consume` is discarded for text nodes.\n consumable.consume(child, { name: true });\n }\n };\n}\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n/**\n * @module code-block/indentcodeblockcommand\n */\nimport { Command } from 'ckeditor5/src/core.js';\nimport { getIndentOutdentPositions, isModelSelectionInCodeBlock } from './utils.js';\n/**\n * The code block indentation increase command plugin.\n */\nexport default class IndentCodeBlockCommand extends Command {\n constructor(editor) {\n super(editor);\n this._indentSequence = editor.config.get('codeBlock.indentSequence');\n }\n /**\n * @inheritDoc\n */\n refresh() {\n this.isEnabled = this._checkEnabled();\n }\n /**\n * Executes the command. When the command {@link #isEnabled is enabled}, the indentation of the\n * code lines in the selection will be increased.\n *\n * @fires execute\n */\n execute() {\n const editor = this.editor;\n const model = editor.model;\n model.change(writer => {\n const positions = getIndentOutdentPositions(model);\n // Indent all positions, for instance assuming the indent sequence is 4x space (\" \"):\n //\n //\t\t^foo -> foo \n //\n //\t\tfoo^bar -> foo bar \n //\n // Also, when there is more than one position:\n //\n //\t\t\n //\t\t\t^foobar\n //\t\t\t \n //\t\t\t^bazqux\n //\t\t \n //\n //\t\t->\n //\n //\t\t\n //\t\t\t foobar\n //\t\t\t \n //\t\t\t bazqux\n //\t\t \n //\n for (const position of positions) {\n const indentSequenceTextElement = writer.createText(this._indentSequence);\n // Previously insertion was done by writer.insertText(). It was changed to insertContent() to enable\n // integration of code block with track changes. It's the easiest way of integration because insertContent()\n // is already integrated with track changes, but if it ever cause any troubles it can be reverted, however\n // some additional work will be required in track changes integration of code block.\n model.insertContent(indentSequenceTextElement, position);\n }\n });\n }\n /**\n * Checks whether the command can be enabled in the current context.\n */\n _checkEnabled() {\n if (!this._indentSequence) {\n return false;\n }\n // Indent (forward) command is always enabled when there's any code block in the selection\n // because you can always indent code lines.\n return isModelSelectionInCodeBlock(this.editor.model.document.selection);\n }\n}\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n/**\n * @module code-block\n */\nexport { default as CodeBlock } from './codeblock.js';\nexport { default as CodeBlockEditing } from './codeblockediting.js';\nexport { default as CodeBlockUI } from './codeblockui.js';\nimport './augmentation.js';\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\nimport { Command } from 'ckeditor5/src/core.js';\nimport { getLeadingWhiteSpaces, getIndentOutdentPositions, isModelSelectionInCodeBlock } from './utils.js';\n/**\n * The code block indentation decrease command plugin.\n */\nexport default class OutdentCodeBlockCommand extends Command {\n constructor(editor) {\n super(editor);\n this._indentSequence = editor.config.get('codeBlock.indentSequence');\n }\n /**\n * @inheritDoc\n */\n refresh() {\n this.isEnabled = this._checkEnabled();\n }\n /**\n * Executes the command. When the command {@link #isEnabled is enabled}, the indentation of the\n * code lines in the selection will be decreased.\n *\n * @fires execute\n */\n execute() {\n const editor = this.editor;\n const model = editor.model;\n model.change(() => {\n const positions = getIndentOutdentPositions(model);\n // Outdent all positions, for instance assuming the indent sequence is 4x space (\" \"):\n //\n //\t\t^foo -> foo \n //\n //\t\t ^bar -> bar \n //\n // Also, when there is more than one position:\n //\n //\t\t\n //\t\t\t ^foobar\n //\t\t\t \n //\t\t\t ^bazqux\n //\t\t \n //\n //\t\t->\n //\n //\t\t\n //\t\t\tfoobar\n //\t\t\t \n //\t\t\tbazqux\n //\t\t \n for (const position of positions) {\n const range = getLastOutdentableSequenceRange(model, position, this._indentSequence);\n if (range) {\n // Previously deletion was done by writer.remove(). It was changed to deleteContent() to enable\n // integration of code block with track changes. It's the easiest way of integration because deleteContent()\n // is already integrated with track changes, but if it ever cause any troubles it can be reverted, however\n // some additional work will be required in track changes integration of code block.\n model.deleteContent(model.createSelection(range));\n }\n }\n });\n }\n /**\n * Checks whether the command can be enabled in the current context.\n *\n * @private\n * @returns {Boolean} Whether the command should be enabled.\n */\n _checkEnabled() {\n if (!this._indentSequence) {\n return false;\n }\n const model = this.editor.model;\n if (!isModelSelectionInCodeBlock(model.document.selection)) {\n return false;\n }\n // Outdent command can execute only when there is an indent character sequence\n // in some of the lines.\n return getIndentOutdentPositions(model).some(position => {\n return getLastOutdentableSequenceRange(model, position, this._indentSequence);\n });\n }\n}\n// For a position coming from `getIndentOutdentPositions()`, it returns the range representing\n// the last occurrence of the indent sequence among the leading whitespaces of the code line the\n// position represents.\n//\n// For instance, assuming the indent sequence is 4x space (\" \"):\n//\n//\t\tfoo^ -> null\n//\t\tfoo^ bar -> null\n//\t\t ^foo -> null\n//\t\t ^foo -> [ ]foo \n//\t\t ^foo bar -> [ ]foo bar \n//\n// @param {} model\n// @param {} position\n// @param {String} sequence\n// @returns {|null}\nfunction getLastOutdentableSequenceRange(model, position, sequence) {\n // Positions start before each text node (code line). Get the node corresponding to the position.\n const nodeAtPosition = getCodeLineTextNodeAtPosition(position);\n if (!nodeAtPosition) {\n return null;\n }\n const leadingWhiteSpaces = getLeadingWhiteSpaces(nodeAtPosition);\n const lastIndexOfSequence = leadingWhiteSpaces.lastIndexOf(sequence);\n // For instance, assuming the indent sequence is 4x space (\" \"):\n //\n //\t\t \t^foo -> null\n //\n if (lastIndexOfSequence + sequence.length !== leadingWhiteSpaces.length) {\n return null;\n }\n // For instance, assuming the indent sequence is 4x space (\" \"):\n //\n //\t\t ^foo -> null\n //\n if (lastIndexOfSequence === -1) {\n return null;\n }\n const { parent, startOffset } = nodeAtPosition;\n // Create a range that contains the **last** indent sequence among the leading whitespaces\n // of the line.\n //\n // For instance, assuming the indent sequence is 4x space (\" \"):\n //\n //\t\t ^foo -> [ ]foo \n //\n return model.createRange(model.createPositionAt(parent, startOffset + lastIndexOfSequence), model.createPositionAt(parent, startOffset + lastIndexOfSequence + sequence.length));\n}\nfunction getCodeLineTextNodeAtPosition(position) {\n // Positions start before each text node (code line). Get the node corresponding to the position.\n let nodeAtPosition = position.parent.getChild(position.index);\n // foo^ \n // foo^ bar \n if (!nodeAtPosition || nodeAtPosition.is('element', 'softBreak')) {\n nodeAtPosition = position.nodeBefore;\n }\n // ^ \n // foo^ bar \n if (!nodeAtPosition || nodeAtPosition.is('element', 'softBreak')) {\n return null;\n }\n return nodeAtPosition;\n}\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\nimport { first } from 'ckeditor5/src/utils.js';\n/**\n * Returns code block languages as defined in `config.codeBlock.languages` but processed:\n *\n * * To consider the editor localization, i.e. to display {@link module:code-block/codeblockconfig~CodeBlockLanguageDefinition}\n * in the correct language. There is no way to use {@link module:utils/locale~Locale#t} when the user\n * configuration is defined because the editor does not exist yet.\n * * To make sure each definition has a CSS class associated with it even if not specified\n * in the original configuration.\n */\nexport function getNormalizedAndLocalizedLanguageDefinitions(editor) {\n const t = editor.t;\n const languageDefs = editor.config.get('codeBlock.languages');\n for (const def of languageDefs) {\n if (def.label === 'Plain text') {\n def.label = t('Plain text');\n }\n if (def.class === undefined) {\n def.class = `language-${def.language}`;\n }\n }\n return languageDefs;\n}\n/**\n * Returns an object associating certain language definition properties with others. For instance:\n *\n * For:\n *\n * ```ts\n * const definitions = {\n * \t{ language: 'php', class: 'language-php', label: 'PHP' },\n * \t{ language: 'javascript', class: 'js', label: 'JavaScript' },\n * };\n *\n * getPropertyAssociation( definitions, 'class', 'language' );\n * ```\n *\n * returns:\n *\n * ```ts\n * {\n * \t'language-php': 'php',\n * \t'js': 'javascript'\n * }\n * ```\n *\n * and\n *\n * ```ts\n * getPropertyAssociation( definitions, 'language', 'label' );\n * ```\n *\n * returns:\n *\n * ```ts\n * {\n * \t'php': 'PHP',\n * \t'javascript': 'JavaScript'\n * }\n * ```\n */\nexport function getPropertyAssociation(languageDefs, key, value) {\n const association = {};\n for (const def of languageDefs) {\n if (key === 'class') {\n // Only the first class is considered.\n const newKey = (def[key]).split(' ').shift();\n association[newKey] = def[value];\n }\n else {\n association[def[key]] = def[value];\n }\n }\n return association;\n}\n/**\n * For a given model text node, it returns white spaces that precede other characters in that node.\n * This corresponds to the indentation part of the code block line.\n */\nexport function getLeadingWhiteSpaces(textNode) {\n return textNode.data.match(/^(\\s*)/)[0];\n}\n/**\n * For plain text containing the code (a snippet), it returns a document fragment containing\n * view text nodes separated by `
` elements (in place of new line characters \"\\n\"), for instance:\n *\n * Input:\n *\n * ```ts\n * \"foo()\\n\n * bar()\"\n * ```\n *\n * Output:\n *\n * ```html\n * \n * \t\"foo()\"\n * \t
\n * \t\"bar()\"\n * \n * ```\n *\n * @param text The raw code text to be converted.\n */\nexport function rawSnippetTextToViewDocumentFragment(writer, text) {\n const fragment = writer.createDocumentFragment();\n const textLines = text.split('\\n');\n const items = textLines.reduce((nodes, line, lineIndex) => {\n nodes.push(line);\n if (lineIndex < textLines.length - 1) {\n nodes.push(writer.createElement('br'));\n }\n return nodes;\n }, []);\n writer.appendChild(items, fragment);\n return fragment;\n}\n/**\n * Returns an array of all model positions within the selection that represent code block lines.\n *\n * If the selection is collapsed, it returns the exact selection anchor position:\n *\n * ```html\n * []foo -> ^foo \n * foo[]bar -> foo^bar \n * ```\n *\n * Otherwise, it returns positions **before** each text node belonging to all code blocks contained by the selection:\n *\n * ```html\n * \n * foo[bar ^foobar\n * -> \n * baz]qux ^bazqux\n * \n * ```\n *\n * It also works across other non–code blocks:\n *\n * ```html\n * \n * foo[bar ^foobar\n * \n * text -> text \n * \n * baz]qux ^bazqux\n * \n * ```\n *\n * **Note:** The positions are in reverse order so they do not get outdated when iterating over them and\n * the writer inserts or removes elements at the same time.\n *\n * **Note:** The position is located after the leading white spaces in the text node.\n */\nexport function getIndentOutdentPositions(model) {\n const selection = model.document.selection;\n const positions = [];\n // When the selection is collapsed, there's only one position we can indent or outdent.\n if (selection.isCollapsed) {\n return [selection.anchor];\n }\n // When the selection is NOT collapsed, collect all positions starting before text nodes\n // (code lines) in any within the selection.\n // Walk backward so positions we are about to collect here do not get outdated when\n // inserting or deleting using the writer.\n const walker = selection.getFirstRange().getWalker({\n ignoreElementEnd: true,\n direction: 'backward'\n });\n for (const { item } of walker) {\n if (!item.is('$textProxy')) {\n continue;\n }\n const { parent, startOffset } = item.textNode;\n if (!parent.is('element', 'codeBlock')) {\n continue;\n }\n const leadingWhiteSpaces = getLeadingWhiteSpaces(item.textNode);\n // Make sure the position is after all leading whitespaces in the text node.\n const position = model.createPositionAt(parent, startOffset + leadingWhiteSpaces.length);\n positions.push(position);\n }\n return positions;\n}\n/**\n * Checks if any of the blocks within the model selection is a code block.\n */\nexport function isModelSelectionInCodeBlock(selection) {\n const firstBlock = first(selection.getSelectedBlocks());\n return !!firstBlock && firstBlock.is('element', 'codeBlock');\n}\n/**\n * Checks if an {@link module:engine/model/element~Element Element} can become a code block.\n *\n * @param schema Model's schema.\n * @param element The element to be checked.\n * @returns Check result.\n */\nexport function canBeCodeBlock(schema, element) {\n if (element.is('rootElement') || schema.isLimit(element)) {\n return false;\n }\n return schema.checkChild(element.parent, 'codeBlock');\n}\n/**\n * Get the translated message read by the screen reader when you enter or exit an element with your cursor.\n */\nexport function getCodeBlockAriaAnnouncement(t, languageDefs, element, direction) {\n const languagesToLabels = getPropertyAssociation(languageDefs, 'language', 'label');\n const codeBlockLanguage = element.getAttribute('language');\n if (codeBlockLanguage in languagesToLabels) {\n const language = languagesToLabels[codeBlockLanguage];\n if (direction === 'enter') {\n return t('Entering %0 code snippet', language);\n }\n return t('Leaving %0 code snippet', language);\n }\n if (direction === 'enter') {\n return t('Entering code snippet');\n }\n return t('Leaving code snippet');\n}\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n/**\n * @module core/accessibility\n */\nimport { CKEditorError } from '@ckeditor/ckeditor5-utils';\nconst DEFAULT_CATEGORY_ID = 'contentEditing';\nexport const DEFAULT_GROUP_ID = 'common';\n/**\n * A common namespace for various accessibility features of the editor.\n *\n * **Information about editor keystrokes**\n *\n * * The information about keystrokes available in the editor is stored in the {@link #keystrokeInfos} property.\n * * New info entries can be added using the {@link #addKeystrokeInfoCategory}, {@link #addKeystrokeInfoGroup},\n * and {@link #addKeystrokeInfos} methods.\n */\nexport default class Accessibility {\n /**\n * @inheritDoc\n */\n constructor(editor) {\n /**\n * Stores information about keystrokes brought by editor features for the users to interact with the editor, mainly\n * keystroke combinations and their accessible labels.\n *\n * This information is particularly useful for screen reader and other assistive technology users. It gets displayed\n * by the {@link module:ui/editorui/accessibilityhelp/accessibilityhelp~AccessibilityHelp Accessibility help} dialog.\n *\n * Keystrokes are organized in categories and groups. They can be added using ({@link #addKeystrokeInfoCategory},\n * {@link #addKeystrokeInfoGroup}, and {@link #addKeystrokeInfos}) methods.\n *\n * Please note that:\n * * two categories are always available:\n * * `'contentEditing'` for keystrokes related to content creation,\n * * `'navigation'` for keystrokes related to navigation in the UI and the content.\n * * unless specified otherwise, new keystrokes are added into the `'contentEditing'` category and the `'common'`\n * keystroke group within that category while using the {@link #addKeystrokeInfos} method.\n */\n this.keystrokeInfos = new Map();\n this._editor = editor;\n const isMenuBarVisible = editor.config.get('menuBar.isVisible');\n const t = editor.locale.t;\n this.addKeystrokeInfoCategory({\n id: DEFAULT_CATEGORY_ID,\n label: t('Content editing keystrokes'),\n description: t('These keyboard shortcuts allow for quick access to content editing features.')\n });\n const navigationKeystrokes = [\n {\n label: t('Close contextual balloons, dropdowns, and dialogs'),\n keystroke: 'Esc'\n },\n {\n label: t('Open the accessibility help dialog'),\n keystroke: 'Alt+0'\n },\n {\n label: t('Move focus between form fields (inputs, buttons, etc.)'),\n keystroke: [['Tab'], ['Shift+Tab']]\n },\n {\n label: t('Move focus to the toolbar, navigate between toolbars'),\n keystroke: 'Alt+F10',\n mayRequireFn: true\n },\n {\n label: t('Navigate through the toolbar or menu bar'),\n keystroke: [['arrowup'], ['arrowright'], ['arrowdown'], ['arrowleft']]\n },\n {\n // eslint-disable-next-line max-len\n label: t('Execute the currently focused button. Executing buttons that interact with the editor content moves the focus back to the content.'),\n keystroke: [['Enter'], ['Space']]\n }\n ];\n if (isMenuBarVisible) {\n navigationKeystrokes.push({\n label: t('Move focus to the menu bar, navigate between menu bars'),\n keystroke: 'Alt+F9',\n mayRequireFn: true\n });\n }\n this.addKeystrokeInfoCategory({\n id: 'navigation',\n label: t('User interface and content navigation keystrokes'),\n description: t('Use the following keystrokes for more efficient navigation in the CKEditor 5 user interface.'),\n groups: [\n {\n id: 'common',\n keystrokes: navigationKeystrokes\n }\n ]\n });\n }\n /**\n * Adds a top-level category in the {@link #keystrokeInfos keystroke information database} with a label and optional description.\n *\n * Categories organize keystrokes and help users to find the right keystroke. Each category can have multiple groups\n * of keystrokes that narrow down the context in which the keystrokes are available. Every keystroke category comes\n * with a `'common'` group by default.\n *\n * By default, two categories are available:\n * * `'contentEditing'` for keystrokes related to content creation,\n * * `'navigation'` for keystrokes related to navigation in the UI and the content.\n *\n * To create a new keystroke category with new groups, use the following code:\n *\n * ```js\n * class MyPlugin extends Plugin {\n * \t// ...\n * \tinit() {\n * \t\tconst editor = this.editor;\n * \t\tconst t = editor.t;\n *\n * \t\t// ...\n *\n * \t\teditor.accessibility.addKeystrokeInfoCategory( {\n * \t\t\tid: 'myCategory',\n * \t\t\tlabel: t( 'My category' ),\n * \t\t\tdescription: t( 'My category description.' ),\n * \t\t\tgroups: [\n * \t\t\t\t{\n * \t\t\t\t\tid: 'myGroup',\n * \t\t\t\t\tlabel: t( 'My keystroke group' ),\n * \t\t\t\t\tkeystrokes: [\n * \t\t\t\t\t\t{\n * \t\t\t\t\t\t\tlabel: t( 'Keystroke label 1' ),\n * \t\t\t\t\t\t\tkeystroke: 'Ctrl+Shift+N'\n * \t\t\t\t\t\t},\n * \t\t\t\t\t\t{\n * \t\t\t\t\t\t\tlabel: t( 'Keystroke label 2' ),\n * \t\t\t\t\t\t\tkeystroke: 'Ctrl+Shift+M'\n * \t\t\t\t\t\t}\n * \t\t\t\t\t]\n * \t\t\t\t}\n * \t\t\t]\n * \t\t};\n * \t}\n * }\n * ```\n *\n * See {@link #keystrokeInfos}, {@link #addKeystrokeInfoGroup}, and {@link #addKeystrokeInfos}.\n */\n addKeystrokeInfoCategory({ id, label, description, groups }) {\n this.keystrokeInfos.set(id, {\n id,\n label,\n description,\n groups: new Map()\n });\n this.addKeystrokeInfoGroup({\n categoryId: id,\n id: DEFAULT_GROUP_ID\n });\n if (groups) {\n groups.forEach(group => {\n this.addKeystrokeInfoGroup({\n categoryId: id,\n ...group\n });\n });\n }\n }\n /**\n * Adds a group of keystrokes in a specific category to the {@link #keystrokeInfos keystroke information database}.\n *\n * Groups narrow down the context in which the keystrokes are available. When `categoryId` is not specified,\n * the group goes to the `'contentEditing'` category (default).\n *\n * To create a new group within an existing category, use the following code:\n *\n * ```js\n * class MyPlugin extends Plugin {\n * \t// ...\n * \tinit() {\n * \t\tconst editor = this.editor;\n * \t\tconst t = editor.t;\n *\n * \t\t// ...\n *\n * \t\teditor.accessibility.addKeystrokeInfoGroup( {\n * \t\t\tid: 'myGroup',\n * \t\t\tcategoryId: 'navigation',\n * \t\t\tlabel: t( 'My keystroke group' ),\n * \t\t\tkeystrokes: [\n * \t\t\t\t{\n * \t\t\t\t\tlabel: t( 'Keystroke label 1' ),\n * \t\t\t\t\tkeystroke: 'Ctrl+Shift+N'\n * \t\t\t\t},\n * \t\t\t\t{\n * \t\t\t\t\tlabel: t( 'Keystroke label 2' ),\n * \t\t\t\t\tkeystroke: 'Ctrl+Shift+M'\n * \t\t\t\t}\n * \t\t\t]\n * \t\t} );\n * \t}\n * }\n * ```\n *\n * See {@link #keystrokeInfos}, {@link #addKeystrokeInfoCategory}, and {@link #addKeystrokeInfos}.\n */\n addKeystrokeInfoGroup({ categoryId = DEFAULT_CATEGORY_ID, id, label, keystrokes }) {\n const category = this.keystrokeInfos.get(categoryId);\n if (!category) {\n throw new CKEditorError('accessibility-unknown-keystroke-info-category', this._editor, { groupId: id, categoryId });\n }\n category.groups.set(id, {\n id,\n label,\n keystrokes: keystrokes || []\n });\n }\n /**\n * Adds information about keystrokes to the {@link #keystrokeInfos keystroke information database}.\n *\n * Keystrokes without specified `groupId` or `categoryId` go to the `'common'` group in the `'contentEditing'` category (default).\n *\n * To add a keystroke brought by your plugin (using default group and category), use the following code:\n *\n * ```js\n * class MyPlugin extends Plugin {\n * \t// ...\n * \tinit() {\n * \t\tconst editor = this.editor;\n * \t\tconst t = editor.t;\n *\n * \t\t// ...\n *\n * \t\teditor.accessibility.addKeystrokeInfos( {\n * \t\t\tkeystrokes: [\n * \t\t\t\t{\n * \t\t\t\t\tlabel: t( 'Keystroke label' ),\n * \t\t\t\t\tkeystroke: 'CTRL+B'\n * \t\t\t\t}\n * \t\t\t]\n * \t\t} );\n * \t}\n * }\n * ```\n * To add a keystroke in a specific existing `'widget'` group in the default `'contentEditing'` category:\n *\n * ```js\n * class MyPlugin extends Plugin {\n * \t// ...\n * \tinit() {\n * \t\tconst editor = this.editor;\n * \t\tconst t = editor.t;\n *\n * \t\t// ...\n *\n * \t\teditor.accessibility.addKeystrokeInfos( {\n * \t\t\t// Add a keystroke to the existing \"widget\" group.\n * \t\t\tgroupId: 'widget',\n * \t\t\tkeystrokes: [\n * \t\t\t\t{\n * \t\t\t\t\tlabel: t( 'A an action on a selected widget' ),\n * \t\t\t\t\tkeystroke: 'Ctrl+D',\n * \t\t\t\t}\n * \t\t\t]\n * \t\t} );\n * \t}\n * }\n * ```\n *\n * To add a keystroke to another existing category (using default group):\n *\n * ```js\n * class MyPlugin extends Plugin {\n * \t// ...\n * \tinit() {\n * \t\tconst editor = this.editor;\n * \t\tconst t = editor.t;\n *\n * \t\t// ...\n *\n * \t\teditor.accessibility.addKeystrokeInfos( {\n * \t\t\t// Add keystrokes to the \"navigation\" category (one of defaults).\n * \t\t\tcategoryId: 'navigation',\n * \t\t\tkeystrokes: [\n * \t\t\t\t{\n * \t\t\t\t\tlabel: t( 'Keystroke label' ),\n * \t\t\t\t\tkeystroke: 'CTRL+B'\n * \t\t\t\t}\n * \t\t\t]\n * \t\t} );\n * \t}\n * }\n * ```\n *\n * See {@link #keystrokeInfos}, {@link #addKeystrokeInfoGroup}, and {@link #addKeystrokeInfoCategory}.\n */\n addKeystrokeInfos({ categoryId = DEFAULT_CATEGORY_ID, groupId = DEFAULT_GROUP_ID, keystrokes }) {\n if (!this.keystrokeInfos.has(categoryId)) {\n /**\n * Cannot add keystrokes in an unknown category. Use\n * {@link module:core/accessibility~Accessibility#addKeystrokeInfoCategory}\n * to add a new category or make sure the specified category exists.\n *\n * @error accessibility-unknown-keystroke-info-category\n * @param categoryId The id of the unknown keystroke category.\n * @param keystrokes Keystroke definitions about to be added.\n */\n throw new CKEditorError('accessibility-unknown-keystroke-info-category', this._editor, { categoryId, keystrokes });\n }\n const category = this.keystrokeInfos.get(categoryId);\n if (!category.groups.has(groupId)) {\n /**\n * Cannot add keystrokes to an unknown group.\n *\n * Use {@link module:core/accessibility~Accessibility#addKeystrokeInfoGroup}\n * to add a new group or make sure the specified group exists.\n *\n * @error accessibility-unknown-keystroke-info-group\n * @param groupId The id of the unknown keystroke group.\n * @param categoryId The id of category the unknown group should belong to.\n * @param keystrokes Keystroke definitions about to be added.\n */\n throw new CKEditorError('accessibility-unknown-keystroke-info-group', this._editor, { groupId, categoryId, keystrokes });\n }\n category.groups.get(groupId).keystrokes.push(...keystrokes);\n }\n}\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\nexport {};\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n/**\n * @module core/command\n */\nimport { ObservableMixin } from '@ckeditor/ckeditor5-utils';\n/**\n * Base class for the CKEditor commands.\n *\n * Commands are the main way to manipulate the editor contents and state. They are mostly used by UI elements (or by other\n * commands) to make changes in the model. Commands are available in every part of the code that has access to\n * the {@link module:core/editor/editor~Editor editor} instance.\n *\n * Instances of registered commands can be retrieved from {@link module:core/editor/editor~Editor#commands `editor.commands`}.\n * The easiest way to execute a command is through {@link module:core/editor/editor~Editor#execute `editor.execute()`}.\n *\n * By default, commands are disabled when the editor is in the {@link module:core/editor/editor~Editor#isReadOnly read-only} mode\n * but commands with the {@link module:core/command~Command#affectsData `affectsData`} flag set to `false` will not be disabled.\n */\nexport default class Command extends ObservableMixin() {\n /**\n * Creates a new `Command` instance.\n *\n * @param editor The editor on which this command will be used.\n */\n constructor(editor) {\n super();\n this.editor = editor;\n this.set('value', undefined);\n this.set('isEnabled', false);\n this._affectsData = true;\n this._isEnabledBasedOnSelection = true;\n this._disableStack = new Set();\n this.decorate('execute');\n // By default, every command is refreshed when changes are applied to the model.\n this.listenTo(this.editor.model.document, 'change', () => {\n this.refresh();\n });\n this.listenTo(editor, 'change:isReadOnly', () => {\n this.refresh();\n });\n // By default, commands are disabled if the selection is in non-editable place or editor is in read-only mode.\n this.on('set:isEnabled', evt => {\n if (!this.affectsData) {\n return;\n }\n const selection = editor.model.document.selection;\n const selectionInGraveyard = selection.getFirstPosition().root.rootName == '$graveyard';\n const canEditAtSelection = !selectionInGraveyard && editor.model.canEditAt(selection);\n // Disable if editor is read only, or when selection is in a place which cannot be edited.\n //\n // Checking `editor.isReadOnly` is needed for all commands that have `_isEnabledBasedOnSelection == false`.\n // E.g. undo does not base on selection, but affects data and should be disabled when the editor is in read-only mode.\n if (editor.isReadOnly || this._isEnabledBasedOnSelection && !canEditAtSelection) {\n evt.return = false;\n evt.stop();\n }\n }, { priority: 'highest' });\n this.on('execute', evt => {\n if (!this.isEnabled) {\n evt.stop();\n }\n }, { priority: 'high' });\n }\n /**\n * A flag indicating whether a command execution changes the editor data or not.\n *\n * Commands with `affectsData` set to `false` will not be automatically disabled in\n * the {@link module:core/editor/editor~Editor#isReadOnly read-only mode} and\n * {@glink features/read-only#related-features other editor modes} with restricted user write permissions.\n *\n * **Note:** You do not have to set it for your every command. It is `true` by default.\n *\n * @default true\n */\n get affectsData() {\n return this._affectsData;\n }\n set affectsData(affectsData) {\n this._affectsData = affectsData;\n }\n /**\n * Refreshes the command. The command should update its {@link #isEnabled} and {@link #value} properties\n * in this method.\n *\n * This method is automatically called when\n * {@link module:engine/model/document~Document#event:change any changes are applied to the document}.\n */\n refresh() {\n this.isEnabled = true;\n }\n /**\n * Disables the command.\n *\n * Command may be disabled by multiple features or algorithms (at once). When disabling a command, unique id should be passed\n * (e.g. the feature name). The same identifier should be used when {@link #clearForceDisabled enabling back} the command.\n * The command becomes enabled only after all features {@link #clearForceDisabled enabled it back}.\n *\n * Disabling and enabling a command:\n *\n * ```ts\n * command.isEnabled; // -> true\n * command.forceDisabled( 'MyFeature' );\n * command.isEnabled; // -> false\n * command.clearForceDisabled( 'MyFeature' );\n * command.isEnabled; // -> true\n * ```\n *\n * Command disabled by multiple features:\n *\n * ```ts\n * command.forceDisabled( 'MyFeature' );\n * command.forceDisabled( 'OtherFeature' );\n * command.clearForceDisabled( 'MyFeature' );\n * command.isEnabled; // -> false\n * command.clearForceDisabled( 'OtherFeature' );\n * command.isEnabled; // -> true\n * ```\n *\n * Multiple disabling with the same identifier is redundant:\n *\n * ```ts\n * command.forceDisabled( 'MyFeature' );\n * command.forceDisabled( 'MyFeature' );\n * command.clearForceDisabled( 'MyFeature' );\n * command.isEnabled; // -> true\n * ```\n *\n * **Note:** some commands or algorithms may have more complex logic when it comes to enabling or disabling certain commands,\n * so the command might be still disabled after {@link #clearForceDisabled} was used.\n *\n * @param id Unique identifier for disabling. Use the same id when {@link #clearForceDisabled enabling back} the command.\n */\n forceDisabled(id) {\n this._disableStack.add(id);\n if (this._disableStack.size == 1) {\n this.on('set:isEnabled', forceDisable, { priority: 'highest' });\n this.isEnabled = false;\n }\n }\n /**\n * Clears forced disable previously set through {@link #forceDisabled}. See {@link #forceDisabled}.\n *\n * @param id Unique identifier, equal to the one passed in {@link #forceDisabled} call.\n */\n clearForceDisabled(id) {\n this._disableStack.delete(id);\n if (this._disableStack.size == 0) {\n this.off('set:isEnabled', forceDisable);\n this.refresh();\n }\n }\n /**\n * Executes the command.\n *\n * A command may accept parameters. They will be passed from {@link module:core/editor/editor~Editor#execute `editor.execute()`}\n * to the command.\n *\n * The `execute()` method will automatically abort when the command is disabled ({@link #isEnabled} is `false`).\n * This behavior is implemented by a high priority listener to the {@link #event:execute} event.\n *\n * In order to see how to disable a command from \"outside\" see the {@link #isEnabled} documentation.\n *\n * This method may return a value, which would be forwarded all the way down to the\n * {@link module:core/editor/editor~Editor#execute `editor.execute()`}.\n *\n * @fires execute\n */\n execute(...args) { return undefined; } // eslint-disable-line @typescript-eslint/no-unused-vars\n /**\n * Destroys the command.\n */\n destroy() {\n this.stopListening();\n }\n}\n/**\n * Helper function that forces command to be disabled.\n */\nfunction forceDisable(evt) {\n evt.return = false;\n evt.stop();\n}\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n/**\n * @module core/commandcollection\n */\nimport { CKEditorError } from '@ckeditor/ckeditor5-utils';\n/**\n * Collection of commands. Its instance is available in {@link module:core/editor/editor~Editor#commands `editor.commands`}.\n */\nexport default class CommandCollection {\n /**\n * Creates collection instance.\n */\n constructor() {\n this._commands = new Map();\n }\n /**\n * Registers a new command.\n *\n * @param commandName The name of the command.\n */\n add(commandName, command) {\n this._commands.set(commandName, command);\n }\n /**\n * Retrieves a command from the collection.\n *\n * @param commandName The name of the command.\n */\n get(commandName) {\n return this._commands.get(commandName);\n }\n /**\n * Executes a command.\n *\n * @param commandName The name of the command.\n * @param commandParams Command parameters.\n * @returns The value returned by the {@link module:core/command~Command#execute `command.execute()`}.\n */\n execute(commandName, ...commandParams) {\n const command = this.get(commandName);\n if (!command) {\n /**\n * Command does not exist.\n *\n * @error commandcollection-command-not-found\n * @param commandName Name of the command.\n */\n throw new CKEditorError('commandcollection-command-not-found', this, { commandName });\n }\n return command.execute(...commandParams);\n }\n /**\n * Returns iterator of command names.\n */\n *names() {\n yield* this._commands.keys();\n }\n /**\n * Returns iterator of command instances.\n */\n *commands() {\n yield* this._commands.values();\n }\n /**\n * Iterable interface.\n *\n * Returns `[ commandName, commandInstance ]` pairs.\n */\n [Symbol.iterator]() {\n return this._commands[Symbol.iterator]();\n }\n /**\n * Destroys all collection commands.\n */\n destroy() {\n for (const command of this.commands()) {\n command.destroy();\n }\n }\n}\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n/**\n * @module core/context\n */\nimport { Config, Collection, CKEditorError, Locale } from '@ckeditor/ckeditor5-utils';\nimport PluginCollection from './plugincollection.js';\n/**\n * Provides a common, higher-level environment for solutions that use multiple {@link module:core/editor/editor~Editor editors}\n * or plugins that work outside the editor. Use it instead of {@link module:core/editor/editor~Editor.create `Editor.create()`}\n * in advanced application integrations.\n *\n * All configuration options passed to a context will be used as default options for the editor instances initialized in that context.\n *\n * {@link module:core/contextplugin~ContextPlugin Context plugins} passed to a context instance will be shared among all\n * editor instances initialized in this context. These will be the same plugin instances for all the editors.\n *\n * **Note:** The context can only be initialized with {@link module:core/contextplugin~ContextPlugin context plugins}\n * (e.g. [comments](https://ckeditor.com/collaboration/comments/)). Regular {@link module:core/plugin~Plugin plugins} require an\n * editor instance to work and cannot be added to a context.\n *\n * **Note:** You can add a context plugin to an editor instance, though.\n *\n * If you are using multiple editor instances on one page and use any context plugins, create a context to share the configuration and\n * plugins among these editors. Some plugins will use the information about all existing editors to better integrate between them.\n *\n * If you are using plugins that do not require an editor to work (e.g. [comments](https://ckeditor.com/collaboration/comments/)),\n * enable and configure them using the context.\n *\n * If you are using only a single editor on each page, use {@link module:core/editor/editor~Editor.create `Editor.create()`} instead.\n * In such a case, a context instance will be created by the editor instance in a transparent way.\n *\n * See {@link ~Context.create `Context.create()`} for usage examples.\n */\nexport default class Context {\n /**\n * Creates a context instance with a given configuration.\n *\n * Usually not to be used directly. See the static {@link module:core/context~Context.create `create()`} method.\n *\n * @param config The context configuration.\n */\n constructor(config) {\n /**\n * Reference to the editor which created the context.\n * Null when the context was created outside of the editor.\n *\n * It is used to destroy the context when removing the editor that has created the context.\n */\n this._contextOwner = null;\n // We don't pass translations to the config, because its behavior of splitting keys\n // with dots (e.g. `resize.width` => `resize: { width }`) breaks the translations.\n const { translations, ...rest } = config || {};\n this.config = new Config(rest, this.constructor.defaultConfig);\n const availablePlugins = this.constructor.builtinPlugins;\n this.config.define('plugins', availablePlugins);\n this.plugins = new PluginCollection(this, availablePlugins);\n const languageConfig = this.config.get('language') || {};\n this.locale = new Locale({\n uiLanguage: typeof languageConfig === 'string' ? languageConfig : languageConfig.ui,\n contentLanguage: this.config.get('language.content'),\n translations\n });\n this.t = this.locale.t;\n this.editors = new Collection();\n }\n /**\n * Loads and initializes plugins specified in the configuration.\n *\n * @returns A promise which resolves once the initialization is completed, providing an array of loaded plugins.\n */\n initPlugins() {\n const plugins = this.config.get('plugins') || [];\n const substitutePlugins = this.config.get('substitutePlugins') || [];\n // Plugins for substitution should be checked as well.\n for (const Plugin of plugins.concat(substitutePlugins)) {\n if (typeof Plugin != 'function') {\n /**\n * Only a constructor function is allowed as a {@link module:core/contextplugin~ContextPlugin context plugin}.\n *\n * @error context-initplugins-constructor-only\n */\n throw new CKEditorError('context-initplugins-constructor-only', null, { Plugin });\n }\n if (Plugin.isContextPlugin !== true) {\n /**\n * Only a plugin marked as a {@link module:core/contextplugin~ContextPlugin.isContextPlugin context plugin}\n * is allowed to be used with a context.\n *\n * @error context-initplugins-invalid-plugin\n */\n throw new CKEditorError('context-initplugins-invalid-plugin', null, { Plugin });\n }\n }\n return this.plugins.init(plugins, [], substitutePlugins);\n }\n /**\n * Destroys the context instance and all editors used with the context,\n * releasing all resources used by the context.\n *\n * @returns A promise that resolves once the context instance is fully destroyed.\n */\n destroy() {\n return Promise.all(Array.from(this.editors, editor => editor.destroy()))\n .then(() => this.plugins.destroy());\n }\n /**\n * Adds a reference to the editor which is used with this context.\n *\n * When the given editor has created the context, the reference to this editor will be stored\n * as a {@link ~Context#_contextOwner}.\n *\n * This method should only be used by the editor.\n *\n * @internal\n * @param isContextOwner Stores the given editor as a context owner.\n */\n _addEditor(editor, isContextOwner) {\n if (this._contextOwner) {\n /**\n * Cannot add multiple editors to the context which is created by the editor.\n *\n * @error context-addeditor-private-context\n */\n throw new CKEditorError('context-addeditor-private-context');\n }\n this.editors.add(editor);\n if (isContextOwner) {\n this._contextOwner = editor;\n }\n }\n /**\n * Removes a reference to the editor which was used with this context.\n * When the context was created by the given editor, the context will be destroyed.\n *\n * This method should only be used by the editor.\n *\n * @internal\n * @return A promise that resolves once the editor is removed from the context or when the context was destroyed.\n */\n _removeEditor(editor) {\n if (this.editors.has(editor)) {\n this.editors.remove(editor);\n }\n if (this._contextOwner === editor) {\n return this.destroy();\n }\n return Promise.resolve();\n }\n /**\n * Returns the context configuration which will be copied to the editors created using this context.\n *\n * The configuration returned by this method has the plugins configuration removed – plugins are shared with all editors\n * through another mechanism.\n *\n * This method should only be used by the editor.\n *\n * @internal\n * @returns Configuration as a plain object.\n */\n _getEditorConfig() {\n const result = {};\n for (const name of this.config.names()) {\n if (!['plugins', 'removePlugins', 'extraPlugins'].includes(name)) {\n result[name] = this.config.get(name);\n }\n }\n return result;\n }\n /**\n * Creates and initializes a new context instance.\n *\n * ```ts\n * const commonConfig = { ... }; // Configuration for all the plugins and editors.\n * const editorPlugins = [ ... ]; // Regular plugins here.\n *\n * Context\n * \t.create( {\n * \t\t// Only context plugins here.\n * \t\tplugins: [ ... ],\n *\n * \t\t// Configure the language for all the editors (it cannot be overwritten).\n * \t\tlanguage: { ... },\n *\n * \t\t// Configuration for context plugins.\n * \t\tcomments: { ... },\n * \t\t...\n *\n * \t\t// Default configuration for editor plugins.\n * \t\ttoolbar: { ... },\n * \t\timage: { ... },\n * \t\t...\n * \t} )\n * \t.then( context => {\n * \t\tconst promises = [];\n *\n * \t\tpromises.push( ClassicEditor.create(\n * \t\t\tdocument.getElementById( 'editor1' ),\n * \t\t\t{\n * \t\t\t\teditorPlugins,\n * \t\t\t\tcontext\n * \t\t\t}\n * \t\t) );\n *\n * \t\tpromises.push( ClassicEditor.create(\n * \t\t\tdocument.getElementById( 'editor2' ),\n * \t\t\t{\n * \t\t\t\teditorPlugins,\n * \t\t\t\tcontext,\n * \t\t\t\ttoolbar: { ... } // You can overwrite the configuration of the context.\n * \t\t\t}\n * \t\t) );\n *\n * \t\treturn Promise.all( promises );\n * \t} );\n * ```\n *\n * @param config The context configuration.\n * @returns A promise resolved once the context is ready. The promise resolves with the created context instance.\n */\n static create(config) {\n return new Promise(resolve => {\n const context = new this(config);\n resolve(context.initPlugins().then(() => context));\n });\n }\n}\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n/**\n * @module core/contextplugin\n */\nimport { ObservableMixin } from '@ckeditor/ckeditor5-utils';\n/**\n * The base class for {@link module:core/context~Context} plugin classes.\n *\n * A context plugin can either be initialized for an {@link module:core/editor/editor~Editor editor} or for\n * a {@link module:core/context~Context context}. In other words, it can either\n * work within one editor instance or with one or more editor instances that use a single context.\n * It is the context plugin's role to implement handling for both modes.\n *\n * There are a few rules for interaction between the editor plugins and context plugins:\n *\n * * A context plugin can require another context plugin.\n * * An {@link module:core/plugin~Plugin editor plugin} can require a context plugin.\n * * A context plugin MUST NOT require an {@link module:core/plugin~Plugin editor plugin}.\n */\nexport default class ContextPlugin extends ObservableMixin() {\n /**\n * Creates a new plugin instance.\n */\n constructor(context) {\n super();\n this.context = context;\n }\n /**\n * @inheritDoc\n */\n destroy() {\n this.stopListening();\n }\n /**\n * @inheritDoc\n */\n static get isContextPlugin() {\n return true;\n }\n}\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n/**\n * @module core/editingkeystrokehandler\n */\nimport { KeystrokeHandler } from '@ckeditor/ckeditor5-utils';\n/**\n * A keystroke handler for editor editing. Its instance is available\n * in {@link module:core/editor/editor~Editor#keystrokes} so plugins\n * can register their keystrokes.\n *\n * E.g. an undo plugin would do this:\n *\n * ```ts\n * editor.keystrokes.set( 'Ctrl+Z', 'undo' );\n * editor.keystrokes.set( 'Ctrl+Shift+Z', 'redo' );\n * editor.keystrokes.set( 'Ctrl+Y', 'redo' );\n * ```\n */\nexport default class EditingKeystrokeHandler extends KeystrokeHandler {\n /**\n * Creates an instance of the keystroke handler.\n */\n constructor(editor) {\n super();\n this.editor = editor;\n }\n /**\n * Registers a handler for the specified keystroke.\n *\n * The handler can be specified as a command name or a callback.\n *\n * @param keystroke Keystroke defined in a format accepted by\n * the {@link module:utils/keyboard~parseKeystroke} function.\n * @param callback If a string is passed, then the keystroke will\n * {@link module:core/editor/editor~Editor#execute execute a command}.\n * If a function, then it will be called with the\n * {@link module:engine/view/observer/keyobserver~KeyEventData key event data} object and\n * a `cancel()` helper to both `preventDefault()` and `stopPropagation()` of the event.\n * @param options Additional options.\n * @param options.priority The priority of the keystroke callback. The higher the priority value\n * the sooner the callback will be executed. Keystrokes having the same priority\n * are called in the order they were added.\n */\n set(keystroke, callback, options = {}) {\n if (typeof callback == 'string') {\n const commandName = callback;\n callback = (evtData, cancel) => {\n this.editor.execute(commandName);\n cancel();\n };\n }\n super.set(keystroke, callback, options);\n }\n}\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n/**\n * @module core/editor/editor\n */\nimport { Config, CKEditorError, ObservableMixin } from '@ckeditor/ckeditor5-utils';\nimport { Conversion, DataController, EditingController, Model, StylesProcessor } from '@ckeditor/ckeditor5-engine';\nimport Context from '../context.js';\nimport PluginCollection from '../plugincollection.js';\nimport CommandCollection from '../commandcollection.js';\nimport EditingKeystrokeHandler from '../editingkeystrokehandler.js';\nimport Accessibility from '../accessibility.js';\n/**\n * The class representing a basic, generic editor.\n *\n * Check out the list of its subclasses to learn about specific editor implementations.\n *\n * All editor implementations (like {@link module:editor-classic/classiceditor~ClassicEditor} or\n * {@link module:editor-inline/inlineeditor~InlineEditor}) should extend this class. They can add their\n * own methods and properties.\n *\n * When you are implementing a plugin, this editor represents the API\n * which your plugin can expect to get when using its {@link module:core/plugin~Plugin#editor} property.\n *\n * This API should be sufficient in order to implement the \"editing\" part of your feature\n * (schema definition, conversion, commands, keystrokes, etc.).\n * It does not define the editor UI, which is available only if\n * the specific editor implements also the {@link ~Editor#ui} property\n * (as most editor implementations do).\n */\nexport default class Editor extends ObservableMixin() {\n /**\n * Creates a new instance of the editor class.\n *\n * Usually, not to be used directly. See the static {@link module:core/editor/editor~Editor.create `create()`} method.\n *\n * @param config The editor configuration.\n */\n constructor(config = {}) {\n super();\n const constructor = this.constructor;\n // We don't pass translations to the config, because its behavior of splitting keys\n // with dots (e.g. `resize.width` => `resize: { width }`) breaks the translations.\n const { translations: defaultTranslations, ...defaultConfig } = constructor.defaultConfig || {};\n const { translations = defaultTranslations, ...rest } = config;\n // Prefer the language passed as the argument to the constructor instead of the constructor's `defaultConfig`, if both are set.\n const language = config.language || defaultConfig.language;\n this._context = config.context || new Context({ language, translations });\n this._context._addEditor(this, !config.context);\n // Clone the plugins to make sure that the plugin array will not be shared\n // between editors and make the watchdog feature work correctly.\n const availablePlugins = Array.from(constructor.builtinPlugins || []);\n this.config = new Config(rest, defaultConfig);\n this.config.define('plugins', availablePlugins);\n this.config.define(this._context._getEditorConfig());\n this.plugins = new PluginCollection(this, availablePlugins, this._context.plugins);\n this.locale = this._context.locale;\n this.t = this.locale.t;\n this._readOnlyLocks = new Set();\n this.commands = new CommandCollection();\n this.set('state', 'initializing');\n this.once('ready', () => (this.state = 'ready'), { priority: 'high' });\n this.once('destroy', () => (this.state = 'destroyed'), { priority: 'high' });\n this.model = new Model();\n this.on('change:isReadOnly', () => {\n this.model.document.isReadOnly = this.isReadOnly;\n });\n const stylesProcessor = new StylesProcessor();\n this.data = new DataController(this.model, stylesProcessor);\n this.editing = new EditingController(this.model, stylesProcessor);\n this.editing.view.document.bind('isReadOnly').to(this);\n this.conversion = new Conversion([this.editing.downcastDispatcher, this.data.downcastDispatcher], this.data.upcastDispatcher);\n this.conversion.addAlias('dataDowncast', this.data.downcastDispatcher);\n this.conversion.addAlias('editingDowncast', this.editing.downcastDispatcher);\n this.keystrokes = new EditingKeystrokeHandler(this);\n this.keystrokes.listenTo(this.editing.view.document);\n this.accessibility = new Accessibility(this);\n }\n /**\n * Defines whether the editor is in the read-only mode.\n *\n * In read-only mode the editor {@link #commands commands} are disabled so it is not possible\n * to modify the document by using them. Also, the editable element(s) become non-editable.\n *\n * In order to make the editor read-only, you need to call the {@link #enableReadOnlyMode} method:\n *\n * ```ts\n * editor.enableReadOnlyMode( 'feature-id' );\n * ```\n *\n * Later, to turn off the read-only mode, call {@link #disableReadOnlyMode}:\n *\n * ```ts\n * editor.disableReadOnlyMode( 'feature-id' );\n * ```\n *\n * @readonly\n * @observable\n */\n get isReadOnly() {\n return this._readOnlyLocks.size > 0;\n }\n set isReadOnly(value) {\n /**\n * The {@link module:core/editor/editor~Editor#isReadOnly Editor#isReadOnly} property is read-only since version `34.0.0`\n * and can be set only using {@link module:core/editor/editor~Editor#enableReadOnlyMode `Editor#enableReadOnlyMode( lockId )`} and\n * {@link module:core/editor/editor~Editor#disableReadOnlyMode `Editor#disableReadOnlyMode( lockId )`}.\n *\n * Usage before version `34.0.0`:\n *\n * ```ts\n * editor.isReadOnly = true;\n * editor.isReadOnly = false;\n * ```\n *\n * Usage since version `34.0.0`:\n *\n * ```ts\n * editor.enableReadOnlyMode( 'my-feature-id' );\n * editor.disableReadOnlyMode( 'my-feature-id' );\n * ```\n *\n * @error editor-isreadonly-has-no-setter\n */\n throw new CKEditorError('editor-isreadonly-has-no-setter');\n }\n /**\n * Turns on the read-only mode in the editor.\n *\n * Editor can be switched to or out of the read-only mode by many features, under various circumstances. The editor supports locking\n * mechanism for the read-only mode. It enables easy control over the read-only mode when many features wants to turn it on or off at\n * the same time, without conflicting with each other. It guarantees that you will not make the editor editable accidentally (which\n * could lead to errors).\n *\n * Each read-only mode request is identified by a unique id (also called \"lock\"). If multiple plugins requested to turn on the\n * read-only mode, then, the editor will become editable only after all these plugins turn the read-only mode off (using the same ids).\n *\n * Note, that you cannot force the editor to disable the read-only mode if other plugins set it.\n *\n * After the first `enableReadOnlyMode()` call, the {@link #isReadOnly `isReadOnly` property} will be set to `true`:\n *\n * ```ts\n * editor.isReadOnly; // `false`.\n * editor.enableReadOnlyMode( 'my-feature-id' );\n * editor.isReadOnly; // `true`.\n * ```\n *\n * You can turn off the read-only mode (\"clear the lock\") using the {@link #disableReadOnlyMode `disableReadOnlyMode()`} method:\n *\n * ```ts\n * editor.enableReadOnlyMode( 'my-feature-id' );\n * // ...\n * editor.disableReadOnlyMode( 'my-feature-id' );\n * editor.isReadOnly; // `false`.\n * ```\n *\n * All \"locks\" need to be removed to enable editing:\n *\n * ```ts\n * editor.enableReadOnlyMode( 'my-feature-id' );\n * editor.enableReadOnlyMode( 'my-other-feature-id' );\n * // ...\n * editor.disableReadOnlyMode( 'my-feature-id' );\n * editor.isReadOnly; // `true`.\n * editor.disableReadOnlyMode( 'my-other-feature-id' );\n * editor.isReadOnly; // `false`.\n * ```\n *\n * @param lockId A unique ID for setting the editor to the read-only state.\n */\n enableReadOnlyMode(lockId) {\n if (typeof lockId !== 'string' && typeof lockId !== 'symbol') {\n /**\n * The lock ID is missing or it is not a string or symbol.\n *\n * @error editor-read-only-lock-id-invalid\n */\n throw new CKEditorError('editor-read-only-lock-id-invalid', null, { lockId });\n }\n if (this._readOnlyLocks.has(lockId)) {\n return;\n }\n this._readOnlyLocks.add(lockId);\n if (this._readOnlyLocks.size === 1) {\n // Manually fire the `change:isReadOnly` event as only getter is provided.\n this.fire('change:isReadOnly', 'isReadOnly', true, false);\n }\n }\n /**\n * Removes the read-only lock from the editor with given lock ID.\n *\n * When no lock is present on the editor anymore, then the {@link #isReadOnly `isReadOnly` property} will be set to `false`.\n *\n * @param lockId The lock ID for setting the editor to the read-only state.\n */\n disableReadOnlyMode(lockId) {\n if (typeof lockId !== 'string' && typeof lockId !== 'symbol') {\n throw new CKEditorError('editor-read-only-lock-id-invalid', null, { lockId });\n }\n if (!this._readOnlyLocks.has(lockId)) {\n return;\n }\n this._readOnlyLocks.delete(lockId);\n if (this._readOnlyLocks.size === 0) {\n // Manually fire the `change:isReadOnly` event as only getter is provided.\n this.fire('change:isReadOnly', 'isReadOnly', false, true);\n }\n }\n /**\n * Sets the data in the editor.\n *\n * ```ts\n * editor.setData( 'This is editor!
' );\n * ```\n *\n * If your editor implementation uses multiple roots, you should pass an object with keys corresponding\n * to the editor root names and values equal to the data that should be set in each root:\n *\n * ```ts\n * editor.setData( {\n * header: 'Content for header part.
',\n * content: 'Content for main part.
',\n * footer: 'Content for footer part.
'\n * } );\n * ```\n *\n * By default the editor accepts HTML. This can be controlled by injecting a different data processor.\n * See the {@glink features/markdown Markdown output} guide for more details.\n *\n * @param data Input data.\n */\n setData(data) {\n this.data.set(data);\n }\n /**\n * Gets the data from the editor.\n *\n * ```ts\n * editor.getData(); // -> 'This is editor!
'\n * ```\n *\n * If your editor implementation uses multiple roots, you should pass root name as one of the options:\n *\n * ```ts\n * editor.getData( { rootName: 'header' } ); // -> 'Content for header part.
'\n * ```\n *\n * By default, the editor outputs HTML. This can be controlled by injecting a different data processor.\n * See the {@glink features/markdown Markdown output} guide for more details.\n *\n * A warning is logged when you try to retrieve data for a detached root, as most probably this is a mistake. A detached root should\n * be treated like it is removed, and you should not save its data. Note, that the detached root data is always an empty string.\n *\n * @param options Additional configuration for the retrieved data.\n * Editor features may introduce more configuration options that can be set through this parameter.\n * @param options.rootName Root name. Defaults to `'main'`.\n * @param options.trim Whether returned data should be trimmed. This option is set to `'empty'` by default,\n * which means that whenever editor content is considered empty, an empty string is returned. To turn off trimming\n * use `'none'`. In such cases exact content will be returned (for example `'
'` for an empty editor).\n * @returns Output data.\n */\n getData(options) {\n return this.data.get(options);\n }\n /**\n * Loads and initializes plugins specified in the configuration.\n *\n * @returns A promise which resolves once the initialization is completed, providing an array of loaded plugins.\n */\n initPlugins() {\n const config = this.config;\n const plugins = config.get('plugins');\n const removePlugins = config.get('removePlugins') || [];\n const extraPlugins = config.get('extraPlugins') || [];\n const substitutePlugins = config.get('substitutePlugins') || [];\n return this.plugins.init(plugins.concat(extraPlugins), removePlugins, substitutePlugins);\n }\n /**\n * Destroys the editor instance, releasing all resources used by it.\n *\n * **Note** The editor cannot be destroyed during the initialization phase so if it is called\n * while the editor {@link #state is being initialized}, it will wait for the editor initialization before destroying it.\n *\n * @fires destroy\n * @returns A promise that resolves once the editor instance is fully destroyed.\n */\n destroy() {\n let readyPromise = Promise.resolve();\n if (this.state == 'initializing') {\n readyPromise = new Promise(resolve => this.once('ready', resolve));\n }\n return readyPromise\n .then(() => {\n this.fire('destroy');\n this.stopListening();\n this.commands.destroy();\n })\n .then(() => this.plugins.destroy())\n .then(() => {\n this.model.destroy();\n this.data.destroy();\n this.editing.destroy();\n this.keystrokes.destroy();\n })\n // Remove the editor from the context.\n // When the context was created by this editor, the context will be destroyed.\n .then(() => this._context._removeEditor(this));\n }\n /**\n * Executes the specified command with given parameters.\n *\n * Shorthand for:\n *\n * ```ts\n * editor.commands.get( commandName ).execute( ... );\n * ```\n *\n * @param commandName The name of the command to execute.\n * @param commandParams Command parameters.\n * @returns The value returned by the {@link module:core/commandcollection~CommandCollection#execute `commands.execute()`}.\n */\n execute(commandName, ...commandParams) {\n try {\n return this.commands.execute(commandName, ...commandParams);\n }\n catch (err) {\n // @if CK_DEBUG // throw err;\n /* istanbul ignore next -- @preserve */\n CKEditorError.rethrowUnexpectedError(err, this);\n }\n }\n /**\n * Focuses the editor.\n *\n * **Note** To explicitly focus the editing area of the editor, use the\n * {@link module:engine/view/view~View#focus `editor.editing.view.focus()`} method of the editing view.\n *\n * Check out the {@glink framework/deep-dive/ui/focus-tracking#focus-in-the-editor-ui Focus in the editor UI} section\n * of the {@glink framework/deep-dive/ui/focus-tracking Deep dive into focus tracking} guide to learn more.\n */\n focus() {\n this.editing.view.focus();\n }\n /* istanbul ignore next -- @preserve */\n /**\n * Creates and initializes a new editor instance.\n *\n * This is an abstract method. Every editor type needs to implement its own initialization logic.\n *\n * See the `create()` methods of the existing editor types to learn how to use them:\n *\n * * {@link module:editor-classic/classiceditor~ClassicEditor.create `ClassicEditor.create()`}\n * * {@link module:editor-balloon/ballooneditor~BalloonEditor.create `BalloonEditor.create()`}\n * * {@link module:editor-decoupled/decouplededitor~DecoupledEditor.create `DecoupledEditor.create()`}\n * * {@link module:editor-inline/inlineeditor~InlineEditor.create `InlineEditor.create()`}\n */\n static create(...args) {\n throw new Error('This is an abstract method.');\n }\n}\n/**\n * This error is thrown when trying to pass a `