File: //home/arjun/projects/buyercall/node_modules/ckeditor5-custom-build/build/ckeditor.js
(function(d){ const l = d['en'] = d['en'] || {}; l.dictionary=Object.assign( l.dictionary||{}, {"%0 of %1":"%0 of %1","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","Austral sign":"Austral sign","back with leftwards arrow above":"back with leftwards arrow above",Background:"Background",Big:"Big","Bitcoin sign":"Bitcoin sign",Black:"Black","Block quote":"Block quote",Blue:"Blue","Blue marker":"Blue marker",Bold:"Bold",Border:"Border","Break text":"Break text","Bulleted List":"Bulleted List",Cancel:"Cancel","Cannot upload file:":"Cannot upload file:","Cedi sign":"Cedi sign","Cell properties":"Cell properties","Cent sign":"Cent sign","Center table":"Center table","Centered image":"Centered image","Change image text alternative":"Change image text alternative","Character categories":"Character categories","Choose heading":"Choose heading",Code:"Code","Colon sign":"Colon sign",Color:"Color","Color picker":"Color picker",Column:"Column","Cruzeiro sign":"Cruzeiro sign","Currency sign":"Currency sign",Dashed:"Dashed","Decrease indent":"Decrease indent",Default:"Default","Delete column":"Delete column","Delete row":"Delete row","Dim grey":"Dim grey",Dimensions:"Dimensions","Document colors":"Document colors","Dollar sign":"Dollar sign","Dong sign":"Dong sign",Dotted:"Dotted",Double:"Double",Downloadable:"Downloadable","downwards arrow to bar":"downwards arrow to bar","downwards dashed arrow":"downwards dashed arrow","downwards double arrow":"downwards double arrow","Drachma sign":"Drachma sign","Dropdown toolbar":"Dropdown toolbar","Edit block":"Edit block","Edit link":"Edit link","Edit source":"Edit source","Editor toolbar":"Editor toolbar","Empty snippet content":"Empty snippet content","end with leftwards arrow above":"end with leftwards arrow above","Enter image caption":"Enter image caption","Enter table caption":"Enter table caption","Euro sign":"Euro sign","Euro-currency sign":"Euro-currency sign",Find:"Find","Find and replace":"Find and replace","Find in text…":"Find in text…","Font Background Color":"Font Background Color","Font Color":"Font Color","Font Family":"Font Family","Font Size":"Font Size","French franc sign":"French franc sign","Full size image":"Full size image","German penny sign":"German penny sign",Green:"Green","Green marker":"Green marker","Green pen":"Green pen",Grey:"Grey",Groove:"Groove","Guarani sign":"Guarani sign","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",Highlight:"Highlight","Horizontal line":"Horizontal line","Horizontal text alignment toolbar":"Horizontal text alignment toolbar","Hryvnia sign":"Hryvnia sign","HTML object":"HTML object","HTML snippet":"HTML snippet",Huge:"Huge","Image resize list":"Image resize list","Image toolbar":"Image toolbar","image widget":"image widget","In line":"In line","Increase indent":"Increase indent","Indian rupee sign":"Indian rupee sign",Insert:"Insert","Insert code block":"Insert code block","Insert column left":"Insert column left","Insert column right":"Insert column right","Insert HTML":"Insert HTML","Insert image":"Insert image","Insert image via URL":"Insert image via URL","Insert media":"Insert media","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",Italic:"Italic",Justify:"Justify","Justify cell text":"Justify cell text","Kip sign":"Kip sign","Left aligned image":"Left aligned image","leftwards arrow to bar":"leftwards arrow to bar","leftwards dashed arrow":"leftwards dashed arrow","leftwards double arrow":"leftwards double arrow","Light blue":"Light blue","Light green":"Light green","Light grey":"Light grey",Link:"Link","Link image":"Link image","Link URL":"Link URL","Lira sign":"Lira sign","Livre tournois sign":"Livre tournois sign","Manat sign":"Manat sign","Match case":"Match case","Media URL":"Media URL","media widget":"media widget","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","Mill sign":"Mill sign","Naira sign":"Naira sign","New sheqel sign":"New sheqel sign",Next:"Next","Next result":"Next result","No preview available":"No preview available",None:"None","Nordic mark sign":"Nordic mark sign","Numbered List":"Numbered List","on with exclamation mark with left right arrow above":"on with exclamation mark with left right arrow above","Open in a new tab":"Open in a new tab","Open link in new tab":"Open link in new tab",Orange:"Orange",Original:"Original",Outset:"Outset",Padding:"Padding",Paragraph:"Paragraph","Paste raw HTML here...":"Paste raw HTML here...","Paste the media URL in the input.":"Paste the media URL in the input.","Peseta sign":"Peseta sign","Peso sign":"Peso sign","Pink marker":"Pink marker","Plain text":"Plain text","Pound sign":"Pound sign",Previous:"Previous","Previous result":"Previous result",Purple:"Purple",Red:"Red","Red pen":"Red pen",Redo:"Redo","Remove color":"Remove color","Remove highlight":"Remove highlight",Replace:"Replace","Replace all":"Replace all","Replace with…":"Replace with…","Resize image":"Resize image","Resize image to %0":"Resize image to %0","Resize image to the original size":"Resize image to the original size","Restore default":"Restore default","Rich Text Editor":"Rich Text Editor","Rich Text Editor, %0":"Rich Text Editor, %0",Ridge:"Ridge","Right aligned image":"Right aligned image","rightwards arrow to bar":"rightwards arrow to bar","rightwards dashed arrow":"rightwards dashed arrow","rightwards double arrow":"rightwards double arrow",Row:"Row","Ruble sign":"Ruble sign","Rupee sign":"Rupee sign",Save:"Save","Save changes":"Save changes","Saving changes":"Saving changes","Select all":"Select all","Select column":"Select column","Select row":"Select row","Show more items":"Show more items","Show options":"Show options","Side image":"Side image",Small:"Small",Solid:"Solid","soon with rightwards arrow above":"soon with rightwards arrow above","Special characters":"Special characters","Spesmilo sign":"Spesmilo sign","Split cell horizontally":"Split cell horizontally","Split cell vertically":"Split cell vertically",Style:"Style","Table alignment toolbar":"Table alignment toolbar","Table cell text alignment":"Table cell text alignment","Table properties":"Table properties","Table toolbar":"Table toolbar","Tenge sign":"Tenge sign","Text alignment":"Text alignment","Text alignment toolbar":"Text alignment toolbar","Text alternative":"Text alternative","Text highlight toolbar":"Text highlight toolbar","Text to find must not be empty.":"Text to find must not be empty.","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 URL must not be empty.":"The URL must not be empty.","The value is invalid. Try \"10px\" or \"2em\" or simply \"2\".":"The value is invalid. Try \"10px\" or \"2em\" or simply \"2\".","This link has no URL":"This link has no URL","This media URL is not supported.":"This media URL is not supported.",Tiny:"Tiny","Tip: Find some text first in order to replace it.":"Tip: Find some text first in order to replace it.","Tip: Paste the URL into the content to embed faster.":"Tip: Paste the URL into the content to embed faster.","Toggle caption off":"Toggle caption off","Toggle caption on":"Toggle caption on","top with upwards arrow above":"top with upwards arrow above","Tugrik sign":"Tugrik sign","Turkish lira sign":"Turkish lira sign",Turquoise:"Turquoise",Undo:"Undo",Unlink:"Unlink","up down arrow with base":"up down arrow with base",Update:"Update","Update image URL":"Update image URL","Upload failed":"Upload failed","Upload in progress":"Upload in progress","upwards arrow to bar":"upwards arrow to bar","upwards dashed arrow":"upwards dashed arrow","upwards double arrow":"upwards double arrow","Vertical text alignment toolbar":"Vertical text alignment toolbar",White:"White","Whole words only":"Whole words only","Widget toolbar":"Widget toolbar",Width:"Width","Won sign":"Won sign","Wrap text":"Wrap text",Yellow:"Yellow","Yellow marker":"Yellow marker","Yen sign":"Yen sign"} );})(window.CKEDITOR_TRANSLATIONS||(window.CKEDITOR_TRANSLATIONS={}));
/*!
* @license Copyright (c) 2003-2022, CKSource - Frederico Knabben. 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
/******/ "use strict";
/******/ var __webpack_modules__ = ({
/***/ "./node_modules/@ckeditor/ckeditor5-adapter-ckfinder/src/uploadadapter.js":
/*!********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-adapter-ckfinder/src/uploadadapter.js ***!
\********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ CKFinderUploadAdapter)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_upload__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/upload */ "./node_modules/ckeditor5/src/upload.js");
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./utils */ "./node_modules/@ckeditor/ckeditor5-adapter-ckfinder/src/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/* globals XMLHttpRequest, FormData */
/**
* @module adapter-ckfinder/uploadadapter
*/
/**
* A plugin that enables file uploads in CKEditor 5 using the CKFinder server–side connector.
*
* See the {@glink features/images/image-upload/ckfinder "CKFinder file manager integration" guide} to learn how to configure
* and use this feature as well as find out more about the full integration with the file manager
* provided by the {@link module:ckfinder/ckfinder~CKFinder} plugin.
*
* Check out the {@glink features/images/image-upload/image-upload comprehensive "Image upload overview"} to learn about
* other ways to upload images into CKEditor 5.
*
* @extends module:core/plugin~Plugin
*/
class CKFinderUploadAdapter extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [ ckeditor5_src_upload__WEBPACK_IMPORTED_MODULE_1__.FileRepository ];
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'CKFinderUploadAdapter';
}
/**
* @inheritDoc
*/
init() {
const url = this.editor.config.get( 'ckfinder.uploadUrl' );
if ( !url ) {
return;
}
// Register CKFinderAdapter
this.editor.plugins.get( ckeditor5_src_upload__WEBPACK_IMPORTED_MODULE_1__.FileRepository ).createUploadAdapter = loader => new UploadAdapter( loader, url, this.editor.t );
}
}
/**
* Upload adapter for CKFinder.
*
* @private
* @implements module:upload/filerepository~UploadAdapter
*/
class UploadAdapter {
/**
* Creates a new adapter instance.
*
* @param {module:upload/filerepository~FileLoader} loader
* @param {String} url
* @param {module:utils/locale~Locale#t} t
*/
constructor( loader, url, t ) {
/**
* FileLoader instance to use during the upload.
*
* @member {module:upload/filerepository~FileLoader} #loader
*/
this.loader = loader;
/**
* Upload URL.
*
* @member {String} #url
*/
this.url = url;
/**
* Locale translation method.
*
* @member {module:utils/locale~Locale#t} #t
*/
this.t = t;
}
/**
* Starts the upload process.
*
* @see module:upload/filerepository~UploadAdapter#upload
* @returns {Promise.<Object>}
*/
upload() {
return this.loader.file.then( file => {
return new Promise( ( resolve, reject ) => {
this._initRequest();
this._initListeners( resolve, reject, file );
this._sendRequest( file );
} );
} );
}
/**
* Aborts the upload process.
*
* @see module:upload/filerepository~UploadAdapter#abort
*/
abort() {
if ( this.xhr ) {
this.xhr.abort();
}
}
/**
* Initializes the XMLHttpRequest object.
*
* @private
*/
_initRequest() {
const xhr = this.xhr = new XMLHttpRequest();
xhr.open( 'POST', this.url, true );
xhr.responseType = 'json';
}
/**
* Initializes XMLHttpRequest listeners.
*
* @private
* @param {Function} resolve Callback function to be called when the request is successful.
* @param {Function} reject Callback function to be called when the request cannot be completed.
* @param {File} file File instance to be uploaded.
*/
_initListeners( resolve, reject, file ) {
const xhr = this.xhr;
const loader = this.loader;
const t = this.t;
const genericError = t( 'Cannot upload file:' ) + ` ${ file.name }.`;
xhr.addEventListener( 'error', () => reject( genericError ) );
xhr.addEventListener( 'abort', () => reject() );
xhr.addEventListener( 'load', () => {
const response = xhr.response;
if ( !response || !response.uploaded ) {
return reject( response && response.error && response.error.message ? response.error.message : genericError );
}
resolve( {
default: response.url
} );
} );
// Upload progress when it's supported.
/* istanbul ignore else */
if ( xhr.upload ) {
xhr.upload.addEventListener( 'progress', evt => {
if ( evt.lengthComputable ) {
loader.uploadTotal = evt.total;
loader.uploaded = evt.loaded;
}
} );
}
}
/**
* Prepares the data and sends the request.
*
* @private
* @param {File} file File instance to be uploaded.
*/
_sendRequest( file ) {
// Prepare form data.
const data = new FormData();
data.append( 'upload', file );
data.append( 'ckCsrfToken', (0,_utils__WEBPACK_IMPORTED_MODULE_2__.getCsrfToken)() );
// Send request.
this.xhr.send( data );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-adapter-ckfinder/src/utils.js":
/*!************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-adapter-ckfinder/src/utils.js ***!
\************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "getCookie": () => (/* binding */ getCookie),
/* harmony export */ "getCsrfToken": () => (/* binding */ getCsrfToken),
/* harmony export */ "setCookie": () => (/* binding */ setCookie)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/* globals window, document */
/**
* @module adapter-ckfinder/utils
*/
const TOKEN_COOKIE_NAME = 'ckCsrfToken';
const TOKEN_LENGTH = 40;
const tokenCharset = 'abcdefghijklmnopqrstuvwxyz0123456789';
/**
* Returns the CSRF token value. The value is a hash stored in `document.cookie`
* under the `ckCsrfToken` key. The CSRF token can be used to secure the communication
* between the web browser and the CKFinder server.
*
* @returns {String}
*/
function getCsrfToken() {
let token = getCookie( TOKEN_COOKIE_NAME );
if ( !token || token.length != TOKEN_LENGTH ) {
token = generateToken( TOKEN_LENGTH );
setCookie( TOKEN_COOKIE_NAME, token );
}
return token;
}
/**
* Returns the value of the cookie with a given name or `null` if the cookie is not found.
*
* @param {String} name
* @returns {String|null}
*/
function getCookie( name ) {
name = name.toLowerCase();
const parts = document.cookie.split( ';' );
for ( const part of parts ) {
const pair = part.split( '=' );
const key = decodeURIComponent( pair[ 0 ].trim().toLowerCase() );
if ( key === name ) {
return decodeURIComponent( pair[ 1 ] );
}
}
return null;
}
/**
* Sets the value of the cookie with a given name.
*
* @param {String} name
* @param {String} value
*/
function setCookie( name, value ) {
document.cookie = encodeURIComponent( name ) + '=' + encodeURIComponent( value ) + ';path=/';
}
// Generates the CSRF token with the given length.
//
// @private
// @param {Number} length
// @returns {string}
function generateToken( length ) {
let result = '';
const randValues = new Uint8Array( length );
window.crypto.getRandomValues( randValues );
for ( let j = 0; j < randValues.length; j++ ) {
const character = tokenCharset.charAt( randValues[ j ] % tokenCharset.length );
result += Math.random() > 0.5 ? character.toUpperCase() : character;
}
return result;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-alignment/src/alignment.js":
/*!*********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-alignment/src/alignment.js ***!
\*********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ Alignment)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var _alignmentediting__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./alignmentediting */ "./node_modules/@ckeditor/ckeditor5-alignment/src/alignmentediting.js");
/* harmony import */ var _alignmentui__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./alignmentui */ "./node_modules/@ckeditor/ckeditor5-alignment/src/alignmentui.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module alignment/alignment
*/
/**
* The text alignment plugin.
*
* For a detailed overview, check the {@glink features/text-alignment Text alignment feature documentation}
* and the {@glink api/alignment package page}.
*
* This is a "glue" plugin which loads the {@link module:alignment/alignmentediting~AlignmentEditing} and
* {@link module:alignment/alignmentui~AlignmentUI} plugins.
*
* @extends module:core/plugin~Plugin
*/
class Alignment extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [ _alignmentediting__WEBPACK_IMPORTED_MODULE_1__["default"], _alignmentui__WEBPACK_IMPORTED_MODULE_2__["default"] ];
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'Alignment';
}
}
/**
* The configuration of the {@link module:alignment/alignment~Alignment alignment feature}.
*
* Read more in {@link module:alignment/alignment~AlignmentConfig}.
*
* @member {module:alignment/alignment~AlignmentConfig} module:core/editor/editorconfig~EditorConfig#alignment
*/
/**
* The configuration of the {@link module:alignment/alignment~Alignment alignment feature}.
*
* ClassicEditor
* .create( editorElement, {
* alignment: {
* options: [ 'left', 'right' ]
* }
* } )
* .then( ... )
* .catch( ... );
*
* See {@link module:core/editor/editorconfig~EditorConfig all editor configuration options}.
*
* @interface AlignmentConfig
*/
/**
* Available alignment options.
*
* The available options are: `'left'`, `'right'`, `'center'` and `'justify'`. Other values are ignored.
*
* **Note:** It is recommended to always use `'left'` or `'right'` as these are default values which the user should
* normally be able to choose depending on the
* {@glink features/ui-language#setting-the-language-of-the-content language of the editor content}.
*
* ClassicEditor
* .create( editorElement, {
* alignment: {
* options: [ 'left', 'right' ]
* }
* } )
* .then( ... )
* .catch( ... );
*
* By default the alignment is set inline using the `text-align` CSS property. To further customize the alignment,
* you can provide names of classes for each alignment option using the `className` property.
*
* **Note:** Once you define the `className` property for one option, you need to specify it for all other options.
*
* ClassicEditor
* .create( editorElement, {
* alignment: {
* options: [
* { name: 'left', className: 'my-align-left' },
* { name: 'right', className: 'my-align-right' }
* ]
* }
* } )
* .then( ... )
* .catch( ... );
*
* See the demo of {@glink features/text-alignment#configuring-alignment-options custom alignment options}.
*
* @member {Array.<String|module:alignment/alignmentediting~AlignmentFormat>} module:alignment/alignment~AlignmentConfig#options
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-alignment/src/alignmentcommand.js":
/*!****************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-alignment/src/alignmentcommand.js ***!
\****************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ AlignmentCommand)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./utils */ "./node_modules/@ckeditor/ckeditor5-alignment/src/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module alignment/alignmentcommand
*/
const ALIGNMENT = 'alignment';
/**
* The alignment command plugin.
*
* @extends module:core/command~Command
*/
class AlignmentCommand extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Command {
/**
* @inheritDoc
*/
refresh() {
const editor = this.editor;
const locale = editor.locale;
const firstBlock = (0,ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__.first)( this.editor.model.document.selection.getSelectedBlocks() );
// As first check whether to enable or disable the command as the value will always be false if the command cannot be enabled.
this.isEnabled = !!firstBlock && this._canBeAligned( firstBlock );
/**
* A value of the current block's alignment.
*
* @observable
* @readonly
* @member {String} #value
*/
if ( this.isEnabled && firstBlock.hasAttribute( 'alignment' ) ) {
this.value = firstBlock.getAttribute( 'alignment' );
} else {
this.value = locale.contentLanguageDirection === 'rtl' ? 'right' : 'left';
}
}
/**
* Executes the command. Applies the alignment `value` to the selected blocks.
* If no `value` is passed, the `value` is the default one or it is equal to the currently selected block's alignment attribute,
* the command will remove the attribute from the selected blocks.
*
* @param {Object} [options] Options for the executed command.
* @param {String} [options.value] The value to apply.
* @fires execute
*/
execute( options = {} ) {
const editor = this.editor;
const locale = editor.locale;
const model = editor.model;
const doc = model.document;
const value = options.value;
model.change( writer => {
// Get only those blocks from selected that can have alignment set
const blocks = Array.from( doc.selection.getSelectedBlocks() ).filter( block => this._canBeAligned( block ) );
const currentAlignment = blocks[ 0 ].getAttribute( 'alignment' );
// Remove alignment attribute if current alignment is:
// - default (should not be stored in model as it will bloat model data)
// - equal to currently set
// - or no value is passed - denotes default alignment.
const removeAlignment = (0,_utils__WEBPACK_IMPORTED_MODULE_2__.isDefault)( value, locale ) || currentAlignment === value || !value;
if ( removeAlignment ) {
removeAlignmentFromSelection( blocks, writer );
} else {
setAlignmentOnSelection( blocks, writer, value );
}
} );
}
/**
* Checks whether a block can have alignment set.
*
* @private
* @param {module:engine/model/element~Element} block The block to be checked.
* @returns {Boolean}
*/
_canBeAligned( block ) {
return this.editor.model.schema.checkAttribute( block, ALIGNMENT );
}
}
// Removes the alignment attribute from blocks.
// @private
function removeAlignmentFromSelection( blocks, writer ) {
for ( const block of blocks ) {
writer.removeAttribute( ALIGNMENT, block );
}
}
// Sets the alignment attribute on blocks.
// @private
function setAlignmentOnSelection( blocks, writer, alignment ) {
for ( const block of blocks ) {
writer.setAttribute( ALIGNMENT, alignment, block );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-alignment/src/alignmentediting.js":
/*!****************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-alignment/src/alignmentediting.js ***!
\****************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ AlignmentEditing)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var _alignmentcommand__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./alignmentcommand */ "./node_modules/@ckeditor/ckeditor5-alignment/src/alignmentcommand.js");
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./utils */ "./node_modules/@ckeditor/ckeditor5-alignment/src/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module alignment/alignmentediting
*/
/**
* The alignment editing feature. It introduces the {@link module:alignment/alignmentcommand~AlignmentCommand command} and adds
* the `alignment` attribute for block elements in the {@link module:engine/model/model~Model model}.
* @extends module:core/plugin~Plugin
*/
class AlignmentEditing extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'AlignmentEditing';
}
/**
* @inheritDoc
*/
constructor( editor ) {
super( editor );
editor.config.define( 'alignment', {
options: [ ..._utils__WEBPACK_IMPORTED_MODULE_2__.supportedOptions.map( option => ( { name: option } ) ) ]
} );
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const locale = editor.locale;
const schema = editor.model.schema;
const options = (0,_utils__WEBPACK_IMPORTED_MODULE_2__.normalizeAlignmentOptions)( editor.config.get( 'alignment.options' ) );
// Filter out unsupported options and those that are redundant, e.g. `left` in LTR / `right` in RTL mode.
const optionsToConvert = options.filter(
option => (0,_utils__WEBPACK_IMPORTED_MODULE_2__.isSupported)( option.name ) && !(0,_utils__WEBPACK_IMPORTED_MODULE_2__.isDefault)( option.name, locale )
);
// Once there is at least one `className` defined, we switch to alignment with classes.
const shouldUseClasses = optionsToConvert.some( option => !!option.className );
// Allow alignment attribute on all blocks.
schema.extend( '$block', { allowAttributes: 'alignment' } );
editor.model.schema.setAttributeProperties( 'alignment', { isFormatting: true } );
if ( shouldUseClasses ) {
editor.conversion.attributeToAttribute( buildClassDefinition( optionsToConvert ) );
} else {
// Downcast inline styles.
editor.conversion.for( 'downcast' ).attributeToAttribute( buildDowncastInlineDefinition( optionsToConvert ) );
}
const upcastInlineDefinitions = buildUpcastInlineDefinitions( optionsToConvert );
// Always upcast from inline styles.
for ( const definition of upcastInlineDefinitions ) {
editor.conversion.for( 'upcast' ).attributeToAttribute( definition );
}
const upcastCompatibilityDefinitions = buildUpcastCompatibilityDefinitions( optionsToConvert );
// Always upcast from deprecated `align` attribute.
for ( const definition of upcastCompatibilityDefinitions ) {
editor.conversion.for( 'upcast' ).attributeToAttribute( definition );
}
editor.commands.add( 'alignment', new _alignmentcommand__WEBPACK_IMPORTED_MODULE_1__["default"]( editor ) );
}
}
// Prepare downcast conversion definition for inline alignment styling.
// @private
function buildDowncastInlineDefinition( options ) {
const definition = {
model: {
key: 'alignment',
values: options.map( option => option.name )
},
view: {}
};
for ( const { name } of options ) {
definition.view[ name ] = {
key: 'style',
value: {
'text-align': name
}
};
}
return definition;
}
// Prepare upcast definitions for inline alignment styles.
// @private
function buildUpcastInlineDefinitions( options ) {
const definitions = [];
for ( const { name } of options ) {
definitions.push( {
view: {
key: 'style',
value: {
'text-align': name
}
},
model: {
key: 'alignment',
value: name
}
} );
}
return definitions;
}
// Prepare upcast definitions for deprecated `align` attribute.
// @private
function buildUpcastCompatibilityDefinitions( options ) {
const definitions = [];
for ( const { name } of options ) {
definitions.push( {
view: {
key: 'align',
value: name
},
model: {
key: 'alignment',
value: name
}
} );
}
return definitions;
}
// Prepare conversion definitions for upcast and downcast alignment with classes.
// @private
function buildClassDefinition( options ) {
const definition = {
model: {
key: 'alignment',
values: options.map( option => option.name )
},
view: {}
};
for ( const option of options ) {
definition.view[ option.name ] = {
key: 'class',
value: option.className
};
}
return definition;
}
/**
* The alignment configuration format descriptor.
*
* const alignmentFormat = {
* name: 'right',
* className: 'my-align-right-class'
* }
*
* @typedef {Object} module:alignment/alignmentediting~AlignmentFormat
*
* @property {'left'|'right'|'center'|'justify'} name One of the alignment names options.
*
* @property {String} className The CSS class used to represent the style in the view.
* Used to override default, inline styling for alignment.
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-alignment/src/alignmentui.js":
/*!***********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-alignment/src/alignmentui.js ***!
\***********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ AlignmentUI)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/ui */ "./node_modules/ckeditor5/src/ui.js");
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./utils */ "./node_modules/@ckeditor/ckeditor5-alignment/src/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module alignment/alignmentui
*/
const iconsMap = new Map( [
[ 'left', ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.icons.alignLeft ],
[ 'right', ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.icons.alignRight ],
[ 'center', ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.icons.alignCenter ],
[ 'justify', ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.icons.alignJustify ]
] );
/**
* The default alignment UI plugin.
*
* It introduces the `'alignment:left'`, `'alignment:right'`, `'alignment:center'` and `'alignment:justify'` buttons
* and the `'alignment'` dropdown.
*
* @extends module:core/plugin~Plugin
*/
class AlignmentUI extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* Returns the localized option titles provided by the plugin.
*
* The following localized titles corresponding with
* {@link module:alignment/alignment~AlignmentConfig#options} are available:
*
* * `'left'`,
* * `'right'`,
* * `'center'`,
* * `'justify'`.
*
* @readonly
* @type {Object.<String,String>}
*/
get localizedOptionTitles() {
const t = this.editor.t;
return {
'left': t( 'Align left' ),
'right': t( 'Align right' ),
'center': t( 'Align center' ),
'justify': t( 'Justify' )
};
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'AlignmentUI';
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const componentFactory = editor.ui.componentFactory;
const t = editor.t;
const options = (0,_utils__WEBPACK_IMPORTED_MODULE_2__.normalizeAlignmentOptions)( editor.config.get( 'alignment.options' ) );
options
.map( option => option.name )
.filter( _utils__WEBPACK_IMPORTED_MODULE_2__.isSupported )
.forEach( option => this._addButton( option ) );
componentFactory.add( 'alignment', locale => {
const dropdownView = (0,ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.createDropdown)( locale );
// Add existing alignment buttons to dropdown's toolbar.
const buttons = options.map( option => componentFactory.create( `alignment:${ option.name }` ) );
(0,ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.addToolbarToDropdown)( dropdownView, buttons );
// Configure dropdown properties an behavior.
dropdownView.buttonView.set( {
label: t( 'Text alignment' ),
tooltip: true
} );
dropdownView.toolbarView.isVertical = true;
dropdownView.toolbarView.ariaLabel = t( 'Text alignment toolbar' );
dropdownView.extendTemplate( {
attributes: {
class: 'ck-alignment-dropdown'
}
} );
// The default icon depends on the direction of the content.
const defaultIcon = locale.contentLanguageDirection === 'rtl' ? iconsMap.get( 'right' ) : iconsMap.get( 'left' );
// Change icon to reflect current selection's alignment.
dropdownView.buttonView.bind( 'icon' ).toMany( buttons, 'isOn', ( ...areActive ) => {
// Get the index of an active button.
const index = areActive.findIndex( value => value );
// If none of the commands is active, display either defaultIcon or the first button's icon.
if ( index < 0 ) {
return defaultIcon;
}
// Return active button's icon.
return buttons[ index ].icon;
} );
// Enable button if any of the buttons is enabled.
dropdownView.bind( 'isEnabled' ).toMany( buttons, 'isEnabled', ( ...areEnabled ) => areEnabled.some( isEnabled => isEnabled ) );
return dropdownView;
} );
}
/**
* Helper method for initializing the button and linking it with an appropriate command.
*
* @private
* @param {String} option The name of the alignment option for which the button is added.
*/
_addButton( option ) {
const editor = this.editor;
editor.ui.componentFactory.add( `alignment:${ option }`, locale => {
const command = editor.commands.get( 'alignment' );
const buttonView = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.ButtonView( locale );
buttonView.set( {
label: this.localizedOptionTitles[ option ],
icon: iconsMap.get( option ),
tooltip: true,
isToggleable: true
} );
// Bind button model to command.
buttonView.bind( 'isEnabled' ).to( command );
buttonView.bind( 'isOn' ).to( command, 'value', value => value === option );
// Execute command.
this.listenTo( buttonView, 'execute', () => {
editor.execute( 'alignment', { value: option } );
editor.editing.view.focus();
} );
return buttonView;
} );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-alignment/src/utils.js":
/*!*****************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-alignment/src/utils.js ***!
\*****************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "isDefault": () => (/* binding */ isDefault),
/* harmony export */ "isSupported": () => (/* binding */ isSupported),
/* harmony export */ "normalizeAlignmentOptions": () => (/* binding */ normalizeAlignmentOptions),
/* harmony export */ "supportedOptions": () => (/* binding */ supportedOptions)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module alignment/utils
*/
/**
* The list of supported alignment options:
*
* * `'left'`,
* * `'right'`,
* * `'center'`,
* * `'justify'`
*/
const supportedOptions = [ 'left', 'right', 'center', 'justify' ];
/**
* Checks whether the passed option is supported by {@link module:alignment/alignmentediting~AlignmentEditing}.
*
* @param {String} option The option value to check.
* @returns {Boolean}
*/
function isSupported( option ) {
return supportedOptions.includes( option );
}
/**
* Checks whether alignment is the default one considering the direction
* of the editor content.
*
* @param {String} alignment The name of the alignment to check.
* @param {module:utils/locale~Locale} locale The {@link module:core/editor/editor~Editor#locale} instance.
* @returns {Boolean}
*/
function isDefault( alignment, locale ) {
// Right now only LTR is supported so the 'left' value is always the default one.
if ( locale.contentLanguageDirection == 'rtl' ) {
return alignment === 'right';
} else {
return alignment === 'left';
}
}
/**
* Brings the configuration to the common form, an array of objects.
*
* @param {Array.<String|module:alignment/alignmentediting~AlignmentFormat>} configuredOptions Alignment plugin configuration.
* @returns {Array.<module:alignment/alignmentediting~AlignmentFormat>} Normalized object holding the configuration.
*/
function normalizeAlignmentOptions( configuredOptions ) {
const normalizedOptions = configuredOptions
.map( option => {
let result;
if ( typeof option == 'string' ) {
result = { name: option };
} else {
result = option;
}
return result;
} )
// Remove all unknown options.
.filter( option => {
const isNameValid = !!supportedOptions.includes( option.name );
if ( !isNameValid ) {
/**
* The `name` in one of the `alignment.options` is not recognized.
* The available options are: `'left'`, `'right'`, `'center'` and `'justify'`.
*
* @error alignment-config-name-not-recognized
* @param {Object} option Options with unknown value of the `name` property.
*/
(0,ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_0__.logWarning)( 'alignment-config-name-not-recognized', { option } );
}
return isNameValid;
} );
const classNameCount = normalizedOptions.filter( option => !!option.className ).length;
// We either use classes for all styling options or for none.
if ( classNameCount && classNameCount < normalizedOptions.length ) {
/**
* The `className` property has to be defined for all options once at least one option declares `className`.
*
* @error alignment-config-classnames-are-missing
* @param {Array.<String|module:alignment/alignmentediting~AlignmentFormat>} configuredOptions Contents of `alignment.options`.
*/
throw new ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_0__.CKEditorError( 'alignment-config-classnames-are-missing', { configuredOptions } );
}
// Validate resulting config.
normalizedOptions.forEach( ( option, index, allOptions ) => {
const succeedingOptions = allOptions.slice( index + 1 );
const nameAlreadyExists = succeedingOptions.some( item => item.name == option.name );
if ( nameAlreadyExists ) {
/**
* The same `name` in one of the `alignment.options` was already declared.
* Each `name` representing one alignment option can be set exactly once.
*
* @error alignment-config-name-already-defined
* @param {Object} option First option that declares given `name`.
* @param {Array.<String|module:alignment/alignmentediting~AlignmentFormat>} configuredOptions Contents of `alignment.options`.
*/
throw new ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_0__.CKEditorError( 'alignment-config-name-already-defined', { option, configuredOptions } );
}
// The `className` property is present. Check for duplicates then.
if ( option.className ) {
const classNameAlreadyExists = succeedingOptions.some( item => item.className == option.className );
if ( classNameAlreadyExists ) {
/**
* The same `className` in one of the `alignment.options` was already declared.
*
* @error alignment-config-classname-already-defined
* @param {Object} option First option that declares given `className`.
* @param {Array.<String|module:alignment/alignmentediting~AlignmentFormat>} configuredOptions
* Contents of `alignment.options`.
*/
throw new ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_0__.CKEditorError( 'alignment-config-classname-already-defined', { option, configuredOptions } );
}
}
} );
return normalizedOptions;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-autoformat/src/autoformat.js":
/*!***********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-autoformat/src/autoformat.js ***!
\***********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ Autoformat)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_typing__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/typing */ "./node_modules/ckeditor5/src/typing.js");
/* harmony import */ var _blockautoformatediting__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./blockautoformatediting */ "./node_modules/@ckeditor/ckeditor5-autoformat/src/blockautoformatediting.js");
/* harmony import */ var _inlineautoformatediting__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./inlineautoformatediting */ "./node_modules/@ckeditor/ckeditor5-autoformat/src/inlineautoformatediting.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module autoformat/autoformat
*/
/**
* Enables a set of predefined autoformatting actions.
*
* For a detailed overview, check the {@glink features/autoformat Autoformatting feature documentation}
* and the {@glink api/autoformat package page}.
*
* @extends module:core/plugin~Plugin
*/
class Autoformat extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritdoc
*/
static get requires() {
return [ ckeditor5_src_typing__WEBPACK_IMPORTED_MODULE_1__.Delete ];
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'Autoformat';
}
/**
* @inheritDoc
*/
afterInit() {
this._addListAutoformats();
this._addBasicStylesAutoformats();
this._addHeadingAutoformats();
this._addBlockQuoteAutoformats();
this._addCodeBlockAutoformats();
this._addHorizontalLineAutoformats();
}
/**
* Adds autoformatting related to the {@link module:list/list~List}.
*
* When typed:
* - `* ` or `- ` – A paragraph will be changed to a bulleted list.
* - `1. ` or `1) ` – A paragraph will be changed to a numbered list ("1" can be any digit or a list of digits).
* - `[] ` or `[ ] ` – A paragraph will be changed to a to-do list.
* - `[x] ` or `[ x ] ` – A paragraph will be changed to a checked to-do list.
*
* @private
*/
_addListAutoformats() {
const commands = this.editor.commands;
if ( commands.get( 'bulletedList' ) ) {
(0,_blockautoformatediting__WEBPACK_IMPORTED_MODULE_2__["default"])( this.editor, this, /^[*-]\s$/, 'bulletedList' );
}
if ( commands.get( 'numberedList' ) ) {
(0,_blockautoformatediting__WEBPACK_IMPORTED_MODULE_2__["default"])( this.editor, this, /^1[.|)]\s$/, 'numberedList' );
}
if ( commands.get( 'todoList' ) ) {
(0,_blockautoformatediting__WEBPACK_IMPORTED_MODULE_2__["default"])( this.editor, this, /^\[\s?\]\s$/, 'todoList' );
}
if ( commands.get( 'checkTodoList' ) ) {
(0,_blockautoformatediting__WEBPACK_IMPORTED_MODULE_2__["default"])( this.editor, this, /^\[\s?x\s?\]\s$/, () => {
this.editor.execute( 'todoList' );
this.editor.execute( 'checkTodoList' );
} );
}
}
/**
* Adds autoformatting related to the {@link module:basic-styles/bold~Bold},
* {@link module:basic-styles/italic~Italic}, {@link module:basic-styles/code~Code}
* and {@link module:basic-styles/strikethrough~Strikethrough}
*
* When typed:
* - `**foobar**` – `**` characters are removed and `foobar` is set to bold,
* - `__foobar__` – `__` characters are removed and `foobar` is set to bold,
* - `*foobar*` – `*` characters are removed and `foobar` is set to italic,
* - `_foobar_` – `_` characters are removed and `foobar` is set to italic,
* - ``` `foobar` – ``` ` ``` characters are removed and `foobar` is set to code,
* - `~~foobar~~` – `~~` characters are removed and `foobar` is set to strikethrough.
*
* @private
*/
_addBasicStylesAutoformats() {
const commands = this.editor.commands;
if ( commands.get( 'bold' ) ) {
const boldCallback = getCallbackFunctionForInlineAutoformat( this.editor, 'bold' );
(0,_inlineautoformatediting__WEBPACK_IMPORTED_MODULE_3__["default"])( this.editor, this, /(?:^|\s)(\*\*)([^*]+)(\*\*)$/g, boldCallback );
(0,_inlineautoformatediting__WEBPACK_IMPORTED_MODULE_3__["default"])( this.editor, this, /(?:^|\s)(__)([^_]+)(__)$/g, boldCallback );
}
if ( commands.get( 'italic' ) ) {
const italicCallback = getCallbackFunctionForInlineAutoformat( this.editor, 'italic' );
// The italic autoformatter cannot be triggered by the bold markers, so we need to check the
// text before the pattern (e.g. `(?:^|[^\*])`).
(0,_inlineautoformatediting__WEBPACK_IMPORTED_MODULE_3__["default"])( this.editor, this, /(?:^|\s)(\*)([^*_]+)(\*)$/g, italicCallback );
(0,_inlineautoformatediting__WEBPACK_IMPORTED_MODULE_3__["default"])( this.editor, this, /(?:^|\s)(_)([^_]+)(_)$/g, italicCallback );
}
if ( commands.get( 'code' ) ) {
const codeCallback = getCallbackFunctionForInlineAutoformat( this.editor, 'code' );
(0,_inlineautoformatediting__WEBPACK_IMPORTED_MODULE_3__["default"])( this.editor, this, /(`)([^`]+)(`)$/g, codeCallback );
}
if ( commands.get( 'strikethrough' ) ) {
const strikethroughCallback = getCallbackFunctionForInlineAutoformat( this.editor, 'strikethrough' );
(0,_inlineautoformatediting__WEBPACK_IMPORTED_MODULE_3__["default"])( this.editor, this, /(~~)([^~]+)(~~)$/g, strikethroughCallback );
}
}
/**
* Adds autoformatting related to {@link module:heading/heading~Heading}.
*
* It is using a number at the end of the command name to associate it with the proper trigger:
*
* * `heading` with value `heading1` will be executed when typing `#`,
* * `heading` with value `heading2` will be executed when typing `##`,
* * ... up to `heading6` and `######`.
*
* @private
*/
_addHeadingAutoformats() {
const command = this.editor.commands.get( 'heading' );
if ( command ) {
command.modelElements
.filter( name => name.match( /^heading[1-6]$/ ) )
.forEach( modelName => {
const level = modelName[ 7 ];
const pattern = new RegExp( `^(#{${ level }})\\s$` );
(0,_blockautoformatediting__WEBPACK_IMPORTED_MODULE_2__["default"])( this.editor, this, pattern, () => {
// Should only be active if command is enabled and heading style associated with pattern is inactive.
if ( !command.isEnabled || command.value === modelName ) {
return false;
}
this.editor.execute( 'heading', { value: modelName } );
} );
} );
}
}
/**
* Adds autoformatting related to {@link module:block-quote/blockquote~BlockQuote}.
*
* When typed:
* * `> ` – A paragraph will be changed to a block quote.
*
* @private
*/
_addBlockQuoteAutoformats() {
if ( this.editor.commands.get( 'blockQuote' ) ) {
(0,_blockautoformatediting__WEBPACK_IMPORTED_MODULE_2__["default"])( this.editor, this, /^>\s$/, 'blockQuote' );
}
}
/**
* Adds autoformatting related to {@link module:code-block/codeblock~CodeBlock}.
*
* When typed:
* - `` ``` `` – A paragraph will be changed to a code block.
*
* @private
*/
_addCodeBlockAutoformats() {
const editor = this.editor;
const selection = editor.model.document.selection;
if ( editor.commands.get( 'codeBlock' ) ) {
(0,_blockautoformatediting__WEBPACK_IMPORTED_MODULE_2__["default"])( editor, this, /^```$/, () => {
if ( selection.getFirstPosition().parent.is( 'element', 'listItem' ) ) {
return false;
}
this.editor.execute( 'codeBlock', {
usePreviousLanguageChoice: true
} );
} );
}
}
/**
* Adds autoformatting related to {@link module:horizontal-line/horizontalline~HorizontalLine}.
*
* When typed:
* - `` --- `` – Will be replaced with a horizontal line.
*
* @private
*/
_addHorizontalLineAutoformats() {
if ( this.editor.commands.get( 'horizontalLine' ) ) {
(0,_blockautoformatediting__WEBPACK_IMPORTED_MODULE_2__["default"])( this.editor, this, /^---$/, 'horizontalLine' );
}
}
}
// Helper function for getting `inlineAutoformatEditing` callbacks that checks if command is enabled.
//
// @param {module:core/editor/editor~Editor} editor
// @param {String} attributeKey
// @returns {Function}
function getCallbackFunctionForInlineAutoformat( editor, attributeKey ) {
return ( writer, rangesToFormat ) => {
const command = editor.commands.get( attributeKey );
if ( !command.isEnabled ) {
return false;
}
const validRanges = editor.model.schema.getValidRanges( rangesToFormat, attributeKey );
for ( const range of validRanges ) {
writer.setAttribute( attributeKey, true, range );
}
// After applying attribute to the text, remove given attribute from the selection.
// This way user is able to type a text without attribute used by auto formatter.
writer.removeSelectionAttribute( attributeKey );
};
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-autoformat/src/blockautoformatediting.js":
/*!***********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-autoformat/src/blockautoformatediting.js ***!
\***********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ blockAutoformatEditing)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/engine */ "./node_modules/ckeditor5/src/engine.js");
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* The block autoformatting engine. It allows to format various block patterns. For example,
* it can be configured to turn a paragraph starting with `*` and followed by a space into a list item.
*
* The autoformatting operation is integrated with the undo manager,
* so the autoformatting step can be undone if the user's intention was not to format the text.
*
* See the {@link module:autoformat/blockautoformatediting~blockAutoformatEditing `blockAutoformatEditing`} documentation
* to learn how to create custom block autoformatters. You can also use
* the {@link module:autoformat/autoformat~Autoformat} feature which enables a set of default autoformatters
* (lists, headings, bold and italic).
*
* @module autoformat/blockautoformatediting
*/
/**
* Creates a listener triggered on {@link module:engine/model/document~Document#event:change:data `change:data`} event in the document.
* Calls the callback when inserted text matches the regular expression or the command name
* if provided instead of the callback.
*
* Examples of usage:
*
* To convert a paragraph to heading 1 when `- ` is typed, using just the command name:
*
* blockAutoformatEditing( editor, plugin, /^\- $/, 'heading1' );
*
* To convert a paragraph to heading 1 when `- ` is typed, using just the callback:
*
* blockAutoformatEditing( editor, plugin, /^\- $/, ( context ) => {
* const { match } = context;
* const headingLevel = match[ 1 ].length;
*
* editor.execute( 'heading', {
* formatId: `heading${ headingLevel }`
* } );
* } );
*
* @param {module:core/editor/editor~Editor} editor The editor instance.
* @param {module:autoformat/autoformat~Autoformat} plugin The autoformat plugin instance.
* @param {RegExp} pattern The regular expression to execute on just inserted text. The regular expression is tested against the text
* from the beginning until the caret position.
* @param {Function|String} callbackOrCommand The callback to execute or the command to run when the text is matched.
* In case of providing the callback, it receives the following parameter:
* * {Object} match RegExp.exec() result of matching the pattern to inserted text.
*/
function blockAutoformatEditing( editor, plugin, pattern, callbackOrCommand ) {
let callback;
let command = null;
if ( typeof callbackOrCommand == 'function' ) {
callback = callbackOrCommand;
} else {
// We assume that the actual command name was provided.
command = editor.commands.get( callbackOrCommand );
callback = () => {
editor.execute( callbackOrCommand );
};
}
editor.model.document.on( 'change:data', ( evt, batch ) => {
if ( command && !command.isEnabled || !plugin.isEnabled ) {
return;
}
const range = (0,ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__.first)( editor.model.document.selection.getRanges() );
if ( !range.isCollapsed ) {
return;
}
if ( batch.isUndo || !batch.isLocal ) {
return;
}
const changes = Array.from( editor.model.document.differ.getChanges() );
const entry = changes[ 0 ];
// Typing is represented by only a single change.
if ( changes.length != 1 || entry.type !== 'insert' || entry.name != '$text' || entry.length != 1 ) {
return;
}
const blockToFormat = entry.position.parent;
// Block formatting should be disabled in codeBlocks (#5800).
if ( blockToFormat.is( 'element', 'codeBlock' ) ) {
return;
}
// Only list commands and custom callbacks can be applied inside a list.
if ( blockToFormat.is( 'element', 'listItem' ) &&
typeof callbackOrCommand !== 'function' &&
![ 'numberedList', 'bulletedList', 'todoList' ].includes( callbackOrCommand )
) {
return;
}
// In case a command is bound, do not re-execute it over an existing block style which would result with a style removal.
// Instead just drop processing so that autoformat trigger text is not lost. E.g. writing "# " in a level 1 heading.
if ( command && command.value === true ) {
return;
}
const firstNode = blockToFormat.getChild( 0 );
const firstNodeRange = editor.model.createRangeOn( firstNode );
// Range is only expected to be within or at the very end of the first text node.
if ( !firstNodeRange.containsRange( range ) && !range.end.isEqual( firstNodeRange.end ) ) {
return;
}
const match = pattern.exec( firstNode.data.substr( 0, range.end.offset ) );
// ...and this text node's data match the pattern.
if ( !match ) {
return;
}
// Use enqueueChange to create new batch to separate typing batch from the auto-format changes.
editor.model.enqueueChange( writer => {
// Matched range.
const start = writer.createPositionAt( blockToFormat, 0 );
const end = writer.createPositionAt( blockToFormat, match[ 0 ].length );
const range = new ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_0__.LiveRange( start, end );
const wasChanged = callback( { match } );
// Remove matched text.
if ( wasChanged !== false ) {
writer.remove( range );
const selectionRange = editor.model.document.selection.getFirstRange();
const blockRange = writer.createRangeIn( blockToFormat );
// If the block is empty and the document selection has been moved when
// applying formatting (e.g. is now in newly created block).
if ( blockToFormat.isEmpty && !blockRange.isEqual( selectionRange ) && !blockRange.containsRange( selectionRange, true ) ) {
writer.remove( blockToFormat );
}
}
range.detach();
editor.model.enqueueChange( () => {
editor.plugins.get( 'Delete' ).requestUndoOnBackspace();
} );
} );
} );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-autoformat/src/inlineautoformatediting.js":
/*!************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-autoformat/src/inlineautoformatediting.js ***!
\************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ inlineAutoformatEditing)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* The inline autoformatting engine. It allows to format various inline patterns. For example,
* it can be configured to make "foo" bold when typed `**foo**` (the `**` markers will be removed).
*
* The autoformatting operation is integrated with the undo manager,
* so the autoformatting step can be undone if the user's intention was not to format the text.
*
* See the {@link module:autoformat/inlineautoformatediting~inlineAutoformatEditing `inlineAutoformatEditing`} documentation
* to learn how to create custom inline autoformatters. You can also use
* the {@link module:autoformat/autoformat~Autoformat} feature which enables a set of default autoformatters
* (lists, headings, bold and italic).
*
* @module autoformat/inlineautoformatediting
*/
/**
* Enables autoformatting mechanism for a given {@link module:core/editor/editor~Editor}.
*
* It formats the matched text by applying the given model attribute or by running the provided formatting callback.
* On every {@link module:engine/model/document~Document#event:change:data data change} in the model document
* the autoformatting engine checks the text on the left of the selection
* and executes the provided action if the text matches given criteria (regular expression or callback).
*
* @param {module:core/editor/editor~Editor} editor The editor instance.
* @param {module:autoformat/autoformat~Autoformat} plugin The autoformat plugin instance.
* @param {Function|RegExp} testRegexpOrCallback The regular expression or callback to execute on text.
* Provided regular expression *must* have three capture groups. The first and the third capture group
* should match opening and closing delimiters. The second capture group should match the text to format.
*
* // Matches the `**bold text**` pattern.
* // There are three capturing groups:
* // - The first to match the starting `**` delimiter.
* // - The second to match the text to format.
* // - The third to match the ending `**` delimiter.
* inlineAutoformatEditing( editor, plugin, /(\*\*)([^\*]+?)(\*\*)$/g, formatCallback );
*
* When a function is provided instead of the regular expression, it will be executed with the text to match as a parameter.
* The function should return proper "ranges" to delete and format.
*
* {
* remove: [
* [ 0, 1 ], // Remove the first letter from the given text.
* [ 5, 6 ] // Remove the 6th letter from the given text.
* ],
* format: [
* [ 1, 5 ] // Format all letters from 2nd to 5th.
* ]
* }
*
* @param {Function} formatCallback A callback to apply actual formatting.
* It should return `false` if changes should not be applied (e.g. if a command is disabled).
*
* inlineAutoformatEditing( editor, plugin, /(\*\*)([^\*]+?)(\*\*)$/g, ( writer, rangesToFormat ) => {
* const command = editor.commands.get( 'bold' );
*
* if ( !command.isEnabled ) {
* return false;
* }
*
* const validRanges = editor.model.schema.getValidRanges( rangesToFormat, 'bold' );
*
* for ( let range of validRanges ) {
* writer.setAttribute( 'bold', true, range );
* }
* } );
*/
function inlineAutoformatEditing( editor, plugin, testRegexpOrCallback, formatCallback ) {
let regExp;
let testCallback;
if ( testRegexpOrCallback instanceof RegExp ) {
regExp = testRegexpOrCallback;
} else {
testCallback = testRegexpOrCallback;
}
// A test callback run on changed text.
testCallback = testCallback || ( text => {
let result;
const remove = [];
const format = [];
while ( ( result = regExp.exec( text ) ) !== null ) {
// There should be full match and 3 capture groups.
if ( result && result.length < 4 ) {
break;
}
let {
index,
'1': leftDel,
'2': content,
'3': rightDel
} = result;
// Real matched string - there might be some non-capturing groups so we need to recalculate starting index.
const found = leftDel + content + rightDel;
index += result[ 0 ].length - found.length;
// Start and End offsets of delimiters to remove.
const delStart = [
index,
index + leftDel.length
];
const delEnd = [
index + leftDel.length + content.length,
index + leftDel.length + content.length + rightDel.length
];
remove.push( delStart );
remove.push( delEnd );
format.push( [ index + leftDel.length, index + leftDel.length + content.length ] );
}
return {
remove,
format
};
} );
editor.model.document.on( 'change:data', ( evt, batch ) => {
if ( batch.isUndo || !batch.isLocal || !plugin.isEnabled ) {
return;
}
const model = editor.model;
const selection = model.document.selection;
// Do nothing if selection is not collapsed.
if ( !selection.isCollapsed ) {
return;
}
const changes = Array.from( model.document.differ.getChanges() );
const entry = changes[ 0 ];
// Typing is represented by only a single change.
if ( changes.length != 1 || entry.type !== 'insert' || entry.name != '$text' || entry.length != 1 ) {
return;
}
const focus = selection.focus;
const block = focus.parent;
const { text, range } = getTextAfterCode( model.createRange( model.createPositionAt( block, 0 ), focus ), model );
const testOutput = testCallback( text );
const rangesToFormat = testOutputToRanges( range.start, testOutput.format, model );
const rangesToRemove = testOutputToRanges( range.start, testOutput.remove, model );
if ( !( rangesToFormat.length && rangesToRemove.length ) ) {
return;
}
// Use enqueueChange to create new batch to separate typing batch from the auto-format changes.
model.enqueueChange( writer => {
// Apply format.
const hasChanged = formatCallback( writer, rangesToFormat );
// Strict check on `false` to have backward compatibility (when callbacks were returning `undefined`).
if ( hasChanged === false ) {
return;
}
// Remove delimiters - use reversed order to not mix the offsets while removing.
for ( const range of rangesToRemove.reverse() ) {
writer.remove( range );
}
model.enqueueChange( () => {
editor.plugins.get( 'Delete' ).requestUndoOnBackspace();
} );
} );
} );
}
// Converts output of the test function provided to the inlineAutoformatEditing and converts it to the model ranges
// inside provided block.
//
// @private
// @param {module:engine/model/position~Position} start
// @param {Array.<Array>} arrays
// @param {module:engine/model/model~Model} model
function testOutputToRanges( start, arrays, model ) {
return arrays
.filter( array => ( array[ 0 ] !== undefined && array[ 1 ] !== undefined ) )
.map( array => {
return model.createRange( start.getShiftedBy( array[ 0 ] ), start.getShiftedBy( array[ 1 ] ) );
} );
}
// Returns the last text line after the last code element from the given range.
// It is similar to {@link module:typing/utils/getlasttextline.getLastTextLine `getLastTextLine()`},
// but it ignores any text before the last `code`.
//
// @param {module:engine/model/range~Range} range
// @param {module:engine/model/model~Model} model
// @returns {module:typing/utils/getlasttextline~LastTextLineData}
function getTextAfterCode( range, model ) {
let start = range.start;
const text = Array.from( range.getItems() ).reduce( ( rangeText, node ) => {
// Trim text to a last occurrence of an inline element and update range start.
if ( !( node.is( '$text' ) || node.is( '$textProxy' ) ) || node.getAttribute( 'code' ) ) {
start = model.createPositionAfter( node );
return '';
}
return rangeText + node.data;
}, '' );
return { text, range: model.createRange( start, range.end ) };
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-autosave/src/autosave.js":
/*!*******************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-autosave/src/autosave.js ***!
\*******************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ Autosave)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/* harmony import */ var lodash_es__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! lodash-es */ "./node_modules/lodash-es/debounce.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module autosave/autosave
*/
/* globals window */
/**
* The {@link module:autosave/autosave~Autosave} plugin allows you to automatically save the data (e.g. send it to the server)
* when needed (when the user changed the content).
*
* It listens to the {@link module:engine/model/document~Document#event:change:data `editor.model.document#change:data`}
* and `window#beforeunload` events and calls the
* {@link module:autosave/autosave~AutosaveAdapter#save `config.autosave.save()`} function.
*
* ClassicEditor
* .create( document.querySelector( '#editor' ), {
* plugins: [ ArticlePluginSet, Autosave ],
* toolbar: [ 'heading', '|', 'bold', 'italic', 'link', 'bulletedList', 'numberedList', 'blockQuote', 'undo', 'redo' ],
* image: {
* toolbar: [ 'imageStyle:block', 'imageStyle:side', '|', 'toggleImageCaption', 'imageTextAlternative' ],
* },
* autosave: {
* save( editor ) {
* // The saveData() function must return a promise
* // which should be resolved when the data is successfully saved.
* return saveData( editor.getData() );
* }
* }
* } );
*
* Read more about this feature in the {@glink builds/guides/integration/saving-data#autosave-feature Autosave feature}
* section of the {@glink builds/guides/integration/saving-data Saving and getting data}.
*
* @extends module:core/plugin~Plugin
*/
class Autosave extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'Autosave';
}
/**
* @inheritDoc
*/
static get requires() {
return [ ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.PendingActions ];
}
/**
* @inheritDoc
*/
constructor( editor ) {
super( editor );
const config = editor.config.get( 'autosave' ) || {};
// A minimum amount of time that needs to pass after the last action.
// After that time the provided save callbacks are being called.
const waitingTime = config.waitingTime || 1000;
/**
* The adapter is an object with a `save()` method. That method will be called whenever
* the data changes. It might be called some time after the change,
* since the event is throttled for performance reasons.
*
* @member {module:autosave/autosave~AutosaveAdapter} #adapter
*/
/**
* The state of this plugin.
*
* The plugin can be in the following states:
*
* * synchronized – When all changes are saved.
* * waiting – When the plugin is waiting for other changes before calling `adapter#save()` and `config.autosave.save()`.
* * saving – When the provided save method is called and the plugin waits for the response.
* * error &ndash When the provided save method will throw an error. This state immediately changes to the `saving` state and
* the save method will be called again in the short period of time.
*
* @readonly
* @member {'synchronized'|'waiting'|'saving'} #state
*/
this.set( 'state', 'synchronized' );
/**
* Debounced save method. The `save()` method is called the specified `waitingTime` after `debouncedSave()` is called,
* unless a new action happens in the meantime.
*
* @private
* @type {Function}
*/
this._debouncedSave = (0,lodash_es__WEBPACK_IMPORTED_MODULE_2__["default"])( this._save.bind( this ), waitingTime );
/**
* The last saved document version.
*
* @private
* @type {Number}
*/
this._lastDocumentVersion = editor.model.document.version;
/**
* Promise used for asynchronous save calls.
*
* Created to handle the autosave call to an external data source. It resolves when that call is finished. It is re-used if
* save is called before the promise has been resolved. It is set to `null` if there is no call in progress.
*
* @type {Promise|null}
* @private
*/
this._savePromise = null;
/**
* DOM emitter.
*
* @private
* @type {DomEmitterMixin}
*/
this._domEmitter = Object.create( ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__.DomEmitterMixin );
/**
* The configuration of this plugins.
*
* @private
* @type {Object}
*/
this._config = config;
/**
* Editor's pending actions manager.
*
* @private
* @member {module:core/pendingactions~PendingActions} #_pendingActions
*/
this._pendingActions = editor.plugins.get( ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.PendingActions );
/**
* Informs whether there should be another autosave callback performed, immediately after current autosave callback finishes.
*
* This is set to `true` when there is a save request while autosave callback is already being processed
* and the model has changed since the last save.
*
* @private
* @type {Boolean}
*/
this._makeImmediateSave = false;
/**
* An action that will be added to the pending action manager for actions happening in that plugin.
*
* @private
* @member {Object} #_action
*/
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const doc = editor.model.document;
// Add the listener only after the editor is initialized to prevent firing save callback on data init.
this.listenTo( editor, 'ready', () => {
this.listenTo( doc, 'change:data', ( evt, batch ) => {
if ( !this._saveCallbacks.length ) {
return;
}
if ( !batch.isLocal ) {
return;
}
if ( this.state === 'synchronized' ) {
this.state = 'waiting';
// Set pending action already when we are waiting for the autosave callback.
this._setPendingAction();
}
if ( this.state === 'waiting' ) {
this._debouncedSave();
}
// If the plugin is in `saving` state, it will change its state later basing on the `document.version`.
// If the `document.version` will be higher than stored `#_lastDocumentVersion`, then it means, that some `change:data`
// event has fired in the meantime.
} );
} );
// Flush on the editor's destroy listener with the highest priority to ensure that
// `editor.getData()` will be called before plugins are destroyed.
this.listenTo( editor, 'destroy', () => this._flush(), { priority: 'highest' } );
// It's not possible to easy test it because karma uses `beforeunload` event
// to warn before full page reload and this event cannot be dispatched manually.
/* istanbul ignore next */
this._domEmitter.listenTo( window, 'beforeunload', ( evtInfo, domEvt ) => {
if ( this._pendingActions.hasAny ) {
domEvt.returnValue = this._pendingActions.first.message;
}
} );
}
/**
* @inheritDoc
*/
destroy() {
// There's no need for canceling or flushing the throttled save, as
// it's done on the editor's destroy event with the highest priority.
this._domEmitter.stopListening();
super.destroy();
}
/**
* Immediately calls autosave callback. All previously queued (debounced) callbacks are cleared. If there is already an autosave
* callback in progress, then the requested save will be performed immediately after the current callback finishes.
*
* @returns {Promise} A promise that will be resolved when the autosave callback is finished.
*/
save() {
this._debouncedSave.cancel();
return this._save();
}
/**
* Invokes the remaining `_save()` method call.
*
* @protected
*/
_flush() {
this._debouncedSave.flush();
}
/**
* If the adapter is set and a new document version exists,
* the `_save()` method creates a pending action and calls the `adapter.save()` method.
* It waits for the result and then removes the created pending action.
*
* @private
* @returns {Promise} A promise that will be resolved when the autosave callback is finished.
*/
_save() {
if ( this._savePromise ) {
this._makeImmediateSave = this.editor.model.document.version > this._lastDocumentVersion;
return this._savePromise;
}
// Make sure there is a pending action (in case if `_save()` was called through manual `save()` call).
this._setPendingAction();
this.state = 'saving';
this._lastDocumentVersion = this.editor.model.document.version;
// Wait one promise cycle to be sure that save callbacks are not called inside a conversion or when the editor's state changes.
this._savePromise = Promise.resolve()
// Make autosave callback.
.then( () => Promise.all(
this._saveCallbacks.map( cb => cb( this.editor ) )
) )
// When the autosave callback is finished, always clear `this._savePromise`, no matter if it was successful or not.
.finally( () => {
this._savePromise = null;
} )
// If the save was successful, we have three scenarios:
//
// 1. If a save was requested when an autosave callback was already processed, we need to immediately call
// another autosave callback. In this case, `this._savePromise` will not be resolved until the next callback is done.
// 2. Otherwise, if changes happened to the model, make a delayed autosave callback (like the change just happened).
// 3. If no changes happened to the model, return to the `synchronized` state.
.then( () => {
if ( this._makeImmediateSave ) {
this._makeImmediateSave = false;
// Start another autosave callback. Return a promise that will be resolved after the new autosave callback.
// This way promises returned by `_save()` will not be resolved until all changes are saved.
//
// If `save()` was called when another (most often automatic) autosave callback was already processed,
// the promise returned by `save()` call will be resolved only after new changes have been saved.
//
// Note that it would not work correctly if `this._savePromise` is not cleared.
return this._save();
} else {
if ( this.editor.model.document.version > this._lastDocumentVersion ) {
this.state = 'waiting';
this._debouncedSave();
} else {
this.state = 'synchronized';
this._pendingActions.remove( this._action );
this._action = null;
}
}
} )
// In case of an error, retry the autosave callback after a delay (and also throw the original error).
.catch( err => {
// Change state to `error` so that listeners handling autosave error can be called.
this.state = 'error';
// Then, immediately change to the `saving` state as described above.
// Being in the `saving` state ensures that the autosave callback won't be delayed further by the `change:data` listener.
this.state = 'saving';
this._debouncedSave();
throw err;
} );
return this._savePromise;
}
/**
* Creates a pending action if it is not set already.
*
* @private
*/
_setPendingAction() {
const t = this.editor.t;
if ( !this._action ) {
this._action = this._pendingActions.add( t( 'Saving changes' ) );
}
}
/**
* Saves callbacks.
*
* @private
* @type {Array.<Function>}
*/
get _saveCallbacks() {
const saveCallbacks = [];
if ( this.adapter && this.adapter.save ) {
saveCallbacks.push( this.adapter.save );
}
if ( this._config.save ) {
saveCallbacks.push( this._config.save );
}
return saveCallbacks;
}
}
(0,ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__.mix)( Autosave, ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__.ObservableMixin );
/**
* An interface that requires the `save()` method.
*
* Used by {@link module:autosave/autosave~Autosave#adapter}.
*
* @interface module:autosave/autosave~AutosaveAdapter
*/
/**
* The method that will be called when the data changes. It should return a promise (e.g. in case of saving content to the database),
* so the autosave plugin will wait for that action before removing it from pending actions.
*
* @method #save
* @param {module:core/editor/editor~Editor} editor The editor instance.
* @returns {Promise.<*>}
*/
/**
* The configuration of the {@link module:autosave/autosave~Autosave autosave feature}.
*
* Read more in {@link module:autosave/autosave~AutosaveConfig}.
*
* @member {module:autosave/autosave~AutosaveConfig} module:core/editor/editorconfig~EditorConfig#autosave
*/
/**
* The configuration of the {@link module:autosave/autosave~Autosave autosave feature}.
*
* ClassicEditor
* .create( editorElement, {
* autosave: {
* save( editor ) {
* // The saveData() function must return a promise
* // which should be resolved when the data is successfully saved.
* return saveData( editor.getData() );
* }
* }
* } );
* .then( ... )
* .catch( ... );
*
* See {@link module:core/editor/editorconfig~EditorConfig all editor configuration options}.
*
* See also the demo of the {@glink builds/guides/integration/saving-data#autosave-feature autosave feature}.
*
* @interface AutosaveConfig
*/
/**
* The callback to be executed when the data needs to be saved.
*
* This function must return a promise which should be resolved when the data is successfully saved.
*
* ClassicEditor
* .create( editorElement, {
* autosave: {
* save( editor ) {
* return saveData( editor.getData() );
* }
* }
* } );
* .then( ... )
* .catch( ... );
*
* @method module:autosave/autosave~AutosaveConfig#save
* @param {module:core/editor/editor~Editor} editor The editor instance.
* @returns {Promise.<*>}
*/
/**
* The minimum amount of time that needs to pass after the last action to call the provided callback.
* By default it is 1000 ms.
*
* ClassicEditor
* .create( editorElement, {
* autosave: {
* save( editor ) {
* return saveData( editor.getData() );
* },
* waitingTime: 2000
* }
* } );
* .then( ... )
* .catch( ... );
*
* @member {Number} module:autosave/autosave~AutosaveConfig#waitingTime
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-basic-styles/src/attributecommand.js":
/*!*******************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-basic-styles/src/attributecommand.js ***!
\*******************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ AttributeCommand)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module basic-styles/attributecommand
*/
/**
* An extension of the base {@link module:core/command~Command} class, which provides utilities for a command
* that toggles a single attribute on a text or an element.
*
* `AttributeCommand` uses {@link module:engine/model/document~Document#selection}
* to decide which nodes (if any) should be changed, and applies or removes the attribute from them.
*
* The command checks the {@link module:engine/model/model~Model#schema} to decide if it can be enabled
* for the current selection and to which nodes the attribute can be applied.
*
* @extends module:core/command~Command
*/
class AttributeCommand extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Command {
/**
* @param {module:core/editor/editor~Editor} editor
* @param {String} attributeKey Attribute that will be set by the command.
*/
constructor( editor, attributeKey ) {
super( editor );
/**
* The attribute that will be set by the command.
*
* @readonly
* @member {String}
*/
this.attributeKey = attributeKey;
/**
* Flag indicating whether the command is active. The command is active when the
* {@link module:engine/model/selection~Selection#hasAttribute selection has the attribute} which means that:
*
* * If the selection is not empty – That the attribute is set on the first node in the selection that allows this attribute.
* * If the selection is empty – That the selection has the attribute itself (which means that newly typed
* text will have this attribute, too).
*
* @observable
* @readonly
* @member {Boolean} #value
*/
}
/**
* Updates the command's {@link #value} and {@link #isEnabled} based on the current selection.
*/
refresh() {
const model = this.editor.model;
const doc = model.document;
this.value = this._getValueFromFirstAllowedNode();
this.isEnabled = model.schema.checkAttributeInSelection( doc.selection, this.attributeKey );
}
/**
* Executes the command — applies the attribute to the selection or removes it from the selection.
*
* If the command is active (`value == true`), it will remove attributes. Otherwise, it will set attributes.
*
* The execution result differs, depending on the {@link module:engine/model/document~Document#selection}:
*
* * If the selection is on a range, the command applies the attribute to all nodes in that range
* (if they are allowed to have this attribute by the {@link module:engine/model/schema~Schema schema}).
* * If the selection is collapsed in a non-empty node, the command applies the attribute to the
* {@link module:engine/model/document~Document#selection} itself (note that typed characters copy attributes from the selection).
* * If the selection is collapsed in an empty node, the command applies the attribute to the parent node of the selection (note
* that the selection inherits all attributes from a node if it is in an empty node).
*
* @fires execute
* @param {Object} [options] Command options.
* @param {Boolean} [options.forceValue] If set, it will force the command behavior. If `true`, the command will apply the attribute,
* otherwise the command will remove the attribute.
* If not set, the command will look for its current value to decide what it should do.
*/
execute( options = {} ) {
const model = this.editor.model;
const doc = model.document;
const selection = doc.selection;
const value = ( options.forceValue === undefined ) ? !this.value : options.forceValue;
model.change( writer => {
if ( selection.isCollapsed ) {
if ( value ) {
writer.setSelectionAttribute( this.attributeKey, true );
} else {
writer.removeSelectionAttribute( this.attributeKey );
}
} else {
const ranges = model.schema.getValidRanges( selection.getRanges(), this.attributeKey );
for ( const range of ranges ) {
if ( value ) {
writer.setAttribute( this.attributeKey, value, range );
} else {
writer.removeAttribute( this.attributeKey, range );
}
}
}
} );
}
/**
* Checks the attribute value of the first node in the selection that allows the attribute.
* For the collapsed selection returns the selection attribute.
*
* @private
* @returns {Boolean} The attribute value.
*/
_getValueFromFirstAllowedNode() {
const model = this.editor.model;
const schema = model.schema;
const selection = model.document.selection;
if ( selection.isCollapsed ) {
return selection.hasAttribute( this.attributeKey );
}
for ( const range of selection.getRanges() ) {
for ( const item of range.getItems() ) {
if ( schema.checkAttribute( item, this.attributeKey ) ) {
return item.hasAttribute( this.attributeKey );
}
}
}
return false;
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-basic-styles/src/bold.js":
/*!*******************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-basic-styles/src/bold.js ***!
\*******************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ Bold)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var _bold_boldediting__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./bold/boldediting */ "./node_modules/@ckeditor/ckeditor5-basic-styles/src/bold/boldediting.js");
/* harmony import */ var _bold_boldui__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./bold/boldui */ "./node_modules/@ckeditor/ckeditor5-basic-styles/src/bold/boldui.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module basic-styles/bold
*/
/**
* The bold feature.
*
* For a detailed overview check the {@glink features/basic-styles Basic styles feature documentation}
* and the {@glink api/basic-styles package page}.
*
* This is a "glue" plugin which loads the {@link module:basic-styles/bold/boldediting~BoldEditing bold editing feature}
* and {@link module:basic-styles/bold/boldui~BoldUI bold UI feature}.
*
* @extends module:core/plugin~Plugin
*/
class Bold extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [ _bold_boldediting__WEBPACK_IMPORTED_MODULE_1__["default"], _bold_boldui__WEBPACK_IMPORTED_MODULE_2__["default"] ];
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'Bold';
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-basic-styles/src/bold/boldediting.js":
/*!*******************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-basic-styles/src/bold/boldediting.js ***!
\*******************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ BoldEditing)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var _attributecommand__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../attributecommand */ "./node_modules/@ckeditor/ckeditor5-basic-styles/src/attributecommand.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module basic-styles/bold/boldediting
*/
const BOLD = 'bold';
/**
* The bold editing feature.
*
* It registers the `'bold'` command and introduces the `bold` attribute in the model which renders to the view
* as a `<strong>` element.
*
* @extends module:core/plugin~Plugin
*/
class BoldEditing extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'BoldEditing';
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
// Allow bold attribute on text nodes.
editor.model.schema.extend( '$text', { allowAttributes: BOLD } );
editor.model.schema.setAttributeProperties( BOLD, {
isFormatting: true,
copyOnEnter: true
} );
// Build converter from model to view for data and editing pipelines.
editor.conversion.attributeToElement( {
model: BOLD,
view: 'strong',
upcastAlso: [
'b',
viewElement => {
const fontWeight = viewElement.getStyle( 'font-weight' );
if ( !fontWeight ) {
return null;
}
// Value of the `font-weight` attribute can be defined as a string or a number.
if ( fontWeight == 'bold' || Number( fontWeight ) >= 600 ) {
return {
name: true,
styles: [ 'font-weight' ]
};
}
}
]
} );
// Create bold command.
editor.commands.add( BOLD, new _attributecommand__WEBPACK_IMPORTED_MODULE_1__["default"]( editor, BOLD ) );
// Set the Ctrl+B keystroke.
editor.keystrokes.set( 'CTRL+B', BOLD );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-basic-styles/src/bold/boldui.js":
/*!**************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-basic-styles/src/bold/boldui.js ***!
\**************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ BoldUI)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/ui */ "./node_modules/ckeditor5/src/ui.js");
/* harmony import */ var _theme_icons_bold_svg__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../theme/icons/bold.svg */ "./node_modules/@ckeditor/ckeditor5-basic-styles/theme/icons/bold.svg");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module basic-styles/bold/boldui
*/
const BOLD = 'bold';
/**
* The bold UI feature. It introduces the Bold button.
*
* @extends module:core/plugin~Plugin
*/
class BoldUI extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'BoldUI';
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const t = editor.t;
// Add bold button to feature components.
editor.ui.componentFactory.add( BOLD, locale => {
const command = editor.commands.get( BOLD );
const view = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.ButtonView( locale );
view.set( {
label: t( 'Bold' ),
icon: _theme_icons_bold_svg__WEBPACK_IMPORTED_MODULE_2__["default"],
keystroke: 'CTRL+B',
tooltip: true,
isToggleable: true
} );
view.bind( 'isOn', 'isEnabled' ).to( command, 'value', 'isEnabled' );
// Execute command.
this.listenTo( view, 'execute', () => {
editor.execute( BOLD );
editor.editing.view.focus();
} );
return view;
} );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-basic-styles/src/code.js":
/*!*******************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-basic-styles/src/code.js ***!
\*******************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ Code)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var _code_codeediting__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./code/codeediting */ "./node_modules/@ckeditor/ckeditor5-basic-styles/src/code/codeediting.js");
/* harmony import */ var _code_codeui__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./code/codeui */ "./node_modules/@ckeditor/ckeditor5-basic-styles/src/code/codeui.js");
/* harmony import */ var _theme_code_css__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../theme/code.css */ "./node_modules/@ckeditor/ckeditor5-basic-styles/theme/code.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module basic-styles/code
*/
/**
* The code feature.
*
* For a detailed overview check the {@glink features/basic-styles Basic styles feature documentation}
* and the {@glink api/basic-styles package page}.
*
* This is a "glue" plugin which loads the {@link module:basic-styles/code/codeediting~CodeEditing code editing feature}
* and {@link module:basic-styles/code/codeui~CodeUI code UI feature}.
*
* @extends module:core/plugin~Plugin
*/
class Code extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [ _code_codeediting__WEBPACK_IMPORTED_MODULE_1__["default"], _code_codeui__WEBPACK_IMPORTED_MODULE_2__["default"] ];
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'Code';
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-basic-styles/src/code/codeediting.js":
/*!*******************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-basic-styles/src/code/codeediting.js ***!
\*******************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ CodeEditing)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_typing__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/typing */ "./node_modules/ckeditor5/src/typing.js");
/* harmony import */ var _attributecommand__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../attributecommand */ "./node_modules/@ckeditor/ckeditor5-basic-styles/src/attributecommand.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module basic-styles/code/codeediting
*/
const CODE = 'code';
const HIGHLIGHT_CLASS = 'ck-code_selected';
/**
* The code editing feature.
*
* It registers the `'code'` command and introduces the `code` attribute in the model which renders to the view
* as a `<code>` element.
*
* @extends module:core/plugin~Plugin
*/
class CodeEditing extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'CodeEditing';
}
/**
* @inheritDoc
*/
static get requires() {
return [ ckeditor5_src_typing__WEBPACK_IMPORTED_MODULE_1__.TwoStepCaretMovement ];
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
// Allow code attribute on text nodes.
editor.model.schema.extend( '$text', { allowAttributes: CODE } );
editor.model.schema.setAttributeProperties( CODE, {
isFormatting: true,
copyOnEnter: false
} );
editor.conversion.attributeToElement( {
model: CODE,
view: 'code',
upcastAlso: {
styles: {
'word-wrap': 'break-word'
}
}
} );
// Create code command.
editor.commands.add( CODE, new _attributecommand__WEBPACK_IMPORTED_MODULE_2__["default"]( editor, CODE ) );
// Enable two-step caret movement for `code` attribute.
editor.plugins.get( ckeditor5_src_typing__WEBPACK_IMPORTED_MODULE_1__.TwoStepCaretMovement ).registerAttribute( CODE );
// Setup highlight over selected element.
(0,ckeditor5_src_typing__WEBPACK_IMPORTED_MODULE_1__.inlineHighlight)( editor, CODE, 'code', HIGHLIGHT_CLASS );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-basic-styles/src/code/codeui.js":
/*!**************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-basic-styles/src/code/codeui.js ***!
\**************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ CodeUI)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/ui */ "./node_modules/ckeditor5/src/ui.js");
/* harmony import */ var _theme_icons_code_svg__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../theme/icons/code.svg */ "./node_modules/@ckeditor/ckeditor5-basic-styles/theme/icons/code.svg");
/* harmony import */ var _theme_code_css__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../../theme/code.css */ "./node_modules/@ckeditor/ckeditor5-basic-styles/theme/code.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module basic-styles/code/codeui
*/
const CODE = 'code';
/**
* The code UI feature. It introduces the Code button.
*
* @extends module:core/plugin~Plugin
*/
class CodeUI extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'CodeUI';
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const t = editor.t;
// Add code button to feature components.
editor.ui.componentFactory.add( CODE, locale => {
const command = editor.commands.get( CODE );
const view = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.ButtonView( locale );
view.set( {
label: t( 'Code' ),
icon: _theme_icons_code_svg__WEBPACK_IMPORTED_MODULE_2__["default"],
tooltip: true,
isToggleable: true
} );
view.bind( 'isOn', 'isEnabled' ).to( command, 'value', 'isEnabled' );
// Execute command.
this.listenTo( view, 'execute', () => {
editor.execute( CODE );
editor.editing.view.focus();
} );
return view;
} );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-basic-styles/src/italic.js":
/*!*********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-basic-styles/src/italic.js ***!
\*********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ Italic)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var _italic_italicediting__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./italic/italicediting */ "./node_modules/@ckeditor/ckeditor5-basic-styles/src/italic/italicediting.js");
/* harmony import */ var _italic_italicui__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./italic/italicui */ "./node_modules/@ckeditor/ckeditor5-basic-styles/src/italic/italicui.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module basic-styles/italic
*/
/**
* The italic feature.
*
* For a detailed overview check the {@glink features/basic-styles Basic styles feature documentation}
* and the {@glink api/basic-styles package page}.
*
* This is a "glue" plugin which loads the {@link module:basic-styles/italic/italicediting~ItalicEditing} and
* {@link module:basic-styles/italic/italicui~ItalicUI} plugins.
*
* @extends module:core/plugin~Plugin
*/
class Italic extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [ _italic_italicediting__WEBPACK_IMPORTED_MODULE_1__["default"], _italic_italicui__WEBPACK_IMPORTED_MODULE_2__["default"] ];
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'Italic';
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-basic-styles/src/italic/italicediting.js":
/*!***********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-basic-styles/src/italic/italicediting.js ***!
\***********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ItalicEditing)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var _attributecommand__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../attributecommand */ "./node_modules/@ckeditor/ckeditor5-basic-styles/src/attributecommand.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module basic-styles/italic/italicediting
*/
const ITALIC = 'italic';
/**
* The italic editing feature.
*
* It registers the `'italic'` command, the <kbd>Ctrl+I</kbd> keystroke and introduces the `italic` attribute in the model
* which renders to the view as an `<i>` element.
*
* @extends module:core/plugin~Plugin
*/
class ItalicEditing extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'ItalicEditing';
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
// Allow italic attribute on text nodes.
editor.model.schema.extend( '$text', { allowAttributes: ITALIC } );
editor.model.schema.setAttributeProperties( ITALIC, {
isFormatting: true,
copyOnEnter: true
} );
editor.conversion.attributeToElement( {
model: ITALIC,
view: 'i',
upcastAlso: [
'em',
{
styles: {
'font-style': 'italic'
}
}
]
} );
// Create italic command.
editor.commands.add( ITALIC, new _attributecommand__WEBPACK_IMPORTED_MODULE_1__["default"]( editor, ITALIC ) );
// Set the Ctrl+I keystroke.
editor.keystrokes.set( 'CTRL+I', ITALIC );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-basic-styles/src/italic/italicui.js":
/*!******************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-basic-styles/src/italic/italicui.js ***!
\******************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ItalicUI)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/ui */ "./node_modules/ckeditor5/src/ui.js");
/* harmony import */ var _theme_icons_italic_svg__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../theme/icons/italic.svg */ "./node_modules/@ckeditor/ckeditor5-basic-styles/theme/icons/italic.svg");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module basic-styles/italic/italicui
*/
const ITALIC = 'italic';
/**
* The italic UI feature. It introduces the Italic button.
*
* @extends module:core/plugin~Plugin
*/
class ItalicUI extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'ItalicUI';
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const t = editor.t;
// Add bold button to feature components.
editor.ui.componentFactory.add( ITALIC, locale => {
const command = editor.commands.get( ITALIC );
const view = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.ButtonView( locale );
view.set( {
label: t( 'Italic' ),
icon: _theme_icons_italic_svg__WEBPACK_IMPORTED_MODULE_2__["default"],
keystroke: 'CTRL+I',
tooltip: true,
isToggleable: true
} );
view.bind( 'isOn', 'isEnabled' ).to( command, 'value', 'isEnabled' );
// Execute command.
this.listenTo( view, 'execute', () => {
editor.execute( ITALIC );
editor.editing.view.focus();
} );
return view;
} );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-block-quote/src/blockquote.js":
/*!************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-block-quote/src/blockquote.js ***!
\************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ BlockQuote)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var _blockquoteediting__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./blockquoteediting */ "./node_modules/@ckeditor/ckeditor5-block-quote/src/blockquoteediting.js");
/* harmony import */ var _blockquoteui__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./blockquoteui */ "./node_modules/@ckeditor/ckeditor5-block-quote/src/blockquoteui.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module block-quote/blockquote
*/
/**
* The block quote plugin.
*
* For more information about this feature check the {@glink api/block-quote package page}.
*
* This is a "glue" plugin which loads the {@link module:block-quote/blockquoteediting~BlockQuoteEditing block quote editing feature}
* and {@link module:block-quote/blockquoteui~BlockQuoteUI block quote UI feature}.
*
* @extends module:core/plugin~Plugin
*/
class BlockQuote extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [ _blockquoteediting__WEBPACK_IMPORTED_MODULE_1__["default"], _blockquoteui__WEBPACK_IMPORTED_MODULE_2__["default"] ];
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'BlockQuote';
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-block-quote/src/blockquotecommand.js":
/*!*******************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-block-quote/src/blockquotecommand.js ***!
\*******************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ BlockQuoteCommand)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module block-quote/blockquotecommand
*/
/**
* The block quote command plugin.
*
* @extends module:core/command~Command
*/
class BlockQuoteCommand extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Command {
/**
* Whether the selection starts in a block quote.
*
* @observable
* @readonly
* @member {Boolean} #value
*/
/**
* @inheritDoc
*/
refresh() {
this.value = this._getValue();
this.isEnabled = this._checkEnabled();
}
/**
* Executes the command. When the command {@link #value is on}, all top-most block quotes within
* the selection will be removed. If it is off, all selected blocks will be wrapped with
* a block quote.
*
* @fires execute
* @param {Object} [options] Command options.
* @param {Boolean} [options.forceValue] If set, it will force the command behavior. If `true`, the command will apply a block quote,
* otherwise the command will remove the block quote. If not set, the command will act basing on its current value.
*/
execute( options = {} ) {
const model = this.editor.model;
const schema = model.schema;
const selection = model.document.selection;
const blocks = Array.from( selection.getSelectedBlocks() );
const value = ( options.forceValue === undefined ) ? !this.value : options.forceValue;
model.change( writer => {
if ( !value ) {
this._removeQuote( writer, blocks.filter( findQuote ) );
} else {
const blocksToQuote = blocks.filter( block => {
// Already quoted blocks needs to be considered while quoting too
// in order to reuse their <bQ> elements.
return findQuote( block ) || checkCanBeQuoted( schema, block );
} );
this._applyQuote( writer, blocksToQuote );
}
} );
}
/**
* Checks the command's {@link #value}.
*
* @private
* @returns {Boolean} The current value.
*/
_getValue() {
const selection = this.editor.model.document.selection;
const firstBlock = (0,ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__.first)( selection.getSelectedBlocks() );
// In the current implementation, the block quote must be an immediate parent of a block element.
return !!( firstBlock && findQuote( firstBlock ) );
}
/**
* Checks whether the command can be enabled in the current context.
*
* @private
* @returns {Boolean} Whether the command should be enabled.
*/
_checkEnabled() {
if ( this.value ) {
return true;
}
const selection = this.editor.model.document.selection;
const schema = this.editor.model.schema;
const firstBlock = (0,ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__.first)( selection.getSelectedBlocks() );
if ( !firstBlock ) {
return false;
}
return checkCanBeQuoted( schema, firstBlock );
}
/**
* Removes the quote from given blocks.
*
* If blocks which are supposed to be "unquoted" are in the middle of a quote,
* start it or end it, then the quote will be split (if needed) and the blocks
* will be moved out of it, so other quoted blocks remained quoted.
*
* @private
* @param {module:engine/model/writer~Writer} writer
* @param {Array.<module:engine/model/element~Element>} blocks
*/
_removeQuote( writer, blocks ) {
// Unquote all groups of block. Iterate in the reverse order to not break following ranges.
getRangesOfBlockGroups( writer, blocks ).reverse().forEach( groupRange => {
if ( groupRange.start.isAtStart && groupRange.end.isAtEnd ) {
writer.unwrap( groupRange.start.parent );
return;
}
// The group of blocks are at the beginning of an <bQ> so let's move them left (out of the <bQ>).
if ( groupRange.start.isAtStart ) {
const positionBefore = writer.createPositionBefore( groupRange.start.parent );
writer.move( groupRange, positionBefore );
return;
}
// The blocks are in the middle of an <bQ> so we need to split the <bQ> after the last block
// so we move the items there.
if ( !groupRange.end.isAtEnd ) {
writer.split( groupRange.end );
}
// Now we are sure that groupRange.end.isAtEnd is true, so let's move the blocks right.
const positionAfter = writer.createPositionAfter( groupRange.end.parent );
writer.move( groupRange, positionAfter );
} );
}
/**
* Applies the quote to given blocks.
*
* @private
* @param {module:engine/model/writer~Writer} writer
* @param {Array.<module:engine/model/element~Element>} blocks
*/
_applyQuote( writer, blocks ) {
const quotesToMerge = [];
// Quote all groups of block. Iterate in the reverse order to not break following ranges.
getRangesOfBlockGroups( writer, blocks ).reverse().forEach( groupRange => {
let quote = findQuote( groupRange.start );
if ( !quote ) {
quote = writer.createElement( 'blockQuote' );
writer.wrap( groupRange, quote );
}
quotesToMerge.push( quote );
} );
// Merge subsequent <bQ> elements. Reverse the order again because this time we want to go through
// the <bQ> elements in the source order (due to how merge works – it moves the right element's content
// to the first element and removes the right one. Since we may need to merge a couple of subsequent `<bQ>` elements
// we want to keep the reference to the first (furthest left) one.
quotesToMerge.reverse().reduce( ( currentQuote, nextQuote ) => {
if ( currentQuote.nextSibling == nextQuote ) {
writer.merge( writer.createPositionAfter( currentQuote ) );
return currentQuote;
}
return nextQuote;
} );
}
}
function findQuote( elementOrPosition ) {
return elementOrPosition.parent.name == 'blockQuote' ? elementOrPosition.parent : null;
}
// Returns a minimal array of ranges containing groups of subsequent blocks.
//
// content: abcdefgh
// blocks: [ a, b, d, f, g, h ]
// output ranges: [ab]c[d]e[fgh]
//
// @param {Array.<module:engine/model/element~Element>} blocks
// @returns {Array.<module:engine/model/range~Range>}
function getRangesOfBlockGroups( writer, blocks ) {
let startPosition;
let i = 0;
const ranges = [];
while ( i < blocks.length ) {
const block = blocks[ i ];
const nextBlock = blocks[ i + 1 ];
if ( !startPosition ) {
startPosition = writer.createPositionBefore( block );
}
if ( !nextBlock || block.nextSibling != nextBlock ) {
ranges.push( writer.createRange( startPosition, writer.createPositionAfter( block ) ) );
startPosition = null;
}
i++;
}
return ranges;
}
// Checks whether <bQ> can wrap the block.
function checkCanBeQuoted( schema, block ) {
// TMP will be replaced with schema.checkWrap().
const isBQAllowed = schema.checkChild( block.parent, 'blockQuote' );
const isBlockAllowedInBQ = schema.checkChild( [ '$root', 'blockQuote' ], block );
return isBQAllowed && isBlockAllowedInBQ;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-block-quote/src/blockquoteediting.js":
/*!*******************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-block-quote/src/blockquoteediting.js ***!
\*******************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ BlockQuoteEditing)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_enter__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/enter */ "./node_modules/ckeditor5/src/enter.js");
/* harmony import */ var ckeditor5_src_typing__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ckeditor5/src/typing */ "./node_modules/ckeditor5/src/typing.js");
/* harmony import */ var _blockquotecommand__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./blockquotecommand */ "./node_modules/@ckeditor/ckeditor5-block-quote/src/blockquotecommand.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module block-quote/blockquoteediting
*/
/**
* The block quote editing.
*
* Introduces the `'blockQuote'` command and the `'blockQuote'` model element.
*
* @extends module:core/plugin~Plugin
*/
class BlockQuoteEditing extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'BlockQuoteEditing';
}
/**
* @inheritDoc
*/
static get requires() {
return [ ckeditor5_src_enter__WEBPACK_IMPORTED_MODULE_1__.Enter, ckeditor5_src_typing__WEBPACK_IMPORTED_MODULE_2__.Delete ];
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const schema = editor.model.schema;
editor.commands.add( 'blockQuote', new _blockquotecommand__WEBPACK_IMPORTED_MODULE_3__["default"]( editor ) );
schema.register( 'blockQuote', {
allowWhere: '$block',
allowContentOf: '$root'
} );
editor.conversion.elementToElement( { model: 'blockQuote', view: 'blockquote' } );
// Postfixer which cleans incorrect model states connected with block quotes.
editor.model.document.registerPostFixer( writer => {
const changes = editor.model.document.differ.getChanges();
for ( const entry of changes ) {
if ( entry.type == 'insert' ) {
const element = entry.position.nodeAfter;
if ( !element ) {
// We are inside a text node.
continue;
}
if ( element.is( 'element', 'blockQuote' ) && element.isEmpty ) {
// Added an empty blockQuote - remove it.
writer.remove( element );
return true;
} else if ( element.is( 'element', 'blockQuote' ) && !schema.checkChild( entry.position, element ) ) {
// Added a blockQuote in incorrect place. Unwrap it so the content inside is not lost.
writer.unwrap( element );
return true;
} else if ( element.is( 'element' ) ) {
// Just added an element. Check that all children meet the scheme rules.
const range = writer.createRangeIn( element );
for ( const child of range.getItems() ) {
if (
child.is( 'element', 'blockQuote' ) &&
!schema.checkChild( writer.createPositionBefore( child ), child )
) {
writer.unwrap( child );
return true;
}
}
}
} else if ( entry.type == 'remove' ) {
const parent = entry.position.parent;
if ( parent.is( 'element', 'blockQuote' ) && parent.isEmpty ) {
// Something got removed and now blockQuote is empty. Remove the blockQuote as well.
writer.remove( parent );
return true;
}
}
}
return false;
} );
const viewDocument = this.editor.editing.view.document;
const selection = editor.model.document.selection;
const blockQuoteCommand = editor.commands.get( 'blockQuote' );
// Overwrite default Enter key behavior.
// If Enter key is pressed with selection collapsed in empty block inside a quote, break the quote.
this.listenTo( viewDocument, 'enter', ( evt, data ) => {
if ( !selection.isCollapsed || !blockQuoteCommand.value ) {
return;
}
const positionParent = selection.getLastPosition().parent;
if ( positionParent.isEmpty ) {
editor.execute( 'blockQuote' );
editor.editing.view.scrollToTheSelection();
data.preventDefault();
evt.stop();
}
}, { context: 'blockquote' } );
// Overwrite default Backspace key behavior.
// If Backspace key is pressed with selection collapsed in first empty block inside a quote, break the quote.
this.listenTo( viewDocument, 'delete', ( evt, data ) => {
if ( data.direction != 'backward' || !selection.isCollapsed || !blockQuoteCommand.value ) {
return;
}
const positionParent = selection.getLastPosition().parent;
if ( positionParent.isEmpty && !positionParent.previousSibling ) {
editor.execute( 'blockQuote' );
editor.editing.view.scrollToTheSelection();
data.preventDefault();
evt.stop();
}
}, { context: 'blockquote' } );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-block-quote/src/blockquoteui.js":
/*!**************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-block-quote/src/blockquoteui.js ***!
\**************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ BlockQuoteUI)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/ui */ "./node_modules/ckeditor5/src/ui.js");
/* harmony import */ var _theme_blockquote_css__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../theme/blockquote.css */ "./node_modules/@ckeditor/ckeditor5-block-quote/theme/blockquote.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module block-quote/blockquoteui
*/
/**
* The block quote UI plugin.
*
* It introduces the `'blockQuote'` button.
*
* @extends module:core/plugin~Plugin
*/
class BlockQuoteUI extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'BlockQuoteUI';
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const t = editor.t;
editor.ui.componentFactory.add( 'blockQuote', locale => {
const command = editor.commands.get( 'blockQuote' );
const buttonView = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.ButtonView( locale );
buttonView.set( {
label: t( 'Block quote' ),
icon: ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.icons.quote,
tooltip: true,
isToggleable: true
} );
// Bind button model to command.
buttonView.bind( 'isOn', 'isEnabled' ).to( command, 'value', 'isEnabled' );
// Execute command.
this.listenTo( buttonView, 'execute', () => {
editor.execute( 'blockQuote' );
editor.editing.view.focus();
} );
return buttonView;
} );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-clipboard/src/clipboard.js":
/*!*********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-clipboard/src/clipboard.js ***!
\*********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ Clipboard)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_core_src_plugin__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-core/src/plugin */ "./node_modules/@ckeditor/ckeditor5-core/src/plugin.js");
/* harmony import */ var _clipboardpipeline__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./clipboardpipeline */ "./node_modules/@ckeditor/ckeditor5-clipboard/src/clipboardpipeline.js");
/* harmony import */ var _dragdrop__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./dragdrop */ "./node_modules/@ckeditor/ckeditor5-clipboard/src/dragdrop.js");
/* harmony import */ var _pasteplaintext__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./pasteplaintext */ "./node_modules/@ckeditor/ckeditor5-clipboard/src/pasteplaintext.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module clipboard/clipboard
*/
/**
* The clipboard feature.
*
* Read more about the clipboard integration in the {@glink framework/guides/deep-dive/clipboard clipboard deep dive guide}.
*
* This is a "glue" plugin which loads the following plugins:
* * {@link module:clipboard/clipboardpipeline~ClipboardPipeline}
* * {@link module:clipboard/dragdrop~DragDrop}
* * {@link module:clipboard/pasteplaintext~PastePlainText}
*
* @extends module:core/plugin~Plugin
*/
class Clipboard extends _ckeditor_ckeditor5_core_src_plugin__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* @inheritDoc
*/
static get pluginName() {
return 'Clipboard';
}
/**
* @inheritDoc
*/
static get requires() {
return [ _clipboardpipeline__WEBPACK_IMPORTED_MODULE_1__["default"], _dragdrop__WEBPACK_IMPORTED_MODULE_2__["default"], _pasteplaintext__WEBPACK_IMPORTED_MODULE_3__["default"] ];
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-clipboard/src/clipboardobserver.js":
/*!*****************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-clipboard/src/clipboardobserver.js ***!
\*****************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ClipboardObserver)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_engine_src_view_observer_domeventobserver__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-engine/src/view/observer/domeventobserver */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/domeventobserver.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_eventinfo__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/eventinfo */ "./node_modules/@ckeditor/ckeditor5-utils/src/eventinfo.js");
/* harmony import */ var _datatransfer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./datatransfer */ "./node_modules/@ckeditor/ckeditor5-clipboard/src/datatransfer.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module clipboard/clipboardobserver
*/
/**
* Clipboard events observer.
*
* Fires the following events:
*
* * {@link module:engine/view/document~Document#event:clipboardInput},
* * {@link module:engine/view/document~Document#event:paste},
* * {@link module:engine/view/document~Document#event:copy},
* * {@link module:engine/view/document~Document#event:cut},
* * {@link module:engine/view/document~Document#event:drop},
* * {@link module:engine/view/document~Document#event:dragover},
* * {@link module:engine/view/document~Document#event:dragging},
* * {@link module:engine/view/document~Document#event:dragstart},
* * {@link module:engine/view/document~Document#event:dragend},
* * {@link module:engine/view/document~Document#event:dragenter},
* * {@link module:engine/view/document~Document#event:dragleave}.
*
* **Note**: This observer is not available by default (ckeditor5-engine does not add it on its own).
* To make it available, it needs to be added to {@link module:engine/view/document~Document} by using
* the {@link module:engine/view/view~View#addObserver `View#addObserver()`} method. Alternatively, you can load the
* {@link module:clipboard/clipboard~Clipboard} plugin which adds this observer automatically (because it uses it).
*
* @extends module:engine/view/observer/domeventobserver~DomEventObserver
*/
class ClipboardObserver extends _ckeditor_ckeditor5_engine_src_view_observer_domeventobserver__WEBPACK_IMPORTED_MODULE_0__["default"] {
constructor( view ) {
super( view );
const viewDocument = this.document;
this.domEventType = [ 'paste', 'copy', 'cut', 'drop', 'dragover', 'dragstart', 'dragend', 'dragenter', 'dragleave' ];
this.listenTo( viewDocument, 'paste', handleInput( 'clipboardInput' ), { priority: 'low' } );
this.listenTo( viewDocument, 'drop', handleInput( 'clipboardInput' ), { priority: 'low' } );
this.listenTo( viewDocument, 'dragover', handleInput( 'dragging' ), { priority: 'low' } );
function handleInput( type ) {
return ( evt, data ) => {
data.preventDefault();
const targetRanges = data.dropRange ? [ data.dropRange ] : null;
const eventInfo = new _ckeditor_ckeditor5_utils_src_eventinfo__WEBPACK_IMPORTED_MODULE_1__["default"]( viewDocument, type );
viewDocument.fire( eventInfo, {
dataTransfer: data.dataTransfer,
method: evt.name,
targetRanges,
target: data.target
} );
// If CKEditor handled the input, do not bubble the original event any further.
// This helps external integrations recognize that fact and act accordingly.
// https://github.com/ckeditor/ckeditor5-upload/issues/92
if ( eventInfo.stop.called ) {
data.stopPropagation();
}
};
}
}
onDomEvent( domEvent ) {
const evtData = {
dataTransfer: new _datatransfer__WEBPACK_IMPORTED_MODULE_2__["default"]( domEvent.clipboardData ? domEvent.clipboardData : domEvent.dataTransfer )
};
if ( domEvent.type == 'drop' || domEvent.type == 'dragover' ) {
evtData.dropRange = getDropViewRange( this.view, domEvent );
}
this.fire( domEvent.type, domEvent, evtData );
}
}
function getDropViewRange( view, domEvent ) {
const domDoc = domEvent.target.ownerDocument;
const x = domEvent.clientX;
const y = domEvent.clientY;
let domRange;
// Webkit & Blink.
if ( domDoc.caretRangeFromPoint && domDoc.caretRangeFromPoint( x, y ) ) {
domRange = domDoc.caretRangeFromPoint( x, y );
}
// FF.
else if ( domEvent.rangeParent ) {
domRange = domDoc.createRange();
domRange.setStart( domEvent.rangeParent, domEvent.rangeOffset );
domRange.collapse( true );
}
if ( domRange ) {
return view.domConverter.domRangeToView( domRange );
}
return null;
}
/**
* Fired as a continuation of the {@link #event:paste} and {@link #event:drop} events.
*
* It is a part of the {@glink framework/guides/deep-dive/clipboard#input-pipeline clipboard input pipeline}.
*
* This event carries a `dataTransfer` object which comes from the clipboard and whose content should be processed
* and inserted into the editor.
*
* **Note**: This event is not available by default. To make it available, {@link module:clipboard/clipboardobserver~ClipboardObserver}
* needs to be added to the {@link module:engine/view/document~Document} by using the {@link module:engine/view/view~View#addObserver}
* method. This is usually done by the {@link module:clipboard/clipboard~Clipboard} plugin, but if for some reason it is not loaded,
* the observer must be added manually.
*
* @see module:clipboard/clipboardobserver~ClipboardObserver
* @see module:clipboard/clipboard~Clipboard
* @event module:engine/view/document~Document#event:clipboardInput
* @param {Object} data The event data.
* @param {module:clipboard/datatransfer~DataTransfer} data.dataTransfer The data transfer instance.
* @param {'paste'|'drop'} method Whether the event was triggered by a paste or drop operation.
* @param {module:engine/view/element~Element} target The tree view element representing the target.
* @param {Array.<module:engine/view/range~Range>} data.targetRanges Ranges which are the target of the operation
* (usually – into which the content should be inserted).
* If the clipboard input was triggered by a paste operation, this property is not set. If by a drop operation,
* then it is the drop position (which can be different than the selection at the moment of drop).
*/
/**
* Fired when the user drags the content over one of the editing roots of the editor.
*
* Introduced by {@link module:clipboard/clipboardobserver~ClipboardObserver}.
*
* **Note**: This event is not available by default. To make it available, {@link module:clipboard/clipboardobserver~ClipboardObserver}
* needs to be added to the {@link module:engine/view/document~Document} by using the {@link module:engine/view/view~View#addObserver}
* method. This is usually done by the {@link module:clipboard/clipboard~Clipboard} plugin, but if for some reason it is not loaded,
* the observer must be added manually.
*
* @see module:engine/view/document~Document#event:clipboardInput
* @event module:engine/view/document~Document#event:dragover
* @param {module:clipboard/clipboardobserver~ClipboardEventData} data The event data.
*/
/**
* Fired when the user dropped the content into one of the editing roots of the editor.
*
* Introduced by {@link module:clipboard/clipboardobserver~ClipboardObserver}.
*
* **Note**: This event is not available by default. To make it available, {@link module:clipboard/clipboardobserver~ClipboardObserver}
* needs to be added to the {@link module:engine/view/document~Document} by using the {@link module:engine/view/view~View#addObserver}
* method. This is usually done by the {@link module:clipboard/clipboard~Clipboard} plugin, but if for some reason it is not loaded,
* the observer must be added manually.
*
* @see module:engine/view/document~Document#event:clipboardInput
* @event module:engine/view/document~Document#event:drop
* @param {module:clipboard/clipboardobserver~ClipboardEventData} data The event data.
* @param {module:engine/view/range~Range} dropRange The position into which the content is dropped.
*/
/**
* Fired when the user pasted the content into one of the editing roots of the editor.
*
* Introduced by {@link module:clipboard/clipboardobserver~ClipboardObserver}.
*
* **Note**: This event is not available by default. To make it available, {@link module:clipboard/clipboardobserver~ClipboardObserver}
* needs to be added to the {@link module:engine/view/document~Document} by using the {@link module:engine/view/view~View#addObserver}
* method. This is usually done by the {@link module:clipboard/clipboard~Clipboard} plugin, but if for some reason it is not loaded,
* the observer must be added manually.
*
* @see module:engine/view/document~Document#event:clipboardInput
* @event module:engine/view/document~Document#event:paste
* @param {module:clipboard/clipboardobserver~ClipboardEventData} data The event data.
*/
/**
* Fired when the user copied the content from one of the editing roots of the editor.
*
* Introduced by {@link module:clipboard/clipboardobserver~ClipboardObserver}.
*
* **Note**: This event is not available by default. To make it available, {@link module:clipboard/clipboardobserver~ClipboardObserver}
* needs to be added to the {@link module:engine/view/document~Document} by using the {@link module:engine/view/view~View#addObserver}
* method. This is usually done by the {@link module:clipboard/clipboard~Clipboard} plugin, but if for some reason it is not loaded,
* the observer must be added manually.
*
* @see module:clipboard/clipboardobserver~ClipboardObserver
* @event module:engine/view/document~Document#event:copy
* @param {module:clipboard/clipboardobserver~ClipboardEventData} data The event data.
*/
/**
* Fired when the user cut the content from one of the editing roots of the editor.
*
* Introduced by {@link module:clipboard/clipboardobserver~ClipboardObserver}.
*
* **Note**: This event is not available by default. To make it available, {@link module:clipboard/clipboardobserver~ClipboardObserver}
* needs to be added to the {@link module:engine/view/document~Document} by using the {@link module:engine/view/view~View#addObserver}
* method. This is usually done by the {@link module:clipboard/clipboard~Clipboard} plugin, but if for some reason it is not loaded,
* the observer must be added manually.
*
* @see module:clipboard/clipboardobserver~ClipboardObserver
* @event module:engine/view/document~Document#event:cut
* @param {module:clipboard/clipboardobserver~ClipboardEventData} data The event data.
*/
/**
* The value of the {@link module:engine/view/document~Document#event:paste},
* {@link module:engine/view/document~Document#event:copy} and {@link module:engine/view/document~Document#event:cut} events.
*
* In order to access the clipboard data, use the `dataTransfer` property.
*
* @class module:clipboard/clipboardobserver~ClipboardEventData
* @extends module:engine/view/observer/domeventdata~DomEventData
*/
/**
* The data transfer instance.
*
* @readonly
* @member {module:clipboard/datatransfer~DataTransfer} module:clipboard/clipboardobserver~ClipboardEventData#dataTransfer
*/
/**
* Fired as a continuation of the {@link #event:dragover} event.
*
* It is a part of the {@glink framework/guides/deep-dive/clipboard#input-pipeline clipboard input pipeline}.
*
* This event carries a `dataTransfer` object which comes from the clipboard and whose content should be processed
* and inserted into the editor.
*
* **Note**: This event is not available by default. To make it available, {@link module:clipboard/clipboardobserver~ClipboardObserver}
* needs to be added to the {@link module:engine/view/document~Document} by using the {@link module:engine/view/view~View#addObserver}
* method. This is usually done by the {@link module:clipboard/clipboard~Clipboard} plugin, but if for some reason it is not loaded,
* the observer must be added manually.
*
* @see module:clipboard/clipboardobserver~ClipboardObserver
* @see module:clipboard/clipboard~Clipboard
* @event module:engine/view/document~Document#event:dragging
* @param {Object} data The event data.
* @param {module:clipboard/datatransfer~DataTransfer} data.dataTransfer The data transfer instance.
* @param {module:engine/view/element~Element} target The tree view element representing the target.
* @param {Array.<module:engine/view/range~Range>} data.targetRanges Ranges which are the target of the operation
* (usually – into which the content should be inserted).
* It is the drop position (which can be different than the selection at the moment of drop).
*/
/**
* Fired when the user starts dragging the content in one of the editing roots of the editor.
*
* Introduced by {@link module:clipboard/clipboardobserver~ClipboardObserver}.
*
* **Note**: This event is not available by default. To make it available, {@link module:clipboard/clipboardobserver~ClipboardObserver}
* needs to be added to the {@link module:engine/view/document~Document} by using the {@link module:engine/view/view~View#addObserver}
* method. This is usually done by the {@link module:clipboard/clipboard~Clipboard} plugin, but if for some reason it is not loaded,
* the observer must be added manually.
*
* @see module:engine/view/document~Document#event:clipboardInput
* @event module:engine/view/document~Document#event:dragstart
* @param {module:clipboard/clipboardobserver~ClipboardEventData} data The event data.
*/
/**
* Fired when the user ended dragging the content.
*
* Introduced by {@link module:clipboard/clipboardobserver~ClipboardObserver}.
*
* **Note**: This event is not available by default. To make it available, {@link module:clipboard/clipboardobserver~ClipboardObserver}
* needs to be added to the {@link module:engine/view/document~Document} by using the {@link module:engine/view/view~View#addObserver}
* method. This is usually done by the {@link module:clipboard/clipboard~Clipboard} plugin, but if for some reason it is not loaded,
* the observer must be added manually.
*
* @see module:engine/view/document~Document#event:clipboardInput
* @event module:engine/view/document~Document#event:dragend
* @param {module:clipboard/clipboardobserver~ClipboardEventData} data The event data.
*/
/**
* Fired when the user drags the content into one of the editing roots of the editor.
*
* Introduced by {@link module:clipboard/clipboardobserver~ClipboardObserver}.
*
* **Note**: This event is not available by default. To make it available, {@link module:clipboard/clipboardobserver~ClipboardObserver}
* needs to be added to the {@link module:engine/view/document~Document} by using the {@link module:engine/view/view~View#addObserver}
* method. This is usually done by the {@link module:clipboard/clipboard~Clipboard} plugin, but if for some reason it is not loaded,
* the observer must be added manually.
*
* @see module:engine/view/document~Document#event:clipboardInput
* @event module:engine/view/document~Document#event:dragenter
* @param {module:clipboard/clipboardobserver~ClipboardEventData} data The event data.
*/
/**
* Fired when the user drags the content out of one of the editing roots of the editor.
*
* Introduced by {@link module:clipboard/clipboardobserver~ClipboardObserver}.
*
* **Note**: This event is not available by default. To make it available, {@link module:clipboard/clipboardobserver~ClipboardObserver}
* needs to be added to the {@link module:engine/view/document~Document} by using the {@link module:engine/view/view~View#addObserver}
* method. This is usually done by the {@link module:clipboard/clipboard~Clipboard} plugin, but if for some reason it is not loaded,
* the observer must be added manually.
*
* @see module:engine/view/document~Document#event:clipboardInput
* @event module:engine/view/document~Document#event:dragleave
* @param {module:clipboard/clipboardobserver~ClipboardEventData} data The event data.
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-clipboard/src/clipboardpipeline.js":
/*!*****************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-clipboard/src/clipboardpipeline.js ***!
\*****************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ClipboardPipeline)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_core_src_plugin__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-core/src/plugin */ "./node_modules/@ckeditor/ckeditor5-core/src/plugin.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_eventinfo__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/eventinfo */ "./node_modules/@ckeditor/ckeditor5-utils/src/eventinfo.js");
/* harmony import */ var _clipboardobserver__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./clipboardobserver */ "./node_modules/@ckeditor/ckeditor5-clipboard/src/clipboardobserver.js");
/* harmony import */ var _utils_plaintexttohtml__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./utils/plaintexttohtml */ "./node_modules/@ckeditor/ckeditor5-clipboard/src/utils/plaintexttohtml.js");
/* harmony import */ var _utils_normalizeclipboarddata__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./utils/normalizeclipboarddata */ "./node_modules/@ckeditor/ckeditor5-clipboard/src/utils/normalizeclipboarddata.js");
/* harmony import */ var _utils_viewtoplaintext_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./utils/viewtoplaintext.js */ "./node_modules/@ckeditor/ckeditor5-clipboard/src/utils/viewtoplaintext.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module clipboard/clipboardpipeline
*/
// Input pipeline events overview:
//
// ┌──────────────────────┐ ┌──────────────────────┐
// │ view.Document │ │ view.Document │
// │ paste │ │ drop │
// └───────────┬──────────┘ └───────────┬──────────┘
// │ │
// └────────────────┌────────────────┘
// │
// ┌─────────V────────┐
// │ view.Document │ Retrieves text/html or text/plain from data.dataTransfer
// │ clipboardInput │ and processes it to view.DocumentFragment.
// └─────────┬────────┘
// │
// ┌───────────V───────────┐
// │ ClipboardPipeline │ Converts view.DocumentFragment to model.DocumentFragment.
// │ inputTransformation │
// └───────────┬───────────┘
// │
// ┌──────────V──────────┐
// │ ClipboardPipeline │ Calls model.insertContent().
// │ contentInsertion │
// └─────────────────────┘
//
//
// Output pipeline events overview:
//
// ┌──────────────────────┐ ┌──────────────────────┐
// │ view.Document │ │ view.Document │ Retrieves the selected model.DocumentFragment
// │ copy │ │ cut │ and converts it to view.DocumentFragment.
// └───────────┬──────────┘ └───────────┬──────────┘
// │ │
// └────────────────┌────────────────┘
// │
// ┌─────────V────────┐
// │ view.Document │ Processes view.DocumentFragment to text/html and text/plain
// │ clipboardOutput │ and stores the results in data.dataTransfer.
// └──────────────────┘
//
/**
* The clipboard pipeline feature. It is responsible for intercepting the `paste` and `drop` events and
* passing the pasted content through a series of events in order to insert it into the editor's content.
* It also handles the `cut` and `copy` events to fill the native clipboard with the serialized editor's data.
*
* # Input pipeline
*
* The behavior of the default handlers (all at a `low` priority):
*
* ## Event: `paste` or `drop`
*
* 1. Translates the event data.
* 2. Fires the {@link module:engine/view/document~Document#event:clipboardInput `view.Document#clipboardInput`} event.
*
* ## Event: `view.Document#clipboardInput`
*
* 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
* from the last point.
* 2. Otherwise, it retrieves `text/html` or `text/plain` from `data.dataTransfer`.
* 3. Normalizes the raw data by applying simple filters on string data.
* 4. Processes the raw data to {@link module:engine/view/documentfragment~DocumentFragment `view.DocumentFragment`} with the
* {@link module:engine/controller/datacontroller~DataController#htmlProcessor `DataController#htmlProcessor`}.
* 5. Fires the {@link module:clipboard/clipboardpipeline~ClipboardPipeline#event:inputTransformation
* `ClipboardPipeline#inputTransformation`} event with the view document fragment in the `data.content` event field.
*
* ## Event: `ClipboardPipeline#inputTransformation`
*
* 1. Converts {@link module:engine/view/documentfragment~DocumentFragment `view.DocumentFragment`} from the `data.content` field to
* {@link module:engine/model/documentfragment~DocumentFragment `model.DocumentFragment`}.
* 2. Fires the {@link module:clipboard/clipboardpipeline~ClipboardPipeline#event:contentInsertion `ClipboardPipeline#contentInsertion`}
* event with the model document fragment in the `data.content` event field.
* **Note**: The `ClipboardPipeline#contentInsertion` event is fired within a model change block to allow other handlers
* to run in the same block without post-fixers called in between (i.e., the selection post-fixer).
*
* ## Event: `ClipboardPipeline#contentInsertion`
*
* 1. Calls {@link module:engine/model/model~Model#insertContent `model.insertContent()`} to insert `data.content`
* at the current selection position.
*
* # Output pipeline
*
* The behavior of the default handlers (all at a `low` priority):
*
* ## Event: `copy`, `cut` or `dragstart`
*
* 1. Retrieves the selected {@link module:engine/model/documentfragment~DocumentFragment `model.DocumentFragment`} by calling
* {@link module:engine/model/model~Model#getSelectedContent `model#getSelectedContent()`}.
* 2. Converts the model document fragment to {@link module:engine/view/documentfragment~DocumentFragment `view.DocumentFragment`}.
* 3. Fires the {@link module:engine/view/document~Document#event:clipboardOutput `view.Document#clipboardOutput`} event
* with the view document fragment in the `data.content` event field.
*
* ## Event: `view.Document#clipboardOutput`
*
* 1. Processes `data.content` to HTML and plain text with the
* {@link module:engine/controller/datacontroller~DataController#htmlProcessor `DataController#htmlProcessor`}.
* 2. Updates the `data.dataTransfer` data for `text/html` and `text/plain` with the processed data.
* 3. For the `cut` method, calls {@link module:engine/model/model~Model#deleteContent `model.deleteContent()`}
* on the current selection.
*
* Read more about the clipboard integration in the {@glink framework/guides/deep-dive/clipboard clipboard deep dive guide}.
*
* @extends module:core/plugin~Plugin
*/
class ClipboardPipeline extends _ckeditor_ckeditor5_core_src_plugin__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* @inheritDoc
*/
static get pluginName() {
return 'ClipboardPipeline';
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const view = editor.editing.view;
view.addObserver( _clipboardobserver__WEBPACK_IMPORTED_MODULE_2__["default"] );
this._setupPasteDrop();
this._setupCopyCut();
}
/**
* The clipboard paste pipeline.
*
* @private
*/
_setupPasteDrop() {
const editor = this.editor;
const model = editor.model;
const view = editor.editing.view;
const viewDocument = view.document;
// Pasting and dropping is disabled when editor is in the read-only mode.
// See: https://github.com/ckeditor/ckeditor5-clipboard/issues/26.
this.listenTo( viewDocument, 'clipboardInput', evt => {
if ( editor.isReadOnly ) {
evt.stop();
}
}, { priority: 'highest' } );
this.listenTo( viewDocument, 'clipboardInput', ( evt, data ) => {
const dataTransfer = data.dataTransfer;
let content = data.content || '';
// Some feature could already inject content in the higher priority event handler (i.e., codeBlock).
if ( !content ) {
if ( dataTransfer.getData( 'text/html' ) ) {
content = (0,_utils_normalizeclipboarddata__WEBPACK_IMPORTED_MODULE_4__["default"])( dataTransfer.getData( 'text/html' ) );
} else if ( dataTransfer.getData( 'text/plain' ) ) {
content = (0,_utils_plaintexttohtml__WEBPACK_IMPORTED_MODULE_3__["default"])( dataTransfer.getData( 'text/plain' ) );
}
content = this.editor.data.htmlProcessor.toView( content );
}
const eventInfo = new _ckeditor_ckeditor5_utils_src_eventinfo__WEBPACK_IMPORTED_MODULE_1__["default"]( this, 'inputTransformation' );
this.fire( eventInfo, {
content,
dataTransfer,
targetRanges: data.targetRanges,
method: data.method
} );
// If CKEditor handled the input, do not bubble the original event any further.
// This helps external integrations recognize this fact and act accordingly.
// https://github.com/ckeditor/ckeditor5-upload/issues/92
if ( eventInfo.stop.called ) {
evt.stop();
}
view.scrollToTheSelection();
}, { priority: 'low' } );
this.listenTo( this, 'inputTransformation', ( evt, data ) => {
if ( data.content.isEmpty ) {
return;
}
const dataController = this.editor.data;
// Convert the pasted content into a model document fragment.
// The conversion is contextual, but in this case an "all allowed" context is needed
// and for that we use the $clipboardHolder item.
const modelFragment = dataController.toModel( data.content, '$clipboardHolder' );
if ( modelFragment.childCount == 0 ) {
return;
}
evt.stop();
// Fire content insertion event in a single change block to allow other handlers to run in the same block
// without post-fixers called in between (i.e., the selection post-fixer).
model.change( () => {
this.fire( 'contentInsertion', {
content: modelFragment,
method: data.method,
dataTransfer: data.dataTransfer,
targetRanges: data.targetRanges
} );
} );
}, { priority: 'low' } );
this.listenTo( this, 'contentInsertion', ( evt, data ) => {
data.resultRange = model.insertContent( data.content );
}, { priority: 'low' } );
}
/**
* The clipboard copy/cut pipeline.
*
* @private
*/
_setupCopyCut() {
const editor = this.editor;
const modelDocument = editor.model.document;
const view = editor.editing.view;
const viewDocument = view.document;
function onCopyCut( evt, data ) {
const dataTransfer = data.dataTransfer;
data.preventDefault();
const content = editor.data.toView( editor.model.getSelectedContent( modelDocument.selection ) );
viewDocument.fire( 'clipboardOutput', { dataTransfer, content, method: evt.name } );
}
this.listenTo( viewDocument, 'copy', onCopyCut, { priority: 'low' } );
this.listenTo( viewDocument, 'cut', ( evt, data ) => {
// Cutting is disabled when editor is in the read-only mode.
// See: https://github.com/ckeditor/ckeditor5-clipboard/issues/26.
if ( editor.isReadOnly ) {
data.preventDefault();
} else {
onCopyCut( evt, data );
}
}, { priority: 'low' } );
this.listenTo( viewDocument, 'clipboardOutput', ( evt, data ) => {
if ( !data.content.isEmpty ) {
data.dataTransfer.setData( 'text/html', this.editor.data.htmlProcessor.toData( data.content ) );
data.dataTransfer.setData( 'text/plain', (0,_utils_viewtoplaintext_js__WEBPACK_IMPORTED_MODULE_5__["default"])( data.content ) );
}
if ( data.method == 'cut' ) {
editor.model.deleteContent( modelDocument.selection );
}
}, { priority: 'low' } );
}
}
/**
* Fired with the `content`, `dataTransfer`, `method`, and `targetRanges` properties:
*
* * The `content` which comes from the clipboard (it was pasted or dropped) should be processed in order to be inserted into the editor.
* * The `dataTransfer` object is available in case the transformation functions need access to the raw clipboard data.
* * The `method` indicates the original DOM event (for example `'drop'` or `'paste'`).
* * The `targetRanges` property is an array of view ranges (it is available only for `'drop'`).
*
* It is a part of the {@glink framework/guides/deep-dive/clipboard#input-pipeline clipboard input pipeline}.
*
* **Note**: You should not stop this event if you want to change the input data. You should modify the `content` property instead.
*
* @see module:clipboard/clipboardobserver~ClipboardObserver
* @see module:clipboard/clipboardpipeline~ClipboardPipeline
* @event module:clipboard/clipboardpipeline~ClipboardPipeline#event:inputTransformation
* @param {Object} data The event data.
* @param {module:engine/view/documentfragment~DocumentFragment} data.content The event data. The content to be inserted into the editor.
* It can be modified by event listeners. Read more about the clipboard pipelines in
* the {@glink framework/guides/deep-dive/clipboard clipboard deep dive guide}.
* @param {module:clipboard/datatransfer~DataTransfer} data.dataTransfer The data transfer instance.
* @param {'paste'|'drop'} data.method Whether the event was triggered by a paste or drop operation.
* @param {Array.<module:engine/view/range~Range>} data.targetRanges The target drop ranges.
*/
/**
* Fired with the `content`, `dataTransfer`, `method`, and `targetRanges` properties:
*
* * The `content` which comes from the clipboard (was pasted or dropped) should be processed in order to be inserted into the editor.
* * The `dataTransfer` object is available in case the transformation functions need access to the raw clipboard data.
* * The `method` indicates the original DOM event (for example `'drop'` or `'paste'`).
* * The `targetRanges` property is an array of view ranges (it is available only for `'drop'`).
*
* Event handlers can modify the content according to the final insertion position.
*
* It is a part of the {@glink framework/guides/deep-dive/clipboard#input-pipeline clipboard input pipeline}.
*
* **Note**: You should not stop this event if you want to change the input data. You should modify the `content` property instead.
*
* @see module:clipboard/clipboardobserver~ClipboardObserver
* @see module:clipboard/clipboardpipeline~ClipboardPipeline
* @see module:clipboard/clipboardpipeline~ClipboardPipeline#event:inputTransformation
* @event module:clipboard/clipboardpipeline~ClipboardPipeline#event:contentInsertion
* @param {Object} data The event data.
* @param {module:engine/model/documentfragment~DocumentFragment} data.content The event data. The content to be inserted into the editor.
* Read more about the clipboard pipelines in the {@glink framework/guides/deep-dive/clipboard clipboard deep dive guide}.
* @param {module:clipboard/datatransfer~DataTransfer} data.dataTransfer The data transfer instance.
* @param {'paste'|'drop'} data.method Whether the event was triggered by a paste or drop operation.
* @param {Array.<module:engine/view/range~Range>} data.targetRanges The target drop ranges.
* @param {module:engine/model/range~Range} data.resultRange The result of the `model.insertContent()` call
* (inserted by the event handler at a low priority).
*/
/**
* Fired on {@link module:engine/view/document~Document#event:copy} and {@link module:engine/view/document~Document#event:cut}
* with a copy of the selected content. The content can be processed before it ends up in the clipboard.
*
* It is a part of the {@glink framework/guides/deep-dive/clipboard#output-pipeline clipboard output pipeline}.
*
* @see module:clipboard/clipboardobserver~ClipboardObserver
* @see module:clipboard/clipboardpipeline~ClipboardPipeline
* @event module:engine/view/document~Document#event:clipboardOutput
* @param {module:clipboard/clipboardpipeline~ClipboardOutputEventData} data The event data.
*/
/**
* The value of the {@link module:engine/view/document~Document#event:clipboardOutput} event.
*
* @class module:clipboard/clipboardpipeline~ClipboardOutputEventData
*/
/**
* The data transfer instance.
*
* @readonly
* @member {module:clipboard/datatransfer~DataTransfer} module:clipboard/clipboardpipeline~ClipboardOutputEventData#dataTransfer
*/
/**
* Content to be put into the clipboard. It can be modified by the event listeners.
* Read more about the clipboard pipelines in the {@glink framework/guides/deep-dive/clipboard clipboard deep dive guide}.
*
* @member {module:engine/view/documentfragment~DocumentFragment} module:clipboard/clipboardpipeline~ClipboardOutputEventData#content
*/
/**
* Whether the event was triggered by a copy or cut operation.
*
* @member {'copy'|'cut'} module:clipboard/clipboardpipeline~ClipboardOutputEventData#method
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-clipboard/src/datatransfer.js":
/*!************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-clipboard/src/datatransfer.js ***!
\************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ DataTransfer)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module clipboard/datatransfer
*/
/**
* A facade over the native [`DataTransfer`](https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer) object.
*/
class DataTransfer {
constructor( nativeDataTransfer ) {
/**
* The array of files created from the native `DataTransfer#files` or `DataTransfer#items`.
*
* @readonly
* @member {Array.<File>} #files
*/
this.files = getFiles( nativeDataTransfer );
/**
* The native DataTransfer object.
*
* @private
* @member {DataTransfer} #_native
*/
this._native = nativeDataTransfer;
}
/**
* Returns an array of available native content types.
*
* @returns {Array.<String>}
*/
get types() {
return this._native.types;
}
/**
* Gets the data from the data transfer by its MIME type.
*
* dataTransfer.getData( 'text/plain' );
*
* @param {String} type The MIME type. E.g. `text/html` or `text/plain`.
* @returns {String}
*/
getData( type ) {
return this._native.getData( type );
}
/**
* Sets the data in the data transfer.
*
* @param {String} type The MIME type. E.g. `text/html` or `text/plain`.
* @param {String} data
*/
setData( type, data ) {
this._native.setData( type, data );
}
/**
* The effect that is allowed for a drag operation.
*
* @param {String} value
*/
set effectAllowed( value ) {
this._native.effectAllowed = value;
}
get effectAllowed() {
return this._native.effectAllowed;
}
/**
* The actual drop effect.
*
* @param {String} value
*/
set dropEffect( value ) {
this._native.dropEffect = value;
}
get dropEffect() {
return this._native.dropEffect;
}
/**
* Whether the dragging operation was canceled.
*
* @returns {Boolean}
*/
get isCanceled() {
return this._native.dropEffect == 'none' || !!this._native.mozUserCancelled;
}
}
function getFiles( nativeDataTransfer ) {
// DataTransfer.files and items are array-like and might not have an iterable interface.
const files = Array.from( nativeDataTransfer.files || [] );
const items = Array.from( nativeDataTransfer.items || [] );
if ( files.length ) {
return files;
}
// Chrome has empty DataTransfer.files, but allows getting files through the items interface.
return items
.filter( item => item.kind === 'file' )
.map( item => item.getAsFile() );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-clipboard/src/dragdrop.js":
/*!********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-clipboard/src/dragdrop.js ***!
\********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ DragDrop)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_core_src_plugin__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-core/src/plugin */ "./node_modules/@ckeditor/ckeditor5-core/src/plugin.js");
/* harmony import */ var _ckeditor_ckeditor5_engine_src_model_liverange__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-engine/src/model/liverange */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/liverange.js");
/* harmony import */ var _ckeditor_ckeditor5_engine_src_view_observer_mouseobserver__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @ckeditor/ckeditor5-engine/src/view/observer/mouseobserver */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/mouseobserver.js");
/* harmony import */ var _ckeditor_ckeditor5_widget_src_widget__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @ckeditor/ckeditor5-widget/src/widget */ "./node_modules/@ckeditor/ckeditor5-widget/src/widget.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_uid__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/uid */ "./node_modules/@ckeditor/ckeditor5-utils/src/uid.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_env__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/env */ "./node_modules/@ckeditor/ckeditor5-utils/src/env.js");
/* harmony import */ var _ckeditor_ckeditor5_widget_src_utils__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! @ckeditor/ckeditor5-widget/src/utils */ "./node_modules/@ckeditor/ckeditor5-widget/src/utils.js");
/* harmony import */ var _clipboardpipeline__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./clipboardpipeline */ "./node_modules/@ckeditor/ckeditor5-clipboard/src/clipboardpipeline.js");
/* harmony import */ var _clipboardobserver__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./clipboardobserver */ "./node_modules/@ckeditor/ckeditor5-clipboard/src/clipboardobserver.js");
/* harmony import */ var lodash_es__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! lodash-es */ "./node_modules/lodash-es/throttle.js");
/* harmony import */ var _theme_clipboard_css__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ../theme/clipboard.css */ "./node_modules/@ckeditor/ckeditor5-clipboard/theme/clipboard.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module clipboard/dragdrop
*/
/* globals setTimeout, clearTimeout */
// Drag and drop events overview:
//
// ┌──────────────────┐
// │ mousedown │ Sets the draggable attribute.
// └─────────┬────────┘
// │
// └─────────────────────┐
// │ │
// │ ┌─────────V────────┐
// │ │ mouseup │ Dragging did not start, removes the draggable attribute.
// │ └──────────────────┘
// │
// ┌─────────V────────┐ Retrieves the selected model.DocumentFragment
// │ dragstart │ and converts it to view.DocumentFragment.
// └─────────┬────────┘
// │
// ┌─────────V────────┐ Processes view.DocumentFragment to text/html and text/plain
// │ clipboardOutput │ and stores the results in data.dataTransfer.
// └─────────┬────────┘
// │
// │ DOM dragover
// ┌────────────┐
// │ │
// ┌─────────V────────┐ │
// │ dragging │ │ Updates the drop target marker.
// └─────────┬────────┘ │
// │ │
// ┌─────────────└────────────┘
// │ │ │
// │ ┌─────────V────────┐ │
// │ │ dragleave │ │ Removes the drop target marker.
// │ └─────────┬────────┘ │
// │ │ │
// ┌───│─────────────┘ │
// │ │ │ │
// │ │ ┌─────────V────────┐ │
// │ │ │ dragenter │ │ Focuses the editor view.
// │ │ └─────────┬────────┘ │
// │ │ │ │
// │ │ └────────────┘
// │ │
// │ └─────────────┐
// │ │ │
// │ │ ┌─────────V────────┐
// └───┐ │ drop │ (The default handler of the clipboard pipeline).
// │ └─────────┬────────┘
// │ │
// │ ┌─────────V────────┐ Resolves the final data.targetRanges.
// │ │ clipboardInput │ Aborts if dropping on dragged content.
// │ └─────────┬────────┘
// │ │
// │ ┌─────────V────────┐
// │ │ clipboardInput │ (The default handler of the clipboard pipeline).
// │ └─────────┬────────┘
// │ │
// │ ┌───────────V───────────┐
// │ │ inputTransformation │ (The default handler of the clipboard pipeline).
// │ └───────────┬───────────┘
// │ │
// │ ┌──────────V──────────┐
// │ │ contentInsertion │ Updates the document selection to drop range.
// │ └──────────┬──────────┘
// │ │
// │ ┌──────────V──────────┐
// │ │ contentInsertion │ (The default handler of the clipboard pipeline).
// │ └──────────┬──────────┘
// │ │
// │ ┌──────────V──────────┐
// │ │ contentInsertion │ Removes the content from the original range if the insertion was successful.
// │ └──────────┬──────────┘
// │ │
// └─────────────┐
// │
// ┌─────────V────────┐
// │ dragend │ Removes the drop marker and cleans the state.
// └──────────────────┘
//
/**
* The drag and drop feature. It works on top of the {@link module:clipboard/clipboardpipeline~ClipboardPipeline}.
*
* Read more about the clipboard integration in the {@glink framework/guides/deep-dive/clipboard clipboard deep dive guide}.
*
* @extends module:core/plugin~Plugin
*/
class DragDrop extends _ckeditor_ckeditor5_core_src_plugin__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* @inheritDoc
*/
static get pluginName() {
return 'DragDrop';
}
/**
* @inheritDoc
*/
static get requires() {
return [ _clipboardpipeline__WEBPACK_IMPORTED_MODULE_7__["default"], _ckeditor_ckeditor5_widget_src_widget__WEBPACK_IMPORTED_MODULE_3__["default"] ];
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const view = editor.editing.view;
/**
* The live range over the original content that is being dragged.
*
* @private
* @type {module:engine/model/liverange~LiveRange}
*/
this._draggedRange = null;
/**
* The UID of current dragging that is used to verify if the drop started in the same editor as the drag start.
*
* **Note**: This is a workaround for broken 'dragend' events (they are not fired if the source text node got removed).
*
* @private
* @type {String}
*/
this._draggingUid = '';
/**
* The reference to the model element that currently has a `draggable` attribute set (it is set while dragging).
*
* @private
* @type {module:engine/model/element~Element}
*/
this._draggableElement = null;
/**
* A throttled callback updating the drop marker.
*
* @private
* @type {Function}
*/
this._updateDropMarkerThrottled = (0,lodash_es__WEBPACK_IMPORTED_MODULE_10__["default"])( targetRange => this._updateDropMarker( targetRange ), 40 );
/**
* A delayed callback removing the drop marker.
*
* @private
* @type {Function}
*/
this._removeDropMarkerDelayed = delay( () => this._removeDropMarker(), 40 );
/**
* A delayed callback removing draggable attributes.
*
* @private
* @type {Function}
*/
this._clearDraggableAttributesDelayed = delay( () => this._clearDraggableAttributes(), 40 );
view.addObserver( _clipboardobserver__WEBPACK_IMPORTED_MODULE_8__["default"] );
view.addObserver( _ckeditor_ckeditor5_engine_src_view_observer_mouseobserver__WEBPACK_IMPORTED_MODULE_2__["default"] );
this._setupDragging();
this._setupContentInsertionIntegration();
this._setupClipboardInputIntegration();
this._setupDropMarker();
this._setupDraggableAttributeHandling();
this.listenTo( editor, 'change:isReadOnly', ( evt, name, isReadOnly ) => {
if ( isReadOnly ) {
this.forceDisabled( 'readOnlyMode' );
} else {
this.clearForceDisabled( 'readOnlyMode' );
}
} );
this.on( 'change:isEnabled', ( evt, name, isEnabled ) => {
if ( !isEnabled ) {
this._finalizeDragging( false );
}
} );
if ( _ckeditor_ckeditor5_utils_src_env__WEBPACK_IMPORTED_MODULE_5__["default"].isAndroid ) {
this.forceDisabled( 'noAndroidSupport' );
}
}
/**
* @inheritDoc
*/
destroy() {
if ( this._draggedRange ) {
this._draggedRange.detach();
this._draggedRange = null;
}
this._updateDropMarkerThrottled.cancel();
this._removeDropMarkerDelayed.cancel();
this._clearDraggableAttributesDelayed.cancel();
return super.destroy();
}
/**
* Drag and drop events handling.
*
* @private
*/
_setupDragging() {
const editor = this.editor;
const model = editor.model;
const modelDocument = model.document;
const view = editor.editing.view;
const viewDocument = view.document;
// The handler for the drag start; it is responsible for setting data transfer object.
this.listenTo( viewDocument, 'dragstart', ( evt, data ) => {
const selection = modelDocument.selection;
// Don't drag the editable element itself.
if ( data.target && data.target.is( 'editableElement' ) ) {
data.preventDefault();
return;
}
// TODO we could clone this node somewhere and style it to match editing view but without handles,
// selection outline, WTA buttons, etc.
// data.dataTransfer._native.setDragImage( data.domTarget, 0, 0 );
// Check if this is dragstart over the widget (but not a nested editable).
const draggableWidget = data.target ? findDraggableWidget( data.target ) : null;
if ( draggableWidget ) {
const modelElement = editor.editing.mapper.toModelElement( draggableWidget );
this._draggedRange = _ckeditor_ckeditor5_engine_src_model_liverange__WEBPACK_IMPORTED_MODULE_1__["default"].fromRange( model.createRangeOn( modelElement ) );
// Disable toolbars so they won't obscure the drop area.
if ( editor.plugins.has( 'WidgetToolbarRepository' ) ) {
editor.plugins.get( 'WidgetToolbarRepository' ).forceDisabled( 'dragDrop' );
}
}
// If this was not a widget we should check if we need to drag some text content.
else if ( !viewDocument.selection.isCollapsed ) {
const selectedElement = viewDocument.selection.getSelectedElement();
if ( !selectedElement || !(0,_ckeditor_ckeditor5_widget_src_utils__WEBPACK_IMPORTED_MODULE_6__.isWidget)( selectedElement ) ) {
this._draggedRange = _ckeditor_ckeditor5_engine_src_model_liverange__WEBPACK_IMPORTED_MODULE_1__["default"].fromRange( selection.getFirstRange() );
}
}
if ( !this._draggedRange ) {
data.preventDefault();
return;
}
this._draggingUid = (0,_ckeditor_ckeditor5_utils_src_uid__WEBPACK_IMPORTED_MODULE_4__["default"])();
data.dataTransfer.effectAllowed = this.isEnabled ? 'copyMove' : 'copy';
data.dataTransfer.setData( 'application/ckeditor5-dragging-uid', this._draggingUid );
const draggedSelection = model.createSelection( this._draggedRange.toRange() );
const content = editor.data.toView( model.getSelectedContent( draggedSelection ) );
viewDocument.fire( 'clipboardOutput', { dataTransfer: data.dataTransfer, content, method: evt.name } );
if ( !this.isEnabled ) {
this._draggedRange.detach();
this._draggedRange = null;
this._draggingUid = '';
}
}, { priority: 'low' } );
// The handler for finalizing drag and drop. It should always be triggered after dragging completes
// even if it was completed in a different application.
// Note: This is not fired if source text node got removed while downcasting a marker.
this.listenTo( viewDocument, 'dragend', ( evt, data ) => {
this._finalizeDragging( !data.dataTransfer.isCanceled && data.dataTransfer.dropEffect == 'move' );
}, { priority: 'low' } );
// Dragging over the editable.
this.listenTo( viewDocument, 'dragenter', () => {
if ( !this.isEnabled ) {
return;
}
view.focus();
} );
// Dragging out of the editable.
this.listenTo( viewDocument, 'dragleave', () => {
// We do not know if the mouse left the editor or just some element in it, so let us wait a few milliseconds
// to check if 'dragover' is not fired.
this._removeDropMarkerDelayed();
} );
// Handler for moving dragged content over the target area.
this.listenTo( viewDocument, 'dragging', ( evt, data ) => {
if ( !this.isEnabled ) {
data.dataTransfer.dropEffect = 'none';
return;
}
this._removeDropMarkerDelayed.cancel();
const targetRange = findDropTargetRange( editor, data.targetRanges, data.target );
// If this is content being dragged from another editor, moving out of current editor instance
// is not possible until 'dragend' event case will be fixed.
if ( !this._draggedRange ) {
data.dataTransfer.dropEffect = 'copy';
}
// In Firefox it is already set and effect allowed remains the same as originally set.
if ( !_ckeditor_ckeditor5_utils_src_env__WEBPACK_IMPORTED_MODULE_5__["default"].isGecko ) {
if ( data.dataTransfer.effectAllowed == 'copy' ) {
data.dataTransfer.dropEffect = 'copy';
} else if ( [ 'all', 'copyMove' ].includes( data.dataTransfer.effectAllowed ) ) {
data.dataTransfer.dropEffect = 'move';
}
}
/* istanbul ignore else */
if ( targetRange ) {
this._updateDropMarkerThrottled( targetRange );
}
}, { priority: 'low' } );
}
/**
* Integration with the `clipboardInput` event.
*
* @private
*/
_setupClipboardInputIntegration() {
const editor = this.editor;
const view = editor.editing.view;
const viewDocument = view.document;
// Update the event target ranges and abort dropping if dropping over itself.
this.listenTo( viewDocument, 'clipboardInput', ( evt, data ) => {
if ( data.method != 'drop' ) {
return;
}
const targetRange = findDropTargetRange( editor, data.targetRanges, data.target );
// The dragging markers must be removed after searching for the target range because sometimes
// the target lands on the marker itself.
this._removeDropMarker();
/* istanbul ignore if */
if ( !targetRange ) {
this._finalizeDragging( false );
evt.stop();
return;
}
// Since we cannot rely on the drag end event, we must check if the local drag range is from the current drag and drop
// or it is from some previous not cleared one.
if ( this._draggedRange && this._draggingUid != data.dataTransfer.getData( 'application/ckeditor5-dragging-uid' ) ) {
this._draggedRange.detach();
this._draggedRange = null;
this._draggingUid = '';
}
// Do not do anything if some content was dragged within the same document to the same position.
const isMove = getFinalDropEffect( data.dataTransfer ) == 'move';
if ( isMove && this._draggedRange && this._draggedRange.containsRange( targetRange, true ) ) {
this._finalizeDragging( false );
evt.stop();
return;
}
// Override the target ranges with the one adjusted to the best one for a drop.
data.targetRanges = [ editor.editing.mapper.toViewRange( targetRange ) ];
}, { priority: 'high' } );
}
/**
* Integration with the `contentInsertion` event of the clipboard pipeline.
*
* @private
*/
_setupContentInsertionIntegration() {
const clipboardPipeline = this.editor.plugins.get( _clipboardpipeline__WEBPACK_IMPORTED_MODULE_7__["default"] );
clipboardPipeline.on( 'contentInsertion', ( evt, data ) => {
if ( !this.isEnabled || data.method !== 'drop' ) {
return;
}
// Update the selection to the target range in the same change block to avoid selection post-fixing
// and to be able to clone text attributes for plain text dropping.
const ranges = data.targetRanges.map( viewRange => this.editor.editing.mapper.toModelRange( viewRange ) );
this.editor.model.change( writer => writer.setSelection( ranges ) );
}, { priority: 'high' } );
clipboardPipeline.on( 'contentInsertion', ( evt, data ) => {
if ( !this.isEnabled || data.method !== 'drop' ) {
return;
}
// Remove dragged range content, remove markers, clean after dragging.
const isMove = getFinalDropEffect( data.dataTransfer ) == 'move';
// Whether any content was inserted (insertion might fail if the schema is disallowing some elements
// (for example an image caption allows only the content of a block but not blocks themselves.
// Some integrations might not return valid range (i.e., table pasting).
const isSuccess = !data.resultRange || !data.resultRange.isCollapsed;
this._finalizeDragging( isSuccess && isMove );
}, { priority: 'lowest' } );
}
/**
* Adds listeners that add the `draggable` attribute to the elements while the mouse button is down so the dragging could start.
*
* @private
*/
_setupDraggableAttributeHandling() {
const editor = this.editor;
const view = editor.editing.view;
const viewDocument = view.document;
// Add the 'draggable' attribute to the widget while pressing the selection handle.
// This is required for widgets to be draggable. In Chrome it will enable dragging text nodes.
this.listenTo( viewDocument, 'mousedown', ( evt, data ) => {
// The lack of data can be caused by editor tests firing fake mouse events. This should not occur
// in real-life scenarios but this greatly simplifies editor tests that would otherwise fail a lot.
if ( _ckeditor_ckeditor5_utils_src_env__WEBPACK_IMPORTED_MODULE_5__["default"].isAndroid || !data ) {
return;
}
this._clearDraggableAttributesDelayed.cancel();
// Check if this is a mousedown over the widget (but not a nested editable).
let draggableElement = findDraggableWidget( data.target );
// Note: There is a limitation that if more than a widget is selected (a widget and some text)
// and dragging starts on the widget, then only the widget is dragged.
// If this was not a widget then we should check if we need to drag some text content.
// In Chrome set a 'draggable' attribute on closest editable to allow immediate dragging of the selected text range.
// In Firefox this is not needed. In Safari it makes the whole editable draggable (not just textual content).
// Disabled in read-only mode because draggable="true" + contenteditable="false" results
// in not firing selectionchange event ever, which makes the selection stuck in read-only mode.
if ( _ckeditor_ckeditor5_utils_src_env__WEBPACK_IMPORTED_MODULE_5__["default"].isBlink && !editor.isReadOnly && !draggableElement && !viewDocument.selection.isCollapsed ) {
const selectedElement = viewDocument.selection.getSelectedElement();
if ( !selectedElement || !(0,_ckeditor_ckeditor5_widget_src_utils__WEBPACK_IMPORTED_MODULE_6__.isWidget)( selectedElement ) ) {
draggableElement = viewDocument.selection.editableElement;
}
}
if ( draggableElement ) {
view.change( writer => {
writer.setAttribute( 'draggable', 'true', draggableElement );
} );
// Keep the reference to the model element in case the view element gets removed while dragging.
this._draggableElement = editor.editing.mapper.toModelElement( draggableElement );
}
} );
// Remove the draggable attribute in case no dragging started (only mousedown + mouseup).
this.listenTo( viewDocument, 'mouseup', () => {
if ( !_ckeditor_ckeditor5_utils_src_env__WEBPACK_IMPORTED_MODULE_5__["default"].isAndroid ) {
this._clearDraggableAttributesDelayed();
}
} );
}
/**
* Removes the `draggable` attribute from the element that was used for dragging.
*
* @private
*/
_clearDraggableAttributes() {
const editing = this.editor.editing;
editing.view.change( writer => {
// Remove 'draggable' attribute.
if ( this._draggableElement && this._draggableElement.root.rootName != '$graveyard' ) {
writer.removeAttribute( 'draggable', editing.mapper.toViewElement( this._draggableElement ) );
}
this._draggableElement = null;
} );
}
/**
* Creates downcast conversion for the drop target marker.
*
* @private
*/
_setupDropMarker() {
const editor = this.editor;
// Drop marker conversion for hovering over widgets.
editor.conversion.for( 'editingDowncast' ).markerToHighlight( {
model: 'drop-target',
view: {
classes: [ 'ck-clipboard-drop-target-range' ]
}
} );
// Drop marker conversion for in text drop target.
editor.conversion.for( 'editingDowncast' ).markerToElement( {
model: 'drop-target',
view: ( data, { writer } ) => {
const inText = editor.model.schema.checkChild( data.markerRange.start, '$text' );
if ( !inText ) {
return;
}
return writer.createUIElement( 'span', { class: 'ck ck-clipboard-drop-target-position' }, function( domDocument ) {
const domElement = this.toDomElement( domDocument );
// Using word joiner to make this marker as high as text and also making text not break on marker.
domElement.innerHTML = '⁠<span></span>⁠';
return domElement;
} );
}
} );
}
/**
* Updates the drop target marker to the provided range.
*
* @private
* @param {module:engine/model/range~Range} targetRange The range to set the marker to.
*/
_updateDropMarker( targetRange ) {
const editor = this.editor;
const markers = editor.model.markers;
editor.model.change( writer => {
if ( markers.has( 'drop-target' ) ) {
if ( !markers.get( 'drop-target' ).getRange().isEqual( targetRange ) ) {
writer.updateMarker( 'drop-target', { range: targetRange } );
}
} else {
writer.addMarker( 'drop-target', {
range: targetRange,
usingOperation: false,
affectsData: false
} );
}
} );
}
/**
* Removes the drop target marker.
*
* @private
*/
_removeDropMarker() {
const model = this.editor.model;
this._removeDropMarkerDelayed.cancel();
this._updateDropMarkerThrottled.cancel();
if ( model.markers.has( 'drop-target' ) ) {
model.change( writer => {
writer.removeMarker( 'drop-target' );
} );
}
}
/**
* Deletes the dragged content from its original range and clears the dragging state.
*
* @private
* @param {Boolean} moved Whether the move succeeded.
*/
_finalizeDragging( moved ) {
const editor = this.editor;
const model = editor.model;
this._removeDropMarker();
this._clearDraggableAttributes();
if ( editor.plugins.has( 'WidgetToolbarRepository' ) ) {
editor.plugins.get( 'WidgetToolbarRepository' ).clearForceDisabled( 'dragDrop' );
}
this._draggingUid = '';
if ( !this._draggedRange ) {
return;
}
// Delete moved content.
if ( moved && this.isEnabled ) {
model.deleteContent( model.createSelection( this._draggedRange ), { doNotAutoparagraph: true } );
}
this._draggedRange.detach();
this._draggedRange = null;
}
}
// Returns fixed selection range for given position and target element.
//
// @param {module:core/editor/editor~Editor} editor
// @param {Array.<module:engine/view/range~Range>} targetViewRanges
// @param {module:engine/view/element~Element} targetViewElement
// @returns {module:engine/model/range~Range|null}
function findDropTargetRange( editor, targetViewRanges, targetViewElement ) {
const model = editor.model;
const mapper = editor.editing.mapper;
let range = null;
const targetViewPosition = targetViewRanges ? targetViewRanges[ 0 ].start : null;
// A UIElement is not a valid drop element, use parent (this could be a drop marker or any other UIElement).
if ( targetViewElement.is( 'uiElement' ) ) {
targetViewElement = targetViewElement.parent;
}
// Quick win if the target is a widget (but not a nested editable).
range = findDropTargetRangeOnWidget( editor, targetViewElement );
if ( range ) {
return range;
}
// The easiest part is over, now we need to move to the model space.
// Find target model element and position.
const targetModelElement = getClosestMappedModelElement( editor, targetViewElement );
const targetModelPosition = targetViewPosition ? mapper.toModelPosition( targetViewPosition ) : null;
// There is no target position while hovering over an empty table cell.
// In Safari, target position can be empty while hovering over a widget (e.g., a page-break).
// Find the drop position inside the element.
if ( !targetModelPosition ) {
return findDropTargetRangeInElement( editor, targetModelElement );
}
// Check if target position is between blocks and adjust drop position to the next object.
// This is because while hovering over a root element next to a widget the target position can jump in crazy places.
range = findDropTargetRangeBetweenBlocks( editor, targetModelPosition, targetModelElement );
if ( range ) {
return range;
}
// Try fixing selection position.
// In Firefox, the target position lands before widgets but in other browsers it tends to land after a widget.
range = model.schema.getNearestSelectionRange( targetModelPosition, _ckeditor_ckeditor5_utils_src_env__WEBPACK_IMPORTED_MODULE_5__["default"].isGecko ? 'forward' : 'backward' );
if ( range ) {
return range;
}
// There is no valid selection position inside the current limit element so find a closest object ancestor.
// This happens if the model position lands directly in the <table> element itself (view target element was a `<td>`
// so a nested editable, but view target position was directly in the `<figure>` element).
return findDropTargetRangeOnAncestorObject( editor, targetModelPosition.parent );
}
// Returns fixed selection range for a given position and a target element if it is over the widget but not over its nested editable.
//
// @param {module:core/editor/editor~Editor} editor
// @param {module:engine/view/element~Element} targetViewElement
// @returns {module:engine/model/range~Range|null}
function findDropTargetRangeOnWidget( editor, targetViewElement ) {
const model = editor.model;
const mapper = editor.editing.mapper;
// Quick win if the target is a widget.
if ( (0,_ckeditor_ckeditor5_widget_src_utils__WEBPACK_IMPORTED_MODULE_6__.isWidget)( targetViewElement ) ) {
return model.createRangeOn( mapper.toModelElement( targetViewElement ) );
}
// Check if we are deeper over a widget (but not over a nested editable).
if ( !targetViewElement.is( 'editableElement' ) ) {
// Find a closest ancestor that is either a widget or an editable element...
const ancestor = targetViewElement.findAncestor( node => (0,_ckeditor_ckeditor5_widget_src_utils__WEBPACK_IMPORTED_MODULE_6__.isWidget)( node ) || node.is( 'editableElement' ) );
// ...and if the widget was closer then it is a drop target.
if ( (0,_ckeditor_ckeditor5_widget_src_utils__WEBPACK_IMPORTED_MODULE_6__.isWidget)( ancestor ) ) {
return model.createRangeOn( mapper.toModelElement( ancestor ) );
}
}
return null;
}
// Returns fixed selection range inside a model element.
//
// @param {module:core/editor/editor~Editor} editor
// @param {module:engine/model/element~Element} targetModelElement
// @returns {module:engine/model/range~Range}
function findDropTargetRangeInElement( editor, targetModelElement ) {
const model = editor.model;
const schema = model.schema;
const positionAtElementStart = model.createPositionAt( targetModelElement, 0 );
return schema.getNearestSelectionRange( positionAtElementStart, 'forward' );
}
// Returns fixed selection range for a given position and a target element if the drop is between blocks.
//
// @param {module:core/editor/editor~Editor} editor
// @param {module:engine/model/position~Position} targetModelPosition
// @param {module:engine/model/element~Element} targetModelElement
// @returns {module:engine/model/range~Range|null}
function findDropTargetRangeBetweenBlocks( editor, targetModelPosition, targetModelElement ) {
const model = editor.model;
// Check if target is between blocks.
if ( !model.schema.checkChild( targetModelElement, '$block' ) ) {
return null;
}
// Find position between blocks.
const positionAtElementStart = model.createPositionAt( targetModelElement, 0 );
// Get the common part of the path (inside the target element and the target position).
const commonPath = targetModelPosition.path.slice( 0, positionAtElementStart.path.length );
// Position between the blocks.
const betweenBlocksPosition = model.createPositionFromPath( targetModelPosition.root, commonPath );
const nodeAfter = betweenBlocksPosition.nodeAfter;
// Adjust drop position to the next object.
// This is because while hovering over a root element next to a widget the target position can jump in crazy places.
if ( nodeAfter && model.schema.isObject( nodeAfter ) ) {
return model.createRangeOn( nodeAfter );
}
return null;
}
// Returns a selection range on the ancestor object.
//
// @param {module:core/editor/editor~Editor} editor
// @param {module:engine/model/element~Element} element
// @returns {module:engine/model/range~Range}
function findDropTargetRangeOnAncestorObject( editor, element ) {
const model = editor.model;
while ( element ) {
if ( model.schema.isObject( element ) ) {
return model.createRangeOn( element );
}
element = element.parent;
}
}
// Returns the closest model element for the specified view element.
//
// @param {module:core/editor/editor~Editor} editor
// @param {module:engine/view/element~Element} element
// @returns {module:engine/model/element~Element}
function getClosestMappedModelElement( editor, element ) {
const mapper = editor.editing.mapper;
const view = editor.editing.view;
const targetModelElement = mapper.toModelElement( element );
if ( targetModelElement ) {
return targetModelElement;
}
// Find mapped ancestor if the target is inside not mapped element (for example inline code element).
const viewPosition = view.createPositionBefore( element );
const viewElement = mapper.findMappedViewAncestor( viewPosition );
return mapper.toModelElement( viewElement );
}
// Returns the drop effect that should be a result of dragging the content.
// This function is handling a quirk when checking the effect in the 'drop' DOM event.
function getFinalDropEffect( dataTransfer ) {
if ( _ckeditor_ckeditor5_utils_src_env__WEBPACK_IMPORTED_MODULE_5__["default"].isGecko ) {
return dataTransfer.dropEffect;
}
return [ 'all', 'copyMove' ].includes( dataTransfer.effectAllowed ) ? 'move' : 'copy';
}
// Returns a function wrapper that will trigger a function after a specified wait time.
// The timeout can be canceled by calling the cancel function on the returned wrapped function.
//
// @param {Function} func The function to wrap.
// @param {Number} wait The timeout in ms.
// @returns {Function}
function delay( func, wait ) {
let timer;
function delayed( ...args ) {
delayed.cancel();
timer = setTimeout( () => func( ...args ), wait );
}
delayed.cancel = () => {
clearTimeout( timer );
};
return delayed;
}
// Returns a widget element that should be dragged.
//
// @param {module:engine/view/element~Element} target
// @returns {module:engine/view/element~Element}
function findDraggableWidget( target ) {
// This is directly an editable so not a widget for sure.
if ( target.is( 'editableElement' ) ) {
return null;
}
// TODO: Let's have a isWidgetSelectionHandleDomElement() helper in ckeditor5-widget utils.
if ( target.hasClass( 'ck-widget__selection-handle' ) ) {
return target.findAncestor( _ckeditor_ckeditor5_widget_src_utils__WEBPACK_IMPORTED_MODULE_6__.isWidget );
}
// Direct hit on a widget.
if ( (0,_ckeditor_ckeditor5_widget_src_utils__WEBPACK_IMPORTED_MODULE_6__.isWidget)( target ) ) {
return target;
}
// Find closest ancestor that is either a widget or an editable element...
const ancestor = target.findAncestor( node => (0,_ckeditor_ckeditor5_widget_src_utils__WEBPACK_IMPORTED_MODULE_6__.isWidget)( node ) || node.is( 'editableElement' ) );
// ...and if closer was the widget then enable dragging it.
if ( (0,_ckeditor_ckeditor5_widget_src_utils__WEBPACK_IMPORTED_MODULE_6__.isWidget)( ancestor ) ) {
return ancestor;
}
return null;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-clipboard/src/index.js":
/*!*****************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-clipboard/src/index.js ***!
\*****************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "Clipboard": () => (/* reexport safe */ _clipboard__WEBPACK_IMPORTED_MODULE_0__["default"]),
/* harmony export */ "ClipboardPipeline": () => (/* reexport safe */ _clipboardpipeline__WEBPACK_IMPORTED_MODULE_1__["default"]),
/* harmony export */ "DragDrop": () => (/* reexport safe */ _dragdrop__WEBPACK_IMPORTED_MODULE_2__["default"]),
/* harmony export */ "PastePlainText": () => (/* reexport safe */ _pasteplaintext__WEBPACK_IMPORTED_MODULE_3__["default"])
/* harmony export */ });
/* harmony import */ var _clipboard__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./clipboard */ "./node_modules/@ckeditor/ckeditor5-clipboard/src/clipboard.js");
/* harmony import */ var _clipboardpipeline__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./clipboardpipeline */ "./node_modules/@ckeditor/ckeditor5-clipboard/src/clipboardpipeline.js");
/* harmony import */ var _dragdrop__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./dragdrop */ "./node_modules/@ckeditor/ckeditor5-clipboard/src/dragdrop.js");
/* harmony import */ var _pasteplaintext__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./pasteplaintext */ "./node_modules/@ckeditor/ckeditor5-clipboard/src/pasteplaintext.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module clipboard
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-clipboard/src/pasteplaintext.js":
/*!**************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-clipboard/src/pasteplaintext.js ***!
\**************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ PastePlainText)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_core_src_plugin__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-core/src/plugin */ "./node_modules/@ckeditor/ckeditor5-core/src/plugin.js");
/* harmony import */ var _clipboardobserver__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./clipboardobserver */ "./node_modules/@ckeditor/ckeditor5-clipboard/src/clipboardobserver.js");
/* harmony import */ var _clipboardpipeline__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./clipboardpipeline */ "./node_modules/@ckeditor/ckeditor5-clipboard/src/clipboardpipeline.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module clipboard/pasteplaintext
*/
/**
* The plugin detects the user's intention to paste plain text.
*
* For example, it detects the <kbd>Ctrl/Cmd</kbd> + <kbd>Shift</kbd> + <kbd>V</kbd> keystroke.
*
* @extends module:core/plugin~Plugin
*/
class PastePlainText extends _ckeditor_ckeditor5_core_src_plugin__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* @inheritDoc
*/
static get pluginName() {
return 'PastePlainText';
}
/**
* @inheritDoc
*/
static get requires() {
return [ _clipboardpipeline__WEBPACK_IMPORTED_MODULE_2__["default"] ];
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const model = editor.model;
const view = editor.editing.view;
const viewDocument = view.document;
const selection = model.document.selection;
let shiftPressed = false;
view.addObserver( _clipboardobserver__WEBPACK_IMPORTED_MODULE_1__["default"] );
this.listenTo( viewDocument, 'keydown', ( evt, data ) => {
shiftPressed = data.shiftKey;
} );
editor.plugins.get( _clipboardpipeline__WEBPACK_IMPORTED_MODULE_2__["default"] ).on( 'contentInsertion', ( evt, data ) => {
// Plain text can be determined based on the event flag (#7799) or auto-detection (#1006). If detected,
// preserve selection attributes on pasted items.
if ( !shiftPressed && !isPlainTextFragment( data.content, model.schema ) ) {
return;
}
model.change( writer => {
// Formatting attributes should be preserved.
const textAttributes = Array.from( selection.getAttributes() )
.filter( ( [ key ] ) => model.schema.getAttributeProperties( key ).isFormatting );
if ( !selection.isCollapsed ) {
model.deleteContent( selection, { doNotAutoparagraph: true } );
}
// Also preserve other attributes if they survived the content deletion (because they were not fully selected).
// For example linkHref is not a formatting attribute but it should be preserved if pasted text was in the middle
// of a link.
textAttributes.push( ...selection.getAttributes() );
const range = writer.createRangeIn( data.content );
for ( const item of range.getItems() ) {
if ( item.is( '$textProxy' ) ) {
writer.setAttributes( textAttributes, item );
}
}
} );
} );
}
}
// Returns true if specified `documentFragment` represents a plain text.
//
// @param {module:engine/view/documentfragment~DocumentFragment} documentFragment
// @param {module:engine/model/schema~Schema} schema
// @returns {Boolean}
function isPlainTextFragment( documentFragment, schema ) {
if ( documentFragment.childCount > 1 ) {
return false;
}
const child = documentFragment.getChild( 0 );
if ( schema.isObject( child ) ) {
return false;
}
return [ ...child.getAttributeKeys() ].length == 0;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-clipboard/src/utils/normalizeclipboarddata.js":
/*!****************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-clipboard/src/utils/normalizeclipboarddata.js ***!
\****************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ normalizeClipboardData)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module clipboard/utils/normalizeclipboarddata
*/
/**
* Removes some popular browser quirks out of the clipboard data (HTML).
* Removes all HTML comments. These are considered an internal thing and it makes little sense if they leak into the editor data.
*
* @param {String} data The HTML data to normalize.
* @returns {String} Normalized HTML.
*/
function normalizeClipboardData( data ) {
return data
.replace( /<span(?: class="Apple-converted-space"|)>(\s+)<\/span>/g, ( fullMatch, spaces ) => {
// Handle the most popular and problematic case when even a single space becomes an nbsp;.
// Decode those to normal spaces. Read more in https://github.com/ckeditor/ckeditor5-clipboard/issues/2.
if ( spaces.length == 1 ) {
return ' ';
}
return spaces;
} )
// Remove all HTML comments.
.replace( /<!--[\s\S]*?-->/g, '' );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-clipboard/src/utils/plaintexttohtml.js":
/*!*********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-clipboard/src/utils/plaintexttohtml.js ***!
\*********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ plainTextToHtml)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module clipboard/utils/plaintexttohtml
*/
/**
* Converts plain text to its HTML-ized version.
*
* @param {String} text The plain text to convert.
* @returns {String} HTML generated from the plain text.
*/
function plainTextToHtml( text ) {
text = text
// Encode <>.
.replace( /</g, '<' )
.replace( />/g, '>' )
// Creates a paragraph for each double line break.
.replace( /\r?\n\r?\n/g, '</p><p>' )
// Creates a line break for each single line break.
.replace( /\r?\n/g, '<br>' )
// Preserve trailing spaces (only the first and last one – the rest is handled below).
.replace( /^\s/, ' ' )
.replace( /\s$/, ' ' )
// Preserve other subsequent spaces now.
.replace( /\s\s/g, ' ' );
if ( text.includes( '</p><p>' ) || text.includes( '<br>' ) ) {
// If we created paragraphs above, add the trailing ones.
text = `<p>${ text }</p>`;
}
// TODO:
// * What about '\nfoo' vs ' foo'?
return text;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-clipboard/src/utils/viewtoplaintext.js":
/*!*********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-clipboard/src/utils/viewtoplaintext.js ***!
\*********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ viewToPlainText)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module clipboard/utils/viewtoplaintext
*/
// Elements which should not have empty-line padding.
// Most `view.ContainerElement` want to be separate by new-line, but some are creating one structure
// together (like `<li>`) so it is better to separate them by only one "\n".
const smallPaddingElements = [ 'figcaption', 'li' ];
/**
* Converts {@link module:engine/view/item~Item view item} and all of its children to plain text.
*
* @param {module:engine/view/item~Item} viewItem View item to convert.
* @returns {String} Plain text representation of `viewItem`.
*/
function viewToPlainText( viewItem ) {
let text = '';
if ( viewItem.is( '$text' ) || viewItem.is( '$textProxy' ) ) {
// If item is `Text` or `TextProxy` simple take its text data.
text = viewItem.data;
} else if ( viewItem.is( 'element', 'img' ) && viewItem.hasAttribute( 'alt' ) ) {
// Special case for images - use alt attribute if it is provided.
text = viewItem.getAttribute( 'alt' );
} else if ( viewItem.is( 'element', 'br' ) ) {
// A soft break should be converted into a single line break (#8045).
text = '\n';
} else {
// Other elements are document fragments, attribute elements or container elements.
// They don't have their own text value, so convert their children.
let prev = null;
for ( const child of viewItem.getChildren() ) {
const childText = viewToPlainText( child );
// Separate container element children with one or more new-line characters.
if ( prev && ( prev.is( 'containerElement' ) || child.is( 'containerElement' ) ) ) {
if ( smallPaddingElements.includes( prev.name ) || smallPaddingElements.includes( child.name ) ) {
text += '\n';
} else {
text += '\n\n';
}
}
text += childText;
prev = child;
}
}
return text;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-cloud-services/src/cloudservices.js":
/*!******************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-cloud-services/src/cloudservices.js ***!
\******************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ CloudServices)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/* harmony import */ var _cloudservicescore__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./cloudservicescore */ "./node_modules/@ckeditor/ckeditor5-cloud-services/src/cloudservicescore.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module cloud-services/cloudservices
*/
/**
* Plugin introducing the integration between CKEditor 5 and CKEditor Cloud Services .
*
* It initializes the token provider based on
* the {@link module:cloud-services/cloudservices~CloudServicesConfig `config.cloudService`}.
*
* @extends module:core/contextplugin~ContextPlugin
*/
class CloudServices extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.ContextPlugin {
/**
* @inheritdoc
*/
static get pluginName() {
return 'CloudServices';
}
/**
* @inheritDoc
*/
static get requires() {
return [ _cloudservicescore__WEBPACK_IMPORTED_MODULE_2__["default"] ];
}
/**
* @inheritDoc
*/
init() {
const config = this.context.config;
const options = config.get( 'cloudServices' ) || {};
for ( const optionName in options ) {
this[ optionName ] = options[ optionName ];
}
/**
* A map of token object instances keyed by the token URLs.
*
* @private
* @type {Map.<String, module:cloud-services/token~Token>}
*/
this._tokens = new Map();
/**
* The authentication token URL for CKEditor Cloud Services or a callback to the token value promise. See the
* {@link module:cloud-services/cloudservices~CloudServicesConfig#tokenUrl} for more details.
*
* @readonly
* @member {String|Function|undefined} #tokenUrl
*/
/**
* The URL to which the files should be uploaded.
*
* @readonly
* @member {String} #uploadUrl
*/
/**
* Other plugins use this token for the authorization process. It handles token requesting and refreshing.
* Its value is `null` when {@link module:cloud-services/cloudservices~CloudServicesConfig#tokenUrl} is not provided.
*
* @readonly
* @member {module:cloud-services/token~Token|null} #token
*/
if ( !this.tokenUrl ) {
this.token = null;
return;
}
this.token = this.context.plugins.get( 'CloudServicesCore' ).createToken( this.tokenUrl );
this._tokens.set( this.tokenUrl, this.token );
return this.token.init();
}
/**
* Registers an additional authentication token URL for CKEditor Cloud Services or a callback to the token value promise. See the
* {@link module:cloud-services/cloudservices~CloudServicesConfig#tokenUrl} for more details.
*
* @param {String|Function} tokenUrl The authentication token URL for CKEditor Cloud Services or a callback to the token value promise.
* @returns {Promise.<module:cloud-services/token~Token>}
*/
registerTokenUrl( tokenUrl ) {
// Reuse the token instance in case of multiple features using the same token URL.
if ( this._tokens.has( tokenUrl ) ) {
return Promise.resolve( this.getTokenFor( tokenUrl ) );
}
const token = this.context.plugins.get( 'CloudServicesCore' ).createToken( tokenUrl );
this._tokens.set( tokenUrl, token );
return token.init();
}
/**
* Returns an authentication token provider previously registered by {@link #registerTokenUrl}.
*
* @param {String|Function} tokenUrl The authentication token URL for CKEditor Cloud Services or a callback to the token value promise.
* @returns {module:cloud-services/token~Token}
*/
getTokenFor( tokenUrl ) {
const token = this._tokens.get( tokenUrl );
if ( !token ) {
/**
* The provided `tokenUrl` was not registered by {@link module:cloud-services/cloudservices~CloudServices#registerTokenUrl}.
*
* @error cloudservices-token-not-registered
*/
throw new ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__.CKEditorError( 'cloudservices-token-not-registered', this );
}
return token;
}
/**
* @inheritDoc
*/
destroy() {
super.destroy();
for ( const token of this._tokens.values() ) {
token.destroy();
}
}
}
/**
* The configuration of CKEditor Cloud Services. Introduced by the {@link module:cloud-services/cloudservices~CloudServices} plugin.
*
* Read more in {@link module:cloud-services/cloudservices~CloudServicesConfig}.
*
* @member {module:cloud-services/cloudservices~CloudServicesConfig} module:core/editor/editorconfig~EditorConfig#cloudServices
*/
/**
* The configuration for all plugins using CKEditor Cloud Services.
*
* ClassicEditor
* .create( document.querySelector( '#editor' ), {
* cloudServices: {
* tokenUrl: 'https://example.com/cs-token-endpoint',
* uploadUrl: 'https://your-organization-id.cke-cs.com/easyimage/upload/'
* }
* } )
* .then( ... )
* .catch( ... );
*
* See {@link module:core/editor/editorconfig~EditorConfig all editor options}.
*
* @interface CloudServicesConfig
*/
/**
* A token URL or a token request function.
*
* As a string, it should be a URL to the security token endpoint in your application. The role of this endpoint is to securely authorize
* the end users of your application to use [CKEditor Cloud Services](https://ckeditor.com/ckeditor-cloud-services) only
* if they should have access e.g. to upload files with {@glink @cs guides/easy-image/quick-start Easy Image} or to use the
* {@glink @cs guides/collaboration/quick-start Collaboration} service.
*
* ClassicEditor
* .create( document.querySelector( '#editor' ), {
* cloudServices: {
* tokenUrl: 'https://example.com/cs-token-endpoint',
* ...
* }
* } )
* .then( ... )
* .catch( ... );
*
* As a function, it should provide a promise to the token value, so you can highly customize the token and provide your token URL endpoint.
* By using this approach you can set your own headers for the request.
*
* ClassicEditor
* .create( document.querySelector( '#editor' ), {
* cloudServices: {
* tokenUrl: () => new Promise( ( resolve, reject ) => {
* const xhr = new XMLHttpRequest();
*
* xhr.open( 'GET', 'https://example.com/cs-token-endpoint' );
*
* xhr.addEventListener( 'load', () => {
* const statusCode = xhr.status;
* const xhrResponse = xhr.response;
*
* if ( statusCode < 200 || statusCode > 299 ) {
* return reject( new Error( 'Cannot download new token!' ) );
* }
*
* return resolve( xhrResponse );
* } );
*
* xhr.addEventListener( 'error', () => reject( new Error( 'Network Error' ) ) );
* xhr.addEventListener( 'abort', () => reject( new Error( 'Abort' ) ) );
*
* xhr.setRequestHeader( customHeader, customValue );
*
* xhr.send();
* } ),
* ...
* }
* } )
*
* You can find more information about token endpoints in the
* {@glink @cs guides/easy-image/quick-start#create-token-endpoint Cloud Services - Quick start}
* and {@glink @cs guides/security/token-endpoint Cloud Services - Token endpoint} documentation.
*
* Without a properly working token endpoint (token URL) CKEditor plugins will not be able to connect to CKEditor Cloud Services.
*
* @member {String|Function} module:cloud-services/cloudservices~CloudServicesConfig#tokenUrl
*/
/**
* The endpoint URL for [CKEditor Cloud Services](https://ckeditor.com/ckeditor-cloud-services) uploads.
* This option must be set for Easy Image to work correctly.
*
* The upload URL is unique for each customer and can be found in the
* [CKEditor Ecosystem customer dashboard](https://dashboard.ckeditor.com) after subscribing to the Easy Image service.
* To learn how to start using Easy Image, check the {@glink @cs guides/easy-image/quick-start Easy Image - Quick start} documentation.
*
* Note: Make sure to also set the {@link module:cloud-services/cloudservices~CloudServicesConfig#tokenUrl} configuration option.
*
* @member {String} module:cloud-services/cloudservices~CloudServicesConfig#uploadUrl
*/
/**
* The URL for web socket communication, used by the `RealTimeCollaborativeEditing` plugin. Every customer (organization in the CKEditor
* Ecosystem dashboard) has their own, unique URLs to communicate with CKEditor Cloud Services. The URL can be found in the
* CKEditor Ecosystem customer dashboard.
*
* Note: Unlike most plugins, `RealTimeCollaborativeEditing` is not included in any CKEditor 5 build and needs to be installed manually.
* Check [Collaboration overview](https://ckeditor.com/docs/ckeditor5/latest/features/collaboration/overview.html) for more details.
*
* @member {String} module:cloud-services/cloudservices~CloudServicesConfig#webSocketUrl
*/
/**
* An optional parameter used for integration with CKEditor Cloud Services when uploading the editor build to cloud services.
*
* Whenever the editor build or the configuration changes, this parameter should be set to a new, unique value to differentiate
* the new bundle (build + configuration) from the old ones.
*
* @member {String} module:cloud-services/cloudservices~CloudServicesConfig#bundleVersion
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-cloud-services/src/cloudservicescore.js":
/*!**********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-cloud-services/src/cloudservicescore.js ***!
\**********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ CloudServicesCore)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var _token_token__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./token/token */ "./node_modules/@ckeditor/ckeditor5-cloud-services/src/token/token.js");
/* harmony import */ var _uploadgateway_uploadgateway__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./uploadgateway/uploadgateway */ "./node_modules/@ckeditor/ckeditor5-cloud-services/src/uploadgateway/uploadgateway.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module cloud-services/cloudservicescore
*/
/**
* The `CloudServicesCore` plugin exposes the base API for communication with CKEditor Cloud Services.
*
* @extends module:core/contextplugin~ContextPlugin
*/
class CloudServicesCore extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.ContextPlugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'CloudServicesCore';
}
/**
* Creates the {@link module:cloud-services/token~Token} instance.
*
* @param {String|Function} tokenUrlOrRefreshToken Endpoint address to download the token or a callback that provides the token. If the
* value is a function it has to match the {@link module:cloud-services/token~refreshToken} interface.
* @param {Object} [options]
* @param {String} [options.initValue] Initial value of the token.
* @param {Boolean} [options.autoRefresh=true] Specifies whether to start the refresh automatically.
* @returns {module:cloud-services/token~Token}
*/
createToken( tokenUrlOrRefreshToken, options ) {
return new _token_token__WEBPACK_IMPORTED_MODULE_1__["default"]( tokenUrlOrRefreshToken, options );
}
/**
* Creates the {@link module:cloud-services/uploadgateway/uploadgateway~UploadGateway} instance.
*
* @param {module:cloud-services/token~Token} token Token used for authentication.
* @param {String} apiAddress API address.
* @returns {module:cloud-services/uploadgateway/uploadgateway~UploadGateway}
*/
createUploadGateway( token, apiAddress ) {
return new _uploadgateway_uploadgateway__WEBPACK_IMPORTED_MODULE_2__["default"]( token, apiAddress );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-cloud-services/src/token/token.js":
/*!****************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-cloud-services/src/token/token.js ***!
\****************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module cloud-services/token
*/
/* globals XMLHttpRequest, setTimeout, clearTimeout, atob */
const DEFAULT_OPTIONS = { autoRefresh: true };
const DEFAULT_TOKEN_REFRESH_TIMEOUT_TIME = 3600000;
/**
* Class representing the token used for communication with CKEditor Cloud Services.
* Value of the token is retrieving from the specified URL and is refreshed every 1 hour by default.
*
* @mixes ObservableMixin
*/
class Token {
/**
* Creates `Token` instance.
* Method `init` should be called after using the constructor or use `create` method instead.
*
* @param {String|Function} tokenUrlOrRefreshToken Endpoint address to download the token or a callback that provides the token. If the
* value is a function it has to match the {@link module:cloud-services/token~refreshToken} interface.
* @param {Object} options
* @param {String} [options.initValue] Initial value of the token.
* @param {Boolean} [options.autoRefresh=true] Specifies whether to start the refresh automatically.
*/
constructor( tokenUrlOrRefreshToken, options = DEFAULT_OPTIONS ) {
if ( !tokenUrlOrRefreshToken ) {
/**
* A `tokenUrl` must be provided as the first constructor argument.
*
* @error token-missing-token-url
*/
throw new ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_0__.CKEditorError(
'token-missing-token-url',
this
);
}
if ( options.initValue ) {
this._validateTokenValue( options.initValue );
}
/**
* Value of the token.
* The value of the token is null if `initValue` is not provided or `init` method was not called.
* `create` method creates token with initialized value from url.
*
* @name value
* @member {String} #value
* @observable
* @readonly
*/
this.set( 'value', options.initValue );
/**
* Base refreshing function.
*
* @private
* @member {String|Function} #_refresh
*/
if ( typeof tokenUrlOrRefreshToken === 'function' ) {
this._refresh = tokenUrlOrRefreshToken;
} else {
this._refresh = () => defaultRefreshToken( tokenUrlOrRefreshToken );
}
/**
* @type {Object}
* @private
*/
this._options = Object.assign( {}, DEFAULT_OPTIONS, options );
}
/**
* Initializes the token.
*
* @returns {Promise.<module:cloud-services/token~Token>}
*/
init() {
return new Promise( ( resolve, reject ) => {
if ( !this.value ) {
this.refreshToken()
.then( resolve )
.catch( reject );
return;
}
if ( this._options.autoRefresh ) {
this._registerRefreshTokenTimeout();
}
resolve( this );
} );
}
/**
* Refresh token method. Useful in a method form as it can be override in tests.
* @returns {Promise.<String>}
*/
refreshToken() {
return this._refresh()
.then( value => {
this._validateTokenValue( value );
this.set( 'value', value );
if ( this._options.autoRefresh ) {
this._registerRefreshTokenTimeout();
}
} )
.then( () => this );
}
/**
* Destroys token instance. Stops refreshing.
*/
destroy() {
clearTimeout( this._tokenRefreshTimeout );
}
/**
* Checks whether the provided token follows the JSON Web Tokens (JWT) format.
*
* @protected
* @param {String} tokenValue The token to validate.
*/
_validateTokenValue( tokenValue ) {
// The token must be a string.
const isString = typeof tokenValue === 'string';
// The token must be a plain string without quotes ("").
const isPlainString = !/^".*"$/.test( tokenValue );
// JWT token contains 3 parts: header, payload, and signature.
// Each part is separated by a dot.
const isJWTFormat = isString && tokenValue.split( '.' ).length === 3;
if ( !( isPlainString && isJWTFormat ) ) {
/**
* The provided token must follow the [JSON Web Tokens](https://jwt.io/introduction/) format.
*
* @error token-not-in-jwt-format
*/
throw new ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_0__.CKEditorError( 'token-not-in-jwt-format', this );
}
}
/**
* Registers a refresh token timeout for the time taken from token.
*
* @protected
*/
_registerRefreshTokenTimeout() {
const tokenRefreshTimeoutTime = this._getTokenRefreshTimeoutTime();
clearTimeout( this._tokenRefreshTimeout );
this._tokenRefreshTimeout = setTimeout( () => {
this.refreshToken();
}, tokenRefreshTimeoutTime );
}
/**
* Returns token refresh timeout time calculated from expire time in the token payload.
*
* If the token parse fails or the token payload doesn't contain, the default DEFAULT_TOKEN_REFRESH_TIMEOUT_TIME is returned.
*
* @protected
* @returns {Number}
*/
_getTokenRefreshTimeoutTime() {
try {
const [ , binaryTokenPayload ] = this.value.split( '.' );
const { exp: tokenExpireTime } = JSON.parse( atob( binaryTokenPayload ) );
if ( !tokenExpireTime ) {
return DEFAULT_TOKEN_REFRESH_TIMEOUT_TIME;
}
const tokenRefreshTimeoutTime = Math.floor( ( ( tokenExpireTime * 1000 ) - Date.now() ) / 2 );
return tokenRefreshTimeoutTime;
} catch ( err ) {
return DEFAULT_TOKEN_REFRESH_TIMEOUT_TIME;
}
}
/**
* Creates a initialized {@link module:cloud-services/token~Token} instance.
*
* @param {String|Function} tokenUrlOrRefreshToken Endpoint address to download the token or a callback that provides the token. If the
* value is a function it has to match the {@link module:cloud-services/token~refreshToken} interface.
* @param {Object} options
* @param {String} [options.initValue] Initial value of the token.
* @param {Boolean} [options.autoRefresh=true] Specifies whether to start the refresh automatically.
* @returns {Promise.<module:cloud-services/token~Token>}
*/
static create( tokenUrlOrRefreshToken, options = DEFAULT_OPTIONS ) {
const token = new Token( tokenUrlOrRefreshToken, options );
return token.init();
}
}
(0,ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_0__.mix)( Token, ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_0__.ObservableMixin );
/**
* This function is called in a defined interval by the {@link ~Token} class. It also can be invoked manually.
* It should return a promise, which resolves with the new token value.
* If any error occurs it should return a rejected promise with an error message.
*
* @function refreshToken
* @returns {Promise.<String>}
*/
/**
* @private
* @param {String} tokenUrl
*/
function defaultRefreshToken( tokenUrl ) {
return new Promise( ( resolve, reject ) => {
const xhr = new XMLHttpRequest();
xhr.open( 'GET', tokenUrl );
xhr.addEventListener( 'load', () => {
const statusCode = xhr.status;
const xhrResponse = xhr.response;
if ( statusCode < 200 || statusCode > 299 ) {
/**
* Cannot download new token from the provided url.
*
* @error token-cannot-download-new-token
*/
return reject(
new ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_0__.CKEditorError( 'token-cannot-download-new-token', null )
);
}
return resolve( xhrResponse );
} );
xhr.addEventListener( 'error', () => reject( new Error( 'Network Error' ) ) );
xhr.addEventListener( 'abort', () => reject( new Error( 'Abort' ) ) );
xhr.send();
} );
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (Token);
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-cloud-services/src/uploadgateway/fileuploader.js":
/*!*******************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-cloud-services/src/uploadgateway/fileuploader.js ***!
\*******************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ FileUploader)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module cloud-services/uploadgateway/fileuploader
*/
/* globals XMLHttpRequest, FormData, Blob, atob */
const BASE64_HEADER_REG_EXP = /^data:(\S*?);base64,/;
/**
* FileUploader class used to upload single file.
*/
class FileUploader {
/**
* Creates `FileUploader` instance.
*
* @param {Blob|String} fileOrData A blob object or a data string encoded with Base64.
* @param {module:cloud-services/token~Token} token Token used for authentication.
* @param {String} apiAddress API address.
*/
constructor( fileOrData, token, apiAddress ) {
if ( !fileOrData ) {
/**
* File must be provided as the first argument.
*
* @error fileuploader-missing-file
*/
throw new ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_0__.CKEditorError( 'fileuploader-missing-file', null );
}
if ( !token ) {
/**
* Token must be provided as the second argument.
*
* @error fileuploader-missing-token
*/
throw new ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_0__.CKEditorError( 'fileuploader-missing-token', null );
}
if ( !apiAddress ) {
/**
* Api address must be provided as the third argument.
*
* @error fileuploader-missing-api-address
*/
throw new ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_0__.CKEditorError( 'fileuploader-missing-api-address', null );
}
/**
* A file that is being uploaded.
*
* @type {Blob}
*/
this.file = _isBase64( fileOrData ) ? _base64ToBlob( fileOrData ) : fileOrData;
/**
* CKEditor Cloud Services access token.
*
* @type {module:cloud-services/token~Token}
* @private
*/
this._token = token;
/**
* CKEditor Cloud Services API address.
*
* @type {String}
* @private
*/
this._apiAddress = apiAddress;
}
/**
* Registers callback on `progress` event.
*
* @chainable
* @param {Function} callback
* @returns {module:cloud-services/uploadgateway/fileuploader~FileUploader}
*/
onProgress( callback ) {
this.on( 'progress', ( event, data ) => callback( data ) );
return this;
}
/**
* Registers callback on `error` event. Event is called once when error occurs.
*
* @chainable
* @param {Function} callback
* @returns {module:cloud-services/uploadgateway/fileuploader~FileUploader}
*/
onError( callback ) {
this.once( 'error', ( event, data ) => callback( data ) );
return this;
}
/**
* Aborts upload process.
*/
abort() {
this.xhr.abort();
}
/**
* Sends XHR request to API.
*
* @chainable
* @returns {Promise.<Object>}
*/
send() {
this._prepareRequest();
this._attachXHRListeners();
return this._sendRequest();
}
/**
* Prepares XHR request.
*
* @private
*/
_prepareRequest() {
const xhr = new XMLHttpRequest();
xhr.open( 'POST', this._apiAddress );
xhr.setRequestHeader( 'Authorization', this._token.value );
xhr.responseType = 'json';
this.xhr = xhr;
}
/**
* Attaches listeners to the XHR.
*
* @private
*/
_attachXHRListeners() {
const that = this;
const xhr = this.xhr;
xhr.addEventListener( 'error', onError( 'Network Error' ) );
xhr.addEventListener( 'abort', onError( 'Abort' ) );
/* istanbul ignore else */
if ( xhr.upload ) {
xhr.upload.addEventListener( 'progress', event => {
if ( event.lengthComputable ) {
this.fire( 'progress', {
total: event.total,
uploaded: event.loaded
} );
}
} );
}
xhr.addEventListener( 'load', () => {
const statusCode = xhr.status;
const xhrResponse = xhr.response;
if ( statusCode < 200 || statusCode > 299 ) {
return this.fire( 'error', xhrResponse.message || xhrResponse.error );
}
} );
function onError( message ) {
return () => that.fire( 'error', message );
}
}
/**
* Sends XHR request.
*
* @private
*/
_sendRequest() {
const formData = new FormData();
const xhr = this.xhr;
formData.append( 'file', this.file );
return new Promise( ( resolve, reject ) => {
xhr.addEventListener( 'load', () => {
const statusCode = xhr.status;
const xhrResponse = xhr.response;
if ( statusCode < 200 || statusCode > 299 ) {
if ( xhrResponse.message ) {
/**
* Uploading file failed.
*
* @error fileuploader-uploading-data-failed
*/
return reject( new ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_0__.CKEditorError(
'fileuploader-uploading-data-failed',
this,
{ message: xhrResponse.message }
) );
}
return reject( xhrResponse.error );
}
return resolve( xhrResponse );
} );
xhr.addEventListener( 'error', () => reject( new Error( 'Network Error' ) ) );
xhr.addEventListener( 'abort', () => reject( new Error( 'Abort' ) ) );
xhr.send( formData );
} );
}
/**
* Fired when error occurs.
*
* @event error
* @param {String} error Error message
*/
/**
* Fired on upload progress.
*
* @event progress
* @param {Object} status Total and uploaded status
*/
}
(0,ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_0__.mix)( FileUploader, ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_0__.EmitterMixin );
/**
* Transforms Base64 string data into file.
*
* @param {String} base64 String data.
* @param {Number} [sliceSize=512]
* @returns {Blob}
* @private
*/
function _base64ToBlob( base64, sliceSize = 512 ) {
try {
const contentType = base64.match( BASE64_HEADER_REG_EXP )[ 1 ];
const base64Data = atob( base64.replace( BASE64_HEADER_REG_EXP, '' ) );
const byteArrays = [];
for ( let offset = 0; offset < base64Data.length; offset += sliceSize ) {
const slice = base64Data.slice( offset, offset + sliceSize );
const byteNumbers = new Array( slice.length );
for ( let i = 0; i < slice.length; i++ ) {
byteNumbers[ i ] = slice.charCodeAt( i );
}
byteArrays.push( new Uint8Array( byteNumbers ) );
}
return new Blob( byteArrays, { type: contentType } );
} catch ( error ) {
/**
* Problem with decoding Base64 image data.
*
* @error fileuploader-decoding-image-data-error
*/
throw new ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_0__.CKEditorError( 'fileuploader-decoding-image-data-error', null );
}
}
/**
* Checks that string is Base64.
*
* @param {String} string
* @returns {Boolean}
* @private
*/
function _isBase64( string ) {
if ( typeof string !== 'string' ) {
return false;
}
const match = string.match( BASE64_HEADER_REG_EXP );
return !!( match && match.length );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-cloud-services/src/uploadgateway/uploadgateway.js":
/*!********************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-cloud-services/src/uploadgateway/uploadgateway.js ***!
\********************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ UploadGateway)
/* harmony export */ });
/* harmony import */ var _fileuploader__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./fileuploader */ "./node_modules/@ckeditor/ckeditor5-cloud-services/src/uploadgateway/fileuploader.js");
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module cloud-services/uploadgateway/uploadgateway
*/
/**
* UploadGateway abstracts file uploads to CKEditor Cloud Services.
*/
class UploadGateway {
/**
* Creates `UploadGateway` instance.
*
* @param {module:cloud-services/token~Token} token Token used for authentication.
* @param {String} apiAddress API address.
*/
constructor( token, apiAddress ) {
if ( !token ) {
/**
* Token must be provided.
*
* @error uploadgateway-missing-token
*/
throw new ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__.CKEditorError( 'uploadgateway-missing-token', null );
}
if ( !apiAddress ) {
/**
* Api address must be provided.
*
* @error uploadgateway-missing-api-address
*/
throw new ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__.CKEditorError( 'uploadgateway-missing-api-address', null );
}
/**
* CKEditor Cloud Services access token.
*
* @type {module:cloud-services/token~Token}
* @private
*/
this._token = token;
/**
* CKEditor Cloud Services API address.
*
* @type {String}
* @private
*/
this._apiAddress = apiAddress;
}
/**
* Creates a {@link module:cloud-services/uploadgateway/fileuploader~FileUploader} instance that wraps
* file upload process. The file is being sent at a time when the
* {@link module:cloud-services/uploadgateway/fileuploader~FileUploader#send} method is called.
*
* const token = await Token.create( 'https://token-endpoint' );
* new UploadGateway( token, 'https://example.org' )
* .upload( 'FILE' )
* .onProgress( ( data ) => console.log( data ) )
* .send()
* .then( ( response ) => console.log( response ) );
*
* @param {Blob|String} fileOrData A blob object or a data string encoded with Base64.
* @returns {module:cloud-services/uploadgateway/fileuploader~FileUploader} Returns `FileUploader` instance.
*/
upload( fileOrData ) {
return new _fileuploader__WEBPACK_IMPORTED_MODULE_0__["default"]( fileOrData, this._token, this._apiAddress );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-code-block/src/codeblock.js":
/*!**********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-code-block/src/codeblock.js ***!
\**********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ CodeBlock)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var _codeblockediting__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./codeblockediting */ "./node_modules/@ckeditor/ckeditor5-code-block/src/codeblockediting.js");
/* harmony import */ var _codeblockui__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./codeblockui */ "./node_modules/@ckeditor/ckeditor5-code-block/src/codeblockui.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module code-block/codeblock
*/
/**
* The code block plugin.
*
* For more information about this feature check the {@glink api/code-block package page} and the
* {@glink features/code-blocks code block feature guide}.
*
* This is a "glue" plugin that loads the {@link module:code-block/codeblockediting~CodeBlockEditing code block editing feature}
* and the {@link module:code-block/codeblockui~CodeBlockUI code block UI feature}.
*
* @extends module:core/plugin~Plugin
*/
class CodeBlock extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [ _codeblockediting__WEBPACK_IMPORTED_MODULE_1__["default"], _codeblockui__WEBPACK_IMPORTED_MODULE_2__["default"] ];
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'CodeBlock';
}
}
/**
* The configuration of the {@link module:code-block/codeblock~CodeBlock} feature.
*
* Read more in {@link module:code-block/codeblock~CodeBlockConfig}.
*
* @member {module:code-block/codeblock~CodeBlockConfig} module:core/editor/editorconfig~EditorConfig#codeBlock
*/
/**
* The configuration of the {@link module:code-block/codeblock~CodeBlock code block feature}.
*
* ClassicEditor
* .create( editorElement, {
* codeBlock: ... // The code block feature configuration.
* } )
* .then( ... )
* .catch( ... );
*
* See {@link module:core/editor/editorconfig~EditorConfig all editor options}.
*
* @interface CodeBlockConfig
*/
/**
* The code block language descriptor. See {@link module:code-block/codeblock~CodeBlockConfig#languages} to learn more.
*
* {
* language: 'javascript',
* label: 'JavaScript'
* }
*
* @typedef {Object} module:code-block/codeblock~CodeBlockLanguageDefinition
* @property {String} language The name of the language that will be stored in the model attribute. Also, when `class`
* is not specified, it will be used to create the CSS class associated with the language (prefixed by "language-").
* @property {String} label The human–readable label associated with the language and displayed in the UI.
* @property {String} [class] The CSS class associated with the language. When not specified the `language`
* property is used to create a class prefixed by "language-".
*/
/**
* The list of code languages available in the user interface to choose for a particular code block.
*
* The language of the code block is represented as a CSS class (by default prefixed by "language-") set on the
* `<code>` element, both when editing and in the editor data. The CSS class associated with the language
* can be used by third–party code syntax highlighters to detect and apply the correct highlighting.
*
* For instance, this language configuration:
*
* ClassicEditor
* .create( document.querySelector( '#editor' ), {
* codeBlock: {
* languages: [
* // ...
* { language: 'javascript', label: 'JavaScript' },
* // ...
* ]
* }
* } )
* .then( ... )
* .catch( ... );
*
* will result in the following structure of JavaScript code blocks in the editor editing and data:
*
* <pre><code class="language-javascript">window.alert( 'Hello world!' )</code></pre>
*
* You can customize the CSS class by specifying an optional `class` property in the language definition.
* You can set **multiple classes** but **only the first one** will be used as defining language class:
*
* ClassicEditor
* .create( document.querySelector( '#editor' ), {
* codeBlock: {
* languages: [
* // Do not render the CSS class for the plain text code blocks.
* { language: 'plaintext', label: 'Plain text', class: '' },
*
* // Use the "php-code" class for PHP code blocks.
* { language: 'php', label: 'PHP', class: 'php-code' },
*
* // Use the "js" class for JavaScript code blocks.
* // Note that only the first ("js") class will determine the language of the block when loading data.
* { language: 'javascript', label: 'JavaScript', class: 'js javascript js-code' },
*
* // Python code blocks will have the default "language-python" CSS class.
* { language: 'python', label: 'Python' }
* ]
* }
* } )
* .then( ... )
* .catch( ... );
*
* The default value of the language configuration is as follows:
*
* languages: [
* { language: 'plaintext', label: 'Plain text' }, // The default language.
* { language: 'c', label: 'C' },
* { language: 'cs', label: 'C#' },
* { language: 'cpp', label: 'C++' },
* { language: 'css', label: 'CSS' },
* { language: 'diff', label: 'Diff' },
* { language: 'html', label: 'HTML' },
* { language: 'java', label: 'Java' },
* { language: 'javascript', label: 'JavaScript' },
* { language: 'php', label: 'PHP' },
* { language: 'python', label: 'Python' },
* { language: 'ruby', label: 'Ruby' },
* { language: 'typescript', label: 'TypeScript' },
* { language: 'xml', label: 'XML' }
* ]
*
* **Note**: The first language defined in the configuration is considered the default one. This means it will be
* applied to code blocks loaded from the data that have no CSS `class` specified (or no matching `class` in the configuration).
* It will also be used when creating new code blocks using the main UI button. By default it is "Plain text".
*
* @member {Array.<module:code-block/codeblock~CodeBlockLanguageDefinition>} module:code-block/codeblock~CodeBlockConfig#languages
*/
/**
* A sequence of characters inserted or removed from the code block lines when its indentation
* is changed by the user, for instance, using <kbd>Tab</kbd> and <kbd>Shift</kbd>+<kbd>Tab</kbd> keys.
*
* The default value is a single tab character (" ", `\u0009` in Unicode).
*
* This configuration is used by `indentCodeBlock` and `outdentCodeBlock` commands (instances of
* {@link module:code-block/indentcodeblockcommand~IndentCodeBlockCommand}).
*
* **Note**: Setting this configuration to `false` will disable the code block indentation commands
* and associated keystrokes.
*
* @member {String} module:code-block/codeblock~CodeBlockConfig#indentSequence
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-code-block/src/codeblockcommand.js":
/*!*****************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-code-block/src/codeblockcommand.js ***!
\*****************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ CodeBlockCommand)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./utils */ "./node_modules/@ckeditor/ckeditor5-code-block/src/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module code-block/codeblockcommand
*/
/**
* The code block command plugin.
*
* @extends module:core/command~Command
*/
class CodeBlockCommand extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Command {
/**
* @inheritDoc
*/
constructor( editor ) {
super( editor );
/**
* Contains the last used language.
* @protected
* @type {String|null}
*/
this._lastLanguage = null;
}
/**
* Whether the selection starts in a code block.
*
* @observable
* @readonly
* @member {Boolean} #value
*/
/**
* @inheritDoc
*/
refresh() {
this.value = this._getValue();
this.isEnabled = this._checkEnabled();
}
/**
* Executes the command. When the command {@link #value is on}, all topmost code blocks within
* the selection will be removed. If it is off, all selected blocks will be flattened and
* wrapped by a code block.
*
* @fires execute
* @param {Object} [options] Command options.
* @param {String} [options.language] The code block language.
* @param {Boolean} [options.forceValue] If set, it will force the command behavior. If `true`, the command will apply a code block,
* otherwise the command will remove the code block. If not set, the command will act basing on its current value.
* @param {Boolean} [options.usePreviousLanguageChoice] If set on `true` and the `options.language` is not specified, the command
* will apply the previous language (if the command was already executed) when inserting the `codeBlock` element.
*/
execute( options = {} ) {
const editor = this.editor;
const model = editor.model;
const selection = model.document.selection;
const normalizedLanguagesDefs = (0,_utils__WEBPACK_IMPORTED_MODULE_2__.getNormalizedAndLocalizedLanguageDefinitions)( editor );
const firstLanguageInConfig = normalizedLanguagesDefs[ 0 ];
const blocks = Array.from( selection.getSelectedBlocks() );
const value = ( options.forceValue === undefined ) ? !this.value : options.forceValue;
const language = getLanguage( options, this._lastLanguage, firstLanguageInConfig.language );
model.change( writer => {
if ( value ) {
this._applyCodeBlock( writer, blocks, language );
} else {
this._removeCodeBlock( writer, blocks );
}
} );
}
/**
* Checks the command's {@link #value}.
*
* @private
* @returns {Boolean} The current value.
*/
_getValue() {
const selection = this.editor.model.document.selection;
const firstBlock = (0,ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__.first)( selection.getSelectedBlocks() );
const isCodeBlock = !!( firstBlock && firstBlock.is( 'element', 'codeBlock' ) );
return isCodeBlock ? firstBlock.getAttribute( 'language' ) : false;
}
/**
* Checks whether the command can be enabled in the current context.
*
* @private
* @returns {Boolean} Whether the command should be enabled.
*/
_checkEnabled() {
if ( this.value ) {
return true;
}
const selection = this.editor.model.document.selection;
const schema = this.editor.model.schema;
const firstBlock = (0,ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__.first)( selection.getSelectedBlocks() );
if ( !firstBlock ) {
return false;
}
return canBeCodeBlock( schema, firstBlock );
}
/**
* @private
* @param {module:engine/model/writer~Writer} writer
* @param {Array.<module:engine/model/element~Element>} blocks
* @param {String} [language]
*/
_applyCodeBlock( writer, blocks, language ) {
this._lastLanguage = language;
const schema = this.editor.model.schema;
const allowedBlocks = blocks.filter( block => canBeCodeBlock( schema, block ) );
for ( const block of allowedBlocks ) {
writer.rename( block, 'codeBlock' );
writer.setAttribute( 'language', language, block );
schema.removeDisallowedAttributes( [ block ], writer );
// Remove children of the `codeBlock` element that are not allowed. See #9567.
Array.from( block.getChildren() )
.filter( child => !schema.checkChild( block, child ) )
.forEach( child => writer.remove( child ) );
}
allowedBlocks.reverse().forEach( ( currentBlock, i ) => {
const nextBlock = allowedBlocks[ i + 1 ];
if ( currentBlock.previousSibling === nextBlock ) {
writer.appendElement( 'softBreak', nextBlock );
writer.merge( writer.createPositionBefore( currentBlock ) );
}
} );
}
/**
* @private
* @param {module:engine/model/writer~Writer} writer
* @param {Array.<module:engine/model/element~Element>} blocks
*/
_removeCodeBlock( writer, blocks ) {
const codeBlocks = blocks.filter( block => block.is( 'element', 'codeBlock' ) );
for ( const block of codeBlocks ) {
const range = writer.createRangeOn( block );
for ( const item of Array.from( range.getItems() ).reverse() ) {
if ( item.is( 'element', 'softBreak' ) && item.parent.is( 'element', 'codeBlock' ) ) {
const { position } = writer.split( writer.createPositionBefore( item ) );
writer.rename( position.nodeAfter, 'paragraph' );
writer.removeAttribute( 'language', position.nodeAfter );
writer.remove( item );
}
}
writer.rename( block, 'paragraph' );
writer.removeAttribute( 'language', block );
}
}
}
function canBeCodeBlock( schema, element ) {
if ( element.is( 'rootElement' ) || schema.isLimit( element ) ) {
return false;
}
return schema.checkChild( element.parent, 'codeBlock' );
}
// Picks the language for the new code block. If any language is passed as an option,
// it will be returned. Else, if option usePreviousLanguageChoice is true and some
// code block was already created (lastLanguage is not null) then previously used
// language will be returned. If not, it will return default language.
//
// @param {Object} options
// @param {Boolean} [options.usePreviousLanguageChoice]
// @param {String} [options.language]
// @param {String|null} lastLanguage
// @param {String} defaultLanguage
// @return {String}
function getLanguage( options, lastLanguage, defaultLanguage ) {
if ( options.language ) {
return options.language;
}
if ( options.usePreviousLanguageChoice && lastLanguage ) {
return lastLanguage;
}
return defaultLanguage;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-code-block/src/codeblockediting.js":
/*!*****************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-code-block/src/codeblockediting.js ***!
\*****************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ CodeBlockEditing)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_enter__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/enter */ "./node_modules/ckeditor5/src/enter.js");
/* harmony import */ var ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ckeditor5/src/engine */ "./node_modules/ckeditor5/src/engine.js");
/* harmony import */ var _codeblockcommand__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./codeblockcommand */ "./node_modules/@ckeditor/ckeditor5-code-block/src/codeblockcommand.js");
/* harmony import */ var _indentcodeblockcommand__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./indentcodeblockcommand */ "./node_modules/@ckeditor/ckeditor5-code-block/src/indentcodeblockcommand.js");
/* harmony import */ var _outdentcodeblockcommand__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./outdentcodeblockcommand */ "./node_modules/@ckeditor/ckeditor5-code-block/src/outdentcodeblockcommand.js");
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./utils */ "./node_modules/@ckeditor/ckeditor5-code-block/src/utils.js");
/* harmony import */ var _converters__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./converters */ "./node_modules/@ckeditor/ckeditor5-code-block/src/converters.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module code-block/codeblockediting
*/
const DEFAULT_ELEMENT = 'paragraph';
/**
* The editing part of the code block feature.
*
* Introduces the `'codeBlock'` command and the `'codeBlock'` model element.
*
* @extends module:core/plugin~Plugin
*/
class CodeBlockEditing extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'CodeBlockEditing';
}
/**
* @inheritDoc
*/
static get requires() {
return [ ckeditor5_src_enter__WEBPACK_IMPORTED_MODULE_1__.ShiftEnter ];
}
/**
* @inheritDoc
*/
constructor( editor ) {
super( editor );
editor.config.define( 'codeBlock', {
languages: [
{ language: 'plaintext', label: 'Plain text' },
{ language: 'c', label: 'C' },
{ language: 'cs', label: 'C#' },
{ language: 'cpp', label: 'C++' },
{ language: 'css', label: 'CSS' },
{ language: 'diff', label: 'Diff' },
{ language: 'html', label: 'HTML' },
{ language: 'java', label: 'Java' },
{ language: 'javascript', label: 'JavaScript' },
{ language: 'php', label: 'PHP' },
{ language: 'python', label: 'Python' },
{ language: 'ruby', label: 'Ruby' },
{ language: 'typescript', label: 'TypeScript' },
{ language: 'xml', label: 'XML' }
],
// A single tab.
indentSequence: '\t'
} );
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const schema = editor.model.schema;
const model = editor.model;
const view = editor.editing.view;
const normalizedLanguagesDefs = (0,_utils__WEBPACK_IMPORTED_MODULE_6__.getNormalizedAndLocalizedLanguageDefinitions)( editor );
// The main command.
editor.commands.add( 'codeBlock', new _codeblockcommand__WEBPACK_IMPORTED_MODULE_3__["default"]( editor ) );
// Commands that change the indentation.
editor.commands.add( 'indentCodeBlock', new _indentcodeblockcommand__WEBPACK_IMPORTED_MODULE_4__["default"]( editor ) );
editor.commands.add( 'outdentCodeBlock', new _outdentcodeblockcommand__WEBPACK_IMPORTED_MODULE_5__["default"]( editor ) );
const getCommandExecuter = commandName => {
return ( data, cancel ) => {
const command = this.editor.commands.get( commandName );
if ( command.isEnabled ) {
this.editor.execute( commandName );
cancel();
}
};
};
editor.keystrokes.set( 'Tab', getCommandExecuter( 'indentCodeBlock' ) );
editor.keystrokes.set( 'Shift+Tab', getCommandExecuter( 'outdentCodeBlock' ) );
schema.register( 'codeBlock', {
allowWhere: '$block',
allowChildren: '$text',
isBlock: true,
allowAttributes: [ 'language' ]
} );
// Disallow all attributes on $text inside `codeBlock`.
schema.addAttributeCheck( context => {
if ( context.endsWith( 'codeBlock $text' ) ) {
return false;
}
} );
// Disallow object elements inside `codeBlock`. See #9567.
editor.model.schema.addChildCheck( ( context, childDefinition ) => {
if ( context.endsWith( 'codeBlock' ) && childDefinition.isObject ) {
return false;
}
} );
// Conversion.
editor.editing.downcastDispatcher.on( 'insert:codeBlock', (0,_converters__WEBPACK_IMPORTED_MODULE_7__.modelToViewCodeBlockInsertion)( model, normalizedLanguagesDefs, true ) );
editor.data.downcastDispatcher.on( 'insert:codeBlock', (0,_converters__WEBPACK_IMPORTED_MODULE_7__.modelToViewCodeBlockInsertion)( model, normalizedLanguagesDefs ) );
editor.data.downcastDispatcher.on( 'insert:softBreak', (0,_converters__WEBPACK_IMPORTED_MODULE_7__.modelToDataViewSoftBreakInsertion)( model ), { priority: 'high' } );
editor.data.upcastDispatcher.on( 'element:code', (0,_converters__WEBPACK_IMPORTED_MODULE_7__.dataViewToModelCodeBlockInsertion)( view, normalizedLanguagesDefs ) );
editor.data.upcastDispatcher.on( 'text', (0,_converters__WEBPACK_IMPORTED_MODULE_7__.dataViewToModelTextNewlinesInsertion)() );
// Intercept the clipboard input (paste) when the selection is anchored in the code block and force the clipboard
// data to be pasted as a single plain text. Otherwise, the code lines will split the code block and
// "spill out" as separate paragraphs.
this.listenTo( editor.editing.view.document, 'clipboardInput', ( evt, data ) => {
let insertionRange = model.createRange( model.document.selection.anchor );
// Use target ranges in case this is a drop.
if ( data.targetRanges ) {
insertionRange = editor.editing.mapper.toModelRange( data.targetRanges[ 0 ] );
}
if ( !insertionRange.start.parent.is( 'element', 'codeBlock' ) ) {
return;
}
const text = data.dataTransfer.getData( 'text/plain' );
const writer = new ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_2__.UpcastWriter( editor.editing.view.document );
// Pass the view fragment to the default clipboardInput handler.
data.content = (0,_utils__WEBPACK_IMPORTED_MODULE_6__.rawSnippetTextToViewDocumentFragment)( writer, text );
} );
// Make sure multi–line selection is always wrapped in a code block when `getSelectedContent()`
// is used (e.g. clipboard copy). Otherwise, only the raw text will be copied to the clipboard and,
// upon next paste, this bare text will not be inserted as a code block, which is not the best UX.
// Similarly, when the selection in a single line, the selected content should be an inline code
// so it can be pasted later on and retain it's preformatted nature.
this.listenTo( model, 'getSelectedContent', ( evt, [ selection ] ) => {
const anchor = selection.anchor;
if ( selection.isCollapsed || !anchor.parent.is( 'element', 'codeBlock' ) || !anchor.hasSameParentAs( selection.focus ) ) {
return;
}
model.change( writer => {
const docFragment = evt.return;
// fo[o<softBreak></softBreak>b]ar -> <codeBlock language="...">[o<softBreak></softBreak>b]<codeBlock>
if ( docFragment.childCount > 1 || selection.containsEntireContent( anchor.parent ) ) {
const codeBlock = writer.createElement( 'codeBlock', anchor.parent.getAttributes() );
writer.append( docFragment, codeBlock );
const newDocumentFragment = writer.createDocumentFragment();
writer.append( codeBlock, newDocumentFragment );
evt.return = newDocumentFragment;
}
// "f[oo]" -> <$text code="true">oo</text>
else {
const textNode = docFragment.getChild( 0 );
if ( schema.checkAttribute( textNode, 'code' ) ) {
writer.setAttribute( 'code', true, textNode );
}
}
} );
} );
}
/**
* @inheritDoc
*/
afterInit() {
const editor = this.editor;
const commands = editor.commands;
const indent = commands.get( 'indent' );
const outdent = commands.get( 'outdent' );
if ( indent ) {
indent.registerChildCommand( commands.get( 'indentCodeBlock' ) );
}
if ( outdent ) {
outdent.registerChildCommand( commands.get( 'outdentCodeBlock' ) );
}
// Customize the response to the <kbd>Enter</kbd> and <kbd>Shift</kbd>+<kbd>Enter</kbd>
// key press when the selection is in the code block. Upon enter key press we can either
// leave the block if it's "two or three enters" in a row or create a new code block line, preserving
// previous line's indentation.
this.listenTo( editor.editing.view.document, 'enter', ( evt, data ) => {
const positionParent = editor.model.document.selection.getLastPosition().parent;
if ( !positionParent.is( 'element', 'codeBlock' ) ) {
return;
}
if ( !leaveBlockStartOnEnter( editor, data.isSoft ) && !leaveBlockEndOnEnter( editor, data.isSoft ) ) {
breakLineOnEnter( editor );
}
data.preventDefault();
evt.stop();
}, { context: 'pre' } );
}
}
// Normally, when the Enter (or Shift+Enter) key is pressed, a soft line break is to be added to the
// code block. Let's try to follow the indentation of the previous line when possible, for instance:
//
// // Before pressing enter (or shift enter)
// <codeBlock>
// " foo()"[] // Indent of 4 spaces.
// </codeBlock>
//
// // After pressing:
// <codeBlock>
// " foo()" // Indent of 4 spaces.
// <softBreak></softBreak> // A new soft break created by pressing enter.
// " "[] // Retain the indent of 4 spaces.
// </codeBlock>
//
// @param {module:core/editor/editor~Editor} editor
function breakLineOnEnter( editor ) {
const model = editor.model;
const modelDoc = model.document;
const lastSelectionPosition = modelDoc.selection.getLastPosition();
const node = lastSelectionPosition.nodeBefore || lastSelectionPosition.textNode;
let leadingWhiteSpaces;
// Figure out the indentation (white space chars) at the beginning of the line.
if ( node && node.is( '$text' ) ) {
leadingWhiteSpaces = (0,_utils__WEBPACK_IMPORTED_MODULE_6__.getLeadingWhiteSpaces)( node );
}
// Keeping everything in a change block for a single undo step.
editor.model.change( writer => {
editor.execute( 'shiftEnter' );
// If the line before being broken in two had some indentation, let's retain it
// in the new line.
if ( leadingWhiteSpaces ) {
writer.insertText( leadingWhiteSpaces, modelDoc.selection.anchor );
}
} );
}
// Leave the code block when Enter (but NOT Shift+Enter) has been pressed twice at the beginning
// of the code block:
//
// // Before:
// <codeBlock>[]<softBreak></softBreak>foo</codeBlock>
//
// // After pressing:
// <paragraph>[]</paragraph><codeBlock>foo</codeBlock>
//
// @param {module:core/editor/editor~Editor} editor
// @param {Boolean} isSoftEnter When `true`, enter was pressed along with <kbd>Shift</kbd>.
// @returns {Boolean} `true` when selection left the block. `false` if stayed.
function leaveBlockStartOnEnter( editor, isSoftEnter ) {
const model = editor.model;
const modelDoc = model.document;
const view = editor.editing.view;
const lastSelectionPosition = modelDoc.selection.getLastPosition();
const nodeAfter = lastSelectionPosition.nodeAfter;
if ( isSoftEnter || !modelDoc.selection.isCollapsed || !lastSelectionPosition.isAtStart ) {
return false;
}
if ( !isSoftBreakNode( nodeAfter ) ) {
return false;
}
// We're doing everything in a single change block to have a single undo step.
editor.model.change( writer => {
// "Clone" the <codeBlock> in the standard way.
editor.execute( 'enter' );
// The cloned block exists now before the original code block.
const newBlock = modelDoc.selection.anchor.parent.previousSibling;
// Make the cloned <codeBlock> a regular <paragraph> (with clean attributes, so no language).
writer.rename( newBlock, DEFAULT_ELEMENT );
writer.setSelection( newBlock, 'in' );
editor.model.schema.removeDisallowedAttributes( [ newBlock ], writer );
// Remove the <softBreak> that originally followed the selection position.
writer.remove( nodeAfter );
} );
// Eye candy.
view.scrollToTheSelection();
return true;
}
// Leave the code block when Enter (but NOT Shift+Enter) has been pressed twice at the end
// of the code block:
//
// // Before:
// <codeBlock>foo[]</codeBlock>
//
// // After first press:
// <codeBlock>foo<softBreak></softBreak>[]</codeBlock>
//
// // After second press:
// <codeBlock>foo</codeBlock><paragraph>[]</paragraph>
//
// @param {module:core/editor/editor~Editor} editor
// @param {Boolean} isSoftEnter When `true`, enter was pressed along with <kbd>Shift</kbd>.
// @returns {Boolean} `true` when selection left the block. `false` if stayed.
function leaveBlockEndOnEnter( editor, isSoftEnter ) {
const model = editor.model;
const modelDoc = model.document;
const view = editor.editing.view;
const lastSelectionPosition = modelDoc.selection.getLastPosition();
const nodeBefore = lastSelectionPosition.nodeBefore;
let emptyLineRangeToRemoveOnEnter;
if ( isSoftEnter || !modelDoc.selection.isCollapsed || !lastSelectionPosition.isAtEnd || !nodeBefore || !nodeBefore.previousSibling ) {
return false;
}
// When the position is directly preceded by two soft breaks
//
// <codeBlock>foo<softBreak></softBreak><softBreak></softBreak>[]</codeBlock>
//
// it creates the following range that will be cleaned up before leaving:
//
// <codeBlock>foo[<softBreak></softBreak><softBreak></softBreak>]</codeBlock>
//
if ( isSoftBreakNode( nodeBefore ) && isSoftBreakNode( nodeBefore.previousSibling ) ) {
emptyLineRangeToRemoveOnEnter = model.createRange(
model.createPositionBefore( nodeBefore.previousSibling ), model.createPositionAfter( nodeBefore )
);
}
// When there's some text before the position that is
// preceded by two soft breaks and made purely of white–space characters
//
// <codeBlock>foo<softBreak></softBreak><softBreak></softBreak> []</codeBlock>
//
// it creates the following range to clean up before leaving:
//
// <codeBlock>foo[<softBreak></softBreak><softBreak></softBreak> ]</codeBlock>
//
else if (
isEmptyishTextNode( nodeBefore ) &&
isSoftBreakNode( nodeBefore.previousSibling ) &&
isSoftBreakNode( nodeBefore.previousSibling.previousSibling )
) {
emptyLineRangeToRemoveOnEnter = model.createRange(
model.createPositionBefore( nodeBefore.previousSibling.previousSibling ), model.createPositionAfter( nodeBefore )
);
}
// When there's some text before the position that is made purely of white–space characters
// and is preceded by some other text made purely of white–space characters
//
// <codeBlock>foo<softBreak></softBreak> <softBreak></softBreak> []</codeBlock>
//
// it creates the following range to clean up before leaving:
//
// <codeBlock>foo[<softBreak></softBreak> <softBreak></softBreak> ]</codeBlock>
//
else if (
isEmptyishTextNode( nodeBefore ) &&
isSoftBreakNode( nodeBefore.previousSibling ) &&
isEmptyishTextNode( nodeBefore.previousSibling.previousSibling ) &&
isSoftBreakNode( nodeBefore.previousSibling.previousSibling.previousSibling )
) {
emptyLineRangeToRemoveOnEnter = model.createRange(
model.createPositionBefore( nodeBefore.previousSibling.previousSibling.previousSibling ),
model.createPositionAfter( nodeBefore )
);
}
// Not leaving the block in the following cases:
//
// <codeBlock> []</codeBlock>
// <codeBlock> a []</codeBlock>
// <codeBlock>foo<softBreak></softBreak>[]</codeBlock>
// <codeBlock>foo<softBreak></softBreak><softBreak></softBreak>bar[]</codeBlock>
// <codeBlock>foo<softBreak></softBreak><softBreak></softBreak> a []</codeBlock>
//
else {
return false;
}
// We're doing everything in a single change block to have a single undo step.
editor.model.change( writer => {
// Remove the last <softBreak>s and all white space characters that followed them.
writer.remove( emptyLineRangeToRemoveOnEnter );
// "Clone" the <codeBlock> in the standard way.
editor.execute( 'enter' );
const newBlock = modelDoc.selection.anchor.parent;
// Make the cloned <codeBlock> a regular <paragraph> (with clean attributes, so no language).
writer.rename( newBlock, DEFAULT_ELEMENT );
editor.model.schema.removeDisallowedAttributes( [ newBlock ], writer );
} );
// Eye candy.
view.scrollToTheSelection();
return true;
}
function isEmptyishTextNode( node ) {
return node && node.is( '$text' ) && !node.data.match( /\S/ );
}
function isSoftBreakNode( node ) {
return node && node.is( 'element', 'softBreak' );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-code-block/src/codeblockui.js":
/*!************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-code-block/src/codeblockui.js ***!
\************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ CodeBlockUI)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/* harmony import */ var ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ckeditor5/src/ui */ "./node_modules/ckeditor5/src/ui.js");
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./utils */ "./node_modules/@ckeditor/ckeditor5-code-block/src/utils.js");
/* harmony import */ var _theme_icons_codeblock_svg__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../theme/icons/codeblock.svg */ "./node_modules/@ckeditor/ckeditor5-code-block/theme/icons/codeblock.svg");
/* harmony import */ var _theme_codeblock_css__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../theme/codeblock.css */ "./node_modules/@ckeditor/ckeditor5-code-block/theme/codeblock.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module code-block/codeblockui
*/
/**
* The code block UI plugin.
*
* Introduces the `'codeBlock'` dropdown.
*
* @extends module:core/plugin~Plugin
*/
class CodeBlockUI extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'CodeBlockUI';
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const t = editor.t;
const componentFactory = editor.ui.componentFactory;
const normalizedLanguageDefs = (0,_utils__WEBPACK_IMPORTED_MODULE_3__.getNormalizedAndLocalizedLanguageDefinitions)( editor );
componentFactory.add( 'codeBlock', locale => {
const command = editor.commands.get( 'codeBlock' );
const dropdownView = (0,ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_2__.createDropdown)( locale, ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_2__.SplitButtonView );
const splitButtonView = dropdownView.buttonView;
splitButtonView.set( {
label: t( 'Insert code block' ),
tooltip: true,
icon: _theme_icons_codeblock_svg__WEBPACK_IMPORTED_MODULE_4__["default"],
isToggleable: true
} );
splitButtonView.bind( 'isOn' ).to( command, 'value', value => !!value );
splitButtonView.on( 'execute', () => {
editor.execute( 'codeBlock', {
usePreviousLanguageChoice: true
} );
editor.editing.view.focus();
} );
dropdownView.on( 'execute', evt => {
editor.execute( 'codeBlock', {
language: evt.source._codeBlockLanguage,
forceValue: true
} );
editor.editing.view.focus();
} );
dropdownView.class = 'ck-code-block-dropdown';
dropdownView.bind( 'isEnabled' ).to( command );
(0,ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_2__.addListToDropdown)( dropdownView, this._getLanguageListItemDefinitions( normalizedLanguageDefs ) );
return dropdownView;
} );
}
/**
* A helper returning a collection of the `codeBlock` dropdown items representing languages
* available for the user to choose from.
*
* @private
* @param {Array.<module:code-block/codeblock~CodeBlockLanguageDefinition>} normalizedLanguageDefs
* @returns {Iterable.<module:ui/dropdown/utils~ListDropdownItemDefinition>}
*/
_getLanguageListItemDefinitions( normalizedLanguageDefs ) {
const editor = this.editor;
const command = editor.commands.get( 'codeBlock' );
const itemDefinitions = new ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__.Collection();
for ( const languageDef of normalizedLanguageDefs ) {
const definition = {
type: 'button',
model: new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_2__.Model( {
_codeBlockLanguage: languageDef.language,
label: languageDef.label,
withText: true
} )
};
definition.model.bind( 'isOn' ).to( command, 'value', value => {
return value === definition.model._codeBlockLanguage;
} );
itemDefinitions.add( definition );
}
return itemDefinitions;
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-code-block/src/converters.js":
/*!***********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-code-block/src/converters.js ***!
\***********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "dataViewToModelCodeBlockInsertion": () => (/* binding */ dataViewToModelCodeBlockInsertion),
/* harmony export */ "dataViewToModelTextNewlinesInsertion": () => (/* binding */ dataViewToModelTextNewlinesInsertion),
/* harmony export */ "modelToDataViewSoftBreakInsertion": () => (/* binding */ modelToDataViewSoftBreakInsertion),
/* harmony export */ "modelToViewCodeBlockInsertion": () => (/* binding */ modelToViewCodeBlockInsertion)
/* harmony export */ });
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./utils */ "./node_modules/@ckeditor/ckeditor5-code-block/src/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module code-block/converters
*/
/**
* A model-to-view (both editing and data) converter for the `codeBlock` element.
*
* Sample input:
*
* <codeBlock language="javascript">foo();<softBreak></softBreak>bar();</codeBlock>
*
* Sample output (editing):
*
* <pre data-language="JavaScript"><code class="language-javascript">foo();<br />bar();</code></pre>
*
* Sample output (data, see {@link module:code-block/converters~modelToDataViewSoftBreakInsertion}):
*
* <pre><code class="language-javascript">foo();\nbar();</code></pre>
*
* @param {module:engine/model/model~Model} model
* @param {Array.<module:code-block/codeblock~CodeBlockLanguageDefinition>} languageDefs The normalized language
* configuration passed to the feature.
* @param {Boolean} [useLabels=false] When `true`, the `<pre>` element will get a `data-language` attribute with a
* human–readable label of the language. Used only in the editing.
* @returns {Function} Returns a conversion callback.
*/
function modelToViewCodeBlockInsertion( model, languageDefs, useLabels = false ) {
// Language CSS classes:
//
// {
// php: 'language-php',
// python: 'language-python',
// javascript: 'js',
// ...
// }
const languagesToClasses = (0,_utils__WEBPACK_IMPORTED_MODULE_0__.getPropertyAssociation)( languageDefs, 'language', 'class' );
// Language labels:
//
// {
// php: 'PHP',
// python: 'Python',
// javascript: 'JavaScript',
// ...
// }
const languagesToLabels = (0,_utils__WEBPACK_IMPORTED_MODULE_0__.getPropertyAssociation)( languageDefs, 'language', 'label' );
return ( evt, data, conversionApi ) => {
const { writer, mapper, consumable } = conversionApi;
if ( !consumable.consume( data.item, 'insert' ) ) {
return;
}
const codeBlockLanguage = data.item.getAttribute( 'language' );
const targetViewPosition = mapper.toViewPosition( model.createPositionBefore( data.item ) );
const preAttributes = {};
// Attributes added only in the editing view.
if ( useLabels ) {
preAttributes[ 'data-language' ] = languagesToLabels[ codeBlockLanguage ];
preAttributes.spellcheck = 'false';
}
const code = writer.createContainerElement( 'code', {
class: languagesToClasses[ codeBlockLanguage ] || null
} );
const pre = writer.createContainerElement( 'pre', preAttributes, code );
writer.insert( targetViewPosition, pre );
mapper.bindElements( data.item, code );
};
}
/**
* A model-to-data view converter for the new line (`softBreak`) separator.
*
* Sample input:
*
* <codeBlock ...>foo();<softBreak></softBreak>bar();</codeBlock>
*
* Sample output:
*
* <pre><code ...>foo();\nbar();</code></pre>
*
* @param {module:engine/model/model~Model} model
* @returns {Function} Returns a conversion callback.
*/
function modelToDataViewSoftBreakInsertion( model ) {
return ( evt, data, conversionApi ) => {
if ( data.item.parent.name !== 'codeBlock' ) {
return;
}
const { writer, mapper, consumable } = conversionApi;
if ( !consumable.consume( data.item, 'insert' ) ) {
return;
}
const position = mapper.toViewPosition( model.createPositionBefore( data.item ) );
writer.insert( position, writer.createText( '\n' ) );
};
}
/**
* A view-to-model converter for `<pre>` with the `<code>` HTML.
*
* Sample input:
*
* <pre><code class="language-javascript">foo();bar();</code></pre>
*
* Sample output:
*
* <codeBlock language="javascript">foo();bar();</codeBlock>
*
* @param {module:engine/view/view~View} editingView
* @param {Array.<module:code-block/codeblock~CodeBlockLanguageDefinition>} languageDefs The normalized language
* configuration passed to the feature.
* @returns {Function} Returns a conversion callback.
*/
function dataViewToModelCodeBlockInsertion( editingView, languageDefs ) {
// Language names associated with CSS classes:
//
// {
// 'language-php': 'php',
// 'language-python': 'python',
// js: 'javascript',
// ...
// }
const classesToLanguages = (0,_utils__WEBPACK_IMPORTED_MODULE_0__.getPropertyAssociation)( languageDefs, 'class', 'language' );
const defaultLanguageName = languageDefs[ 0 ].language;
return ( evt, data, conversionApi ) => {
const viewCodeElement = data.viewItem;
const viewPreElement = viewCodeElement.parent;
if ( !viewPreElement || !viewPreElement.is( 'element', 'pre' ) ) {
return;
}
// In case of nested code blocks we don't want to convert to another code block.
if ( data.modelCursor.findAncestor( 'codeBlock' ) ) {
return;
}
const { consumable, writer } = conversionApi;
if ( !consumable.test( viewCodeElement, { name: true } ) ) {
return;
}
const codeBlock = writer.createElement( 'codeBlock' );
const viewChildClasses = [ ...viewCodeElement.getClassNames() ];
// As we're to associate each class with a model language, a lack of class (empty class) can be
// also associated with a language if the language definition was configured so. Pushing an empty
// string to make sure the association will work.
if ( !viewChildClasses.length ) {
viewChildClasses.push( '' );
}
// Figure out if any of the <code> element's class names is a valid programming
// language class. If so, use it on the model element (becomes the language of the entire block).
for ( const className of viewChildClasses ) {
const language = classesToLanguages[ className ];
if ( language ) {
writer.setAttribute( 'language', language, codeBlock );
break;
}
}
// If no language value was set, use the default language from the config.
if ( !codeBlock.hasAttribute( 'language' ) ) {
writer.setAttribute( 'language', defaultLanguageName, codeBlock );
}
conversionApi.convertChildren( viewCodeElement, codeBlock );
// Let's try to insert code block.
if ( !conversionApi.safeInsert( codeBlock, data.modelCursor ) ) {
return;
}
consumable.consume( viewCodeElement, { name: true } );
conversionApi.updateConversionResult( codeBlock, data );
};
}
/**
* A view-to-model converter for new line characters in `<pre>`.
*
* Sample input:
*
* <pre><code class="language-javascript">foo();\nbar();</code></pre>
*
* Sample output:
*
* <codeBlock language="javascript">foo();<softBreak></softBreak>bar();</codeBlock>
*
* @returns {Function} Returns a conversion callback.
*/
function dataViewToModelTextNewlinesInsertion() {
return ( evt, data, { consumable, writer } ) => {
let position = data.modelCursor;
// When node is already converted then do nothing.
if ( !consumable.test( data.viewItem ) ) {
return;
}
// When not inside `codeBlock` then do nothing.
if ( !position.findAncestor( 'codeBlock' ) ) {
return;
}
consumable.consume( data.viewItem );
const text = data.viewItem.data;
const textLines = text.split( '\n' ).map( data => writer.createText( data ) );
const lastLine = textLines[ textLines.length - 1 ];
for ( const node of textLines ) {
writer.insert( node, position );
position = position.getShiftedBy( node.offsetSize );
if ( node !== lastLine ) {
const softBreak = writer.createElement( 'softBreak' );
writer.insert( softBreak, position );
position = writer.createPositionAfter( softBreak );
}
}
data.modelRange = writer.createRange(
data.modelCursor,
position
);
data.modelCursor = position;
};
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-code-block/src/indentcodeblockcommand.js":
/*!***********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-code-block/src/indentcodeblockcommand.js ***!
\***********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ IndentCodeBlockCommand)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./utils */ "./node_modules/@ckeditor/ckeditor5-code-block/src/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module code-block/indentcodeblockcommand
*/
/**
* The code block indentation increase command plugin.
*
* @extends module:core/command~Command
*/
class IndentCodeBlockCommand extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Command {
constructor( editor ) {
super( editor );
/**
* A sequence of characters added to the line when the command is executed.
*
* @readonly
* @private
* @member {String}
*/
this._indentSequence = editor.config.get( 'codeBlock.indentSequence' );
}
/**
* @inheritDoc
*/
refresh() {
this.isEnabled = this._checkEnabled();
}
/**
* Executes the command. When the command {@link #isEnabled is enabled}, the indentation of the
* code lines in the selection will be increased.
*
* @fires execute
*/
execute() {
const editor = this.editor;
const model = editor.model;
model.change( writer => {
const positions = (0,_utils__WEBPACK_IMPORTED_MODULE_1__.getIndentOutdentPositions)( model );
// Indent all positions, for instance assuming the indent sequence is 4x space (" "):
//
// <codeBlock>^foo</codeBlock> -> <codeBlock> foo</codeBlock>
//
// <codeBlock>foo^bar</codeBlock> -> <codeBlock>foo bar</codeBlock>
//
// Also, when there is more than one position:
//
// <codeBlock>
// ^foobar
// <softBreak></softBreak>
// ^bazqux
// </codeBlock>
//
// ->
//
// <codeBlock>
// foobar
// <softBreak></softBreak>
// bazqux
// </codeBlock>
//
for ( const position of positions ) {
writer.insertText( this._indentSequence, position );
}
} );
}
/**
* Checks whether the command can be enabled in the current context.
*
* @private
* @returns {Boolean} Whether the command should be enabled.
*/
_checkEnabled() {
if ( !this._indentSequence ) {
return false;
}
// Indent (forward) command is always enabled when there's any code block in the selection
// because you can always indent code lines.
return (0,_utils__WEBPACK_IMPORTED_MODULE_1__.isModelSelectionInCodeBlock)( this.editor.model.document.selection );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-code-block/src/outdentcodeblockcommand.js":
/*!************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-code-block/src/outdentcodeblockcommand.js ***!
\************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ OutdentCodeBlockCommand)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./utils */ "./node_modules/@ckeditor/ckeditor5-code-block/src/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module code-block/outdentcodeblockcommand
*/
/**
* The code block indentation decrease command plugin.
*
* @extends module:core/command~Command
*/
class OutdentCodeBlockCommand extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Command {
constructor( editor ) {
super( editor );
/**
* A sequence of characters removed from the line when the command is executed.
*
* @readonly
* @private
* @member {String}
*/
this._indentSequence = editor.config.get( 'codeBlock.indentSequence' );
}
/**
* @inheritDoc
*/
refresh() {
this.isEnabled = this._checkEnabled();
}
/**
* Executes the command. When the command {@link #isEnabled is enabled}, the indentation of the
* code lines in the selection will be decreased.
*
* @fires execute
*/
execute() {
const editor = this.editor;
const model = editor.model;
model.change( writer => {
const positions = (0,_utils__WEBPACK_IMPORTED_MODULE_1__.getIndentOutdentPositions)( model );
// Outdent all positions, for instance assuming the indent sequence is 4x space (" "):
//
// <codeBlock>^foo</codeBlock> -> <codeBlock>foo</codeBlock>
//
// <codeBlock> ^bar</codeBlock> -> <codeBlock>bar</codeBlock>
//
// Also, when there is more than one position:
//
// <codeBlock>
// ^foobar
// <softBreak></softBreak>
// ^bazqux
// </codeBlock>
//
// ->
//
// <codeBlock>
// foobar
// <softBreak></softBreak>
// bazqux
// </codeBlock>
for ( const position of positions ) {
const range = getLastOutdentableSequenceRange( this.editor.model, position, this._indentSequence );
if ( range ) {
writer.remove( range );
}
}
} );
}
/**
* Checks whether the command can be enabled in the current context.
*
* @private
* @returns {Boolean} Whether the command should be enabled.
*/
_checkEnabled() {
if ( !this._indentSequence ) {
return false;
}
const model = this.editor.model;
if ( !(0,_utils__WEBPACK_IMPORTED_MODULE_1__.isModelSelectionInCodeBlock)( model.document.selection ) ) {
return false;
}
// Outdent command can execute only when there is an indent character sequence
// in some of the lines.
return (0,_utils__WEBPACK_IMPORTED_MODULE_1__.getIndentOutdentPositions)( model ).some( position => {
return getLastOutdentableSequenceRange( model, position, this._indentSequence );
} );
}
}
// For a position coming from `getIndentOutdentPositions()`, it returns the range representing
// the last occurrence of the indent sequence among the leading whitespaces of the code line the
// position represents.
//
// For instance, assuming the indent sequence is 4x space (" "):
//
// <codeBlock>foo^</codeBlock> -> null
// <codeBlock>foo^<softBreak></softBreak>bar</codeBlock> -> null
// <codeBlock> ^foo</codeBlock> -> null
// <codeBlock> ^foo</codeBlock> -> <codeBlock> [ ]foo</codeBlock>
// <codeBlock> ^foo bar</codeBlock> -> <codeBlock>[ ]foo bar</codeBlock>
//
// @param {<module:engine/model/model~Model>} model
// @param {<module:engine/model/position~Position>} position
// @param {String} sequence
// @returns {<module:engine/model/range~Range>|null}
function getLastOutdentableSequenceRange( model, position, sequence ) {
// Positions start before each text node (code line). Get the node corresponding to the position.
const nodeAtPosition = getCodeLineTextNodeAtPosition( position );
if ( !nodeAtPosition ) {
return null;
}
const leadingWhiteSpaces = (0,_utils__WEBPACK_IMPORTED_MODULE_1__.getLeadingWhiteSpaces)( nodeAtPosition );
const lastIndexOfSequence = leadingWhiteSpaces.lastIndexOf( sequence );
// For instance, assuming the indent sequence is 4x space (" "):
//
// <codeBlock> ^foo</codeBlock> -> null
//
if ( lastIndexOfSequence + sequence.length !== leadingWhiteSpaces.length ) {
return null;
}
// For instance, assuming the indent sequence is 4x space (" "):
//
// <codeBlock> ^foo</codeBlock> -> null
//
if ( lastIndexOfSequence === -1 ) {
return null;
}
const { parent, startOffset } = nodeAtPosition;
// Create a range that contains the **last** indent sequence among the leading whitespaces
// of the line.
//
// For instance, assuming the indent sequence is 4x space (" "):
//
// <codeBlock> ^foo</codeBlock> -> <codeBlock> [ ]foo</codeBlock>
//
return model.createRange(
model.createPositionAt( parent, startOffset + lastIndexOfSequence ),
model.createPositionAt( parent, startOffset + lastIndexOfSequence + sequence.length )
);
}
function getCodeLineTextNodeAtPosition( position ) {
// Positions start before each text node (code line). Get the node corresponding to the position.
let nodeAtPosition = position.parent.getChild( position.index );
// <codeBlock>foo^</codeBlock>
// <codeBlock>foo^<softBreak></softBreak>bar</codeBlock>
if ( !nodeAtPosition || nodeAtPosition.is( 'element', 'softBreak' ) ) {
nodeAtPosition = position.nodeBefore;
}
// <codeBlock>^</codeBlock>
// <codeBlock>foo^<softBreak></softBreak>bar</codeBlock>
if ( !nodeAtPosition || nodeAtPosition.is( 'element', 'softBreak' ) ) {
return null;
}
return nodeAtPosition;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-code-block/src/utils.js":
/*!******************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-code-block/src/utils.js ***!
\******************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "getIndentOutdentPositions": () => (/* binding */ getIndentOutdentPositions),
/* harmony export */ "getLeadingWhiteSpaces": () => (/* binding */ getLeadingWhiteSpaces),
/* harmony export */ "getNormalizedAndLocalizedLanguageDefinitions": () => (/* binding */ getNormalizedAndLocalizedLanguageDefinitions),
/* harmony export */ "getPropertyAssociation": () => (/* binding */ getPropertyAssociation),
/* harmony export */ "isModelSelectionInCodeBlock": () => (/* binding */ isModelSelectionInCodeBlock),
/* harmony export */ "rawSnippetTextToViewDocumentFragment": () => (/* binding */ rawSnippetTextToViewDocumentFragment)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module code-block/utils
*/
/**
* Returns code block languages as defined in `config.codeBlock.languages` but processed:
*
* * To consider the editor localization, i.e. to display {@link module:code-block/codeblock~CodeBlockLanguageDefinition}
* in the correct language. There is no way to use {@link module:utils/locale~Locale#t} when the user
* configuration is defined because the editor does not exist yet.
* * To make sure each definition has a CSS class associated with it even if not specified
* in the original configuration.
*
* @param {module:core/editor/editor~Editor} editor
* @returns {Array.<module:code-block/codeblock~CodeBlockLanguageDefinition>}.
*/
function getNormalizedAndLocalizedLanguageDefinitions( editor ) {
const t = editor.t;
const languageDefs = editor.config.get( 'codeBlock.languages' );
for ( const def of languageDefs ) {
if ( def.label === 'Plain text' ) {
def.label = t( 'Plain text' );
}
if ( def.class === undefined ) {
def.class = `language-${ def.language }`;
}
}
return languageDefs;
}
/**
* Returns an object associating certain language definition properties with others. For instance:
*
* For:
*
* const definitions = {
* { language: 'php', class: 'language-php', label: 'PHP' },
* { language: 'javascript', class: 'js', label: 'JavaScript' },
* };
*
* getPropertyAssociation( definitions, 'class', 'language' );
*
* returns:
*
* {
* 'language-php': 'php'
* 'js': 'javascript'
* }
*
* and
*
* getPropertyAssociation( definitions, 'language', 'label' );
*
* returns:
*
* {
* 'php': 'PHP'
* 'javascript': 'JavaScript'
* }
*
* @param {Array.<module:code-block/codeblock~CodeBlockLanguageDefinition>}
* @param {String} key
* @param {String} value
* @param {Object.<String,String>}
*/
function getPropertyAssociation( languageDefs, key, value ) {
const association = {};
for ( const def of languageDefs ) {
if ( key === 'class' ) {
// Only the first class is considered.
association[ def[ key ].split( ' ' ).shift() ] = def[ value ];
} else {
association[ def[ key ] ] = def[ value ];
}
}
return association;
}
/**
* For a given model text node, it returns white spaces that precede other characters in that node.
* This corresponds to the indentation part of the code block line.
*
* @param {module:engine/model/text~Text} codeLineNodes
* @returns {String}
*/
function getLeadingWhiteSpaces( textNode ) {
return textNode.data.match( /^(\s*)/ )[ 0 ];
}
/**
* For plain text containing the code (a snippet), it returns a document fragment containing
* view text nodes separated by `<br>` elements (in place of new line characters "\n"), for instance:
*
* Input:
*
* "foo()\n
* bar()"
*
* Output:
*
* <DocumentFragment>
* "foo()"
* <br/>
* "bar()"
* </DocumentFragment>
*
* @param {module:engine/view/upcastwriter~UpcastWriter} writer
* @param {String} text The raw code text to be converted.
* @returns {module:engine/view/documentfragment~DocumentFragment}
*/
function rawSnippetTextToViewDocumentFragment( writer, text ) {
const fragment = writer.createDocumentFragment();
const textLines = text.split( '\n' );
const nodes = textLines.reduce( ( nodes, line, lineIndex ) => {
nodes.push( line );
if ( lineIndex < textLines.length - 1 ) {
nodes.push( writer.createElement( 'br' ) );
}
return nodes;
}, [] );
writer.appendChild( nodes, fragment );
return fragment;
}
/**
* Returns an array of all model positions within the selection that represent code block lines.
*
* If the selection is collapsed, it returns the exact selection anchor position:
*
* <codeBlock>[]foo</codeBlock> -> <codeBlock>^foo</codeBlock>
* <codeBlock>foo[]bar</codeBlock> -> <codeBlock>foo^bar</codeBlock>
*
* Otherwise, it returns positions **before** each text node belonging to all code blocks contained by the selection:
*
* <codeBlock> <codeBlock>
* foo[bar ^foobar
* <softBreak></softBreak> -> <softBreak></softBreak>
* baz]qux ^bazqux
* </codeBlock> </codeBlock>
*
* It also works across other non–code blocks:
*
* <codeBlock> <codeBlock>
* foo[bar ^foobar
* </codeBlock> </codeBlock>
* <paragraph>text</paragraph> -> <paragraph>text</paragraph>
* <codeBlock> <codeBlock>
* baz]qux ^bazqux
* </codeBlock> </codeBlock>
*
* **Note:** The positions are in reverse order so they do not get outdated when iterating over them and
* the writer inserts or removes elements at the same time.
*
* **Note:** The position is located after the leading white spaces in the text node.
*
* @param {module:engine/model/model~Model} model
* @returns {Array.<module:engine/model/position~Position>}
*/
function getIndentOutdentPositions( model ) {
const selection = model.document.selection;
const positions = [];
// When the selection is collapsed, there's only one position we can indent or outdent.
if ( selection.isCollapsed ) {
positions.push( selection.anchor );
}
// When the selection is NOT collapsed, collect all positions starting before text nodes
// (code lines) in any <codeBlock> within the selection.
else {
// Walk backward so positions we are about to collect here do not get outdated when
// inserting or deleting using the writer.
const walker = selection.getFirstRange().getWalker( {
ignoreElementEnd: true,
direction: 'backward'
} );
for ( const { item } of walker ) {
if ( item.is( '$textProxy' ) && item.parent.is( 'element', 'codeBlock' ) ) {
const leadingWhiteSpaces = getLeadingWhiteSpaces( item.textNode );
const { parent, startOffset } = item.textNode;
// Make sure the position is after all leading whitespaces in the text node.
const position = model.createPositionAt( parent, startOffset + leadingWhiteSpaces.length );
positions.push( position );
}
}
}
return positions;
}
/**
* Checks if any of the blocks within the model selection is a code block.
*
* @param {module:engine/model/selection~Selection} selection
* @returns {Boolean}
*/
function isModelSelectionInCodeBlock( selection ) {
const firstBlock = (0,ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_0__.first)( selection.getSelectedBlocks() );
return firstBlock && firstBlock.is( 'element', 'codeBlock' );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-core/src/command.js":
/*!**************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-core/src/command.js ***!
\**************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ Command)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_utils_src_observablemixin__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/observablemixin */ "./node_modules/@ckeditor/ckeditor5-utils/src/observablemixin.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/mix */ "./node_modules/@ckeditor/ckeditor5-utils/src/mix.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module core/command
*/
/**
* The base class for CKEditor commands.
*
* Commands are the main way to manipulate editor contents and state. They are mostly used by UI elements (or by other
* commands) to make changes in the model. Commands are available in every part of code that has access to
* the {@link module:core/editor/editor~Editor editor} instance.
*
* Instances of registered commands can be retrieved from {@link module:core/editor/editor~Editor#commands `editor.commands`}.
* The easiest way to execute a command is through {@link module:core/editor/editor~Editor#execute `editor.execute()`}.
*
* By default, commands are disabled when the editor is in {@link module:core/editor/editor~Editor#isReadOnly read-only} mode
* but commands with the {@link module:core/command~Command#affectsData `affectsData`} flag set to `false` will not be disabled.
*
* @mixes module:utils/observablemixin~ObservableMixin
*/
class Command {
/**
* Creates a new `Command` instance.
*
* @param {module:core/editor/editor~Editor} editor Editor on which this command will be used.
*/
constructor( editor ) {
/**
* The editor on which this command will be used.
*
* @readonly
* @member {module:core/editor/editor~Editor}
*/
this.editor = editor;
/**
* The value of the command. A concrete command class should define what it represents for it.
*
* For example, the `'bold'` command's value indicates whether the selection starts in a bolded text.
* And the value of the `'link'` command may be an object with links details.
*
* It is possible for a command to have no value (e.g. for stateless actions such as `'uploadImage'`).
*
* A concrete command class should control this value by overriding the {@link #refresh `refresh()`} method.
*
* @observable
* @readonly
* @member #value
*/
this.set( 'value', undefined );
/**
* Flag indicating whether a command is enabled or disabled.
* A disabled command will do nothing when executed.
*
* A concrete command class should control this value by overriding the {@link #refresh `refresh()`} method.
*
* It is possible to disable a command from "outside". For instance, in your integration you may want to disable
* a certain set of commands for the time being. To do that, you can use the fact that `isEnabled` is observable
* and it fires the `set:isEnabled` event every time anyone tries to modify its value:
*
* function disableCommand( cmd ) {
* cmd.on( 'set:isEnabled', forceDisable, { priority: 'highest' } );
*
* cmd.isEnabled = false;
*
* // Make it possible to enable the command again.
* return () => {
* cmd.off( 'set:isEnabled', forceDisable );
* cmd.refresh();
* };
*
* function forceDisable( evt ) {
* evt.return = false;
* evt.stop();
* }
* }
*
* // Usage:
*
* // Disabling the command.
* const enableBold = disableCommand( editor.commands.get( 'bold' ) );
*
* // Enabling the command again.
* enableBold();
*
* @observable
* @readonly
* @member {Boolean} #isEnabled
*/
this.set( 'isEnabled', false );
/**
* A flag indicating whether a command execution changes the editor data or not.
*
* Commands with `affectsData` set to `false` will not be automatically disabled in
* the {@link module:core/editor/editor~Editor#isReadOnly read-only mode} and
* {@glink features/read-only#related-features other editor modes} with restricted user write permissions.
*
* **Note:** You do not have to set it for your every command. It is `true` by default.
*
* @readonly
* @default true
* @member {Boolean} #affectsData
*/
this.affectsData = true;
/**
* Holds identifiers for {@link #forceDisabled} mechanism.
*
* @type {Set.<String>}
* @private
*/
this._disableStack = new Set();
this.decorate( 'execute' );
// By default every command is refreshed when changes are applied to the model.
this.listenTo( this.editor.model.document, 'change', () => {
this.refresh();
} );
this.on( 'execute', evt => {
if ( !this.isEnabled ) {
evt.stop();
}
}, { priority: 'high' } );
// By default commands are disabled when the editor is in read-only mode.
this.listenTo( editor, 'change:isReadOnly', ( evt, name, value ) => {
if ( value && this.affectsData ) {
this.forceDisabled( 'readOnlyMode' );
} else {
this.clearForceDisabled( 'readOnlyMode' );
}
} );
}
/**
* Refreshes the command. The command should update its {@link #isEnabled} and {@link #value} properties
* in this method.
*
* This method is automatically called when
* {@link module:engine/model/document~Document#event:change any changes are applied to the document}.
*/
refresh() {
this.isEnabled = true;
}
/**
* Disables the command.
*
* Command may be disabled by multiple features or algorithms (at once). When disabling a command, unique id should be passed
* (e.g. feature name). The same identifier should be used when {@link #clearForceDisabled enabling back} the command.
* The command becomes enabled only after all features {@link #clearForceDisabled enabled it back}.
*
* Disabling and enabling a command:
*
* command.isEnabled; // -> true
* command.forceDisabled( 'MyFeature' );
* command.isEnabled; // -> false
* command.clearForceDisabled( 'MyFeature' );
* command.isEnabled; // -> true
*
* Command disabled by multiple features:
*
* command.forceDisabled( 'MyFeature' );
* command.forceDisabled( 'OtherFeature' );
* command.clearForceDisabled( 'MyFeature' );
* command.isEnabled; // -> false
* command.clearForceDisabled( 'OtherFeature' );
* command.isEnabled; // -> true
*
* Multiple disabling with the same identifier is redundant:
*
* command.forceDisabled( 'MyFeature' );
* command.forceDisabled( 'MyFeature' );
* command.clearForceDisabled( 'MyFeature' );
* command.isEnabled; // -> true
*
* **Note:** some commands or algorithms may have more complex logic when it comes to enabling or disabling certain commands,
* so the command might be still disabled after {@link #clearForceDisabled} was used.
*
* @param {String} id Unique identifier for disabling. Use the same id when {@link #clearForceDisabled enabling back} the command.
*/
forceDisabled( id ) {
this._disableStack.add( id );
if ( this._disableStack.size == 1 ) {
this.on( 'set:isEnabled', forceDisable, { priority: 'highest' } );
this.isEnabled = false;
}
}
/**
* Clears forced disable previously set through {@link #forceDisabled}. See {@link #forceDisabled}.
*
* @param {String} id Unique identifier, equal to the one passed in {@link #forceDisabled} call.
*/
clearForceDisabled( id ) {
this._disableStack.delete( id );
if ( this._disableStack.size == 0 ) {
this.off( 'set:isEnabled', forceDisable );
this.refresh();
}
}
/**
* Executes the command.
*
* A command may accept parameters. They will be passed from {@link module:core/editor/editor~Editor#execute `editor.execute()`}
* to the command.
*
* The `execute()` method will automatically abort when the command is disabled ({@link #isEnabled} is `false`).
* This behavior is implemented by a high priority listener to the {@link #event:execute} event.
*
* In order to see how to disable a command from "outside" see the {@link #isEnabled} documentation.
*
* This method may return a value, which would be forwarded all the way down to the
* {@link module:core/editor/editor~Editor#execute `editor.execute()`}.
*
* @fires execute
*/
execute() {}
/**
* Destroys the command.
*/
destroy() {
this.stopListening();
}
/**
* Event fired by the {@link #execute} method. The command action is a listener to this event so it's
* possible to change/cancel the behavior of the command by listening to this event.
*
* See {@link module:utils/observablemixin~ObservableMixin#decorate} for more information and samples.
*
* **Note:** This event is fired even if command is disabled. However, it is automatically blocked
* by a high priority listener in order to prevent command execution.
*
* @event execute
*/
}
(0,_ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_1__["default"])( Command, _ckeditor_ckeditor5_utils_src_observablemixin__WEBPACK_IMPORTED_MODULE_0__["default"] );
// Helper function that forces command to be disabled.
function forceDisable( evt ) {
evt.return = false;
evt.stop();
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-core/src/commandcollection.js":
/*!************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-core/src/commandcollection.js ***!
\************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ CommandCollection)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module core/commandcollection
*/
/**
* Collection of commands. Its instance is available in {@link module:core/editor/editor~Editor#commands `editor.commands`}.
*/
class CommandCollection {
/**
* Creates collection instance.
*/
constructor() {
/**
* Command map.
*
* @private
* @member {Map}
*/
this._commands = new Map();
}
/**
* Registers a new command.
*
* @param {String} commandName The name of the command.
* @param {module:core/command~Command} command
*/
add( commandName, command ) {
this._commands.set( commandName, command );
}
/**
* Retrieves a command from the collection.
*
* @param {String} commandName The name of the command.
* @returns {module:core/command~Command}
*/
get( commandName ) {
return this._commands.get( commandName );
}
/**
* Executes a command.
*
* @param {String} commandName The name of the command.
* @param {*} [...commandParams] Command parameters.
* @returns {*} The value returned by the {@link module:core/command~Command#execute `command.execute()`}.
*/
execute( commandName, ...args ) {
const command = this.get( commandName );
if ( !command ) {
/**
* Command does not exist.
*
* @error commandcollection-command-not-found
* @param {String} commandName Name of the command.
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_0__["default"]( 'commandcollection-command-not-found', this, { commandName } );
}
return command.execute( ...args );
}
/**
* Returns iterator of command names.
*
* @returns {Iterable.<String>}
*/
* names() {
yield* this._commands.keys();
}
/**
* Returns iterator of command instances.
*
* @returns {Iterable.<module:core/command~Command>}
*/
* commands() {
yield* this._commands.values();
}
/**
* Iterable interface.
*
* Returns `[ commandName, commandInstance ]` pairs.
*
* @returns {Iterable.<Array>}
*/
[ Symbol.iterator ]() {
return this._commands[ Symbol.iterator ]();
}
/**
* Destroys all collection commands.
*/
destroy() {
for ( const command of this.commands() ) {
command.destroy();
}
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-core/src/context.js":
/*!**************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-core/src/context.js ***!
\**************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ Context)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_utils_src_config__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/config */ "./node_modules/@ckeditor/ckeditor5-utils/src/config.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_collection__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/collection */ "./node_modules/@ckeditor/ckeditor5-utils/src/collection.js");
/* harmony import */ var _plugincollection__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./plugincollection */ "./node_modules/@ckeditor/ckeditor5-core/src/plugincollection.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_locale__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/locale */ "./node_modules/@ckeditor/ckeditor5-utils/src/locale.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module core/context
*/
/**
* Provides a common, higher-level environment for solutions that use multiple {@link module:core/editor/editor~Editor editors}
* or plugins that work outside the editor. Use it instead of {@link module:core/editor/editor~Editor.create `Editor.create()`}
* in advanced application integrations.
*
* All configuration options passed to a context will be used as default options for editor instances initialized in that context.
*
* {@link module:core/contextplugin~ContextPlugin Context plugins} passed to a context instance will be shared among all
* editor instances initialized in this context. These will be the same plugin instances for all the editors.
*
* **Note:** The context can only be initialized with {@link module:core/contextplugin~ContextPlugin context plugins}
* (e.g. [comments](https://ckeditor.com/collaboration/comments/)). Regular {@link module:core/plugin~Plugin plugins} require an
* editor instance to work and cannot be added to a context.
*
* **Note:** You can add a context plugin to an editor instance, though.
*
* If you are using multiple editor instances on one page and use any context plugins, create a context to share the configuration and
* plugins among these editors. Some plugins will use the information about all existing editors to better integrate between them.
*
* If you are using plugins that do not require an editor to work (e.g. [comments](https://ckeditor.com/collaboration/comments/)),
* enable and configure them using the context.
*
* If you are using only a single editor on each page, use {@link module:core/editor/editor~Editor.create `Editor.create()`} instead.
* In such case, a context instance will be created by the editor instance in a transparent way.
*
* See {@link module:core/context~Context.create `Context.create()`} for usage examples.
*/
class Context {
/**
* Creates a context instance with a given configuration.
*
* Usually not to be used directly. See the static {@link module:core/context~Context.create `create()`} method.
*
* @param {Object} [config={}] The context configuration.
*/
constructor( config ) {
/**
* Stores all the configurations specific to this context instance.
*
* @readonly
* @type {module:utils/config~Config}
*/
this.config = new _ckeditor_ckeditor5_utils_src_config__WEBPACK_IMPORTED_MODULE_0__["default"]( config, this.constructor.defaultConfig );
const availablePlugins = this.constructor.builtinPlugins;
this.config.define( 'plugins', availablePlugins );
/**
* The plugins loaded and in use by this context instance.
*
* @readonly
* @type {module:core/plugincollection~PluginCollection}
*/
this.plugins = new _plugincollection__WEBPACK_IMPORTED_MODULE_2__["default"]( this, availablePlugins );
const languageConfig = this.config.get( 'language' ) || {};
/**
* @readonly
* @type {module:utils/locale~Locale}
*/
this.locale = new _ckeditor_ckeditor5_utils_src_locale__WEBPACK_IMPORTED_MODULE_3__["default"]( {
uiLanguage: typeof languageConfig === 'string' ? languageConfig : languageConfig.ui,
contentLanguage: this.config.get( 'language.content' )
} );
/**
* Shorthand for {@link module:utils/locale~Locale#t}.
*
* @see module:utils/locale~Locale#t
* @method #t
*/
this.t = this.locale.t;
/**
* A list of editors that this context instance is injected to.
*
* @readonly
* @type {module:utils/collection~Collection}
*/
this.editors = new _ckeditor_ckeditor5_utils_src_collection__WEBPACK_IMPORTED_MODULE_1__["default"]();
/**
* Reference to the editor which created the context.
* Null when the context was created outside of the editor.
*
* It is used to destroy the context when removing the editor that has created the context.
*
* @private
* @type {module:core/editor/editor~Editor|null}
*/
this._contextOwner = null;
}
/**
* Loads and initializes plugins specified in the configuration.
*
* @returns {Promise.<module:core/plugin~LoadedPlugins>} A promise which resolves
* once the initialization is completed, providing an array of loaded plugins.
*/
initPlugins() {
const plugins = this.config.get( 'plugins' ) || [];
const substitutePlugins = this.config.get( 'substitutePlugins' ) || [];
// Plugins for substitution should be checked as well.
for ( const Plugin of plugins.concat( substitutePlugins ) ) {
if ( typeof Plugin != 'function' ) {
/**
* Only a constructor function is allowed as a {@link module:core/contextplugin~ContextPlugin context plugin}.
*
* @error context-initplugins-constructor-only
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_4__["default"](
'context-initplugins-constructor-only',
null,
{ Plugin }
);
}
if ( Plugin.isContextPlugin !== true ) {
/**
* Only a plugin marked as a {@link module:core/contextplugin~ContextPlugin.isContextPlugin context plugin}
* is allowed to be used with a context.
*
* @error context-initplugins-invalid-plugin
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_4__["default"](
'context-initplugins-invalid-plugin',
null,
{ Plugin }
);
}
}
return this.plugins.init( plugins, [], substitutePlugins );
}
/**
* Destroys the context instance and all editors used with the context,
* releasing all resources used by the context.
*
* @returns {Promise} A promise that resolves once the context instance is fully destroyed.
*/
destroy() {
return Promise.all( Array.from( this.editors, editor => editor.destroy() ) )
.then( () => this.plugins.destroy() );
}
/**
* Adds a reference to the editor which is used with this context.
*
* When the given editor has created the context, the reference to this editor will be stored
* as a {@link ~Context#_contextOwner}.
*
* This method should only be used by the editor.
*
* @protected
* @param {module:core/editor/editor~Editor} editor
* @param {Boolean} isContextOwner Stores the given editor as a context owner.
*/
_addEditor( editor, isContextOwner ) {
if ( this._contextOwner ) {
/**
* Cannot add multiple editors to the context which is created by the editor.
*
* @error context-addeditor-private-context
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_4__["default"]( 'context-addeditor-private-context' );
}
this.editors.add( editor );
if ( isContextOwner ) {
this._contextOwner = editor;
}
}
/**
* Removes a reference to the editor which was used with this context.
* When the context was created by the given editor, the context will be destroyed.
*
* This method should only be used by the editor.
*
* @protected
* @param {module:core/editor/editor~Editor} editor
* @return {Promise} A promise that resolves once the editor is removed from the context or when the context was destroyed.
*/
_removeEditor( editor ) {
if ( this.editors.has( editor ) ) {
this.editors.remove( editor );
}
if ( this._contextOwner === editor ) {
return this.destroy();
}
return Promise.resolve();
}
/**
* Returns the context configuration which will be copied to the editors created using this context.
*
* The configuration returned by this method has the plugins configuration removed — plugins are shared with all editors
* through another mechanism.
*
* This method should only be used by the editor.
*
* @protected
* @returns {Object} Configuration as a plain object.
*/
_getEditorConfig() {
const result = {};
for ( const name of this.config.names() ) {
if ( ![ 'plugins', 'removePlugins', 'extraPlugins' ].includes( name ) ) {
result[ name ] = this.config.get( name );
}
}
return result;
}
/**
* Creates and initializes a new context instance.
*
* const commonConfig = { ... }; // Configuration for all the plugins and editors.
* const editorPlugins = [ ... ]; // Regular plugins here.
*
* Context
* .create( {
* // Only context plugins here.
* plugins: [ ... ],
*
* // Configure the language for all the editors (it cannot be overwritten).
* language: { ... },
*
* // Configuration for context plugins.
* comments: { ... },
* ...
*
* // Default configuration for editor plugins.
* toolbar: { ... },
* image: { ... },
* ...
* } )
* .then( context => {
* const promises = [];
*
* promises.push( ClassicEditor.create(
* document.getElementById( 'editor1' ),
* {
* editorPlugins,
* context
* }
* ) );
*
* promises.push( ClassicEditor.create(
* document.getElementById( 'editor2' ),
* {
* editorPlugins,
* context,
* toolbar: { ... } // You can overwrite the configuration of the context.
* }
* ) );
*
* return Promise.all( promises );
* } );
*
* @param {Object} [config] The context configuration.
* @returns {Promise} A promise resolved once the context is ready. The promise resolves with the created context instance.
*/
static create( config ) {
return new Promise( resolve => {
const context = new this( config );
resolve( context.initPlugins().then( () => context ) );
} );
}
}
/**
* An array of plugins built into the `Context` class.
*
* It is used in CKEditor 5 builds featuring `Context` to provide a list of context plugins which are later automatically initialized
* during the context initialization.
*
* They will be automatically initialized by `Context` unless `config.plugins` is passed.
*
* // Build some context plugins into the Context class first.
* Context.builtinPlugins = [ FooPlugin, BarPlugin ];
*
* // Normally, you need to define config.plugins, but since Context.builtinPlugins was
* // defined, now you can call create() without any configuration.
* Context
* .create()
* .then( context => {
* context.plugins.get( FooPlugin ); // -> An instance of the Foo plugin.
* context.plugins.get( BarPlugin ); // -> An instance of the Bar plugin.
* } );
*
* See also {@link module:core/context~Context.defaultConfig `Context.defaultConfig`}
* and {@link module:core/editor/editor~Editor.builtinPlugins `Editor.builtinPlugins`}.
*
* @static
* @member {Array.<Function>} module:core/context~Context.builtinPlugins
*/
/**
* The default configuration which is built into the `Context` class.
*
* It is used in CKEditor 5 builds featuring `Context` to provide the default configuration options which are later used during the
* context initialization.
*
* Context.defaultConfig = {
* foo: 1,
* bar: 2
* };
*
* Context
* .create()
* .then( context => {
* context.config.get( 'foo' ); // -> 1
* context.config.get( 'bar' ); // -> 2
* } );
*
* // The default options can be overridden by the configuration passed to create().
* Context
* .create( { bar: 3 } )
* .then( context => {
* context.config.get( 'foo' ); // -> 1
* context.config.get( 'bar' ); // -> 3
* } );
*
* See also {@link module:core/context~Context.builtinPlugins `Context.builtinPlugins`}
* and {@link module:core/editor/editor~Editor.defaultConfig `Editor.defaultConfig`}.
*
* @static
* @member {Object} module:core/context~Context.defaultConfig
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-core/src/contextplugin.js":
/*!********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-core/src/contextplugin.js ***!
\********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ContextPlugin)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_utils_src_observablemixin__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/observablemixin */ "./node_modules/@ckeditor/ckeditor5-utils/src/observablemixin.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/mix */ "./node_modules/@ckeditor/ckeditor5-utils/src/mix.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module core/contextplugin
*/
/**
* The base class for {@link module:core/context~Context} plugin classes.
*
* A context plugin can either be initialized for an {@link module:core/editor/editor~Editor editor} or for
* a {@link module:core/context~Context context}. In other words, it can either
* work within one editor instance or with one or more editor instances that use a single context.
* It is the context plugin's role to implement handling for both modes.
*
* There are a few rules for interaction between the editor plugins and context plugins:
*
* * A context plugin can require another context plugin.
* * An {@link module:core/plugin~Plugin editor plugin} can require a context plugin.
* * A context plugin MUST NOT require an {@link module:core/plugin~Plugin editor plugin}.
*
* @implements module:core/plugin~PluginInterface
* @mixes module:utils/observablemixin~ObservableMixin
*/
class ContextPlugin {
/**
* Creates a new plugin instance.
*
* @param {module:core/context~Context|module:core/editor/editor~Editor} context
*/
constructor( context ) {
/**
* The context instance.
*
* @readonly
* @type {module:core/context~Context|module:core/editor/editor~Editor}
*/
this.context = context;
}
/**
* @inheritDoc
*/
destroy() {
this.stopListening();
}
/**
* @inheritDoc
*/
static get isContextPlugin() {
return true;
}
}
(0,_ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_1__["default"])( ContextPlugin, _ckeditor_ckeditor5_utils_src_observablemixin__WEBPACK_IMPORTED_MODULE_0__["default"] );
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-core/src/editingkeystrokehandler.js":
/*!******************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-core/src/editingkeystrokehandler.js ***!
\******************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ EditingKeystrokeHandler)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_utils_src_keystrokehandler__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/keystrokehandler */ "./node_modules/@ckeditor/ckeditor5-utils/src/keystrokehandler.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module core/editingkeystrokehandler
*/
/**
* A keystroke handler for editor editing. Its instance is available
* in {@link module:core/editor/editor~Editor#keystrokes} so plugins
* can register their keystrokes.
*
* E.g. an undo plugin would do this:
*
* editor.keystrokes.set( 'Ctrl+Z', 'undo' );
* editor.keystrokes.set( 'Ctrl+Shift+Z', 'redo' );
* editor.keystrokes.set( 'Ctrl+Y', 'redo' );
*
* @extends module:utils/keystrokehandler~KeystrokeHandler
*/
class EditingKeystrokeHandler extends _ckeditor_ckeditor5_utils_src_keystrokehandler__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* Creates an instance of the keystroke handler.
*
* @param {module:core/editor/editor~Editor} editor
*/
constructor( editor ) {
super();
/**
* The editor instance.
*
* @readonly
* @member {module:core/editor/editor~Editor}
*/
this.editor = editor;
}
/**
* Registers a handler for the specified keystroke.
*
* The handler can be specified as a command name or a callback.
*
* @param {String|Array.<String|Number>} keystroke Keystroke defined in a format accepted by
* the {@link module:utils/keyboard~parseKeystroke} function.
* @param {Function|String} callback If a string is passed, then the keystroke will
* {@link module:core/editor/editor~Editor#execute execute a command}.
* If a function, then it will be called with the
* {@link module:engine/view/observer/keyobserver~KeyEventData key event data} object and
* a `cancel()` helper to both `preventDefault()` and `stopPropagation()` of the event.
* @param {Object} [options={}] Additional options.
* @param {module:utils/priorities~PriorityString|Number} [options.priority='normal'] The priority of the keystroke
* callback. The higher the priority value the sooner the callback will be executed. Keystrokes having the same priority
* are called in the order they were added.
*/
set( keystroke, callback, options = {} ) {
if ( typeof callback == 'string' ) {
const commandName = callback;
callback = ( evtData, cancel ) => {
this.editor.execute( commandName );
cancel();
};
}
super.set( keystroke, callback, options );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-core/src/editor/editor.js":
/*!********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-core/src/editor/editor.js ***!
\********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ Editor)
/* harmony export */ });
/* harmony import */ var _context__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../context */ "./node_modules/@ckeditor/ckeditor5-core/src/context.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_config__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/config */ "./node_modules/@ckeditor/ckeditor5-utils/src/config.js");
/* harmony import */ var _ckeditor_ckeditor5_engine_src_controller_editingcontroller__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @ckeditor/ckeditor5-engine/src/controller/editingcontroller */ "./node_modules/@ckeditor/ckeditor5-engine/src/controller/editingcontroller.js");
/* harmony import */ var _plugincollection__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../plugincollection */ "./node_modules/@ckeditor/ckeditor5-core/src/plugincollection.js");
/* harmony import */ var _commandcollection__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../commandcollection */ "./node_modules/@ckeditor/ckeditor5-core/src/commandcollection.js");
/* harmony import */ var _ckeditor_ckeditor5_engine_src_controller_datacontroller__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! @ckeditor/ckeditor5-engine/src/controller/datacontroller */ "./node_modules/@ckeditor/ckeditor5-engine/src/controller/datacontroller.js");
/* harmony import */ var _ckeditor_ckeditor5_engine_src_conversion_conversion__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! @ckeditor/ckeditor5-engine/src/conversion/conversion */ "./node_modules/@ckeditor/ckeditor5-engine/src/conversion/conversion.js");
/* harmony import */ var _ckeditor_ckeditor5_engine_src_model_model__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! @ckeditor/ckeditor5-engine/src/model/model */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/model.js");
/* harmony import */ var _editingkeystrokehandler__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ../editingkeystrokehandler */ "./node_modules/@ckeditor/ckeditor5-core/src/editingkeystrokehandler.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_observablemixin__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/observablemixin */ "./node_modules/@ckeditor/ckeditor5-utils/src/observablemixin.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/mix */ "./node_modules/@ckeditor/ckeditor5-utils/src/mix.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/* harmony import */ var _ckeditor_ckeditor5_engine_src_view_stylesmap__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! @ckeditor/ckeditor5-engine/src/view/stylesmap */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/stylesmap.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module core/editor/editor
*/
/**
* The class representing a basic, generic editor.
*
* Check out the list of its subclasses to learn about specific editor implementations.
*
* All editor implementations (like {@link module:editor-classic/classiceditor~ClassicEditor} or
* {@link module:editor-inline/inlineeditor~InlineEditor}) should extend this class. They can add their
* own methods and properties.
*
* When you are implementing a plugin, this editor represents the API
* which your plugin can expect to get when using its {@link module:core/plugin~Plugin#editor} property.
*
* This API should be sufficient in order to implement the "editing" part of your feature
* (schema definition, conversion, commands, keystrokes, etc.).
* It does not define the editor UI, which is available only if
* the specific editor implements also the {@link module:core/editor/editorwithui~EditorWithUI} interface
* (as most editor implementations do).
*
* @abstract
* @mixes module:utils/observablemixin~ObservableMixin
*/
class Editor {
/**
* Creates a new instance of the editor class.
*
* Usually, not to be used directly. See the static {@link module:core/editor/editor~Editor.create `create()`} method.
*
* @param {Object} [config={}] The editor configuration.
*/
constructor( config = {} ) {
// Prefer the language passed as the argument to the constructor instead of the constructor's `defaultConfig`, if both are set.
const language = config.language || ( this.constructor.defaultConfig && this.constructor.defaultConfig.language );
/**
* The editor context.
* When it is not provided through the configuration, the editor creates it.
*
* @protected
* @type {module:core/context~Context}
*/
this._context = config.context || new _context__WEBPACK_IMPORTED_MODULE_0__["default"]( { language } );
this._context._addEditor( this, !config.context );
// Clone the plugins to make sure that the plugin array will not be shared
// between editors and make the watchdog feature work correctly.
const availablePlugins = Array.from( this.constructor.builtinPlugins || [] );
/**
* Stores all configurations specific to this editor instance.
*
* editor.config.get( 'image.toolbar' );
* // -> [ 'imageStyle:block', 'imageStyle:side', '|', 'toggleImageCaption', 'imageTextAlternative' ]
*
* @readonly
* @member {module:utils/config~Config}
*/
this.config = new _ckeditor_ckeditor5_utils_src_config__WEBPACK_IMPORTED_MODULE_1__["default"]( config, this.constructor.defaultConfig );
this.config.define( 'plugins', availablePlugins );
this.config.define( this._context._getEditorConfig() );
/**
* The plugins loaded and in use by this editor instance.
*
* editor.plugins.get( 'ClipboardPipeline' ); // -> An instance of the clipboard pipeline plugin.
*
* @readonly
* @member {module:core/plugincollection~PluginCollection}
*/
this.plugins = new _plugincollection__WEBPACK_IMPORTED_MODULE_3__["default"]( this, availablePlugins, this._context.plugins );
/**
* The locale instance.
*
* @readonly
* @type {module:utils/locale~Locale}
*/
this.locale = this._context.locale;
/**
* Shorthand for {@link module:utils/locale~Locale#t}.
*
* @see module:utils/locale~Locale#t
* @method #t
*/
this.t = this.locale.t;
/**
* Commands registered to the editor.
*
* Use the shorthand {@link #execute `editor.execute()`} method to execute commands:
*
* // Execute the bold command:
* editor.execute( 'bold' );
*
* // Check the state of the bold command:
* editor.commands.get( 'bold' ).value;
*
* @readonly
* @member {module:core/commandcollection~CommandCollection}
*/
this.commands = new _commandcollection__WEBPACK_IMPORTED_MODULE_4__["default"]();
/**
* Indicates the editor life-cycle state.
*
* The editor is in one of the following states:
*
* * `initializing` – During the editor initialization (before
* {@link module:core/editor/editor~Editor.create `Editor.create()`}) finished its job.
* * `ready` – After the promise returned by the {@link module:core/editor/editor~Editor.create `Editor.create()`}
* method is resolved.
* * `destroyed` – Once the {@link #destroy `editor.destroy()`} method was called.
*
* @observable
* @member {'initializing'|'ready'|'destroyed'} #state
*/
this.set( 'state', 'initializing' );
this.once( 'ready', () => ( this.state = 'ready' ), { priority: 'high' } );
this.once( 'destroy', () => ( this.state = 'destroyed' ), { priority: 'high' } );
/**
* Defines whether this editor is in read-only mode.
*
* In read-only mode the editor {@link #commands commands} are disabled so it is not possible
* to modify the document by using them. Also, the editable element(s) become non-editable.
*
* In order to make the editor read-only, you can set this value directly:
*
* editor.isReadOnly = true;
*
* @observable
* @member {Boolean} #isReadOnly
*/
this.set( 'isReadOnly', false );
/**
* The editor's model.
*
* The central point of the editor's abstract data model.
*
* @readonly
* @member {module:engine/model/model~Model}
*/
this.model = new _ckeditor_ckeditor5_engine_src_model_model__WEBPACK_IMPORTED_MODULE_7__["default"]();
const stylesProcessor = new _ckeditor_ckeditor5_engine_src_view_stylesmap__WEBPACK_IMPORTED_MODULE_12__.StylesProcessor();
/**
* The {@link module:engine/controller/datacontroller~DataController data controller}.
* Used e.g. for setting and retrieving the editor data.
*
* @readonly
* @member {module:engine/controller/datacontroller~DataController}
*/
this.data = new _ckeditor_ckeditor5_engine_src_controller_datacontroller__WEBPACK_IMPORTED_MODULE_5__["default"]( this.model, stylesProcessor );
/**
* The {@link module:engine/controller/editingcontroller~EditingController editing controller}.
* Controls user input and rendering the content for editing.
*
* @readonly
* @member {module:engine/controller/editingcontroller~EditingController}
*/
this.editing = new _ckeditor_ckeditor5_engine_src_controller_editingcontroller__WEBPACK_IMPORTED_MODULE_2__["default"]( this.model, stylesProcessor );
this.editing.view.document.bind( 'isReadOnly' ).to( this );
/**
* Conversion manager through which you can register model-to-view and view-to-model converters.
*
* See the {@link module:engine/conversion/conversion~Conversion} documentation to learn how to add converters.
*
* @readonly
* @member {module:engine/conversion/conversion~Conversion}
*/
this.conversion = new _ckeditor_ckeditor5_engine_src_conversion_conversion__WEBPACK_IMPORTED_MODULE_6__["default"]( [ this.editing.downcastDispatcher, this.data.downcastDispatcher ], this.data.upcastDispatcher );
this.conversion.addAlias( 'dataDowncast', this.data.downcastDispatcher );
this.conversion.addAlias( 'editingDowncast', this.editing.downcastDispatcher );
/**
* An instance of the {@link module:core/editingkeystrokehandler~EditingKeystrokeHandler}.
*
* It allows setting simple keystrokes:
*
* // Execute the bold command on Ctrl+E:
* editor.keystrokes.set( 'Ctrl+E', 'bold' );
*
* // Execute your own callback:
* editor.keystrokes.set( 'Ctrl+E', ( data, cancel ) => {
* console.log( data.keyCode );
*
* // Prevent the default (native) action and stop the underlying keydown event
* // so no other editor feature will interfere.
* cancel();
* } );
*
* Note: Certain typing-oriented keystrokes (like <kbd>Backspace</kbd> or <kbd>Enter</kbd>) are handled
* by a low-level mechanism and trying to listen to them via the keystroke handler will not work reliably.
* To handle these specific keystrokes, see the events fired by the
* {@link module:engine/view/document~Document editing view document} (`editor.editing.view.document`).
*
* @readonly
* @member {module:core/editingkeystrokehandler~EditingKeystrokeHandler}
*/
this.keystrokes = new _editingkeystrokehandler__WEBPACK_IMPORTED_MODULE_8__["default"]( this );
this.keystrokes.listenTo( this.editing.view.document );
}
/**
* Loads and initializes plugins specified in the configuration.
*
* @returns {Promise.<module:core/plugin~LoadedPlugins>} A promise which resolves
* once the initialization is completed, providing an array of loaded plugins.
*/
initPlugins() {
const config = this.config;
const plugins = config.get( 'plugins' );
const removePlugins = config.get( 'removePlugins' ) || [];
const extraPlugins = config.get( 'extraPlugins' ) || [];
const substitutePlugins = config.get( 'substitutePlugins' ) || [];
return this.plugins.init( plugins.concat( extraPlugins ), removePlugins, substitutePlugins );
}
/**
* Destroys the editor instance, releasing all resources used by it.
*
* **Note** The editor cannot be destroyed during the initialization phase so if it is called
* while the editor {@link #state is being initialized}, it will wait for the editor initialization before destroying it.
*
* @fires destroy
* @returns {Promise} A promise that resolves once the editor instance is fully destroyed.
*/
destroy() {
let readyPromise = Promise.resolve();
if ( this.state == 'initializing' ) {
readyPromise = new Promise( resolve => this.once( 'ready', resolve ) );
}
return readyPromise
.then( () => {
this.fire( 'destroy' );
this.stopListening();
this.commands.destroy();
} )
.then( () => this.plugins.destroy() )
.then( () => {
this.model.destroy();
this.data.destroy();
this.editing.destroy();
this.keystrokes.destroy();
} )
// Remove the editor from the context.
// When the context was created by this editor, the context will be destroyed.
.then( () => this._context._removeEditor( this ) );
}
/**
* Executes the specified command with given parameters.
*
* Shorthand for:
*
* editor.commands.get( commandName ).execute( ... );
*
* @param {String} commandName The name of the command to execute.
* @param {*} [...commandParams] Command parameters.
* @returns {*} The value returned by the {@link module:core/commandcollection~CommandCollection#execute `commands.execute()`}.
*/
execute( ...args ) {
try {
return this.commands.execute( ...args );
} catch ( err ) {
// @if CK_DEBUG // throw err;
/* istanbul ignore next */
_ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_11__["default"].rethrowUnexpectedError( err, this );
}
}
/**
* Focuses the editor.
*
* **Note** To explicitly focus the editing area of the editor, use the
* {@link module:engine/view/view~View#focus `editor.editing.view.focus()`} method of the editing view.
*
* Check out the {@glink framework/guides/deep-dive/ui/focus-tracking#focus-in-the-editor-ui Focus in the editor UI} section
* of the {@glink framework/guides/deep-dive/ui/focus-tracking Deep dive into focus tracking} guide to learn more.
*/
focus() {
this.editing.view.focus();
}
/**
* Creates and initializes a new editor instance.
*
* This is an abstract method. Every editor type needs to implement its own initialization logic.
*
* See the `create()` methods of the existing editor types to learn how to use them:
*
* * {@link module:editor-classic/classiceditor~ClassicEditor.create `ClassicEditor.create()`}
* * {@link module:editor-balloon/ballooneditor~BalloonEditor.create `BalloonEditor.create()`}
* * {@link module:editor-decoupled/decouplededitor~DecoupledEditor.create `DecoupledEditor.create()`}
* * {@link module:editor-inline/inlineeditor~InlineEditor.create `InlineEditor.create()`}
*
* @abstract
* @method module:core/editor/editor~Editor.create
*/
}
(0,_ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_10__["default"])( Editor, _ckeditor_ckeditor5_utils_src_observablemixin__WEBPACK_IMPORTED_MODULE_9__["default"] );
/**
* Fired when the {@link module:engine/controller/datacontroller~DataController#event:ready data} and all additional
* editor components are ready.
*
* Note: This event is most useful for plugin developers. When integrating the editor with your website or
* application, you do not have to listen to `editor#ready` because when the promise returned by the static
* {@link module:core/editor/editor~Editor.create `Editor.create()`} event is resolved, the editor is already ready.
* In fact, since the first moment when the editor instance is available to you is inside `then()`'s callback,
* you cannot even add a listener to the `editor#ready` event.
*
* See also the {@link #state `editor.state`} property.
*
* @event ready
*/
/**
* Fired when this editor instance is destroyed. The editor at this point is not usable and this event should be used to
* perform the clean-up in any plugin.
*
*
* See also the {@link #state `editor.state`} property.
*
* @event destroy
*/
/**
* This error is thrown when trying to pass a `<textarea>` element to a `create()` function of an editor class.
*
* The only editor type which can be initialized on `<textarea>` elements is
* the {@glink builds/guides/predefined-builds/overview#classic-editor classic editor}.
* This editor hides the passed element and inserts its own UI next to it. Other types of editors reuse the passed element as their root
* editable element and therefore `<textarea>` is not appropriate for them. Use a `<div>` or another text container instead:
*
* <div id="editor">
* <p>Initial content.</p>
* </div>
*
* @error editor-wrong-element
*/
/**
* An array of plugins built into this editor class.
*
* It is used in CKEditor 5 builds to provide a list of plugins which are later automatically initialized
* during the editor initialization.
*
* They will be automatically initialized by the editor, unless listed in `config.removePlugins` and
* unless `config.plugins` is passed.
*
* // Build some plugins into the editor class first.
* ClassicEditor.builtinPlugins = [ FooPlugin, BarPlugin ];
*
* // Normally, you need to define config.plugins, but since ClassicEditor.builtinPlugins was
* // defined, now you can call create() without any configuration.
* ClassicEditor
* .create( sourceElement )
* .then( editor => {
* editor.plugins.get( FooPlugin ); // -> An instance of the Foo plugin.
* editor.plugins.get( BarPlugin ); // -> An instance of the Bar plugin.
* } );
*
* ClassicEditor
* .create( sourceElement, {
* // Do not initialize these plugins (note: it is defined by a string):
* removePlugins: [ 'Foo' ]
* } )
* .then( editor => {
* editor.plugins.get( FooPlugin ); // -> Undefined.
* editor.config.get( BarPlugin ); // -> An instance of the Bar plugin.
* } );
*
* ClassicEditor
* .create( sourceElement, {
* // Load only this plugin. It can also be defined by a string if
* // this plugin was built into the editor class.
* plugins: [ FooPlugin ]
* } )
* .then( editor => {
* editor.plugins.get( FooPlugin ); // -> An instance of the Foo plugin.
* editor.config.get( BarPlugin ); // -> Undefined.
* } );
*
* See also {@link module:core/editor/editor~Editor.defaultConfig}.
*
* @static
* @member {Array.<Function>} module:core/editor/editor~Editor.builtinPlugins
*/
/**
* The default configuration which is built into the editor class.
*
* It is used in CKEditor 5 builds to provide the default configuration options which are later used during the editor initialization.
*
* ClassicEditor.defaultConfig = {
* foo: 1,
* bar: 2
* };
*
* ClassicEditor
* .create( sourceElement )
* .then( editor => {
* editor.config.get( 'foo' ); // -> 1
* editor.config.get( 'bar' ); // -> 2
* } );
*
* // The default options can be overridden by the configuration passed to create().
* ClassicEditor
* .create( sourceElement, { bar: 3 } )
* .then( editor => {
* editor.config.get( 'foo' ); // -> 1
* editor.config.get( 'bar' ); // -> 3
* } );
*
* See also {@link module:core/editor/editor~Editor.builtinPlugins}.
*
* @static
* @member {Object} module:core/editor/editor~Editor.defaultConfig
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-core/src/editor/editorui.js":
/*!**********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-core/src/editor/editorui.js ***!
\**********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ EditorUI)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_ui_src_componentfactory__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-ui/src/componentfactory */ "./node_modules/@ckeditor/ckeditor5-ui/src/componentfactory.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_focustracker__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/focustracker */ "./node_modules/@ckeditor/ckeditor5-utils/src/focustracker.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_observablemixin__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/observablemixin */ "./node_modules/@ckeditor/ckeditor5-utils/src/observablemixin.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/mix */ "./node_modules/@ckeditor/ckeditor5-utils/src/mix.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module core/editor/editorui
*/
/* globals console */
/**
* A class providing the minimal interface that is required to successfully bootstrap any editor UI.
*
* @mixes module:utils/emittermixin~EmitterMixin
*/
class EditorUI {
/**
* Creates an instance of the editor UI class.
*
* @param {module:core/editor/editor~Editor} editor The editor instance.
*/
constructor( editor ) {
/**
* The editor that the UI belongs to.
*
* @readonly
* @member {module:core/editor/editor~Editor} #editor
*/
this.editor = editor;
/**
* An instance of the {@link module:ui/componentfactory~ComponentFactory}, a registry used by plugins
* to register factories of specific UI components.
*
* @readonly
* @member {module:ui/componentfactory~ComponentFactory} #componentFactory
*/
this.componentFactory = new _ckeditor_ckeditor5_ui_src_componentfactory__WEBPACK_IMPORTED_MODULE_0__["default"]( editor );
/**
* Stores the information about the editor UI focus and propagates it so various plugins and components
* are unified as a focus group.
*
* @readonly
* @member {module:utils/focustracker~FocusTracker} #focusTracker
*/
this.focusTracker = new _ckeditor_ckeditor5_utils_src_focustracker__WEBPACK_IMPORTED_MODULE_1__["default"]();
/**
* Stores viewport offsets from every direction.
*
* Viewport offset can be used to constrain balloons or other UI elements into an element smaller than the viewport.
* This can be useful if there are any other absolutely positioned elements that may interfere with editor UI.
*
* Example `editor.ui.viewportOffset` returns:
*
* ```js
* {
* top: 50,
* right: 50,
* bottom: 50,
* left: 50
* }
* ```
*
* This property can be overriden after editor already being initialized:
*
* ```js
* editor.ui.viewportOffset = {
* top: 100,
* right: 0,
* bottom: 0,
* left: 0
* };
* ```
*
* @observable
* @member {Object} #viewportOffset
*/
this.set( 'viewportOffset', this._readViewportOffsetFromConfig() );
/**
* Stores all editable elements used by the editor instance.
*
* @private
* @member {Map.<String,HTMLElement>}
*/
this._editableElementsMap = new Map();
// Informs UI components that should be refreshed after layout change.
this.listenTo( editor.editing.view.document, 'layoutChanged', () => this.update() );
}
/**
* The main (outermost) DOM element of the editor UI.
*
* For example, in {@link module:editor-classic/classiceditor~ClassicEditor} it is a `<div>` which
* wraps the editable element and the toolbar. In {@link module:editor-inline/inlineeditor~InlineEditor}
* it is the editable element itself (as there is no other wrapper). However, in
* {@link module:editor-decoupled/decouplededitor~DecoupledEditor} it is set to `null` because this editor does not
* come with a single "main" HTML element (its editable element and toolbar are separate).
*
* This property can be understood as a shorthand for retrieving the element that a specific editor integration
* considers to be its main DOM element.
*
* @readonly
* @member {HTMLElement|null} #element
*/
get element() {
return null;
}
/**
* Fires the {@link module:core/editor/editorui~EditorUI#event:update `update`} event.
*
* This method should be called when the editor UI (e.g. positions of its balloons) needs to be updated due to
* some environmental change which CKEditor 5 is not aware of (e.g. resize of a container in which it is used).
*/
update() {
this.fire( 'update' );
}
/**
* Destroys the UI.
*/
destroy() {
this.stopListening();
this.focusTracker.destroy();
// Clean–up the references to the CKEditor instance stored in the native editable DOM elements.
for ( const domElement of this._editableElementsMap.values() ) {
domElement.ckeditorInstance = null;
}
this._editableElementsMap = new Map();
}
/**
* Store the native DOM editable element used by the editor under
* a unique name.
*
* @param {String} rootName The unique name of the editable element.
* @param {HTMLElement} domElement The native DOM editable element.
*/
setEditableElement( rootName, domElement ) {
this._editableElementsMap.set( rootName, domElement );
// Put a reference to the CKEditor instance in the editable native DOM element.
// It helps 3rd–party software (browser extensions, other libraries) access and recognize
// CKEditor 5 instances (editing roots) and use their API (there is no global editor
// instance registry).
if ( !domElement.ckeditorInstance ) {
domElement.ckeditorInstance = this.editor;
}
}
/**
* Returns the editable editor element with the given name or null if editable does not exist.
*
* @param {String} [rootName=main] The editable name.
* @returns {HTMLElement|undefined}
*/
getEditableElement( rootName = 'main' ) {
return this._editableElementsMap.get( rootName );
}
/**
* Returns array of names of all editor editable elements.
*
* @returns {Iterable.<String>}
*/
getEditableElementsNames() {
return this._editableElementsMap.keys();
}
/**
* Stores all editable elements used by the editor instance.
*
* @protected
* @deprecated
* @member {Map.<String,HTMLElement>}
*/
get _editableElements() {
/**
* The {@link module:core/editor/editorui~EditorUI#_editableElements `EditorUI#_editableElements`} property has been
* deprecated and will be removed in the near future. Please use {@link #setEditableElement `setEditableElement()`} and
* {@link #getEditableElement `getEditableElement()`} methods instead.
*
* @error editor-ui-deprecated-editable-elements
* @param {module:core/editor/editorui~EditorUI} editorUI Editor UI instance the deprecated property belongs to.
*/
console.warn(
'editor-ui-deprecated-editable-elements: ' +
'The EditorUI#_editableElements property has been deprecated and will be removed in the near future.',
{ editorUI: this } );
return this._editableElementsMap;
}
/**
* Returns viewport offsets object:
*
* ```js
* {
* top: Number,
* right: Number,
* bottom: Number,
* left: Number
* }
* ```
*
* Only top property is currently supported.
*
* @private
* @return {Object}
*/
_readViewportOffsetFromConfig() {
const editor = this.editor;
const viewportOffsetConfig = editor.config.get( 'ui.viewportOffset' );
if ( viewportOffsetConfig ) {
return viewportOffsetConfig;
}
const legacyOffsetConfig = editor.config.get( 'toolbar.viewportTopOffset' );
// Fall back to deprecated toolbar config.
if ( legacyOffsetConfig ) {
/**
* The {@link module:core/editor/editorconfig~EditorConfig#toolbar `EditorConfig#toolbar.viewportTopOffset`}
* property has been deprecated and will be removed in the near future. Please use
* {@link module:core/editor/editorconfig~EditorConfig#ui `EditorConfig#ui.viewportOffset`} instead.
*
* @error editor-ui-deprecated-viewport-offset-config
*/
console.warn(
'editor-ui-deprecated-viewport-offset-config: ' +
'The `toolbar.vieportTopOffset` configuration option is deprecated. ' +
'It will be removed from future CKEditor versions. Use `ui.viewportOffset.top` instead.'
);
return { top: legacyOffsetConfig };
}
// More keys to come in the future.
return { top: 0 };
}
/**
* Fired when the editor UI is ready.
*
* Fired before {@link module:engine/controller/datacontroller~DataController#event:ready}.
*
* @event ready
*/
/**
* Fired whenever the UI (all related components) should be refreshed.
*
* **Note:**: The event is fired after each {@link module:engine/view/document~Document#event:layoutChanged}.
* It can also be fired manually via the {@link module:core/editor/editorui~EditorUI#update} method.
*
* @event update
*/
}
(0,_ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_3__["default"])( EditorUI, _ckeditor_ckeditor5_utils_src_observablemixin__WEBPACK_IMPORTED_MODULE_2__["default"] );
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-core/src/editor/utils/attachtoform.js":
/*!********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-core/src/editor/utils/attachtoform.js ***!
\********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ attachToForm)
/* harmony export */ });
/* harmony import */ var lodash_es__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! lodash-es */ "./node_modules/lodash-es/isFunction.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module core/editor/utils/attachtoform
*/
/**
* Checks if the editor is initialized on a `<textarea>` element that belongs to a form. If yes, it updates the editor's element
* content before submitting the form.
*
* This helper requires the {@link module:core/editor/utils/elementapimixin~ElementApi ElementApi interface}.
*
* @param {module:core/editor/editor~Editor} editor Editor instance.
*/
function attachToForm( editor ) {
if ( !(0,lodash_es__WEBPACK_IMPORTED_MODULE_1__["default"])( editor.updateSourceElement ) ) {
/**
* The editor passed to `attachToForm()` must implement the
* {@link module:core/editor/utils/elementapimixin~ElementApi} interface.
*
* @error attachtoform-missing-elementapi-interface
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_0__["default"](
'attachtoform-missing-elementapi-interface',
editor
);
}
const sourceElement = editor.sourceElement;
// Only when replacing a textarea which is inside of a form element.
if ( sourceElement && sourceElement.tagName.toLowerCase() === 'textarea' && sourceElement.form ) {
let originalSubmit;
const form = sourceElement.form;
const onSubmit = () => editor.updateSourceElement();
// Replace the original form#submit() to call a custom submit function first.
// Check if #submit is a function because the form might have an input named "submit".
if ( (0,lodash_es__WEBPACK_IMPORTED_MODULE_1__["default"])( form.submit ) ) {
originalSubmit = form.submit;
form.submit = () => {
onSubmit();
originalSubmit.apply( form );
};
}
// Update the replaced textarea with data before each form#submit event.
form.addEventListener( 'submit', onSubmit );
// Remove the submit listener and revert the original submit method on
// editor#destroy.
editor.on( 'destroy', () => {
form.removeEventListener( 'submit', onSubmit );
if ( originalSubmit ) {
form.submit = originalSubmit;
}
} );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-core/src/editor/utils/dataapimixin.js":
/*!********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-core/src/editor/utils/dataapimixin.js ***!
\********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module core/editor/utils/dataapimixin
*/
/**
* Implementation of the {@link module:core/editor/utils/dataapimixin~DataApi}.
*
* @mixin DataApiMixin
* @implements module:core/editor/utils/dataapimixin~DataApi
*/
const DataApiMixin = {
/**
* @inheritDoc
*/
setData( data ) {
this.data.set( data );
},
/**
* @inheritDoc
*/
getData( options ) {
return this.data.get( options );
}
};
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (DataApiMixin);
/**
* Interface defining editor methods for setting and getting data to and from the editor's main root element
* using the {@link module:core/editor/editor~Editor#data data pipeline}.
*
* This interface is not a part of the {@link module:core/editor/editor~Editor} class because one may want to implement
* an editor with multiple root elements, in which case the methods for setting and getting data will need to be implemented
* differently.
*
* @interface DataApi
*/
/**
* Sets the data in the editor.
*
* editor.setData( '<p>This is editor!</p>' );
*
* By default the editor accepts HTML. This can be controlled by injecting a different data processor.
* See the {@glink features/markdown Markdown output} guide for more details.
*
* Note: Not only is the format of the data configurable, but the type of the `setData()`'s parameter does not
* have to be a string either. You can e.g. accept an object or a DOM `DocumentFragment` if you consider this
* the right format for you.
*
* @method #setData
* @param {String} data Input data.
*/
/**
* Gets the data from the editor.
*
* editor.getData(); // -> '<p>This is editor!</p>'
*
* By default the editor outputs HTML. This can be controlled by injecting a different data processor.
* See the {@glink features/markdown Markdown output} guide for more details.
*
* Note: Not only is the format of the data configurable, but the type of the `getData()`'s return value does not
* have to be a string either. You can e.g. return an object or a DOM `DocumentFragment` if you consider this
* the right format for you.
*
* @method #getData
* @param {Object} [options] Additional configuration for the retrieved data.
* Editor features may introduce more configuration options that can be set through this parameter.
* @param {String} [options.rootName='main'] Root name.
* @param {String} [options.trim='empty'] Whether returned data should be trimmed. This option is set to `'empty'` by default,
* which means that whenever editor content is considered empty, an empty string is returned. To turn off trimming
* use `'none'`. In such cases exact content will be returned (for example `'<p> </p>'` for an empty editor).
* @returns {String} Output data.
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-core/src/editor/utils/elementapimixin.js":
/*!***********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-core/src/editor/utils/elementapimixin.js ***!
\***********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_dom_setdatainelement__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/dom/setdatainelement */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/setdatainelement.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module core/editor/utils/elementapimixin
*/
/**
* Implementation of the {@link module:core/editor/utils/elementapimixin~ElementApi}.
*
* @mixin ElementApiMixin
* @implements module:core/editor/utils/elementapimixin~ElementApi
*/
const ElementApiMixin = {
/**
* @inheritDoc
*/
updateSourceElement() {
if ( !this.sourceElement ) {
/**
* Cannot update the source element of a detached editor.
*
* The {@link ~ElementApi#updateSourceElement `updateSourceElement()`} method cannot be called if you did not
* pass an element to `Editor.create()`.
*
* @error editor-missing-sourceelement
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_0__["default"](
'editor-missing-sourceelement',
this
);
}
(0,_ckeditor_ckeditor5_utils_src_dom_setdatainelement__WEBPACK_IMPORTED_MODULE_1__["default"])( this.sourceElement, this.data.get() );
}
};
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (ElementApiMixin);
/**
* Interface describing an editor that replaced a DOM element (was "initialized on an element").
*
* Such an editor should provide a method to
* {@link module:core/editor/utils/elementapimixin~ElementApi#updateSourceElement update the replaced element with the current data}.
*
* @interface ElementApi
*/
/**
* The element on which the editor has been initialized.
*
* @readonly
* @member {HTMLElement} #sourceElement
*/
/**
* Updates the {@link #sourceElement editor source element}'s content with the data.
*
* @method #updateSourceElement
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-core/src/editor/utils/securesourceelement.js":
/*!***************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-core/src/editor/utils/securesourceelement.js ***!
\***************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ secureSourceElement)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module core/editor/utils/securesourceelement
*/
/**
* Marks the source element on which the editor was initialized. This prevents other editor instances from using this element.
*
* Running multiple editor instances on the same source element causes various issues and it is
* crucial this helper is called as soon as the source element is known to prevent collisions.
*
* @param {module:core/editor/editor~Editor} editor Editor instance.
*/
function secureSourceElement( editor ) {
const sourceElement = editor.sourceElement;
// If the editor was initialized without specifying an element, we don't need to secure anything.
if ( !sourceElement ) {
return;
}
if ( sourceElement.ckeditorInstance ) {
/**
* A DOM element used to create the editor (e.g.
* {@link module:editor-inline/inlineeditor~InlineEditor.create `InlineEditor.create()`})
* has already been used to create another editor instance. Make sure each editor is
* created with an unique DOM element.
*
* @error editor-source-element-already-used
* @param {HTMLElement} element DOM element that caused the collision.
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_0__["default"](
'editor-source-element-already-used',
editor
);
}
sourceElement.ckeditorInstance = editor;
editor.once( 'destroy', () => {
delete sourceElement.ckeditorInstance;
} );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-core/src/index.js":
/*!************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-core/src/index.js ***!
\************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "Command": () => (/* reexport safe */ _command__WEBPACK_IMPORTED_MODULE_1__["default"]),
/* harmony export */ "Context": () => (/* reexport safe */ _context__WEBPACK_IMPORTED_MODULE_3__["default"]),
/* harmony export */ "ContextPlugin": () => (/* reexport safe */ _contextplugin__WEBPACK_IMPORTED_MODULE_4__["default"]),
/* harmony export */ "DataApiMixin": () => (/* reexport safe */ _editor_utils_dataapimixin__WEBPACK_IMPORTED_MODULE_8__["default"]),
/* harmony export */ "Editor": () => (/* reexport safe */ _editor_editor__WEBPACK_IMPORTED_MODULE_5__["default"]),
/* harmony export */ "EditorUI": () => (/* reexport safe */ _editor_editorui__WEBPACK_IMPORTED_MODULE_6__["default"]),
/* harmony export */ "ElementApiMixin": () => (/* reexport safe */ _editor_utils_elementapimixin__WEBPACK_IMPORTED_MODULE_9__["default"]),
/* harmony export */ "MultiCommand": () => (/* reexport safe */ _multicommand__WEBPACK_IMPORTED_MODULE_2__["default"]),
/* harmony export */ "PendingActions": () => (/* reexport safe */ _pendingactions__WEBPACK_IMPORTED_MODULE_11__["default"]),
/* harmony export */ "Plugin": () => (/* reexport safe */ _plugin__WEBPACK_IMPORTED_MODULE_0__["default"]),
/* harmony export */ "attachToForm": () => (/* reexport safe */ _editor_utils_attachtoform__WEBPACK_IMPORTED_MODULE_7__["default"]),
/* harmony export */ "icons": () => (/* binding */ icons),
/* harmony export */ "secureSourceElement": () => (/* reexport safe */ _editor_utils_securesourceelement__WEBPACK_IMPORTED_MODULE_10__["default"])
/* harmony export */ });
/* harmony import */ var _plugin__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./plugin */ "./node_modules/@ckeditor/ckeditor5-core/src/plugin.js");
/* harmony import */ var _command__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./command */ "./node_modules/@ckeditor/ckeditor5-core/src/command.js");
/* harmony import */ var _multicommand__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./multicommand */ "./node_modules/@ckeditor/ckeditor5-core/src/multicommand.js");
/* harmony import */ var _context__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./context */ "./node_modules/@ckeditor/ckeditor5-core/src/context.js");
/* harmony import */ var _contextplugin__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./contextplugin */ "./node_modules/@ckeditor/ckeditor5-core/src/contextplugin.js");
/* harmony import */ var _editor_editor__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./editor/editor */ "./node_modules/@ckeditor/ckeditor5-core/src/editor/editor.js");
/* harmony import */ var _editor_editorui__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./editor/editorui */ "./node_modules/@ckeditor/ckeditor5-core/src/editor/editorui.js");
/* harmony import */ var _editor_utils_attachtoform__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./editor/utils/attachtoform */ "./node_modules/@ckeditor/ckeditor5-core/src/editor/utils/attachtoform.js");
/* harmony import */ var _editor_utils_dataapimixin__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./editor/utils/dataapimixin */ "./node_modules/@ckeditor/ckeditor5-core/src/editor/utils/dataapimixin.js");
/* harmony import */ var _editor_utils_elementapimixin__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./editor/utils/elementapimixin */ "./node_modules/@ckeditor/ckeditor5-core/src/editor/utils/elementapimixin.js");
/* harmony import */ var _editor_utils_securesourceelement__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./editor/utils/securesourceelement */ "./node_modules/@ckeditor/ckeditor5-core/src/editor/utils/securesourceelement.js");
/* harmony import */ var _pendingactions__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ./pendingactions */ "./node_modules/@ckeditor/ckeditor5-core/src/pendingactions.js");
/* harmony import */ var _theme_icons_cancel_svg__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! ./../theme/icons/cancel.svg */ "./node_modules/@ckeditor/ckeditor5-core/theme/icons/cancel.svg");
/* harmony import */ var _theme_icons_caption_svg__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(/*! ./../theme/icons/caption.svg */ "./node_modules/@ckeditor/ckeditor5-core/theme/icons/caption.svg");
/* harmony import */ var _theme_icons_check_svg__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(/*! ./../theme/icons/check.svg */ "./node_modules/@ckeditor/ckeditor5-core/theme/icons/check.svg");
/* harmony import */ var _theme_icons_cog_svg__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(/*! ./../theme/icons/cog.svg */ "./node_modules/@ckeditor/ckeditor5-core/theme/icons/cog.svg");
/* harmony import */ var _theme_icons_eraser_svg__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(/*! ./../theme/icons/eraser.svg */ "./node_modules/@ckeditor/ckeditor5-core/theme/icons/eraser.svg");
/* harmony import */ var _theme_icons_low_vision_svg__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(/*! ./../theme/icons/low-vision.svg */ "./node_modules/@ckeditor/ckeditor5-core/theme/icons/low-vision.svg");
/* harmony import */ var _theme_icons_image_svg__WEBPACK_IMPORTED_MODULE_18__ = __webpack_require__(/*! ./../theme/icons/image.svg */ "./node_modules/@ckeditor/ckeditor5-core/theme/icons/image.svg");
/* harmony import */ var _theme_icons_align_bottom_svg__WEBPACK_IMPORTED_MODULE_19__ = __webpack_require__(/*! ./../theme/icons/align-bottom.svg */ "./node_modules/@ckeditor/ckeditor5-core/theme/icons/align-bottom.svg");
/* harmony import */ var _theme_icons_align_middle_svg__WEBPACK_IMPORTED_MODULE_20__ = __webpack_require__(/*! ./../theme/icons/align-middle.svg */ "./node_modules/@ckeditor/ckeditor5-core/theme/icons/align-middle.svg");
/* harmony import */ var _theme_icons_align_top_svg__WEBPACK_IMPORTED_MODULE_21__ = __webpack_require__(/*! ./../theme/icons/align-top.svg */ "./node_modules/@ckeditor/ckeditor5-core/theme/icons/align-top.svg");
/* harmony import */ var _theme_icons_align_left_svg__WEBPACK_IMPORTED_MODULE_22__ = __webpack_require__(/*! ./../theme/icons/align-left.svg */ "./node_modules/@ckeditor/ckeditor5-core/theme/icons/align-left.svg");
/* harmony import */ var _theme_icons_align_center_svg__WEBPACK_IMPORTED_MODULE_23__ = __webpack_require__(/*! ./../theme/icons/align-center.svg */ "./node_modules/@ckeditor/ckeditor5-core/theme/icons/align-center.svg");
/* harmony import */ var _theme_icons_align_right_svg__WEBPACK_IMPORTED_MODULE_24__ = __webpack_require__(/*! ./../theme/icons/align-right.svg */ "./node_modules/@ckeditor/ckeditor5-core/theme/icons/align-right.svg");
/* harmony import */ var _theme_icons_align_justify_svg__WEBPACK_IMPORTED_MODULE_25__ = __webpack_require__(/*! ./../theme/icons/align-justify.svg */ "./node_modules/@ckeditor/ckeditor5-core/theme/icons/align-justify.svg");
/* harmony import */ var _theme_icons_object_left_svg__WEBPACK_IMPORTED_MODULE_26__ = __webpack_require__(/*! ./../theme/icons/object-left.svg */ "./node_modules/@ckeditor/ckeditor5-core/theme/icons/object-left.svg");
/* harmony import */ var _theme_icons_object_center_svg__WEBPACK_IMPORTED_MODULE_27__ = __webpack_require__(/*! ./../theme/icons/object-center.svg */ "./node_modules/@ckeditor/ckeditor5-core/theme/icons/object-center.svg");
/* harmony import */ var _theme_icons_object_right_svg__WEBPACK_IMPORTED_MODULE_28__ = __webpack_require__(/*! ./../theme/icons/object-right.svg */ "./node_modules/@ckeditor/ckeditor5-core/theme/icons/object-right.svg");
/* harmony import */ var _theme_icons_object_full_width_svg__WEBPACK_IMPORTED_MODULE_29__ = __webpack_require__(/*! ./../theme/icons/object-full-width.svg */ "./node_modules/@ckeditor/ckeditor5-core/theme/icons/object-full-width.svg");
/* harmony import */ var _theme_icons_object_inline_svg__WEBPACK_IMPORTED_MODULE_30__ = __webpack_require__(/*! ./../theme/icons/object-inline.svg */ "./node_modules/@ckeditor/ckeditor5-core/theme/icons/object-inline.svg");
/* harmony import */ var _theme_icons_object_inline_left_svg__WEBPACK_IMPORTED_MODULE_31__ = __webpack_require__(/*! ./../theme/icons/object-inline-left.svg */ "./node_modules/@ckeditor/ckeditor5-core/theme/icons/object-inline-left.svg");
/* harmony import */ var _theme_icons_object_inline_right_svg__WEBPACK_IMPORTED_MODULE_32__ = __webpack_require__(/*! ./../theme/icons/object-inline-right.svg */ "./node_modules/@ckeditor/ckeditor5-core/theme/icons/object-inline-right.svg");
/* harmony import */ var _theme_icons_object_size_full_svg__WEBPACK_IMPORTED_MODULE_33__ = __webpack_require__(/*! ./../theme/icons/object-size-full.svg */ "./node_modules/@ckeditor/ckeditor5-core/theme/icons/object-size-full.svg");
/* harmony import */ var _theme_icons_object_size_large_svg__WEBPACK_IMPORTED_MODULE_34__ = __webpack_require__(/*! ./../theme/icons/object-size-large.svg */ "./node_modules/@ckeditor/ckeditor5-core/theme/icons/object-size-large.svg");
/* harmony import */ var _theme_icons_object_size_small_svg__WEBPACK_IMPORTED_MODULE_35__ = __webpack_require__(/*! ./../theme/icons/object-size-small.svg */ "./node_modules/@ckeditor/ckeditor5-core/theme/icons/object-size-small.svg");
/* harmony import */ var _theme_icons_object_size_medium_svg__WEBPACK_IMPORTED_MODULE_36__ = __webpack_require__(/*! ./../theme/icons/object-size-medium.svg */ "./node_modules/@ckeditor/ckeditor5-core/theme/icons/object-size-medium.svg");
/* harmony import */ var _theme_icons_pencil_svg__WEBPACK_IMPORTED_MODULE_37__ = __webpack_require__(/*! ./../theme/icons/pencil.svg */ "./node_modules/@ckeditor/ckeditor5-core/theme/icons/pencil.svg");
/* harmony import */ var _theme_icons_pilcrow_svg__WEBPACK_IMPORTED_MODULE_38__ = __webpack_require__(/*! ./../theme/icons/pilcrow.svg */ "./node_modules/@ckeditor/ckeditor5-core/theme/icons/pilcrow.svg");
/* harmony import */ var _theme_icons_quote_svg__WEBPACK_IMPORTED_MODULE_39__ = __webpack_require__(/*! ./../theme/icons/quote.svg */ "./node_modules/@ckeditor/ckeditor5-core/theme/icons/quote.svg");
/* harmony import */ var _theme_icons_three_vertical_dots_svg__WEBPACK_IMPORTED_MODULE_40__ = __webpack_require__(/*! ./../theme/icons/three-vertical-dots.svg */ "./node_modules/@ckeditor/ckeditor5-core/theme/icons/three-vertical-dots.svg");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module core
*/
const icons = {
cancel: _theme_icons_cancel_svg__WEBPACK_IMPORTED_MODULE_12__["default"],
caption: _theme_icons_caption_svg__WEBPACK_IMPORTED_MODULE_13__["default"],
check: _theme_icons_check_svg__WEBPACK_IMPORTED_MODULE_14__["default"],
cog: _theme_icons_cog_svg__WEBPACK_IMPORTED_MODULE_15__["default"],
eraser: _theme_icons_eraser_svg__WEBPACK_IMPORTED_MODULE_16__["default"],
lowVision: _theme_icons_low_vision_svg__WEBPACK_IMPORTED_MODULE_17__["default"],
image: _theme_icons_image_svg__WEBPACK_IMPORTED_MODULE_18__["default"],
alignBottom: _theme_icons_align_bottom_svg__WEBPACK_IMPORTED_MODULE_19__["default"],
alignMiddle: _theme_icons_align_middle_svg__WEBPACK_IMPORTED_MODULE_20__["default"],
alignTop: _theme_icons_align_top_svg__WEBPACK_IMPORTED_MODULE_21__["default"],
alignLeft: _theme_icons_align_left_svg__WEBPACK_IMPORTED_MODULE_22__["default"],
alignCenter: _theme_icons_align_center_svg__WEBPACK_IMPORTED_MODULE_23__["default"],
alignRight: _theme_icons_align_right_svg__WEBPACK_IMPORTED_MODULE_24__["default"],
alignJustify: _theme_icons_align_justify_svg__WEBPACK_IMPORTED_MODULE_25__["default"],
objectLeft: _theme_icons_object_inline_left_svg__WEBPACK_IMPORTED_MODULE_31__["default"],
objectCenter: _theme_icons_object_center_svg__WEBPACK_IMPORTED_MODULE_27__["default"],
objectRight: _theme_icons_object_inline_right_svg__WEBPACK_IMPORTED_MODULE_32__["default"],
objectFullWidth: _theme_icons_object_full_width_svg__WEBPACK_IMPORTED_MODULE_29__["default"],
objectInline: _theme_icons_object_inline_svg__WEBPACK_IMPORTED_MODULE_30__["default"],
objectBlockLeft: _theme_icons_object_left_svg__WEBPACK_IMPORTED_MODULE_26__["default"],
objectBlockRight: _theme_icons_object_right_svg__WEBPACK_IMPORTED_MODULE_28__["default"],
objectSizeFull: _theme_icons_object_size_full_svg__WEBPACK_IMPORTED_MODULE_33__["default"],
objectSizeLarge: _theme_icons_object_size_large_svg__WEBPACK_IMPORTED_MODULE_34__["default"],
objectSizeSmall: _theme_icons_object_size_small_svg__WEBPACK_IMPORTED_MODULE_35__["default"],
objectSizeMedium: _theme_icons_object_size_medium_svg__WEBPACK_IMPORTED_MODULE_36__["default"],
pencil: _theme_icons_pencil_svg__WEBPACK_IMPORTED_MODULE_37__["default"],
pilcrow: _theme_icons_pilcrow_svg__WEBPACK_IMPORTED_MODULE_38__["default"],
quote: _theme_icons_quote_svg__WEBPACK_IMPORTED_MODULE_39__["default"],
threeVerticalDots: _theme_icons_three_vertical_dots_svg__WEBPACK_IMPORTED_MODULE_40__["default"]
};
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-core/src/multicommand.js":
/*!*******************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-core/src/multicommand.js ***!
\*******************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ MultiCommand)
/* harmony export */ });
/* harmony import */ var _command__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./command */ "./node_modules/@ckeditor/ckeditor5-core/src/command.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module core/multicommand
*/
/**
* A CKEditor command that aggregates other commands.
*
* This command is used to proxy multiple commands. The multi-command is enabled when
* at least one of its registered child commands is enabled.
* When executing a multi-command the first command that is enabled will be executed.
*
* const multiCommand = new MultiCommand( editor );
*
* const commandFoo = new Command( editor );
* const commandBar = new Command( editor );
*
* // Register child commands.
* multiCommand.registerChildCommand( commandFoo );
* multiCommand.registerChildCommand( commandBar );
*
* // Enable one of the commands.
* commandBar.isEnabled = true;
*
* multiCommand.execute(); // Will execute commandBar.
*
* @extends module:core/command~Command
*/
class MultiCommand extends _command__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* @inheritDoc
*/
constructor( editor ) {
super( editor );
/**
* Registered child commands.
*
* @type {Array.<module:core/command~Command>}
* @private
*/
this._childCommands = [];
}
/**
* @inheritDoc
*/
refresh() {
// Override base command refresh(): the command's state is changed when one of child commands changes states.
}
/**
* Executes the first of it registered child commands.
*
* @returns {*} The value returned by the {@link module:core/command~Command#execute `command.execute()`}.
*/
execute( ...args ) {
const command = this._getFirstEnabledCommand();
return command != null && command.execute( args );
}
/**
* Registers a child command.
*
* @param {module:core/command~Command} command
*/
registerChildCommand( command ) {
this._childCommands.push( command );
// Change multi command enabled state when one of registered commands changes state.
command.on( 'change:isEnabled', () => this._checkEnabled() );
this._checkEnabled();
}
/**
* Checks if any of child commands is enabled.
*
* @private
*/
_checkEnabled() {
this.isEnabled = !!this._getFirstEnabledCommand();
}
/**
* Returns a first enabled command or undefined if none of them is enabled.
*
* @returns {module:core/command~Command|undefined}
* @private
*/
_getFirstEnabledCommand() {
return this._childCommands.find( command => command.isEnabled );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-core/src/pendingactions.js":
/*!*********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-core/src/pendingactions.js ***!
\*********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ PendingActions)
/* harmony export */ });
/* harmony import */ var _contextplugin__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./contextplugin */ "./node_modules/@ckeditor/ckeditor5-core/src/contextplugin.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_observablemixin__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/observablemixin */ "./node_modules/@ckeditor/ckeditor5-utils/src/observablemixin.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_collection__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/collection */ "./node_modules/@ckeditor/ckeditor5-utils/src/collection.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module core/pendingactions
*/
/**
* The list of pending editor actions.
*
* This plugin should be used to synchronise plugins that execute long-lasting actions
* (e.g. file upload) with the editor integration. It gives the developer who integrates the editor
* an easy way to check if there are any actions pending whenever such information is needed.
* All plugins that register a pending action also provide a message about the action that is ongoing
* which can be displayed to the user. This lets them decide if they want to interrupt the action or wait.
*
* Adding and updating a pending action:
*
* const pendingActions = editor.plugins.get( 'PendingActions' );
* const action = pendingActions.add( 'Upload in progress: 0%.' );
*
* // You can update the message:
* action.message = 'Upload in progress: 10%.';
*
* Removing a pending action:
*
* const pendingActions = editor.plugins.get( 'PendingActions' );
* const action = pendingActions.add( 'Unsaved changes.' );
*
* pendingActions.remove( action );
*
* Getting pending actions:
*
* const pendingActions = editor.plugins.get( 'PendingActions' );
*
* const action1 = pendingActions.add( 'Action 1' );
* const action2 = pendingActions.add( 'Action 2' );
*
* pendingActions.first; // Returns action1
* Array.from( pendingActions ); // Returns [ action1, action2 ]
*
* This plugin is used by features like {@link module:upload/filerepository~FileRepository} to register their ongoing actions
* and by features like {@link module:autosave/autosave~Autosave} to detect whether there are any ongoing actions.
* Read more about saving the data in the {@glink builds/guides/integration/saving-data Saving and getting data} guide.
*
* @extends module:core/contextplugin~ContextPlugin
*/
class PendingActions extends _contextplugin__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* @inheritDoc
*/
static get pluginName() {
return 'PendingActions';
}
/**
* @inheritDoc
*/
init() {
/**
* Defines whether there is any registered pending action.
*
* @readonly
* @observable
* @member {Boolean} #hasAny
*/
this.set( 'hasAny', false );
/**
* A list of pending actions.
*
* @private
* @type {module:utils/collection~Collection}
*/
this._actions = new _ckeditor_ckeditor5_utils_src_collection__WEBPACK_IMPORTED_MODULE_2__["default"]( { idProperty: '_id' } );
this._actions.delegate( 'add', 'remove' ).to( this );
}
/**
* Adds an action to the list of pending actions.
*
* This method returns an action object with an observable message property.
* The action object can be later used in the {@link #remove} method. It also allows you to change the message.
*
* @param {String} message The action message.
* @returns {Object} An observable object that represents a pending action.
*/
add( message ) {
if ( typeof message !== 'string' ) {
/**
* The message must be a string.
*
* @error pendingactions-add-invalid-message
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_3__["default"]( 'pendingactions-add-invalid-message', this );
}
const action = Object.create( _ckeditor_ckeditor5_utils_src_observablemixin__WEBPACK_IMPORTED_MODULE_1__["default"] );
action.set( 'message', message );
this._actions.add( action );
this.hasAny = true;
return action;
}
/**
* Removes an action from the list of pending actions.
*
* @param {Object} action An action object.
*/
remove( action ) {
this._actions.remove( action );
this.hasAny = !!this._actions.length;
}
/**
* Returns the first action from the list or null when list is empty
*
* returns {Object|null} The pending action object.
*/
get first() {
return this._actions.get( 0 );
}
/**
* Iterable interface.
*
* @returns {Iterable.<*>}
*/
[ Symbol.iterator ]() {
return this._actions[ Symbol.iterator ]();
}
/**
* Fired when an action is added to the list.
*
* @event add
* @param {Object} action The added action.
*/
/**
* Fired when an action is removed from the list.
*
* @event remove
* @param {Object} action The removed action.
*/
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-core/src/plugin.js":
/*!*************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-core/src/plugin.js ***!
\*************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ Plugin)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_utils_src_observablemixin__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/observablemixin */ "./node_modules/@ckeditor/ckeditor5-utils/src/observablemixin.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/mix */ "./node_modules/@ckeditor/ckeditor5-utils/src/mix.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module core/plugin
*/
/**
* The base class for CKEditor plugin classes.
*
* @implements module:core/plugin~PluginInterface
* @mixes module:utils/observablemixin~ObservableMixin
*/
class Plugin {
/**
* @inheritDoc
*/
constructor( editor ) {
/**
* The editor instance.
*
* Note that most editors implement the {@link module:core/editor/editorwithui~EditorWithUI} interface in addition
* to the base {@link module:core/editor/editor~Editor} interface. However, editors with an external UI
* (i.e. Bootstrap-based) or a headless editor may not implement the {@link module:core/editor/editorwithui~EditorWithUI}
* interface.
*
* Because of above, to make plugins more universal, it is recommended to split features into:
* - The "editing" part that only uses the {@link module:core/editor/editor~Editor} interface.
* - The "UI" part that uses both the {@link module:core/editor/editor~Editor} interface and
* the {@link module:core/editor/editorwithui~EditorWithUI} interface.
*
* @readonly
* @member {module:core/editor/editor~Editor} #editor
*/
this.editor = editor;
/**
* Flag indicating whether a plugin is enabled or disabled.
* A disabled plugin will not transform text.
*
* Plugin can be simply disabled like that:
*
* // Disable the plugin so that no toolbars are visible.
* editor.plugins.get( 'TextTransformation' ).isEnabled = false;
*
* You can also use {@link #forceDisabled} method.
*
* @observable
* @readonly
* @member {Boolean} #isEnabled
*/
this.set( 'isEnabled', true );
/**
* Holds identifiers for {@link #forceDisabled} mechanism.
*
* @type {Set.<String>}
* @private
*/
this._disableStack = new Set();
}
/**
* Disables the plugin.
*
* Plugin may be disabled by multiple features or algorithms (at once). When disabling a plugin, unique id should be passed
* (e.g. feature name). The same identifier should be used when {@link #clearForceDisabled enabling back} the plugin.
* The plugin becomes enabled only after all features {@link #clearForceDisabled enabled it back}.
*
* Disabling and enabling a plugin:
*
* plugin.isEnabled; // -> true
* plugin.forceDisabled( 'MyFeature' );
* plugin.isEnabled; // -> false
* plugin.clearForceDisabled( 'MyFeature' );
* plugin.isEnabled; // -> true
*
* Plugin disabled by multiple features:
*
* plugin.forceDisabled( 'MyFeature' );
* plugin.forceDisabled( 'OtherFeature' );
* plugin.clearForceDisabled( 'MyFeature' );
* plugin.isEnabled; // -> false
* plugin.clearForceDisabled( 'OtherFeature' );
* plugin.isEnabled; // -> true
*
* Multiple disabling with the same identifier is redundant:
*
* plugin.forceDisabled( 'MyFeature' );
* plugin.forceDisabled( 'MyFeature' );
* plugin.clearForceDisabled( 'MyFeature' );
* plugin.isEnabled; // -> true
*
* **Note:** some plugins or algorithms may have more complex logic when it comes to enabling or disabling certain plugins,
* so the plugin might be still disabled after {@link #clearForceDisabled} was used.
*
* @param {String} id Unique identifier for disabling. Use the same id when {@link #clearForceDisabled enabling back} the plugin.
*/
forceDisabled( id ) {
this._disableStack.add( id );
if ( this._disableStack.size == 1 ) {
this.on( 'set:isEnabled', forceDisable, { priority: 'highest' } );
this.isEnabled = false;
}
}
/**
* Clears forced disable previously set through {@link #forceDisabled}. See {@link #forceDisabled}.
*
* @param {String} id Unique identifier, equal to the one passed in {@link #forceDisabled} call.
*/
clearForceDisabled( id ) {
this._disableStack.delete( id );
if ( this._disableStack.size == 0 ) {
this.off( 'set:isEnabled', forceDisable );
this.isEnabled = true;
}
}
/**
* @inheritDoc
*/
destroy() {
this.stopListening();
}
/**
* @inheritDoc
*/
static get isContextPlugin() {
return false;
}
}
(0,_ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_1__["default"])( Plugin, _ckeditor_ckeditor5_utils_src_observablemixin__WEBPACK_IMPORTED_MODULE_0__["default"] );
/**
* The base interface for CKEditor plugins.
*
* In its minimal form a plugin can be a simple function that accepts {@link module:core/editor/editor~Editor the editor}
* as a parameter:
*
* // A simple plugin that enables a data processor.
* function MyPlugin( editor ) {
* editor.data.processor = new MyDataProcessor();
* }
*
* In most cases however, you will want to inherit from the {@link module:core/plugin~Plugin} class which implements the
* {@link module:utils/observablemixin~ObservableMixin} and is, therefore, more convenient:
*
* class MyPlugin extends Plugin {
* init() {
* // `listenTo()` and `editor` are available thanks to `Plugin`.
* // By using `listenTo()` you will ensure that the listener is removed when
* // the plugin is destroyed.
* this.listenTo( this.editor.data, 'ready', () => {
* // Do something when the data is ready.
* } );
* }
* }
*
* The plugin can also implement methods (e.g. {@link module:core/plugin~PluginInterface#init `init()`} or
* {@link module:core/plugin~PluginInterface#destroy `destroy()`}) which, when present, will be used to properly
* initialize and destroy the plugin.
*
* **Note:** When defined as a plain function, the plugin acts as a constructor and will be
* called in parallel with other plugins' {@link module:core/plugin~PluginInterface#constructor constructors}.
* This means the code of that plugin will be executed **before** {@link module:core/plugin~PluginInterface#init `init()`} and
* {@link module:core/plugin~PluginInterface#afterInit `afterInit()`} methods of other plugins and, for instance,
* you cannot use it to extend other plugins' {@glink framework/guides/architecture/editing-engine#schema schema}
* rules as they are defined later on during the `init()` stage.
*
* @interface PluginInterface
*/
/**
* Creates a new plugin instance. This is the first step of the plugin initialization.
* See also {@link #init} and {@link #afterInit}.
*
* A plugin is always instantiated after its {@link module:core/plugin~PluginInterface.requires dependencies} and the
* {@link #init} and {@link #afterInit} methods are called in the same order.
*
* Usually, you will want to put your plugin's initialization code in the {@link #init} method.
* The constructor can be understood as "before init" and used in special cases, just like
* {@link #afterInit} serves the special "after init" scenarios (e.g.the code which depends on other
* plugins, but which does not {@link module:core/plugin~PluginInterface.requires explicitly require} them).
*
* @method #constructor
* @param {module:core/editor/editor~Editor} editor
*/
/**
* An array of plugins required by this plugin.
*
* To keep the plugin class definition tight it is recommended to define this property as a static getter:
*
* import Image from './image.js';
*
* export default class ImageCaption {
* static get requires() {
* return [ Image ];
* }
* }
*
* @static
* @readonly
* @member {Array.<Function>|undefined} module:core/plugin~PluginInterface.requires
*/
/**
* An optional name of the plugin. If set, the plugin will be available in
* {@link module:core/plugincollection~PluginCollection#get} by its
* name and its constructor. If not, then only by its constructor.
*
* The name should reflect the constructor name.
*
* To keep the plugin class definition tight, it is recommended to define this property as a static getter:
*
* export default class ImageCaption {
* static get pluginName() {
* return 'ImageCaption';
* }
* }
*
* Note: The native `Function.name` property could not be used to keep the plugin name because
* it will be mangled during code minification.
*
* Naming a plugin is necessary to enable removing it through the
* {@link module:core/editor/editorconfig~EditorConfig#removePlugins `config.removePlugins`} option.
*
* @static
* @readonly
* @member {String|undefined} module:core/plugin~PluginInterface.pluginName
*/
/**
* The second stage (after plugin {@link #constructor}) of the plugin initialization.
* Unlike the plugin constructor this method can be asynchronous.
*
* A plugin's `init()` method is called after its {@link module:core/plugin~PluginInterface.requires dependencies} are initialized,
* so in the same order as the constructors of these plugins.
*
* **Note:** This method is optional. A plugin instance does not need to have it defined.
*
* @method #init
* @returns {null|Promise}
*/
/**
* The third (and last) stage of the plugin initialization. See also {@link #constructor} and {@link #init}.
*
* **Note:** This method is optional. A plugin instance does not need to have it defined.
*
* @method #afterInit
* @returns {null|Promise}
*/
/**
* Destroys the plugin.
*
* **Note:** This method is optional. A plugin instance does not need to have it defined.
*
* @method #destroy
* @returns {null|Promise}
*/
/**
* A flag which defines if a plugin is allowed or not allowed to be used directly by a {@link module:core/context~Context}.
*
* @static
* @readonly
* @member {Boolean} module:core/plugin~PluginInterface.isContextPlugin
*/
/**
* An array of loaded plugins.
*
* @typedef {Array.<module:core/plugin~PluginInterface>} module:core/plugin~LoadedPlugins
*/
// Helper function that forces plugin to be disabled.
function forceDisable( evt ) {
evt.return = false;
evt.stop();
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-core/src/plugincollection.js":
/*!***********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-core/src/plugincollection.js ***!
\***********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ PluginCollection)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_emittermixin__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/emittermixin */ "./node_modules/@ckeditor/ckeditor5-utils/src/emittermixin.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/mix */ "./node_modules/@ckeditor/ckeditor5-utils/src/mix.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module core/plugincollection
*/
/**
* Manages a list of CKEditor plugins, including loading, resolving dependencies and initialization.
*
* @mixes module:utils/emittermixin~EmitterMixin
*/
class PluginCollection {
/**
* Creates an instance of the plugin collection class.
* Allows loading and initializing plugins and their dependencies.
* Allows providing a list of already loaded plugins. These plugins will not be destroyed along with this collection.
*
* @param {module:core/editor/editor~Editor|module:core/context~Context} context
* @param {Array.<Function>} [availablePlugins] Plugins (constructors) which the collection will be able to use
* when {@link module:core/plugincollection~PluginCollection#init} is used with the plugin names (strings, instead of constructors).
* Usually, the editor will pass its built-in plugins to the collection so they can later be
* used in `config.plugins` or `config.removePlugins` by names.
* @param {Iterable.<Array>} contextPlugins A list of already initialized plugins represented by a
* `[ PluginConstructor, pluginInstance ]` pair.
*/
constructor( context, availablePlugins = [], contextPlugins = [] ) {
/**
* @protected
* @type {module:core/editor/editor~Editor|module:core/context~Context}
*/
this._context = context;
/**
* @protected
* @type {Map}
*/
this._plugins = new Map();
/**
* A map of plugin constructors that can be retrieved by their names.
*
* @protected
* @type {Map.<String|Function,Function>}
*/
this._availablePlugins = new Map();
for ( const PluginConstructor of availablePlugins ) {
if ( PluginConstructor.pluginName ) {
this._availablePlugins.set( PluginConstructor.pluginName, PluginConstructor );
}
}
/**
* Map of {@link module:core/contextplugin~ContextPlugin context plugins} which can be retrieved by their constructors or instances.
*
* @protected
* @type {Map<Function,Function>}
*/
this._contextPlugins = new Map();
for ( const [ PluginConstructor, pluginInstance ] of contextPlugins ) {
this._contextPlugins.set( PluginConstructor, pluginInstance );
this._contextPlugins.set( pluginInstance, PluginConstructor );
// To make it possible to require a plugin by its name.
if ( PluginConstructor.pluginName ) {
this._availablePlugins.set( PluginConstructor.pluginName, PluginConstructor );
}
}
}
/**
* Iterable interface.
*
* Returns `[ PluginConstructor, pluginInstance ]` pairs.
*
* @returns {Iterable.<Array>}
*/
* [ Symbol.iterator ]() {
for ( const entry of this._plugins ) {
if ( typeof entry[ 0 ] == 'function' ) {
yield entry;
}
}
}
/**
* Gets the plugin instance by its constructor or name.
*
* // Check if 'Clipboard' plugin was loaded.
* if ( editor.plugins.has( 'ClipboardPipeline' ) ) {
* // Get clipboard plugin instance
* const clipboard = editor.plugins.get( 'ClipboardPipeline' );
*
* this.listenTo( clipboard, 'inputTransformation', ( evt, data ) => {
* // Do something on clipboard input.
* } );
* }
*
* **Note**: This method will throw an error if a plugin is not loaded. Use `{@link #has editor.plugins.has()}`
* to check if a plugin is available.
*
* @param {Function|String} key The plugin constructor or {@link module:core/plugin~PluginInterface.pluginName name}.
* @returns {module:core/plugin~PluginInterface}
*/
get( key ) {
const plugin = this._plugins.get( key );
if ( !plugin ) {
let pluginName = key;
if ( typeof key == 'function' ) {
pluginName = key.pluginName || key.name;
}
/**
* The plugin is not loaded and could not be obtained.
*
* Plugin classes (constructors) need to be provided to the editor and must be loaded before they can be obtained from
* the plugin collection.
* This is usually done in CKEditor 5 builds by setting the {@link module:core/editor/editor~Editor.builtinPlugins}
* property.
*
* **Note**: You can use `{@link module:core/plugincollection~PluginCollection#has editor.plugins.has()}`
* to check if a plugin was loaded.
*
* @error plugincollection-plugin-not-loaded
* @param {String} plugin The name of the plugin which is not loaded.
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_0__["default"]( 'plugincollection-plugin-not-loaded', this._context, { plugin: pluginName } );
}
return plugin;
}
/**
* Checks if a plugin is loaded.
*
* // Check if the 'Clipboard' plugin was loaded.
* if ( editor.plugins.has( 'ClipboardPipeline' ) ) {
* // Now use the clipboard plugin instance:
* const clipboard = editor.plugins.get( 'ClipboardPipeline' );
*
* // ...
* }
*
* @param {Function|String} key The plugin constructor or {@link module:core/plugin~PluginInterface.pluginName name}.
* @returns {Boolean}
*/
has( key ) {
return this._plugins.has( key );
}
/**
* Initializes a set of plugins and adds them to the collection.
*
* @param {Array.<Function|String>} plugins An array of {@link module:core/plugin~PluginInterface plugin constructors}
* or {@link module:core/plugin~PluginInterface.pluginName plugin names}.
* @param {Array.<String|Function>} [pluginsToRemove] Names of the plugins or plugin constructors
* that should not be loaded (despite being specified in the `plugins` array).
* @param {Array.<Function>} [pluginsSubstitutions] An array of {@link module:core/plugin~PluginInterface plugin constructors}
* that will be used to replace plugins of the same names that were passed in `plugins` or that are in their dependency tree.
* A useful option for replacing built-in plugins while creating tests (for mocking their APIs). Plugins that will be replaced
* must follow these rules:
* * The new plugin must be a class.
* * The new plugin must be named.
* * Both plugins must not depend on other plugins.
* @returns {Promise.<module:core/plugin~LoadedPlugins>} A promise which gets resolved once all plugins are loaded
* and available in the collection.
*/
init( plugins, pluginsToRemove = [], pluginsSubstitutions = [] ) {
// Plugin initialization procedure consists of 2 main steps:
// 1) collecting all available plugin constructors,
// 2) verification whether all required plugins can be instantiated.
//
// In the first step, all plugin constructors, available in the provided `plugins` array and inside
// plugin's dependencies (from the `Plugin.requires` array), are recursively collected and added to the existing
// `this._availablePlugins` map, but without any verification at the given moment. Performing the verification
// at this point (during the plugin constructor searching) would cause false errors to occur, that some plugin
// is missing but in fact it may be defined further in the array as the dependency of other plugin. After
// traversing the entire dependency tree, it will be checked if all required "top level" plugins are available.
//
// In the second step, the list of plugins that have not been explicitly removed is traversed to get all the
// plugin constructors to be instantiated in the correct order and to validate against some rules. Finally, if
// no plugin is missing and no other error has been found, they all will be instantiated.
const that = this;
const context = this._context;
findAvailablePluginConstructors( plugins );
validatePlugins( plugins );
const pluginsToLoad = plugins.filter( plugin => !isPluginRemoved( plugin, pluginsToRemove ) );
const pluginConstructors = [ ...getPluginConstructors( pluginsToLoad ) ];
substitutePlugins( pluginConstructors, pluginsSubstitutions );
const pluginInstances = loadPlugins( pluginConstructors );
return initPlugins( pluginInstances, 'init' )
.then( () => initPlugins( pluginInstances, 'afterInit' ) )
.then( () => pluginInstances );
function isPluginConstructor( plugin ) {
return typeof plugin === 'function';
}
function isContextPlugin( plugin ) {
return isPluginConstructor( plugin ) && plugin.isContextPlugin;
}
function isPluginRemoved( plugin, pluginsToRemove ) {
return pluginsToRemove.some( removedPlugin => {
if ( removedPlugin === plugin ) {
return true;
}
if ( getPluginName( plugin ) === removedPlugin ) {
return true;
}
if ( getPluginName( removedPlugin ) === plugin ) {
return true;
}
return false;
} );
}
function getPluginName( plugin ) {
return isPluginConstructor( plugin ) ?
plugin.pluginName || plugin.name :
plugin;
}
function findAvailablePluginConstructors( plugins, processed = new Set() ) {
plugins.forEach( plugin => {
if ( !isPluginConstructor( plugin ) ) {
return;
}
if ( processed.has( plugin ) ) {
return;
}
processed.add( plugin );
if ( plugin.pluginName && !that._availablePlugins.has( plugin.pluginName ) ) {
that._availablePlugins.set( plugin.pluginName, plugin );
}
if ( plugin.requires ) {
findAvailablePluginConstructors( plugin.requires, processed );
}
} );
}
function getPluginConstructors( plugins, processed = new Set() ) {
return plugins
.map( plugin => {
return isPluginConstructor( plugin ) ?
plugin :
that._availablePlugins.get( plugin );
} )
.reduce( ( result, plugin ) => {
if ( processed.has( plugin ) ) {
return result;
}
processed.add( plugin );
if ( plugin.requires ) {
validatePlugins( plugin.requires, plugin );
getPluginConstructors( plugin.requires, processed ).forEach( plugin => result.add( plugin ) );
}
return result.add( plugin );
}, new Set() );
}
function validatePlugins( plugins, parentPluginConstructor = null ) {
plugins
.map( plugin => {
return isPluginConstructor( plugin ) ?
plugin :
that._availablePlugins.get( plugin ) || plugin;
} )
.forEach( plugin => {
checkMissingPlugin( plugin, parentPluginConstructor );
checkContextPlugin( plugin, parentPluginConstructor );
checkRemovedPlugin( plugin, parentPluginConstructor );
} );
}
function checkMissingPlugin( plugin, parentPluginConstructor ) {
if ( isPluginConstructor( plugin ) ) {
return;
}
if ( parentPluginConstructor ) {
/**
* A required "soft" dependency was not found on the plugin list.
*
* When configuring the editor, either prior to building (via
* {@link module:core/editor/editor~Editor.builtinPlugins `Editor.builtinPlugins`}) or when
* creating a new instance of the editor (e.g. via
* {@link module:core/editor/editorconfig~EditorConfig#plugins `config.plugins`}), you need to provide
* some of the dependencies for other plugins that you used.
*
* This error is thrown when one of these dependencies was not provided. The name of the missing plugin
* can be found in `missingPlugin` and the plugin that required it in `requiredBy`.
*
* In order to resolve it, you need to import the missing plugin and add it to the
* current list of plugins (`Editor.builtinPlugins` or `config.plugins`/`config.extraPlugins`).
*
* Soft requirements were introduced in version 26.0.0. If you happen to stumble upon this error
* when upgrading to version 26.0.0, read also the
* {@glink builds/guides/migration/migration-to-26 Migration to 26.0.0} guide.
*
* @error plugincollection-soft-required
* @param {String} missingPlugin The name of the required plugin.
* @param {String} requiredBy The name of the plugin that requires the other plugin.
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_0__["default"](
'plugincollection-soft-required',
context,
{ missingPlugin: plugin, requiredBy: getPluginName( parentPluginConstructor ) }
);
}
/**
* A plugin is not available and could not be loaded.
*
* Plugin classes (constructors) need to be provided to the editor before they can be loaded by name.
* This is usually done in CKEditor 5 builds by setting the {@link module:core/editor/editor~Editor.builtinPlugins}
* property.
*
* **If you see this warning when using one of the {@glink builds/index CKEditor 5 Builds}**, it means
* that you try to enable a plugin which was not included in that build. This may be due to a typo
* in the plugin name or simply because that plugin is not a part of this build. In the latter scenario,
* read more about {@glink builds/guides/development/custom-builds custom builds}.
*
* **If you see this warning when using one of the editor creators directly** (not a build), then it means
* that you tried loading plugins by name. However, unlike CKEditor 4, CKEditor 5 does not implement a "plugin loader".
* This means that CKEditor 5 does not know where to load the plugin modules from. Therefore, you need to
* provide each plugin through a reference (as a constructor function). Check out the examples in
* {@glink builds/guides/integration/advanced-setup#scenario-2-building-from-source "Building from source"}.
*
* @error plugincollection-plugin-not-found
* @param {String} plugin The name of the plugin which could not be loaded.
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_0__["default"](
'plugincollection-plugin-not-found',
context,
{ plugin }
);
}
function checkContextPlugin( plugin, parentPluginConstructor ) {
if ( !isContextPlugin( parentPluginConstructor ) ) {
return;
}
if ( isContextPlugin( plugin ) ) {
return;
}
/**
* If a plugin is a context plugin, all plugins it requires should also be context plugins
* instead of plugins. In other words, if one plugin can be used in the context,
* all its requirements should also be ready to be used in the context. Note that the context
* provides only a part of the API provided by the editor. If one plugin needs a full
* editor API, all plugins which require it are considered as plugins that need a full
* editor API.
*
* @error plugincollection-context-required
* @param {String} plugin The name of the required plugin.
* @param {String} requiredBy The name of the parent plugin.
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_0__["default"](
'plugincollection-context-required',
context,
{ plugin: getPluginName( plugin ), requiredBy: getPluginName( parentPluginConstructor ) }
);
}
function checkRemovedPlugin( plugin, parentPluginConstructor ) {
if ( !parentPluginConstructor ) {
return;
}
if ( !isPluginRemoved( plugin, pluginsToRemove ) ) {
return;
}
/**
* Cannot load a plugin because one of its dependencies is listed in the `removePlugins` option.
*
* @error plugincollection-required
* @param {String} plugin The name of the required plugin.
* @param {String} requiredBy The name of the parent plugin.
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_0__["default"](
'plugincollection-required',
context,
{ plugin: getPluginName( plugin ), requiredBy: getPluginName( parentPluginConstructor ) }
);
}
function loadPlugins( pluginConstructors ) {
return pluginConstructors.map( PluginConstructor => {
const pluginInstance = that._contextPlugins.get( PluginConstructor ) || new PluginConstructor( context );
that._add( PluginConstructor, pluginInstance );
return pluginInstance;
} );
}
function initPlugins( pluginInstances, method ) {
return pluginInstances.reduce( ( promise, plugin ) => {
if ( !plugin[ method ] ) {
return promise;
}
if ( that._contextPlugins.has( plugin ) ) {
return promise;
}
return promise.then( plugin[ method ].bind( plugin ) );
}, Promise.resolve() );
}
// Replaces plugin constructors with the specified set of plugins.
//
// @param {Array.<Function>} pluginConstructors
// @param {Array.<Function>} pluginsSubstitutions
function substitutePlugins( pluginConstructors, pluginsSubstitutions ) {
for ( const pluginItem of pluginsSubstitutions ) {
if ( typeof pluginItem != 'function' ) {
/**
* The plugin replacing an existing plugin must be a function.
*
* @error plugincollection-replace-plugin-invalid-type
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_0__["default"]( 'plugincollection-replace-plugin-invalid-type', null, { pluginItem } );
}
const pluginName = pluginItem.pluginName;
if ( !pluginName ) {
/**
* The plugin replacing an existing plugin must have a name.
*
* @error plugincollection-replace-plugin-missing-name
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_0__["default"]( 'plugincollection-replace-plugin-missing-name', null, { pluginItem } );
}
if ( pluginItem.requires && pluginItem.requires.length ) {
/**
* The plugin replacing an existing plugin cannot depend on other plugins.
*
* @error plugincollection-plugin-for-replacing-cannot-have-dependencies
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_0__["default"]( 'plugincollection-plugin-for-replacing-cannot-have-dependencies', null, { pluginName } );
}
const pluginToReplace = that._availablePlugins.get( pluginName );
if ( !pluginToReplace ) {
/**
* The replaced plugin does not exist in the
* {@link module:core/plugincollection~PluginCollection available plugins} collection.
*
* @error plugincollection-plugin-for-replacing-not-exist
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_0__["default"]( 'plugincollection-plugin-for-replacing-not-exist', null, { pluginName } );
}
const indexInPluginConstructors = pluginConstructors.indexOf( pluginToReplace );
if ( indexInPluginConstructors === -1 ) {
// The Context feature can substitute plugins as well.
// It may happen that the editor will be created with the given context, where the plugin for substitute
// was already replaced. In such a case, we don't want to do it again.
if ( that._contextPlugins.has( pluginToReplace ) ) {
return;
}
/**
* The replaced plugin will not be loaded so it cannot be replaced.
*
* @error plugincollection-plugin-for-replacing-not-loaded
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_0__["default"]( 'plugincollection-plugin-for-replacing-not-loaded', null, { pluginName } );
}
if ( pluginToReplace.requires && pluginToReplace.requires.length ) {
/**
* The replaced plugin cannot depend on other plugins.
*
* @error plugincollection-replaced-plugin-cannot-have-dependencies
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_0__["default"]( 'plugincollection-replaced-plugin-cannot-have-dependencies', null, { pluginName } );
}
pluginConstructors.splice( indexInPluginConstructors, 1, pluginItem );
that._availablePlugins.set( pluginName, pluginItem );
}
}
}
/**
* Destroys all loaded plugins.
*
* @returns {Promise}
*/
destroy() {
const promises = [];
for ( const [ , pluginInstance ] of this ) {
if ( typeof pluginInstance.destroy == 'function' && !this._contextPlugins.has( pluginInstance ) ) {
promises.push( pluginInstance.destroy() );
}
}
return Promise.all( promises );
}
/**
* Adds the plugin to the collection. Exposed mainly for testing purposes.
*
* @protected
* @param {Function} PluginConstructor The plugin constructor.
* @param {module:core/plugin~PluginInterface} plugin The instance of the plugin.
*/
_add( PluginConstructor, plugin ) {
this._plugins.set( PluginConstructor, plugin );
const pluginName = PluginConstructor.pluginName;
if ( !pluginName ) {
return;
}
if ( this._plugins.has( pluginName ) ) {
/**
* Two plugins with the same {@link module:core/plugin~PluginInterface.pluginName} were loaded.
* This will lead to runtime conflicts between these plugins.
*
* In practice, this warning usually means that new plugins were added to an existing CKEditor 5 build.
* Plugins should always be added to a source version of the editor (`@ckeditor/ckeditor5-editor-*`),
* not to an editor imported from one of the `@ckeditor/ckeditor5-build-*` packages.
*
* Check your import paths and the list of plugins passed to
* {@link module:core/editor/editor~Editor.create `Editor.create()`}
* or specified in {@link module:core/editor/editor~Editor.builtinPlugins `Editor.builtinPlugins`}.
*
* The second option is that your `node_modules/` directory contains duplicated versions of the same
* CKEditor 5 packages. Normally, on clean installations, npm deduplicates packages in `node_modules/`, so
* it may be enough to call `rm -rf node_modules && npm i`. However, if you installed conflicting versions
* of some packages, their dependencies may need to be installed in more than one version which may lead to this
* warning.
*
* Technically speaking, this error occurs because after adding a plugin to an existing editor build
* the dependencies of this plugin are being duplicated.
* They are already built into that editor build and now get added for the second time as dependencies
* of the plugin you are installing.
*
* Read more about {@glink builds/guides/integration/installing-plugins installing plugins}.
*
* @error plugincollection-plugin-name-conflict
* @param {String} pluginName The duplicated plugin name.
* @param {Function} plugin1 The first plugin constructor.
* @param {Function} plugin2 The second plugin constructor.
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_0__["default"](
'plugincollection-plugin-name-conflict',
null,
{ pluginName, plugin1: this._plugins.get( pluginName ).constructor, plugin2: PluginConstructor }
);
}
this._plugins.set( pluginName, plugin );
}
}
(0,_ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_2__["default"])( PluginCollection, _ckeditor_ckeditor5_utils_src_emittermixin__WEBPACK_IMPORTED_MODULE_1__["default"] );
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-editor-classic/src/classiceditor.js":
/*!******************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-editor-classic/src/classiceditor.js ***!
\******************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ClassicEditor)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/* harmony import */ var lodash_es__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! lodash-es */ "./node_modules/lodash-es/isElement.js");
/* harmony import */ var _classiceditorui__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./classiceditorui */ "./node_modules/@ckeditor/ckeditor5-editor-classic/src/classiceditorui.js");
/* harmony import */ var _classiceditoruiview__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./classiceditoruiview */ "./node_modules/@ckeditor/ckeditor5-editor-classic/src/classiceditoruiview.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module editor-classic/classiceditor
*/
/**
* The {@glink builds/guides/predefined-builds/overview#classic-editor classic editor} implementation.
* It uses an inline editable and a sticky toolbar, all enclosed in a boxed UI.
* See the {@glink examples/builds/classic-editor demo}.
*
* In order to create a classic editor instance, use the static
* {@link module:editor-classic/classiceditor~ClassicEditor.create `ClassicEditor.create()`} method.
*
* # Classic editor and classic build
*
* The classic editor can be used directly from source (if you installed the
* [`@ckeditor/ckeditor5-editor-classic`](https://www.npmjs.com/package/@ckeditor/ckeditor5-editor-classic) package)
* but it is also available in the {@glink builds/guides/predefined-builds/overview#classic-editor classic build}.
*
* {@glink builds/guides/predefined-builds/overview Builds} are ready-to-use editors with plugins bundled in. When using the editor from
* source you need to take care of loading all plugins by yourself
* (through the {@link module:core/editor/editorconfig~EditorConfig#plugins `config.plugins`} option).
* Using the editor from source gives much better flexibility and allows easier customization.
*
* Read more about initializing the editor from source or as a build in
* {@link module:editor-classic/classiceditor~ClassicEditor.create `ClassicEditor.create()`}.
*
* @mixes module:core/editor/utils/dataapimixin~DataApiMixin
* @mixes module:core/editor/utils/elementapimixin~ElementApiMixin
* @implements module:core/editor/editorwithui~EditorWithUI
* @extends module:core/editor/editor~Editor
*/
class ClassicEditor extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Editor {
/**
* Creates an instance of the classic editor.
*
* **Note:** do not use the constructor to create editor instances. Use the static
* {@link module:editor-classic/classiceditor~ClassicEditor.create `ClassicEditor.create()`} method instead.
*
* @protected
* @param {HTMLElement|String} sourceElementOrData The DOM element that will be the source for the created editor
* or the editor's initial data. For more information see
* {@link module:editor-classic/classiceditor~ClassicEditor.create `ClassicEditor.create()`}.
* @param {module:core/editor/editorconfig~EditorConfig} [config] The editor configuration.
*/
constructor( sourceElementOrData, config = {} ) {
// If both `config.initialData` is set and initial data is passed as the constructor parameter, then throw.
if ( !(0,lodash_es__WEBPACK_IMPORTED_MODULE_4__["default"])( sourceElementOrData ) && config.initialData !== undefined ) {
// Documented in core/editor/editorconfig.jsdoc.
// eslint-disable-next-line ckeditor5-rules/ckeditor-error-message
throw new ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__.CKEditorError( 'editor-create-initial-data', null );
}
super( config );
if ( this.config.get( 'initialData' ) === undefined ) {
this.config.set( 'initialData', getInitialData( sourceElementOrData ) );
}
if ( (0,lodash_es__WEBPACK_IMPORTED_MODULE_4__["default"])( sourceElementOrData ) ) {
this.sourceElement = sourceElementOrData;
}
this.model.document.createRoot();
const shouldToolbarGroupWhenFull = !this.config.get( 'toolbar.shouldNotGroupWhenFull' );
const view = new _classiceditoruiview__WEBPACK_IMPORTED_MODULE_3__["default"]( this.locale, this.editing.view, {
shouldToolbarGroupWhenFull
} );
this.ui = new _classiceditorui__WEBPACK_IMPORTED_MODULE_2__["default"]( this, view );
(0,ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.attachToForm)( this );
}
/**
* Destroys the editor instance, releasing all resources used by it.
*
* Updates the editor's source element with the data.
*
* @returns {Promise}
*/
destroy() {
if ( this.sourceElement ) {
this.updateSourceElement();
}
this.ui.destroy();
return super.destroy();
}
/**
* Creates a new classic editor instance.
*
* There are three ways how the editor can be initialized.
*
* # Replacing a DOM element (and loading data from it)
*
* You can initialize the editor using an existing DOM element:
*
* ClassicEditor
* .create( document.querySelector( '#editor' ) )
* .then( editor => {
* console.log( 'Editor was initialized', editor );
* } )
* .catch( err => {
* console.error( err.stack );
* } );
*
* The element's content will be used as the editor data and the element will be replaced by the editor UI.
*
* # Creating a detached editor
*
* Alternatively, you can initialize the editor by passing the initial data directly as a string.
* In this case, the editor will render an element that must be inserted into the DOM:
*
* ClassicEditor
* .create( '<p>Hello world!</p>' )
* .then( editor => {
* console.log( 'Editor was initialized', editor );
*
* // Initial data was provided so the editor UI element needs to be added manually to the DOM.
* document.body.appendChild( editor.ui.element );
* } )
* .catch( err => {
* console.error( err.stack );
* } );
*
* This lets you dynamically append the editor to your web page whenever it is convenient for you. You may use this method if your
* web page content is generated on the client side and the DOM structure is not ready at the moment when you initialize the editor.
*
* # Replacing a DOM element (and data provided in `config.initialData`)
*
* You can also mix these two ways by providing a DOM element to be used and passing the initial data through the configuration:
*
* ClassicEditor
* .create( document.querySelector( '#editor' ), {
* initialData: '<h2>Initial data</h2><p>Foo bar.</p>'
* } )
* .then( editor => {
* console.log( 'Editor was initialized', editor );
* } )
* .catch( err => {
* console.error( err.stack );
* } );
*
* This method can be used to initialize the editor on an existing element with the specified content in case if your integration
* makes it difficult to set the content of the source element.
*
* Note that an error will be thrown if you pass the initial data both as the first parameter and also in the configuration.
*
* # Configuring the editor
*
* See the {@link module:core/editor/editorconfig~EditorConfig editor configuration documentation} to learn more about
* customizing plugins, toolbar and more.
*
* # Using the editor from source
*
* The code samples listed in the previous sections of this documentation assume that you are using an
* {@glink builds/guides/predefined-builds/overview editor build} (for example – `@ckeditor/ckeditor5-build-classic`).
*
* If you want to use the classic editor from source (`@ckeditor/ckeditor5-editor-classic/src/classiceditor`),
* you need to define the list of
* {@link module:core/editor/editorconfig~EditorConfig#plugins plugins to be initialized} and
* {@link module:core/editor/editorconfig~EditorConfig#toolbar toolbar items}. Read more about using the editor from
* source in the {@glink builds/guides/integration/advanced-setup "Advanced setup" guide}.
*
* @param {HTMLElement|String} sourceElementOrData The DOM element that will be the source for the created editor
* or the editor's initial data.
*
* If a DOM element is passed, its content will be automatically loaded to the editor upon initialization
* and the {@link module:editor-classic/classiceditorui~ClassicEditorUI#element editor element} will replace the passed element
* in the DOM (the original one will be hidden and the editor will be injected next to it).
*
* Moreover, the editor data will be set back to the original element once the editor is destroyed and when a form, in which
* this element is contained, is submitted (if the original element is a `<textarea>`). This ensures seamless integration with native
* web forms.
*
* If the initial data is passed, a detached editor will be created. In this case you need to insert it into the DOM manually.
* It is available under the {@link module:editor-classic/classiceditorui~ClassicEditorUI#element `editor.ui.element`} property.
*
* @param {module:core/editor/editorconfig~EditorConfig} [config] The editor configuration.
* @returns {Promise} A promise resolved once the editor is ready. The promise resolves with the created editor instance.
*/
static create( sourceElementOrData, config = {} ) {
return new Promise( resolve => {
const editor = new this( sourceElementOrData, config );
resolve(
editor.initPlugins()
.then( () => editor.ui.init( (0,lodash_es__WEBPACK_IMPORTED_MODULE_4__["default"])( sourceElementOrData ) ? sourceElementOrData : null ) )
.then( () => editor.data.init( editor.config.get( 'initialData' ) ) )
.then( () => editor.fire( 'ready' ) )
.then( () => editor )
);
} );
}
}
(0,ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__.mix)( ClassicEditor, ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.DataApiMixin );
(0,ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__.mix)( ClassicEditor, ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.ElementApiMixin );
function getInitialData( sourceElementOrData ) {
return (0,lodash_es__WEBPACK_IMPORTED_MODULE_4__["default"])( sourceElementOrData ) ? (0,ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__.getDataFromElement)( sourceElementOrData ) : sourceElementOrData;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-editor-classic/src/classiceditorui.js":
/*!********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-editor-classic/src/classiceditorui.js ***!
\********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ClassicEditorUI)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/ui */ "./node_modules/ckeditor5/src/ui.js");
/* harmony import */ var ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ckeditor5/src/engine */ "./node_modules/ckeditor5/src/engine.js");
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module editor-classic/classiceditorui
*/
/**
* The classic editor UI class.
*
* @extends module:core/editor/editorui~EditorUI
*/
class ClassicEditorUI extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.EditorUI {
/**
* Creates an instance of the classic editor UI class.
*
* @param {module:core/editor/editor~Editor} editor The editor instance.
* @param {module:ui/editorui/editoruiview~EditorUIView} view The view of the UI.
*/
constructor( editor, view ) {
super( editor );
/**
* The main (top–most) view of the editor UI.
*
* @readonly
* @member {module:ui/editorui/editoruiview~EditorUIView} #view
*/
this.view = view;
/**
* A normalized `config.toolbar` object.
*
* @private
* @member {Object}
*/
this._toolbarConfig = (0,ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.normalizeToolbarConfig)( editor.config.get( 'toolbar' ) );
/**
* The element replacer instance used to hide the editor's source element.
*
* @protected
* @member {module:utils/elementreplacer~ElementReplacer}
*/
this._elementReplacer = new ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_3__.ElementReplacer();
}
/**
* @inheritDoc
*/
get element() {
return this.view.element;
}
/**
* Initializes the UI.
*
* @param {HTMLElement|null} replacementElement The DOM element that will be the source for the created editor.
*/
init( replacementElement ) {
const editor = this.editor;
const view = this.view;
const editingView = editor.editing.view;
const editable = view.editable;
const editingRoot = editingView.document.getRoot();
// The editable UI and editing root should share the same name. Then name is used
// to recognize the particular editable, for instance in ARIA attributes.
editable.name = editingRoot.rootName;
view.render();
// The editable UI element in DOM is available for sure only after the editor UI view has been rendered.
// But it can be available earlier if a DOM element has been passed to BalloonEditor.create().
const editableElement = editable.element;
// Register the editable UI view in the editor. A single editor instance can aggregate multiple
// editable areas (roots) but the classic editor has only one.
this.setEditableElement( editable.name, editableElement );
// Let the global focus tracker know that the editable UI element is focusable and
// belongs to the editor. From now on, the focus tracker will sustain the editor focus
// as long as the editable is focused (e.g. the user is typing).
this.focusTracker.add( editableElement );
// Let the editable UI element respond to the changes in the global editor focus
// tracker. It has been added to the same tracker a few lines above but, in reality, there are
// many focusable areas in the editor, like balloons, toolbars or dropdowns and as long
// as they have focus, the editable should act like it is focused too (although technically
// it isn't), e.g. by setting the proper CSS class, visually announcing focus to the user.
// Doing otherwise will result in editable focus styles disappearing, once e.g. the
// toolbar gets focused.
view.editable.bind( 'isFocused' ).to( this.focusTracker );
// Bind the editable UI element to the editing view, making it an end– and entry–point
// of the editor's engine. This is where the engine meets the UI.
editingView.attachDomRoot( editableElement );
// If an element containing the initial data of the editor was provided, replace it with
// an editor instance's UI in DOM until the editor is destroyed. For instance, a <textarea>
// can be such element.
if ( replacementElement ) {
this._elementReplacer.replace( replacementElement, this.element );
}
this._initPlaceholder();
this._initToolbar();
this.fire( 'ready' );
}
/**
* @inheritDoc
*/
destroy() {
const view = this.view;
const editingView = this.editor.editing.view;
this._elementReplacer.restore();
editingView.detachDomRoot( view.editable.name );
view.destroy();
super.destroy();
}
/**
* Initializes the editor toolbar.
*
* @private
*/
_initToolbar() {
const editor = this.editor;
const view = this.view;
const editingView = editor.editing.view;
// Set–up the sticky panel with toolbar.
view.stickyPanel.bind( 'isActive' ).to( this.focusTracker, 'isFocused' );
view.stickyPanel.limiterElement = view.element;
view.stickyPanel.bind( 'viewportTopOffset' ).to( this, 'viewportOffset', ( { top } ) => top );
view.toolbar.fillFromConfig( this._toolbarConfig, this.componentFactory );
(0,ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.enableToolbarKeyboardFocus)( {
origin: editingView,
originFocusTracker: this.focusTracker,
originKeystrokeHandler: editor.keystrokes,
toolbar: view.toolbar
} );
}
/**
* Enable the placeholder text on the editing root, if any was configured.
*
* @private
*/
_initPlaceholder() {
const editor = this.editor;
const editingView = editor.editing.view;
const editingRoot = editingView.document.getRoot();
const sourceElement = editor.sourceElement;
const placeholderText = editor.config.get( 'placeholder' ) ||
sourceElement && sourceElement.tagName.toLowerCase() === 'textarea' && sourceElement.getAttribute( 'placeholder' );
if ( placeholderText ) {
(0,ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_2__.enablePlaceholder)( {
view: editingView,
element: editingRoot,
text: placeholderText,
isDirectHost: false,
keepOnFocus: true
} );
}
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-editor-classic/src/classiceditoruiview.js":
/*!************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-editor-classic/src/classiceditoruiview.js ***!
\************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ClassicEditorUIView)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/ui */ "./node_modules/ckeditor5/src/ui.js");
/* harmony import */ var _theme_classiceditor_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../theme/classiceditor.css */ "./node_modules/@ckeditor/ckeditor5-editor-classic/theme/classiceditor.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module editor-classic/classiceditoruiview
*/
/**
* Classic editor UI view. Uses an inline editable and a sticky toolbar, all
* enclosed in a boxed UI view.
*
* @extends module:ui/editorui/boxed/boxededitoruiview~BoxedEditorUIView
*/
class ClassicEditorUIView extends ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.BoxedEditorUIView {
/**
* Creates an instance of the classic editor UI view.
*
* @param {module:utils/locale~Locale} locale The {@link module:core/editor/editor~Editor#locale} instance.
* @param {module:engine/view/view~View} editingView The editing view instance this view is related to.
* @param {Object} [options={}] Configuration options for the view instance.
* @param {Boolean} [options.shouldToolbarGroupWhenFull] When set `true` enables automatic items grouping
* in the main {@link module:editor-classic/classiceditoruiview~ClassicEditorUIView#toolbar toolbar}.
* See {@link module:ui/toolbar/toolbarview~ToolbarOptions#shouldGroupWhenFull} to learn more.
*/
constructor( locale, editingView, options = {} ) {
super( locale );
/**
* Sticky panel view instance. This is a parent view of a {@link #toolbar}
* that makes toolbar sticky.
*
* @readonly
* @member {module:ui/panel/sticky/stickypanelview~StickyPanelView}
*/
this.stickyPanel = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.StickyPanelView( locale );
/**
* Toolbar view instance.
*
* @readonly
* @member {module:ui/toolbar/toolbarview~ToolbarView}
*/
this.toolbar = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.ToolbarView( locale, {
shouldGroupWhenFull: options.shouldToolbarGroupWhenFull
} );
/**
* Editable UI view.
*
* @readonly
* @member {module:ui/editableui/inline/inlineeditableuiview~InlineEditableUIView}
*/
this.editable = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.InlineEditableUIView( locale, editingView );
}
/**
* @inheritDoc
*/
render() {
super.render();
// Set toolbar as a child of a stickyPanel and makes toolbar sticky.
this.stickyPanel.content.add( this.toolbar );
this.top.add( this.stickyPanel );
this.main.add( this.editable );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/controller/datacontroller.js":
/*!**********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/controller/datacontroller.js ***!
\**********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ DataController)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/mix */ "./node_modules/@ckeditor/ckeditor5-utils/src/mix.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_observablemixin__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/observablemixin */ "./node_modules/@ckeditor/ckeditor5-utils/src/observablemixin.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/* harmony import */ var _conversion_mapper__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../conversion/mapper */ "./node_modules/@ckeditor/ckeditor5-engine/src/conversion/mapper.js");
/* harmony import */ var _conversion_downcastdispatcher__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../conversion/downcastdispatcher */ "./node_modules/@ckeditor/ckeditor5-engine/src/conversion/downcastdispatcher.js");
/* harmony import */ var _conversion_downcasthelpers__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../conversion/downcasthelpers */ "./node_modules/@ckeditor/ckeditor5-engine/src/conversion/downcasthelpers.js");
/* harmony import */ var _conversion_upcastdispatcher__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ../conversion/upcastdispatcher */ "./node_modules/@ckeditor/ckeditor5-engine/src/conversion/upcastdispatcher.js");
/* harmony import */ var _conversion_upcasthelpers__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ../conversion/upcasthelpers */ "./node_modules/@ckeditor/ckeditor5-engine/src/conversion/upcasthelpers.js");
/* harmony import */ var _view_documentfragment__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ../view/documentfragment */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/documentfragment.js");
/* harmony import */ var _view_document__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ../view/document */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/document.js");
/* harmony import */ var _view_downcastwriter__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ../view/downcastwriter */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/downcastwriter.js");
/* harmony import */ var _model_range__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ../model/range */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/range.js");
/* harmony import */ var _model_utils_autoparagraphing__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! ../model/utils/autoparagraphing */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/utils/autoparagraphing.js");
/* harmony import */ var _dataprocessor_htmldataprocessor__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(/*! ../dataprocessor/htmldataprocessor */ "./node_modules/@ckeditor/ckeditor5-engine/src/dataprocessor/htmldataprocessor.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/controller/datacontroller
*/
/**
* Controller for the data pipeline. The data pipeline controls how data is retrieved from the document
* and set inside it. Hence, the controller features two methods which allow to {@link ~DataController#get get}
* and {@link ~DataController#set set} data of the {@link ~DataController#model model}
* using the given:
*
* * {@link module:engine/dataprocessor/dataprocessor~DataProcessor data processor},
* * downcast converters,
* * upcast converters.
*
* An instance of the data controller is always available in the {@link module:core/editor/editor~Editor#data `editor.data`}
* property:
*
* editor.data.get( { rootName: 'customRoot' } ); // -> '<p>Hello!</p>'
*
* @mixes module:utils/observablemixin~ObservableMixin
*/
class DataController {
/**
* Creates a data controller instance.
*
* @param {module:engine/model/model~Model} model Data model.
* @param {module:engine/view/stylesmap~StylesProcessor} stylesProcessor The styles processor instance.
*/
constructor( model, stylesProcessor ) {
/**
* Data model.
*
* @readonly
* @member {module:engine/model/model~Model}
*/
this.model = model;
/**
* Mapper used for the conversion. It has no permanent bindings, because these are created while getting data and
* ae cleared directly after the data are converted. However, the mapper is defined as a class property, because
* it needs to be passed to the `DowncastDispatcher` as a conversion API.
*
* @readonly
* @member {module:engine/conversion/mapper~Mapper}
*/
this.mapper = new _conversion_mapper__WEBPACK_IMPORTED_MODULE_3__["default"]();
/**
* Downcast dispatcher used by the {@link #get get method}. Downcast converters should be attached to it.
*
* @readonly
* @member {module:engine/conversion/downcastdispatcher~DowncastDispatcher}
*/
this.downcastDispatcher = new _conversion_downcastdispatcher__WEBPACK_IMPORTED_MODULE_4__["default"]( {
mapper: this.mapper,
schema: model.schema
} );
this.downcastDispatcher.on( 'insert:$text', (0,_conversion_downcasthelpers__WEBPACK_IMPORTED_MODULE_5__.insertText)(), { priority: 'lowest' } );
this.downcastDispatcher.on( 'insert', (0,_conversion_downcasthelpers__WEBPACK_IMPORTED_MODULE_5__.insertAttributesAndChildren)(), { priority: 'lowest' } );
/**
* Upcast dispatcher used by the {@link #set set method}. Upcast converters should be attached to it.
*
* @readonly
* @member {module:engine/conversion/upcastdispatcher~UpcastDispatcher}
*/
this.upcastDispatcher = new _conversion_upcastdispatcher__WEBPACK_IMPORTED_MODULE_6__["default"]( {
schema: model.schema
} );
/**
* The view document used by the data controller.
*
* @readonly
* @member {module:engine/view/document~Document}
*/
this.viewDocument = new _view_document__WEBPACK_IMPORTED_MODULE_9__["default"]( stylesProcessor );
/**
* Styles processor used during the conversion.
*
* @readonly
* @member {module:engine/view/stylesmap~StylesProcessor}
*/
this.stylesProcessor = stylesProcessor;
/**
* Data processor used specifically for HTML conversion.
*
* @readonly
* @member {module:engine/dataprocessor/htmldataprocessor~HtmlDataProcessor} #htmlProcessor
*/
this.htmlProcessor = new _dataprocessor_htmldataprocessor__WEBPACK_IMPORTED_MODULE_13__["default"]( this.viewDocument );
/**
* Data processor used during the conversion.
* Same instance as {@link #htmlProcessor} by default. Can be replaced at run time to handle different format, e.g. XML or Markdown.
*
* @member {module:engine/dataprocessor/dataprocessor~DataProcessor} #processor
*/
this.processor = this.htmlProcessor;
/**
* The view downcast writer just for data conversion purposes, i.e. to modify
* the {@link #viewDocument}.
*
* @private
* @readonly
* @member {module:engine/view/downcastwriter~DowncastWriter}
*/
this._viewWriter = new _view_downcastwriter__WEBPACK_IMPORTED_MODULE_10__["default"]( this.viewDocument );
// Define default converters for text and elements.
//
// Note that if there is no default converter for the element it will be skipped, for instance `<b>foo</b>` will be
// converted to nothing. We therefore add `convertToModelFragment` as a last converter so it converts children of that
// element to the document fragment so `<b>foo</b>` will still be converted to `foo` even if there is no converter for `<b>`.
this.upcastDispatcher.on( 'text', (0,_conversion_upcasthelpers__WEBPACK_IMPORTED_MODULE_7__.convertText)(), { priority: 'lowest' } );
this.upcastDispatcher.on( 'element', (0,_conversion_upcasthelpers__WEBPACK_IMPORTED_MODULE_7__.convertToModelFragment)(), { priority: 'lowest' } );
this.upcastDispatcher.on( 'documentFragment', (0,_conversion_upcasthelpers__WEBPACK_IMPORTED_MODULE_7__.convertToModelFragment)(), { priority: 'lowest' } );
this.decorate( 'init' );
this.decorate( 'set' );
this.decorate( 'get' );
// Fire the `ready` event when the initialization has completed. Such low-level listener offers the possibility
// to plug into the initialization pipeline without interrupting the initialization flow.
this.on( 'init', () => {
this.fire( 'ready' );
}, { priority: 'lowest' } );
// Fix empty roots after DataController is 'ready' (note that the init method could be decorated and stopped).
// We need to handle this event because initial data could be empty and the post-fixer would not get triggered.
this.on( 'ready', () => {
this.model.enqueueChange( { isUndoable: false }, _model_utils_autoparagraphing__WEBPACK_IMPORTED_MODULE_12__.autoParagraphEmptyRoots );
}, { priority: 'lowest' } );
}
/**
* Returns the model's data converted by downcast dispatchers attached to {@link #downcastDispatcher} and
* formatted by the {@link #processor data processor}.
*
* @fires get
* @param {Object} [options] Additional configuration for the retrieved data. `DataController` provides two optional
* properties: `rootName` and `trim`. Other properties of this object are specified by various editor features.
* @param {String} [options.rootName='main'] Root name.
* @param {String} [options.trim='empty'] Whether returned data should be trimmed. This option is set to `empty` by default,
* which means whenever editor content is considered empty, an empty string will be returned. To turn off trimming completely
* use `'none'`. In such cases the exact content will be returned (for example a `<p> </p>` for an empty editor).
* @returns {String} Output data.
*/
get( options = {} ) {
const { rootName = 'main', trim = 'empty' } = options;
if ( !this._checkIfRootsExists( [ rootName ] ) ) {
/**
* Cannot get data from a non-existing root. This error is thrown when {@link #get DataController#get() method}
* is called with a non-existent root name. For example, if there is an editor instance with only `main` root,
* calling {@link #get} like:
*
* data.get( { rootName: 'root2' } );
*
* will throw this error.
*
* @error datacontroller-get-non-existent-root
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_2__["default"]( 'datacontroller-get-non-existent-root', this );
}
const root = this.model.document.getRoot( rootName );
if ( trim === 'empty' && !this.model.hasContent( root, { ignoreWhitespaces: true } ) ) {
return '';
}
return this.stringify( root, options );
}
/**
* Returns the content of the given {@link module:engine/model/element~Element model's element} or
* {@link module:engine/model/documentfragment~DocumentFragment model document fragment} converted by the downcast converters
* attached to the {@link #downcastDispatcher} and formatted by the {@link #processor data processor}.
*
* @param {module:engine/model/element~Element|module:engine/model/documentfragment~DocumentFragment} modelElementOrFragment
* The element whose content will be stringified.
* @param {Object} [options] Additional configuration passed to the conversion process.
* @returns {String} Output data.
*/
stringify( modelElementOrFragment, options = {} ) {
// Model -> view.
const viewDocumentFragment = this.toView( modelElementOrFragment, options );
// View -> data.
return this.processor.toData( viewDocumentFragment );
}
/**
* Returns the content of the given {@link module:engine/model/element~Element model element} or
* {@link module:engine/model/documentfragment~DocumentFragment model document fragment} converted by the downcast
* converters attached to {@link #downcastDispatcher} into a
* {@link module:engine/view/documentfragment~DocumentFragment view document fragment}.
*
* @param {module:engine/model/element~Element|module:engine/model/documentfragment~DocumentFragment} modelElementOrFragment
* Element or document fragment whose content will be converted.
* @param {Object} [options={}] Additional configuration that will be available through the
* {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi#options} during the conversion process.
* @returns {module:engine/view/documentfragment~DocumentFragment} Output view DocumentFragment.
*/
toView( modelElementOrFragment, options = {} ) {
const viewDocument = this.viewDocument;
const viewWriter = this._viewWriter;
// Clear bindings so the call to this method returns correct results.
this.mapper.clearBindings();
// First, convert elements.
const modelRange = _model_range__WEBPACK_IMPORTED_MODULE_11__["default"]._createIn( modelElementOrFragment );
const viewDocumentFragment = new _view_documentfragment__WEBPACK_IMPORTED_MODULE_8__["default"]( viewDocument );
this.mapper.bindElements( modelElementOrFragment, viewDocumentFragment );
// Prepare list of markers.
// For document fragment, simply take the markers assigned to this document fragment.
// For model root, all markers in that root will be taken.
// For model element, we need to check which markers are intersecting with this element and relatively modify the markers' ranges.
// Collapsed markers at element boundary, although considered as not intersecting with the element, will also be returned.
const markers = modelElementOrFragment.is( 'documentFragment' ) ?
modelElementOrFragment.markers :
_getMarkersRelativeToElement( modelElementOrFragment );
this.downcastDispatcher.convert( modelRange, markers, viewWriter, options );
return viewDocumentFragment;
}
/**
* Sets the initial input data parsed by the {@link #processor data processor} and
* converted by the {@link #upcastDispatcher view-to-model converters}.
* Initial data can be only set to a document whose {@link module:engine/model/document~Document#version} is equal 0.
*
* **Note** This method is {@link module:utils/observablemixin~ObservableMixin#decorate decorated} which is
* used by e.g. collaborative editing plugin that syncs remote data on init.
*
* When data is passed as a string, it is initialized on the default `main` root:
*
* dataController.init( '<p>Foo</p>' ); // Initializes data on the `main` root only, as no other is specified.
*
* To initialize data on a different root or multiple roots at once, an object containing `rootName` - `data` pairs should be passed:
*
* dataController.init( { main: '<p>Foo</p>', title: '<h1>Bar</h1>' } ); // Initializes data on both the `main` and `title` roots.
*
* @fires init
* @param {String|Object.<String,String>} data Input data as a string or an object containing the `rootName` - `data`
* pairs to initialize data on multiple roots at once.
* @returns {Promise} Promise that is resolved after the data is set on the editor.
*/
init( data ) {
if ( this.model.document.version ) {
/**
* Cannot set initial data to a non-empty {@link module:engine/model/document~Document}.
* Initial data should be set once, during the {@link module:core/editor/editor~Editor} initialization,
* when the {@link module:engine/model/document~Document#version} is equal 0.
*
* @error datacontroller-init-document-not-empty
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_2__["default"]( 'datacontroller-init-document-not-empty', this );
}
let initialData = {};
if ( typeof data === 'string' ) {
initialData.main = data; // Default root is 'main'. To initiate data on a different root, object should be passed.
} else {
initialData = data;
}
if ( !this._checkIfRootsExists( Object.keys( initialData ) ) ) {
/**
* Cannot init data on a non-existent root. This error is thrown when {@link #init DataController#init() method}
* is called with non-existent root name. For example, if there is an editor instance with only `main` root,
* calling {@link #init} like:
*
* data.init( { main: '<p>Foo</p>', root2: '<p>Bar</p>' } );
*
* will throw this error.
*
* @error datacontroller-init-non-existent-root
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_2__["default"]( 'datacontroller-init-non-existent-root', this );
}
this.model.enqueueChange( { isUndoable: false }, writer => {
for ( const rootName of Object.keys( initialData ) ) {
const modelRoot = this.model.document.getRoot( rootName );
writer.insert( this.parse( initialData[ rootName ], modelRoot ), modelRoot, 0 );
}
} );
return Promise.resolve();
}
/**
* Sets the input data parsed by the {@link #processor data processor} and
* converted by the {@link #upcastDispatcher view-to-model converters}.
* This method can be used any time to replace existing editor data with the new one without clearing the
* {@link module:engine/model/document~Document#history document history}.
*
* This method also creates a batch with all the changes applied. If all you need is to parse data, use
* the {@link #parse} method.
*
* When data is passed as a string it is set on the default `main` root:
*
* dataController.set( '<p>Foo</p>' ); // Sets data on the `main` root, as no other is specified.
*
* To set data on a different root or multiple roots at once, an object containing `rootName` - `data` pairs should be passed:
*
* dataController.set( { main: '<p>Foo</p>', title: '<h1>Bar</h1>' } ); // Sets data on the `main` and `title` roots as specified.
*
* To set the data with a preserved undo stack and add the change to the undo stack, set `{ isUndoable: true }` as a `batchType` option.
*
* dataController.set( '<p>Foo</p>', { batchType: { isUndoable: true } } );
*
* @fires set
* @param {String|Object.<String,String>} data Input data as a string or an object containing the `rootName` - `data`
* pairs to set data on multiple roots at once.
* @param {Object} [options={}] Options for setting data.
* @param {Object} [options.batchType] The batch type that will be used to create a batch for the changes applied by this method.
* By default, the batch will be set as {@link module:engine/model/batch~Batch#isUndoable not undoable} and the undo stack will be
* cleared after the new data is applied (all undo steps will be removed). If the batch type `isUndoable` flag is be set to `true`,
* the undo stack will be preserved instead and not cleared when new data is applied.
*/
set( data, options = {} ) {
let newData = {};
if ( typeof data === 'string' ) {
newData.main = data; // The default root is 'main'. To set data on a different root, an object should be passed.
} else {
newData = data;
}
if ( !this._checkIfRootsExists( Object.keys( newData ) ) ) {
/**
* Cannot set data on a non-existent root. This error is thrown when the {@link #set DataController#set() method}
* is called with non-existent root name. For example, if there is an editor instance with only the default `main` root,
* calling {@link #set} like:
*
* data.set( { main: '<p>Foo</p>', root2: '<p>Bar</p>' } );
*
* will throw this error.
*
* @error datacontroller-set-non-existent-root
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_2__["default"]( 'datacontroller-set-non-existent-root', this );
}
this.model.enqueueChange( options.batchType || {}, writer => {
writer.setSelection( null );
writer.removeSelectionAttribute( this.model.document.selection.getAttributeKeys() );
for ( const rootName of Object.keys( newData ) ) {
// Save to model.
const modelRoot = this.model.document.getRoot( rootName );
writer.remove( writer.createRangeIn( modelRoot ) );
writer.insert( this.parse( newData[ rootName ], modelRoot ), modelRoot, 0 );
}
} );
}
/**
* Returns the data parsed by the {@link #processor data processor} and then converted by upcast converters
* attached to the {@link #upcastDispatcher}.
*
* @see #set
* @param {String} data Data to parse.
* @param {module:engine/model/schema~SchemaContextDefinition} [context='$root'] Base context in which the view will
* be converted to the model. See: {@link module:engine/conversion/upcastdispatcher~UpcastDispatcher#convert}.
* @returns {module:engine/model/documentfragment~DocumentFragment} Parsed data.
*/
parse( data, context = '$root' ) {
// data -> view
const viewDocumentFragment = this.processor.toView( data );
// view -> model
return this.toModel( viewDocumentFragment, context );
}
/**
* Returns the result of the given {@link module:engine/view/element~Element view element} or
* {@link module:engine/view/documentfragment~DocumentFragment view document fragment} converted by the
* {@link #upcastDispatcher view-to-model converters}, wrapped by {@link module:engine/model/documentfragment~DocumentFragment}.
*
* When marker elements were converted during the conversion process, it will be set as a document fragment's
* {@link module:engine/model/documentfragment~DocumentFragment#markers static markers map}.
*
* @param {module:engine/view/element~Element|module:engine/view/documentfragment~DocumentFragment} viewElementOrFragment
* The element or document fragment whose content will be converted.
* @param {module:engine/model/schema~SchemaContextDefinition} [context='$root'] Base context in which the view will
* be converted to the model. See: {@link module:engine/conversion/upcastdispatcher~UpcastDispatcher#convert}.
* @returns {module:engine/model/documentfragment~DocumentFragment} Output document fragment.
*/
toModel( viewElementOrFragment, context = '$root' ) {
return this.model.change( writer => {
return this.upcastDispatcher.convert( viewElementOrFragment, writer, context );
} );
}
/**
* Adds the style processor normalization rules.
*
* You can implement your own rules as well as use one of the available processor rules:
*
* * background: {@link module:engine/view/styles/background~addBackgroundRules}
* * border: {@link module:engine/view/styles/border~addBorderRules}
* * margin: {@link module:engine/view/styles/margin~addMarginRules}
* * padding: {@link module:engine/view/styles/padding~addPaddingRules}
*
* @param {Function} callback
*/
addStyleProcessorRules( callback ) {
callback( this.stylesProcessor );
}
/**
* Registers a {@link module:engine/view/matcher~MatcherPattern} on an {@link #htmlProcessor htmlProcessor}
* and a {@link #processor processor} for view elements whose content should be treated as raw data
* and not processed during the conversion from DOM to view elements.
*
* The raw data can be later accessed by the {@link module:engine/view/element~Element#getCustomProperty view element custom property}
* `"$rawContent"`.
*
* @param {module:engine/view/matcher~MatcherPattern} pattern Pattern matching all view elements whose content should
* be treated as a raw data.
*/
registerRawContentMatcher( pattern ) {
// No need to register the pattern if both the `htmlProcessor` and `processor` are the same instances.
if ( this.processor && this.processor !== this.htmlProcessor ) {
this.processor.registerRawContentMatcher( pattern );
}
this.htmlProcessor.registerRawContentMatcher( pattern );
}
/**
* Removes all event listeners set by the DataController.
*/
destroy() {
this.stopListening();
}
/**
* Checks whether all provided root names are actually existing editor roots.
*
* @private
* @param {Array.<String>} rootNames Root names to check.
* @returns {Boolean} Whether all provided root names are existing editor roots.
*/
_checkIfRootsExists( rootNames ) {
for ( const rootName of rootNames ) {
if ( !this.model.document.getRootNames().includes( rootName ) ) {
return false;
}
}
return true;
}
/**
* Event fired once the data initialization has finished.
*
* @event ready
*/
/**
* An event fired after the {@link #init `init()` method} was run. It can be {@link #listenTo listened to} in order to adjust or modify
* the initialization flow. However, if the `init` event is stopped or prevented, the {@link #event:ready `ready` event}
* should be fired manually.
*
* The `init` event is fired by the decorated {@link #init} method.
* See {@link module:utils/observablemixin~ObservableMixin#decorate} for more information and samples.
*
* @event init
*/
/**
* An event fired after {@link #set set() method} has been run.
*
* The `set` event is fired by the decorated {@link #set} method.
* See {@link module:utils/observablemixin~ObservableMixin#decorate} for more information and samples.
*
* @event set
*/
/**
* Event fired after the {@link #get get() method} has been run.
*
* The `get` event is fired by the decorated {@link #get} method.
* See {@link module:utils/observablemixin~ObservableMixin#decorate} for more information and samples.
*
* @event get
*/
}
(0,_ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_0__["default"])( DataController, _ckeditor_ckeditor5_utils_src_observablemixin__WEBPACK_IMPORTED_MODULE_1__["default"] );
// Helper function for downcast conversion.
//
// Takes a document element (element that is added to a model document) and checks which markers are inside it. If the marker is collapsed
// at element boundary, it is considered as contained inside the element and marker range is returned. Otherwise, if the marker is
// intersecting with the element, the intersection is returned.
function _getMarkersRelativeToElement( element ) {
const result = [];
const doc = element.root.document;
if ( !doc ) {
return new Map();
}
const elementRange = _model_range__WEBPACK_IMPORTED_MODULE_11__["default"]._createIn( element );
for ( const marker of doc.model.markers ) {
const markerRange = marker.getRange();
const isMarkerCollapsed = markerRange.isCollapsed;
const isMarkerAtElementBoundary = markerRange.start.isEqual( elementRange.start ) || markerRange.end.isEqual( elementRange.end );
if ( isMarkerCollapsed && isMarkerAtElementBoundary ) {
result.push( [ marker.name, markerRange ] );
} else {
const updatedMarkerRange = elementRange.getIntersection( markerRange );
if ( updatedMarkerRange ) {
result.push( [ marker.name, updatedMarkerRange ] );
}
}
}
// Sort the markers in a stable fashion to ensure that the order in which they are
// added to the model's marker collection does not affect how they are
// downcast. One particular use case that we are targeting here, is one where
// two markers are adjacent but not overlapping, such as an insertion/deletion
// suggestion pair representing the replacement of a range of text. In this
// case, putting the markers in DOM order causes the first marker's end to be
// serialized right after the second marker's start, while putting the markers
// in reverse DOM order causes it to be right before the second marker's
// start. So, we sort these in a way that ensures non-intersecting ranges are in
// reverse DOM order, and intersecting ranges are in something approximating
// reverse DOM order (since reverse DOM order doesn't have a precise meaning
// when working with intersecting ranges).
result.sort( ( [ n1, r1 ], [ n2, r2 ] ) => {
if ( r1.end.compareWith( r2.start ) !== 'after' ) {
// m1.end <= m2.start -- m1 is entirely <= m2
return 1;
} else if ( r1.start.compareWith( r2.end ) !== 'before' ) {
// m1.start >= m2.end -- m1 is entirely >= m2
return -1;
} else {
// they overlap, so use their start positions as the primary sort key and
// end positions as the secondary sort key
switch ( r1.start.compareWith( r2.start ) ) {
case 'before':
return 1;
case 'after':
return -1;
default:
switch ( r1.end.compareWith( r2.end ) ) {
case 'before':
return 1;
case 'after':
return -1;
default:
return n2.localeCompare( n1 );
}
}
}
} );
return new Map( result );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/controller/editingcontroller.js":
/*!*************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/controller/editingcontroller.js ***!
\*************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ EditingController)
/* harmony export */ });
/* harmony import */ var _view_rooteditableelement__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../view/rooteditableelement */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/rooteditableelement.js");
/* harmony import */ var _view_view__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../view/view */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/view.js");
/* harmony import */ var _conversion_mapper__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../conversion/mapper */ "./node_modules/@ckeditor/ckeditor5-engine/src/conversion/mapper.js");
/* harmony import */ var _conversion_downcastdispatcher__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../conversion/downcastdispatcher */ "./node_modules/@ckeditor/ckeditor5-engine/src/conversion/downcastdispatcher.js");
/* harmony import */ var _conversion_downcasthelpers__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../conversion/downcasthelpers */ "./node_modules/@ckeditor/ckeditor5-engine/src/conversion/downcasthelpers.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_observablemixin__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/observablemixin */ "./node_modules/@ckeditor/ckeditor5-utils/src/observablemixin.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/mix */ "./node_modules/@ckeditor/ckeditor5-utils/src/mix.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/* harmony import */ var _conversion_upcasthelpers__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ../conversion/upcasthelpers */ "./node_modules/@ckeditor/ckeditor5-engine/src/conversion/upcasthelpers.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/controller/editingcontroller
*/
// @if CK_DEBUG_ENGINE // const { dumpTrees, initDocumentDumping } = require( '../dev-utils/utils' );
/**
* A controller for the editing pipeline. The editing pipeline controls the {@link ~EditingController#model model} rendering,
* including selection handling. It also creates the {@link ~EditingController#view view} which builds a
* browser-independent virtualization over the DOM elements. The editing controller also attaches default converters.
*
* @mixes module:utils/observablemixin~ObservableMixin
*/
class EditingController {
/**
* Creates an editing controller instance.
*
* @param {module:engine/model/model~Model} model Editing model.
* @param {module:engine/view/stylesmap~StylesProcessor} stylesProcessor The styles processor instance.
*/
constructor( model, stylesProcessor ) {
/**
* Editor model.
*
* @readonly
* @member {module:engine/model/model~Model}
*/
this.model = model;
/**
* Editing view controller.
*
* @readonly
* @member {module:engine/view/view~View}
*/
this.view = new _view_view__WEBPACK_IMPORTED_MODULE_1__["default"]( stylesProcessor );
/**
* A mapper that describes the model-view binding.
*
* @readonly
* @member {module:engine/conversion/mapper~Mapper}
*/
this.mapper = new _conversion_mapper__WEBPACK_IMPORTED_MODULE_2__["default"]();
/**
* Downcast dispatcher that converts changes from the model to the {@link #view editing view}.
*
* @readonly
* @member {module:engine/conversion/downcastdispatcher~DowncastDispatcher} #downcastDispatcher
*/
this.downcastDispatcher = new _conversion_downcastdispatcher__WEBPACK_IMPORTED_MODULE_3__["default"]( {
mapper: this.mapper,
schema: model.schema
} );
const doc = this.model.document;
const selection = doc.selection;
const markers = this.model.markers;
// When plugins listen on model changes (on selection change, post fixers, etc.) and change the view as a result of
// the model's change, they might trigger view rendering before the conversion is completed (e.g. before the selection
// is converted). We disable rendering for the length of the outermost model change() block to prevent that.
//
// See https://github.com/ckeditor/ckeditor5-engine/issues/1528
this.listenTo( this.model, '_beforeChanges', () => {
this.view._disableRendering( true );
}, { priority: 'highest' } );
this.listenTo( this.model, '_afterChanges', () => {
this.view._disableRendering( false );
}, { priority: 'lowest' } );
// Whenever model document is changed, convert those changes to the view (using model.Document#differ).
// Do it on 'low' priority, so changes are converted after other listeners did their job.
// Also convert model selection.
this.listenTo( doc, 'change', () => {
this.view.change( writer => {
this.downcastDispatcher.convertChanges( doc.differ, markers, writer );
this.downcastDispatcher.convertSelection( selection, markers, writer );
} );
}, { priority: 'low' } );
// Convert selection from the view to the model when it changes in the view.
this.listenTo( this.view.document, 'selectionChange', (0,_conversion_upcasthelpers__WEBPACK_IMPORTED_MODULE_8__.convertSelectionChange)( this.model, this.mapper ) );
// Attach default model converters.
this.downcastDispatcher.on( 'insert:$text', (0,_conversion_downcasthelpers__WEBPACK_IMPORTED_MODULE_4__.insertText)(), { priority: 'lowest' } );
this.downcastDispatcher.on( 'insert', (0,_conversion_downcasthelpers__WEBPACK_IMPORTED_MODULE_4__.insertAttributesAndChildren)(), { priority: 'lowest' } );
this.downcastDispatcher.on( 'remove', (0,_conversion_downcasthelpers__WEBPACK_IMPORTED_MODULE_4__.remove)(), { priority: 'low' } );
// Attach default model selection converters.
this.downcastDispatcher.on( 'selection', (0,_conversion_downcasthelpers__WEBPACK_IMPORTED_MODULE_4__.clearAttributes)(), { priority: 'high' } );
this.downcastDispatcher.on( 'selection', (0,_conversion_downcasthelpers__WEBPACK_IMPORTED_MODULE_4__.convertRangeSelection)(), { priority: 'low' } );
this.downcastDispatcher.on( 'selection', (0,_conversion_downcasthelpers__WEBPACK_IMPORTED_MODULE_4__.convertCollapsedSelection)(), { priority: 'low' } );
// Binds {@link module:engine/view/document~Document#roots view roots collection} to
// {@link module:engine/model/document~Document#roots model roots collection} so creating
// model root automatically creates corresponding view root.
this.view.document.roots.bindTo( this.model.document.roots ).using( root => {
// $graveyard is a special root that has no reflection in the view.
if ( root.rootName == '$graveyard' ) {
return null;
}
const viewRoot = new _view_rooteditableelement__WEBPACK_IMPORTED_MODULE_0__["default"]( this.view.document, root.name );
viewRoot.rootName = root.rootName;
this.mapper.bindElements( root, viewRoot );
return viewRoot;
} );
// @if CK_DEBUG_ENGINE // initDocumentDumping( this.model.document );
// @if CK_DEBUG_ENGINE // initDocumentDumping( this.view.document );
// @if CK_DEBUG_ENGINE // dumpTrees( this.model.document, this.model.document.version );
// @if CK_DEBUG_ENGINE // dumpTrees( this.view.document, this.model.document.version );
// @if CK_DEBUG_ENGINE // this.model.document.on( 'change', () => {
// @if CK_DEBUG_ENGINE // dumpTrees( this.view.document, this.model.document.version );
// @if CK_DEBUG_ENGINE // }, { priority: 'lowest' } );
}
/**
* Removes all event listeners attached to the `EditingController`. Destroys all objects created
* by `EditingController` that need to be destroyed.
*/
destroy() {
this.view.destroy();
this.stopListening();
}
/**
* Calling this method will refresh the marker by triggering the downcast conversion for it.
*
* Reconverting the marker is useful when you want to change its {@link module:engine/view/element~Element view element}
* without changing any marker data. For instance:
*
* let isCommentActive = false;
*
* model.conversion.markerToHighlight( {
* model: 'comment',
* view: data => {
* const classes = [ 'comment-marker' ];
*
* if ( isCommentActive ) {
* classes.push( 'comment-marker--active' );
* }
*
* return { classes };
* }
* } );
*
* // ...
*
* // Change the property that indicates if marker is displayed as active or not.
* isCommentActive = true;
*
* // Reconverting will downcast and synchronize the marker with the new isCommentActive state value.
* editor.editing.reconvertMarker( 'comment' );
*
* **Note**: If you want to reconvert a model item, use {@link #reconvertItem} instead.
*
* @param {String|module:engine/model/markercollection~Marker} markerOrName Name of a marker to update, or a marker instance.
*/
reconvertMarker( markerOrName ) {
const markerName = typeof markerOrName == 'string' ? markerOrName : markerOrName.name;
const currentMarker = this.model.markers.get( markerName );
if ( !currentMarker ) {
/**
* The marker with the provided name does not exist and cannot be reconverted.
*
* @error editingcontroller-reconvertmarker-marker-not-exist
* @param {String} markerName The name of the reconverted marker.
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_7__["default"]( 'editingcontroller-reconvertmarker-marker-not-exist', this, { markerName } );
}
this.model.change( () => {
this.model.markers._refresh( currentMarker );
} );
}
/**
* Calling this method will downcast a model item on demand (by requesting a refresh in the {@link module:engine/model/differ~Differ}).
*
* You can use it if you want the view representation of a specific item updated as a response to external modifications. For instance,
* when the view structure depends not only on the associated model data but also on some external state.
*
* **Note**: If you want to reconvert a model marker, use {@link #reconvertMarker} instead.
*
* @param {module:engine/model/item~Item} item Item to refresh.
*/
reconvertItem( item ) {
this.model.change( () => {
this.model.document.differ._refreshItem( item );
} );
}
}
(0,_ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_6__["default"])( EditingController, _ckeditor_ckeditor5_utils_src_observablemixin__WEBPACK_IMPORTED_MODULE_5__["default"] );
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/conversion/conversion.js":
/*!******************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/conversion/conversion.js ***!
\******************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ Conversion)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/* harmony import */ var _upcasthelpers__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./upcasthelpers */ "./node_modules/@ckeditor/ckeditor5-engine/src/conversion/upcasthelpers.js");
/* harmony import */ var _downcasthelpers__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./downcasthelpers */ "./node_modules/@ckeditor/ckeditor5-engine/src/conversion/downcasthelpers.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_toarray__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/toarray */ "./node_modules/@ckeditor/ckeditor5-utils/src/toarray.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/conversion/conversion
*/
/**
* A utility class that helps add converters to upcast and downcast dispatchers.
*
* We recommend reading the {@glink framework/guides/deep-dive/conversion/intro editor conversion} guide first to
* understand the core concepts of the conversion mechanisms.
*
* An instance of the conversion manager is available in the
* {@link module:core/editor/editor~Editor#conversion `editor.conversion`} property
* and by default has the following groups of dispatchers (i.e. directions of conversion):
*
* * `downcast` (editing and data downcasts)
* * `editingDowncast`
* * `dataDowncast`
* * `upcast`
*
* # One-way converters
*
* To add a converter to a specific group, use the {@link module:engine/conversion/conversion~Conversion#for `for()`}
* method:
*
* // Add a converter to editing downcast and data downcast.
* editor.conversion.for( 'downcast' ).elementToElement( config ) );
*
* // Add a converter to the data pipepline only:
* editor.conversion.for( 'dataDowncast' ).elementToElement( dataConversionConfig ) );
*
* // And a slightly different one for the editing pipeline:
* editor.conversion.for( 'editingDowncast' ).elementToElement( editingConversionConfig ) );
*
* See {@link module:engine/conversion/conversion~Conversion#for `for()`} method documentation to learn more about
* available conversion helpers and how to use your custom ones.
*
* # Two-way converters
*
* Besides using one-way converters via the `for()` method, you can also use other methods available in this
* class to add two-way converters (upcast and downcast):
*
* * {@link module:engine/conversion/conversion~Conversion#elementToElement `elementToElement()`} –
* Model element to view element and vice versa.
* * {@link module:engine/conversion/conversion~Conversion#attributeToElement `attributeToElement()`} –
* Model attribute to view element and vice versa.
* * {@link module:engine/conversion/conversion~Conversion#attributeToAttribute `attributeToAttribute()`} –
* Model attribute to view attribute and vice versa.
*/
class Conversion {
/**
* Creates a new conversion instance.
*
* @param {module:engine/conversion/downcastdispatcher~DowncastDispatcher|
* Array.<module:engine/conversion/downcastdispatcher~DowncastDispatcher>} downcastDispatchers
* @param {module:engine/conversion/upcastdispatcher~UpcastDispatcher|
* Array.<module:engine/conversion/upcastdispatcher~UpcastDispatcher>} upcastDispatchers
*/
constructor( downcastDispatchers, upcastDispatchers ) {
/**
* Maps dispatchers group name to ConversionHelpers instances.
*
* @private
* @member {Map.<String,module:engine/conversion/conversionhelpers~ConversionHelpers>}
*/
this._helpers = new Map();
// Define default 'downcast' & 'upcast' dispatchers groups. Those groups are always available as two-way converters needs them.
this._downcast = (0,_ckeditor_ckeditor5_utils_src_toarray__WEBPACK_IMPORTED_MODULE_3__["default"])( downcastDispatchers );
this._createConversionHelpers( { name: 'downcast', dispatchers: this._downcast, isDowncast: true } );
this._upcast = (0,_ckeditor_ckeditor5_utils_src_toarray__WEBPACK_IMPORTED_MODULE_3__["default"])( upcastDispatchers );
this._createConversionHelpers( { name: 'upcast', dispatchers: this._upcast, isDowncast: false } );
}
/**
* Define an alias for registered dispatcher.
*
* const conversion = new Conversion(
* [ dataDowncastDispatcher, editingDowncastDispatcher ],
* upcastDispatcher
* );
*
* conversion.addAlias( 'dataDowncast', dataDowncastDispatcher );
*
* @param {String} alias An alias of a dispatcher.
* @param {module:engine/conversion/downcastdispatcher~DowncastDispatcher|
* module:engine/conversion/upcastdispatcher~UpcastDispatcher} dispatcher Dispatcher which should have an alias.
*/
addAlias( alias, dispatcher ) {
const isDowncast = this._downcast.includes( dispatcher );
const isUpcast = this._upcast.includes( dispatcher );
if ( !isUpcast && !isDowncast ) {
/**
* Trying to register an alias for a dispatcher that nas not been registered.
*
* @error conversion-add-alias-dispatcher-not-registered
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_0__["default"](
'conversion-add-alias-dispatcher-not-registered',
this
);
}
this._createConversionHelpers( { name: alias, dispatchers: [ dispatcher ], isDowncast } );
}
/**
* Provides a chainable API to assign converters to a conversion dispatchers group.
*
* If the given group name has not been registered, the
* {@link module:utils/ckeditorerror~CKEditorError `conversion-for-unknown-group` error} is thrown.
*
* You can use conversion helpers available directly in the `for()` chain or your custom ones via
* the {@link module:engine/conversion/conversionhelpers~ConversionHelpers#add `add()`} method.
*
* # Using built-in conversion helpers
*
* The `for()` chain comes with a set of conversion helpers which you can use like this:
*
* editor.conversion.for( 'downcast' )
* .elementToElement( config1 ) // Adds an element-to-element downcast converter.
* .attributeToElement( config2 ); // Adds an attribute-to-element downcast converter.
*
* editor.conversion.for( 'upcast' )
* .elementToAttribute( config3 ); // Adds an element-to-attribute upcast converter.
*
* Refer to the documentation of built-in conversion helpers to learn about their configuration options.
*
* * downcast (model-to-view) conversion helpers:
*
* * {@link module:engine/conversion/downcasthelpers~DowncastHelpers#elementToElement `elementToElement()`},
* * {@link module:engine/conversion/downcasthelpers~DowncastHelpers#attributeToElement `attributeToElement()`},
* * {@link module:engine/conversion/downcasthelpers~DowncastHelpers#attributeToAttribute `attributeToAttribute()`}.
* * {@link module:engine/conversion/downcasthelpers~DowncastHelpers#markerToElement `markerToElement()`}.
* * {@link module:engine/conversion/downcasthelpers~DowncastHelpers#markerToHighlight `markerToHighlight()`}.
*
* * upcast (view-to-model) conversion helpers:
*
* * {@link module:engine/conversion/upcasthelpers~UpcastHelpers#elementToElement `elementToElement()`},
* * {@link module:engine/conversion/upcasthelpers~UpcastHelpers#elementToAttribute `elementToAttribute()`},
* * {@link module:engine/conversion/upcasthelpers~UpcastHelpers#attributeToAttribute `attributeToAttribute()`}.
* * {@link module:engine/conversion/upcasthelpers~UpcastHelpers#elementToMarker `elementToMarker()`}.
*
* # Using custom conversion helpers
*
* If you need to implement an atypical converter, you can do so by calling:
*
* editor.conversion.for( direction ).add( customHelper );
*
* The `.add()` method takes exactly one parameter, which is a function. This function should accept one parameter that
* is a dispatcher instance. The function should add an actual converter to the passed dispatcher instance.
*
* Example:
*
* editor.conversion.for( 'upcast' ).add( dispatcher => {
* dispatcher.on( 'element:a', ( evt, data, conversionApi ) => {
* // Do something with a view <a> element.
* } );
* } );
*
* Refer to the documentation of {@link module:engine/conversion/upcastdispatcher~UpcastDispatcher}
* and {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher} to learn how to write
* custom converters.
*
* @param {String} groupName The name of dispatchers group to add the converters to.
* @returns {module:engine/conversion/downcasthelpers~DowncastHelpers|module:engine/conversion/upcasthelpers~UpcastHelpers}
*/
for( groupName ) {
if ( !this._helpers.has( groupName ) ) {
/**
* Trying to add a converter to an unknown dispatchers group.
*
* @error conversion-for-unknown-group
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_0__["default"]( 'conversion-for-unknown-group', this );
}
return this._helpers.get( groupName );
}
/**
* Sets up converters between the model and the view that convert a model element to a view element (and vice versa).
* For example, the model `<paragraph>Foo</paragraph>` is turned into `<p>Foo</p>` in the view.
*
* // A simple conversion from the `paragraph` model element to the `<p>` view element (and vice versa).
* editor.conversion.elementToElement( { model: 'paragraph', view: 'p' } );
*
* // Override other converters by specifying a converter definition with a higher priority.
* editor.conversion.elementToElement( { model: 'paragraph', view: 'div', converterPriority: 'high' } );
*
* // View specified as an object instead of a string.
* editor.conversion.elementToElement( {
* model: 'fancyParagraph',
* view: {
* name: 'p',
* classes: 'fancy'
* }
* } );
*
* // Use `upcastAlso` to define other view elements that should also be converted to a `paragraph` element.
* editor.conversion.elementToElement( {
* model: 'paragraph',
* view: 'p',
* upcastAlso: [
* 'div',
* {
* // Any element with the `display: block` style.
* styles: {
* display: 'block'
* }
* }
* ]
* } );
*
* // `upcastAlso` set as callback enables a conversion of a wide range of different view elements.
* editor.conversion.elementToElement( {
* model: 'heading',
* view: 'h2',
* // Convert "heading-like" paragraphs to headings.
* upcastAlso: viewElement => {
* const fontSize = viewElement.getStyle( 'font-size' );
*
* if ( !fontSize ) {
* return null;
* }
*
* const match = fontSize.match( /(\d+)\s*px/ );
*
* if ( !match ) {
* return null;
* }
*
* const size = Number( match[ 1 ] );
*
* if ( size > 26 ) {
* // Returned value can be an object with the matched properties.
* // These properties will be "consumed" during the conversion.
* // See `engine.view.Matcher~MatcherPattern` and `engine.view.Matcher#match` for more details.
*
* return { name: true, styles: [ 'font-size' ] };
* }
*
* return null;
* }
* } );
*
* `definition.model` is a `String` with a model element name to convert from or to.
* See {@link module:engine/conversion/conversion~ConverterDefinition} to learn about other parameters.
*
* @param {module:engine/conversion/conversion~ConverterDefinition} definition The converter definition.
*/
elementToElement( definition ) {
// Set up downcast converter.
this.for( 'downcast' ).elementToElement( definition );
// Set up upcast converter.
for ( const { model, view } of _getAllUpcastDefinitions( definition ) ) {
this.for( 'upcast' )
.elementToElement( {
model,
view,
converterPriority: definition.converterPriority
} );
}
}
/**
* Sets up converters between the model and the view that convert a model attribute to a view element (and vice versa).
* For example, a model text node with `"Foo"` as data and the `bold` attribute will be turned to `<strong>Foo</strong>` in the view.
*
* // A simple conversion from the `bold=true` attribute to the `<strong>` view element (and vice versa).
* editor.conversion.attributeToElement( { model: 'bold', view: 'strong' } );
*
* // Override other converters by specifying a converter definition with a higher priority.
* editor.conversion.attributeToElement( { model: 'bold', view: 'b', converterPriority: 'high' } );
*
* // View specified as an object instead of a string.
* editor.conversion.attributeToElement( {
* model: 'bold',
* view: {
* name: 'span',
* classes: 'bold'
* }
* } );
*
* // Use `config.model.name` to define the conversion only from a given node type, `$text` in this case.
* // The same attribute on different elements may then be handled by a different converter.
* editor.conversion.attributeToElement( {
* model: {
* key: 'textDecoration',
* values: [ 'underline', 'lineThrough' ],
* name: '$text'
* },
* view: {
* underline: {
* name: 'span',
* styles: {
* 'text-decoration': 'underline'
* }
* },
* lineThrough: {
* name: 'span',
* styles: {
* 'text-decoration': 'line-through'
* }
* }
* }
* } );
*
* // Use `upcastAlso` to define other view elements that should also be converted to the `bold` attribute.
* editor.conversion.attributeToElement( {
* model: 'bold',
* view: 'strong',
* upcastAlso: [
* 'b',
* {
* name: 'span',
* classes: 'bold'
* },
* {
* name: 'span',
* styles: {
* 'font-weight': 'bold'
* }
* },
* viewElement => {
* const fontWeight = viewElement.getStyle( 'font-weight' );
*
* if ( viewElement.is( 'element', 'span' ) && fontWeight && /\d+/.test() && Number( fontWeight ) > 500 ) {
* // Returned value can be an object with the matched properties.
* // These properties will be "consumed" during the conversion.
* // See `engine.view.Matcher~MatcherPattern` and `engine.view.Matcher#match` for more details.
*
* return {
* name: true,
* styles: [ 'font-weight' ]
* };
* }
* }
* ]
* } );
*
* // Conversion from and to a model attribute key whose value is an enum (`fontSize=big|small`).
* // `upcastAlso` set as callback enables a conversion of a wide range of different view elements.
* editor.conversion.attributeToElement( {
* model: {
* key: 'fontSize',
* values: [ 'big', 'small' ]
* },
* view: {
* big: {
* name: 'span',
* styles: {
* 'font-size': '1.2em'
* }
* },
* small: {
* name: 'span',
* styles: {
* 'font-size': '0.8em'
* }
* }
* },
* upcastAlso: {
* big: viewElement => {
* const fontSize = viewElement.getStyle( 'font-size' );
*
* if ( !fontSize ) {
* return null;
* }
*
* const match = fontSize.match( /(\d+)\s*px/ );
*
* if ( !match ) {
* return null;
* }
*
* const size = Number( match[ 1 ] );
*
* if ( viewElement.is( 'element', 'span' ) && size > 10 ) {
* // Returned value can be an object with the matched properties.
* // These properties will be "consumed" during the conversion.
* // See `engine.view.Matcher~MatcherPattern` and `engine.view.Matcher#match` for more details.
*
* return { name: true, styles: [ 'font-size' ] };
* }
*
* return null;
* },
* small: viewElement => {
* const fontSize = viewElement.getStyle( 'font-size' );
*
* if ( !fontSize ) {
* return null;
* }
*
* const match = fontSize.match( /(\d+)\s*px/ );
*
* if ( !match ) {
* return null;
* }
*
* const size = Number( match[ 1 ] );
*
* if ( viewElement.is( 'element', 'span' ) && size < 10 ) {
* // Returned value can be an object with the matched properties.
* // These properties will be "consumed" during the conversion.
* // See `engine.view.Matcher~MatcherPattern` and `engine.view.Matcher#match` for more details.
*
* return { name: true, styles: [ 'font-size' ] };
* }
*
* return null;
* }
* }
* } );
*
* The `definition.model` parameter specifies which model attribute should be converted from or to. It can be a `{ key, value }` object
* describing the attribute key and value to convert or a `String` specifying just the attribute key (in such a case
* `value` is set to `true`).
* See {@link module:engine/conversion/conversion~ConverterDefinition} to learn about other parameters.
*
* @param {module:engine/conversion/conversion~ConverterDefinition} definition The converter definition.
*/
attributeToElement( definition ) {
// Set up downcast converter.
this.for( 'downcast' ).attributeToElement( definition );
// Set up upcast converter.
for ( const { model, view } of _getAllUpcastDefinitions( definition ) ) {
this.for( 'upcast' )
.elementToAttribute( {
view,
model,
converterPriority: definition.converterPriority
} );
}
}
/**
* Sets up converters between the model and the view that convert a model attribute to a view attribute (and vice versa). For example,
* `<imageBlock src='foo.jpg'></imageBlock>` is converted to `<img src='foo.jpg'></img>` (the same attribute key and value).
* This type of converters is intended to be used with {@link module:engine/model/element~Element model element} nodes.
* To convert the text attributes,
* the {@link module:engine/conversion/conversion~Conversion#attributeToElement `attributeToElement converter`}should be set up.
*
* // A simple conversion from the `source` model attribute to the `src` view attribute (and vice versa).
* editor.conversion.attributeToAttribute( { model: 'source', view: 'src' } );
*
* // Attribute values are strictly specified.
* editor.conversion.attributeToAttribute( {
* model: {
* name: 'imageInline',
* key: 'aside',
* values: [ 'aside' ]
* },
* view: {
* aside: {
* name: 'img',
* key: 'class',
* value: [ 'aside', 'half-size' ]
* }
* }
* } );
*
* // Set the style attribute.
* editor.conversion.attributeToAttribute( {
* model: {
* name: 'imageInline',
* key: 'aside',
* values: [ 'aside' ]
* },
* view: {
* aside: {
* name: 'img',
* key: 'style',
* value: {
* float: 'right',
* width: '50%',
* margin: '5px'
* }
* }
* }
* } );
*
* // Conversion from and to a model attribute key whose value is an enum (`align=right|center`).
* // Use `upcastAlso` to define other view elements that should also be converted to the `align=right` attribute.
* editor.conversion.attributeToAttribute( {
* model: {
* key: 'align',
* values: [ 'right', 'center' ]
* },
* view: {
* right: {
* key: 'class',
* value: 'align-right'
* },
* center: {
* key: 'class',
* value: 'align-center'
* }
* },
* upcastAlso: {
* right: {
* styles: {
* 'text-align': 'right'
* }
* },
* center: {
* styles: {
* 'text-align': 'center'
* }
* }
* }
* } );
*
* The `definition.model` parameter specifies which model attribute should be converted from and to.
* It can be a `{ key, [ values ], [ name ] }` object or a `String`, which will be treated like `{ key: definition.model }`.
* The `key` property is the model attribute key to convert from and to.
* The `values` are the possible model attribute values. If the `values` parameter is not set, the model attribute value
* will be the same as the view attribute value.
* If `name` is set, the conversion will be set up only for model elements with the given name.
*
* The `definition.view` parameter specifies which view attribute should be converted from and to.
* It can be a `{ key, value, [ name ] }` object or a `String`, which will be treated like `{ key: definition.view }`.
* The `key` property is the view attribute key to convert from and to.
* The `value` is the view attribute value to convert from and to. If `definition.value` is not set, the view attribute value will be
* the same as the model attribute value.
* If `key` is `'class'`, `value` can be a `String` or an array of `String`s.
* If `key` is `'style'`, `value` is an object with key-value pairs.
* In other cases, `value` is a `String`.
* If `name` is set, the conversion will be set up only for model elements with the given name.
* If `definition.model.values` is set, `definition.view` is an object that assigns values from `definition.model.values`
* to `{ key, value, [ name ] }` objects.
*
* `definition.upcastAlso` specifies which other matching view elements should also be upcast to the given model configuration.
* If `definition.model.values` is set, `definition.upcastAlso` should be an object assigning values from `definition.model.values`
* to {@link module:engine/view/matcher~MatcherPattern}s or arrays of {@link module:engine/view/matcher~MatcherPattern}s.
*
* **Note:** `definition.model` and `definition.view` form should be mirrored, so the same types of parameters should
* be given in both parameters.
*
* @param {Object} definition The converter definition.
* @param {String|Object} definition.model The model attribute to convert from and to.
* @param {String|Object} definition.view The view attribute to convert from and to.
* @param {module:engine/view/matcher~MatcherPattern|Array.<module:engine/view/matcher~MatcherPattern>} [definition.upcastAlso]
* Any view element matching `definition.upcastAlso` will also be converted to the given model attribute. `definition.upcastAlso`
* is used only if `config.model.values` is specified.
*/
attributeToAttribute( definition ) {
// Set up downcast converter.
this.for( 'downcast' ).attributeToAttribute( definition );
// Set up upcast converter.
for ( const { model, view } of _getAllUpcastDefinitions( definition ) ) {
this.for( 'upcast' )
.attributeToAttribute( {
view,
model
} );
}
}
/**
* Creates and caches conversion helpers for given dispatchers group.
*
* @private
* @param {Object} options
* @param {String} options.name Group name.
* @param {Array.<module:engine/conversion/downcastdispatcher~DowncastDispatcher|
* module:engine/conversion/upcastdispatcher~UpcastDispatcher>} options.dispatchers
* @param {Boolean} options.isDowncast
*/
_createConversionHelpers( { name, dispatchers, isDowncast } ) {
if ( this._helpers.has( name ) ) {
/**
* Trying to register a group name that has already been registered.
*
* @error conversion-group-exists
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_0__["default"]( 'conversion-group-exists', this );
}
const helpers = isDowncast ? new _downcasthelpers__WEBPACK_IMPORTED_MODULE_2__["default"]( dispatchers ) : new _upcasthelpers__WEBPACK_IMPORTED_MODULE_1__["default"]( dispatchers );
this._helpers.set( name, helpers );
}
}
/**
* Defines how the model should be converted from and to the view.
*
* @typedef {Object} module:engine/conversion/conversion~ConverterDefinition
*
* @property {*} [model] The model conversion definition. Describes the model element or model attribute to convert. This parameter differs
* for different functions that accept `ConverterDefinition`. See the description of the function to learn how to set it.
* @property {module:engine/view/elementdefinition~ElementDefinition|Object} view The definition of the view element to convert from and
* to. If `model` describes multiple values, `view` is an object that assigns these values (`view` object keys) to view element definitions
* (`view` object values).
* @property {module:engine/view/matcher~MatcherPattern|Array.<module:engine/view/matcher~MatcherPattern>} [upcastAlso]
* Any view element matching `upcastAlso` will also be converted to the model. If `model` describes multiple values, `upcastAlso`
* is an object that assigns these values (`upcastAlso` object keys) to {@link module:engine/view/matcher~MatcherPattern}s
* (`upcastAlso` object values).
* @property {module:utils/priorities~PriorityString} [converterPriority] The converter priority.
*/
// Helper function that creates a joint array out of an item passed in `definition.view` and items passed in
// `definition.upcastAlso`.
//
// @param {module:engine/conversion/conversion~ConverterDefinition} definition
// @returns {Array} Array containing view definitions.
function* _getAllUpcastDefinitions( definition ) {
if ( definition.model.values ) {
for ( const value of definition.model.values ) {
const model = { key: definition.model.key, value };
const view = definition.view[ value ];
const upcastAlso = definition.upcastAlso ? definition.upcastAlso[ value ] : undefined;
yield* _getUpcastDefinition( model, view, upcastAlso );
}
} else {
yield* _getUpcastDefinition( definition.model, definition.view, definition.upcastAlso );
}
}
function* _getUpcastDefinition( model, view, upcastAlso ) {
yield { model, view };
if ( upcastAlso ) {
for ( const upcastAlsoItem of (0,_ckeditor_ckeditor5_utils_src_toarray__WEBPACK_IMPORTED_MODULE_3__["default"])( upcastAlso ) ) {
yield { model, view: upcastAlsoItem };
}
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/conversion/conversionhelpers.js":
/*!*************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/conversion/conversionhelpers.js ***!
\*************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ConversionHelpers)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/conversion/conversionhelpers
*/
/**
* Base class for conversion helpers.
*/
class ConversionHelpers {
/**
* Creates a conversion helpers instance.
*
* @param {Array.<module:engine/conversion/downcastdispatcher~DowncastDispatcher|
* module:engine/conversion/upcastdispatcher~UpcastDispatcher>} dispatchers
*/
constructor( dispatchers ) {
this._dispatchers = dispatchers;
}
/**
* Registers a conversion helper.
*
* **Note**: See full usage example in the `{@link module:engine/conversion/conversion~Conversion#for conversion.for()}`
* method description.
*
* @param {Function} conversionHelper The function to be called on event.
* @returns {module:engine/conversion/downcasthelpers~DowncastHelpers|module:engine/conversion/upcasthelpers~UpcastHelpers}
*/
add( conversionHelper ) {
for ( const dispatcher of this._dispatchers ) {
conversionHelper( dispatcher );
}
return this;
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/conversion/downcastdispatcher.js":
/*!**************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/conversion/downcastdispatcher.js ***!
\**************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ DowncastDispatcher)
/* harmony export */ });
/* harmony import */ var _modelconsumable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./modelconsumable */ "./node_modules/@ckeditor/ckeditor5-engine/src/conversion/modelconsumable.js");
/* harmony import */ var _model_range__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../model/range */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/range.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_emittermixin__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/emittermixin */ "./node_modules/@ckeditor/ckeditor5-utils/src/emittermixin.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/mix */ "./node_modules/@ckeditor/ckeditor5-utils/src/mix.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/conversion/downcastdispatcher
*/
/**
* The downcast dispatcher is a central point of downcasting (conversion from the model to the view), which is a process of reacting
* to changes in the model and firing a set of events. The callbacks listening to these events are called converters. The
* converters' role is to convert the model changes to changes in view (for example, adding view nodes or
* changing attributes on view elements).
*
* During the conversion process, downcast dispatcher fires events basing on the state of the model and prepares
* data for these events. It is important to understand that the events are connected with the changes done on the model,
* for example: "a node has been inserted" or "an attribute has changed". This is in contrary to upcasting (a view-to-model conversion)
* where you convert the view state (view nodes) to a model tree.
*
* The events are prepared basing on a diff created by the {@link module:engine/model/differ~Differ Differ}, which buffers them
* and then passes to the downcast dispatcher as a diff between the old model state and the new model state.
*
* Note that because the changes are converted, there is a need to have a mapping between the model structure and the view structure.
* To map positions and elements during the downcast (a model-to-view conversion), use {@link module:engine/conversion/mapper~Mapper}.
*
* Downcast dispatcher fires the following events for model tree changes:
*
* * {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:insert `insert`} –
* If a range of nodes was inserted to the model tree.
* * {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:remove `remove`} –
* If a range of nodes was removed from the model tree.
* * {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:attribute `attribute`} –
* If an attribute was added, changed or removed from a model node.
*
* For {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:insert `insert`}
* and {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:attribute `attribute`},
* the downcast dispatcher generates {@link module:engine/conversion/modelconsumable~ModelConsumable consumables}.
* These are used to have control over which changes have already been consumed. It is useful when some converters
* overwrite others or convert multiple changes (for example, it converts an insertion of an element and also converts that
* element's attributes during the insertion).
*
* Additionally, downcast dispatcher fires events for {@link module:engine/model/markercollection~Marker marker} changes:
*
* * {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:addMarker `addMarker`} – If a marker was added.
* * {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:removeMarker `removeMarker`} – If a marker was
* removed.
*
* Note that changing a marker is done through removing the marker from the old range and adding it to the new range,
* so both of these events are fired.
*
* Finally, a downcast dispatcher also handles firing events for the {@link module:engine/model/selection model selection}
* conversion:
*
* * {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:selection `selection`}
* – Converts the selection from the model to the view.
* * {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:attribute `attribute`}
* – Fired for every selection attribute.
* * {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:addMarker `addMarker`}
* – Fired for every marker that contains a selection.
*
* Unlike the model tree and the markers, the events for selection are not fired for changes but for a selection state.
*
* When providing custom listeners for a downcast dispatcher, remember to check whether a given change has not been
* {@link module:engine/conversion/modelconsumable~ModelConsumable#consume consumed} yet.
*
* When providing custom listeners for a downcast dispatcher, keep in mind that you **should not** stop the event. If you stop it,
* then the default converter at the `lowest` priority will not trigger the conversion of this node's attributes and child nodes.
*
* When providing custom listeners for a downcast dispatcher, remember to use the provided
* {@link module:engine/view/downcastwriter~DowncastWriter view downcast writer} to apply changes to the view document.
*
* You can read more about conversion in the following guide:
*
* * {@glink framework/guides/deep-dive/conversion/downcast Downcast conversion}
*
* An example of a custom converter for the downcast dispatcher:
*
* // You will convert inserting a "paragraph" model element into the model.
* downcastDispatcher.on( 'insert:paragraph', ( evt, data, conversionApi ) => {
* // Remember to check whether the change has not been consumed yet and consume it.
* if ( !conversionApi.consumable.consume( data.item, 'insert' ) ) {
* return;
* }
*
* // Translate the position in the model to a position in the view.
* const viewPosition = conversionApi.mapper.toViewPosition( data.range.start );
*
* // Create a <p> element that will be inserted into the view at the `viewPosition`.
* const viewElement = conversionApi.writer.createContainerElement( 'p' );
*
* // Bind the newly created view element to the model element so positions will map accordingly in the future.
* conversionApi.mapper.bindElements( data.item, viewElement );
*
* // Add the newly created view element to the view.
* conversionApi.writer.insert( viewPosition, viewElement );
* } );
*/
class DowncastDispatcher {
/**
* Creates a downcast dispatcher instance.
*
* @see module:engine/conversion/downcastdispatcher~DowncastConversionApi
* @param {Object} conversionApi Additional properties for an interface that will be passed to events fired
* by the downcast dispatcher.
*/
constructor( conversionApi ) {
/**
* A template for an interface passed by the dispatcher to the event callbacks.
*
* @protected
* @member {module:engine/conversion/downcastdispatcher~DowncastConversionApi}
*/
this._conversionApi = { dispatcher: this, ...conversionApi };
/**
* A map of already fired events for a given `ModelConsumable`.
*
* @private
* @member {WeakMap.<module:engine/conversion/downcastdispatcher~DowncastConversionApi,Map>}
*/
this._firedEventsMap = new WeakMap();
}
/**
* Converts changes buffered in the given {@link module:engine/model/differ~Differ model differ}
* and fires conversion events based on it.
*
* @fires insert
* @fires remove
* @fires attribute
* @fires addMarker
* @fires removeMarker
* @fires reduceChanges
* @param {module:engine/model/differ~Differ} differ The differ object with buffered changes.
* @param {module:engine/model/markercollection~MarkerCollection} markers Markers related to the model fragment to convert.
* @param {module:engine/view/downcastwriter~DowncastWriter} writer The view writer that should be used to modify the view document.
*/
convertChanges( differ, markers, writer ) {
const conversionApi = this._createConversionApi( writer, differ.getRefreshedItems() );
// Before the view is updated, remove markers which have changed.
for ( const change of differ.getMarkersToRemove() ) {
this._convertMarkerRemove( change.name, change.range, conversionApi );
}
// Let features modify the change list (for example to allow reconversion).
const changes = this._reduceChanges( differ.getChanges() );
// Convert changes that happened on model tree.
for ( const entry of changes ) {
if ( entry.type === 'insert' ) {
this._convertInsert( _model_range__WEBPACK_IMPORTED_MODULE_1__["default"]._createFromPositionAndShift( entry.position, entry.length ), conversionApi );
} else if ( entry.type === 'reinsert' ) {
this._convertReinsert( _model_range__WEBPACK_IMPORTED_MODULE_1__["default"]._createFromPositionAndShift( entry.position, entry.length ), conversionApi );
} else if ( entry.type === 'remove' ) {
this._convertRemove( entry.position, entry.length, entry.name, conversionApi );
} else {
// Defaults to 'attribute' change.
this._convertAttribute( entry.range, entry.attributeKey, entry.attributeOldValue, entry.attributeNewValue, conversionApi );
}
}
for ( const markerName of conversionApi.mapper.flushUnboundMarkerNames() ) {
const markerRange = markers.get( markerName ).getRange();
this._convertMarkerRemove( markerName, markerRange, conversionApi );
this._convertMarkerAdd( markerName, markerRange, conversionApi );
}
// After the view is updated, convert markers which have changed.
for ( const change of differ.getMarkersToAdd() ) {
this._convertMarkerAdd( change.name, change.range, conversionApi );
}
// Remove mappings for all removed view elements.
conversionApi.mapper.flushDeferredBindings();
// Verify if all insert consumables were consumed.
conversionApi.consumable.verifyAllConsumed( 'insert' );
}
/**
* Starts a conversion of a model range and the provided markers.
*
* @fires insert
* @fires attribute
* @fires addMarker
* @param {module:engine/model/range~Range} range The inserted range.
* @param {Map<String,module:engine/model/range~Range>} markers The map of markers that should be down-casted.
* @param {module:engine/view/downcastwriter~DowncastWriter} writer The view writer that should be used to modify the view document.
* @param {Object} [options] Optional options object passed to `convertionApi.options`.
*/
convert( range, markers, writer, options = {} ) {
const conversionApi = this._createConversionApi( writer, undefined, options );
this._convertInsert( range, conversionApi );
for ( const [ name, range ] of markers ) {
this._convertMarkerAdd( name, range, conversionApi );
}
// Verify if all insert consumables were consumed.
conversionApi.consumable.verifyAllConsumed( 'insert' );
}
/**
* Starts the model selection conversion.
*
* Fires events for a given {@link module:engine/model/selection~Selection selection} to start the selection conversion.
*
* @fires selection
* @fires addMarker
* @fires attribute
* @param {module:engine/model/selection~Selection} selection The selection to convert.
* @param {module:engine/model/markercollection~MarkerCollection} markers Markers connected with the converted model.
* @param {module:engine/view/downcastwriter~DowncastWriter} writer View writer that should be used to modify the view document.
*/
convertSelection( selection, markers, writer ) {
const markersAtSelection = Array.from( markers.getMarkersAtPosition( selection.getFirstPosition() ) );
const conversionApi = this._createConversionApi( writer );
this._addConsumablesForSelection( conversionApi.consumable, selection, markersAtSelection );
this.fire( 'selection', { selection }, conversionApi );
if ( !selection.isCollapsed ) {
return;
}
for ( const marker of markersAtSelection ) {
const markerRange = marker.getRange();
if ( !shouldMarkerChangeBeConverted( selection.getFirstPosition(), marker, conversionApi.mapper ) ) {
continue;
}
const data = {
item: selection,
markerName: marker.name,
markerRange
};
if ( conversionApi.consumable.test( selection, 'addMarker:' + marker.name ) ) {
this.fire( 'addMarker:' + marker.name, data, conversionApi );
}
}
for ( const key of selection.getAttributeKeys() ) {
const data = {
item: selection,
range: selection.getFirstRange(),
attributeKey: key,
attributeOldValue: null,
attributeNewValue: selection.getAttribute( key )
};
// Do not fire event if the attribute has been consumed.
if ( conversionApi.consumable.test( selection, 'attribute:' + data.attributeKey ) ) {
this.fire( 'attribute:' + data.attributeKey + ':$text', data, conversionApi );
}
}
}
/**
* Fires insertion conversion of a range of nodes.
*
* For each node in the range, {@link #event:insert `insert` event is fired}. For each attribute on each node,
* {@link #event:attribute `attribute` event is fired}.
*
* @protected
* @fires insert
* @fires attribute
* @param {module:engine/model/range~Range} range The inserted range.
* @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi The conversion API object.
* @param {Object} [options]
* @param {Boolean} [options.doNotAddConsumables=false] Whether the ModelConsumable should not get populated
* for items in the provided range.
*/
_convertInsert( range, conversionApi, options = {} ) {
if ( !options.doNotAddConsumables ) {
// Collect a list of things that can be consumed, consisting of nodes and their attributes.
this._addConsumablesForInsert( conversionApi.consumable, Array.from( range ) );
}
// Fire a separate insert event for each node and text fragment contained in the range.
for ( const data of Array.from( range.getWalker( { shallow: true } ) ).map( walkerValueToEventData ) ) {
this._testAndFire( 'insert', data, conversionApi );
}
}
/**
* Fires conversion of a single node removal. Fires {@link #event:remove remove event} with provided data.
*
* @protected
* @param {module:engine/model/position~Position} position Position from which node was removed.
* @param {Number} length Offset size of removed node.
* @param {String} name Name of removed node.
* @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi The conversion API object.
*/
_convertRemove( position, length, name, conversionApi ) {
this.fire( 'remove:' + name, { position, length }, conversionApi );
}
/**
* Starts a conversion of an attribute change on a given `range`.
*
* For each node in the given `range`, {@link #event:attribute attribute event} is fired with the passed data.
*
* @protected
* @fires attribute
* @param {module:engine/model/range~Range} range Changed range.
* @param {String} key Key of the attribute that has changed.
* @param {*} oldValue Attribute value before the change or `null` if the attribute has not been set before.
* @param {*} newValue New attribute value or `null` if the attribute has been removed.
* @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi The conversion API object.
*/
_convertAttribute( range, key, oldValue, newValue, conversionApi ) {
// Create a list with attributes to consume.
this._addConsumablesForRange( conversionApi.consumable, range, `attribute:${ key }` );
// Create a separate attribute event for each node in the range.
for ( const value of range ) {
const data = {
item: value.item,
range: _model_range__WEBPACK_IMPORTED_MODULE_1__["default"]._createFromPositionAndShift( value.previousPosition, value.length ),
attributeKey: key,
attributeOldValue: oldValue,
attributeNewValue: newValue
};
this._testAndFire( `attribute:${ key }`, data, conversionApi );
}
}
/**
* Fires re-insertion conversion (with a `reconversion` flag passed to `insert` events)
* of a range of elements (only elements on the range depth, without children).
*
* For each node in the range on its depth (without children), {@link #event:insert `insert` event} is fired.
* For each attribute on each node, {@link #event:attribute `attribute` event} is fired.
*
* @protected
* @fires insert
* @fires attribute
* @param {module:engine/model/range~Range} range The range to reinsert.
* @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi The conversion API object.
*/
_convertReinsert( range, conversionApi ) {
// Convert the elements - without converting children.
const walkerValues = Array.from( range.getWalker( { shallow: true } ) );
// Collect a list of things that can be consumed, consisting of nodes and their attributes.
this._addConsumablesForInsert( conversionApi.consumable, walkerValues );
// Fire a separate insert event for each node and text fragment contained shallowly in the range.
for ( const data of walkerValues.map( walkerValueToEventData ) ) {
this._testAndFire( 'insert', { ...data, reconversion: true }, conversionApi );
}
}
/**
* Converts the added marker. Fires the {@link #event:addMarker `addMarker`} event for each item
* in the marker's range. If the range is collapsed, a single event is dispatched. See the event description for more details.
*
* @protected
* @fires addMarker
* @param {String} markerName Marker name.
* @param {module:engine/model/range~Range} markerRange The marker range.
* @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi The conversion API object.
*/
_convertMarkerAdd( markerName, markerRange, conversionApi ) {
// Do not convert if range is in graveyard.
if ( markerRange.root.rootName == '$graveyard' ) {
return;
}
// In markers' case, event name == consumable name.
const eventName = 'addMarker:' + markerName;
//
// First, fire an event for the whole marker.
//
conversionApi.consumable.add( markerRange, eventName );
this.fire( eventName, { markerName, markerRange }, conversionApi );
//
// Do not fire events for each item inside the range if the range got consumed.
// Also consume the whole marker consumable if it wasn't consumed.
//
if ( !conversionApi.consumable.consume( markerRange, eventName ) ) {
return;
}
//
// Then, fire an event for each item inside the marker range.
//
this._addConsumablesForRange( conversionApi.consumable, markerRange, eventName );
for ( const item of markerRange.getItems() ) {
// Do not fire event for already consumed items.
if ( !conversionApi.consumable.test( item, eventName ) ) {
continue;
}
const data = { item, range: _model_range__WEBPACK_IMPORTED_MODULE_1__["default"]._createOn( item ), markerName, markerRange };
this.fire( eventName, data, conversionApi );
}
}
/**
* Fires the conversion of the marker removal. Fires the {@link #event:removeMarker `removeMarker`} event with the provided data.
*
* @protected
* @fires removeMarker
* @param {String} markerName Marker name.
* @param {module:engine/model/range~Range} markerRange The marker range.
* @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi The conversion API object.
*/
_convertMarkerRemove( markerName, markerRange, conversionApi ) {
// Do not convert if range is in graveyard.
if ( markerRange.root.rootName == '$graveyard' ) {
return;
}
this.fire( 'removeMarker:' + markerName, { markerName, markerRange }, conversionApi );
}
/**
* Fires the reduction of changes buffered in the {@link module:engine/model/differ~Differ `Differ`}.
*
* Features can replace selected {@link module:engine/model/differ~DiffItem `DiffItem`}s with `reinsert` entries to trigger
* reconversion. The {@link module:engine/conversion/downcasthelpers~DowncastHelpers#elementToStructure
* `DowncastHelpers.elementToStructure()`} is using this event to trigger reconversion.
*
* @private
* @fires reduceChanges
* @param {Iterable.<module:engine/model/differ~DiffItem>} changes
* @returns {Iterable.<module:engine/model/differ~DiffItem>}
*/
_reduceChanges( changes ) {
const data = { changes };
this.fire( 'reduceChanges', data );
return data.changes;
}
/**
* Populates provided {@link module:engine/conversion/modelconsumable~ModelConsumable} with values to consume from a given range,
* assuming that the range has just been inserted to the model.
*
* @private
* @param {module:engine/conversion/modelconsumable~ModelConsumable} consumable The consumable.
* @param {Iterable.<module:engine/model/treewalker~TreeWalkerValue>} walkerValues The walker values for the inserted range.
* @returns {module:engine/conversion/modelconsumable~ModelConsumable} The values to consume.
*/
_addConsumablesForInsert( consumable, walkerValues ) {
for ( const value of walkerValues ) {
const item = value.item;
// Add consumable if it wasn't there yet.
if ( consumable.test( item, 'insert' ) === null ) {
consumable.add( item, 'insert' );
for ( const key of item.getAttributeKeys() ) {
consumable.add( item, 'attribute:' + key );
}
}
}
return consumable;
}
/**
* Populates provided {@link module:engine/conversion/modelconsumable~ModelConsumable} with values to consume for a given range.
*
* @private
* @param {module:engine/conversion/modelconsumable~ModelConsumable} consumable The consumable.
* @param {module:engine/model/range~Range} range The affected range.
* @param {String} type Consumable type.
* @returns {module:engine/conversion/modelconsumable~ModelConsumable} The values to consume.
*/
_addConsumablesForRange( consumable, range, type ) {
for ( const item of range.getItems() ) {
consumable.add( item, type );
}
return consumable;
}
/**
* Populates provided {@link module:engine/conversion/modelconsumable~ModelConsumable} with selection consumable values.
*
* @private
* @param {module:engine/conversion/modelconsumable~ModelConsumable} consumable The consumable.
* @param {module:engine/model/selection~Selection} selection The selection to create the consumable from.
* @param {Iterable.<module:engine/model/markercollection~Marker>} markers Markers that contain the selection.
* @returns {module:engine/conversion/modelconsumable~ModelConsumable} The values to consume.
*/
_addConsumablesForSelection( consumable, selection, markers ) {
consumable.add( selection, 'selection' );
for ( const marker of markers ) {
consumable.add( selection, 'addMarker:' + marker.name );
}
for ( const key of selection.getAttributeKeys() ) {
consumable.add( selection, 'attribute:' + key );
}
return consumable;
}
/**
* Tests whether given event wasn't already fired and if so, fires it.
*
* @private
* @fires insert
* @fires attribute
* @param {String} type Event type.
* @param {Object} data Event data.
* @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi The conversion API object.
*/
_testAndFire( type, data, conversionApi ) {
const eventName = getEventName( type, data );
const itemKey = data.item.is( '$textProxy' ) ? conversionApi.consumable._getSymbolForTextProxy( data.item ) : data.item;
const eventsFiredForConversion = this._firedEventsMap.get( conversionApi );
const eventsFiredForItem = eventsFiredForConversion.get( itemKey );
if ( !eventsFiredForItem ) {
eventsFiredForConversion.set( itemKey, new Set( [ eventName ] ) );
} else if ( !eventsFiredForItem.has( eventName ) ) {
eventsFiredForItem.add( eventName );
} else {
return;
}
this.fire( eventName, data, conversionApi );
}
/**
* Fires not already fired events for setting attributes on just inserted item.
*
* @private
* @param {module:engine/model/item~Item} item The model item to convert attributes for.
* @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi The conversion API object.
*/
_testAndFireAddAttributes( item, conversionApi ) {
const data = {
item,
range: _model_range__WEBPACK_IMPORTED_MODULE_1__["default"]._createOn( item )
};
for ( const key of data.item.getAttributeKeys() ) {
data.attributeKey = key;
data.attributeOldValue = null;
data.attributeNewValue = data.item.getAttribute( key );
this._testAndFire( `attribute:${ key }`, data, conversionApi );
}
}
/**
* Builds an instance of the {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi} from a template and a given
* {@link module:engine/view/downcastwriter~DowncastWriter `DowncastWriter`} and options object.
*
* @private
* @param {module:engine/view/downcastwriter~DowncastWriter} writer View writer that should be used to modify the view document.
* @param {Set.<module:engine/model/element~Element>} [refreshedItems] A set of model elements that should not reuse their
* previous view representations.
* @param {Object} [options] Optional options passed to `convertionApi.options`.
* @return {module:engine/conversion/downcastdispatcher~DowncastConversionApi} The conversion API object.
*/
_createConversionApi( writer, refreshedItems = new Set(), options = {} ) {
const conversionApi = {
...this._conversionApi,
consumable: new _modelconsumable__WEBPACK_IMPORTED_MODULE_0__["default"](),
writer,
options,
convertItem: item => this._convertInsert( _model_range__WEBPACK_IMPORTED_MODULE_1__["default"]._createOn( item ), conversionApi ),
convertChildren: element => this._convertInsert( _model_range__WEBPACK_IMPORTED_MODULE_1__["default"]._createIn( element ), conversionApi, { doNotAddConsumables: true } ),
convertAttributes: item => this._testAndFireAddAttributes( item, conversionApi ),
canReuseView: viewElement => !refreshedItems.has( conversionApi.mapper.toModelElement( viewElement ) )
};
this._firedEventsMap.set( conversionApi, new Map() );
return conversionApi;
}
/**
* Fired to enable reducing (transforming) changes buffered in the {@link module:engine/model/differ~Differ `Differ`} before
* {@link #convertChanges `convertChanges()`} will fire any conversion events.
*
* For instance, a feature can replace selected {@link module:engine/model/differ~DiffItem `DiffItem`}s with a `reinsert` entry
* to trigger reconversion of an element when e.g. its attribute has changes.
* The {@link module:engine/conversion/downcasthelpers~DowncastHelpers#elementToStructure
* `DowncastHelpers.elementToStructure()`} helper is using this event to trigger reconversion of an element when the element,
* its attributes or direct children changed.
*
* @param {Object} data
* @param {Iterable.<module:engine/model/differ~DiffItem>} data.changes A buffered changes to get reduced.
* @event reduceChanges
*/
/**
* Fired for inserted nodes.
*
* `insert` is a namespace for a class of events. Names of actually called events follow this pattern:
* `insert:name`. `name` is either `'$text'`, when {@link module:engine/model/text~Text a text node} has been inserted,
* or {@link module:engine/model/element~Element#name name} of inserted element.
*
* This way, the listeners can either listen to a general `insert` event or specific event (for example `insert:paragraph`).
*
* @event insert
* @param {Object} data Additional information about the change.
* @param {module:engine/model/item~Item} data.item The inserted item.
* @param {module:engine/model/range~Range} data.range Range spanning over inserted item.
* @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi Conversion interface
* to be used by callback, passed in the `DowncastDispatcher` constructor.
*/
/**
* Fired for removed nodes.
*
* `remove` is a namespace for a class of events. Names of actually called events follow this pattern:
* `remove:name`. `name` is either `'$text'`, when a {@link module:engine/model/text~Text a text node} has been removed,
* or the {@link module:engine/model/element~Element#name name} of removed element.
*
* This way, listeners can either listen to a general `remove` event or specific event (for example `remove:paragraph`).
*
* @event remove
* @param {Object} data Additional information about the change.
* @param {module:engine/model/position~Position} data.position Position from which the node has been removed.
* @param {Number} data.length Offset size of the removed node.
* @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi Conversion interface
* to be used by callback, passed in `DowncastDispatcher` constructor.
*/
/**
* Fired in the following cases:
*
* * when an attribute has been added, changed, or removed from a node,
* * when a node with an attribute is inserted,
* * when a collapsed model selection attribute is converted.
*
* `attribute` is a namespace for a class of events. Names of actually called events follow this pattern:
* `attribute:attributeKey:name`. `attributeKey` is the key of added/changed/removed attribute.
* `name` is either `'$text'` if change was on {@link module:engine/model/text~Text a text node},
* or the {@link module:engine/model/element~Element#name name} of element which attribute has changed.
*
* This way listeners can either listen to a general `attribute:bold` event or specific event (for example `attribute:src:imageBlock`).
*
* @event attribute
* @param {Object} data Additional information about the change.
* @param {module:engine/model/item~Item|module:engine/model/documentselection~DocumentSelection} data.item Changed item
* or converted selection.
* @param {module:engine/model/range~Range} data.range Range spanning over changed item or selection range.
* @param {String} data.attributeKey Attribute key.
* @param {*} data.attributeOldValue Attribute value before the change. This is `null` when selection attribute is converted.
* @param {*} data.attributeNewValue New attribute value.
* @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi Conversion interface
* to be used by callback, passed in `DowncastDispatcher` constructor.
*/
/**
* Fired for {@link module:engine/model/selection~Selection selection} changes.
*
* @event selection
* @param {module:engine/model/selection~Selection} selection Selection that is converted.
* @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi Conversion interface
* to be used by callback, passed in `DowncastDispatcher` constructor.
*/
/**
* Fired when a new marker is added to the model. Also fired when a collapsed model selection that is inside a marker is converted.
*
* `addMarker` is a namespace for a class of events. Names of actually called events follow this pattern:
* `addMarker:markerName`. By specifying certain marker names, you can make the events even more gradual. For example,
* if markers are named `foo:abc`, `foo:bar`, then it is possible to listen to `addMarker:foo` or `addMarker:foo:abc` and
* `addMarker:foo:bar` events.
*
* If the marker range is not collapsed:
*
* * the event is fired for each item in the marker range one by one,
* * `conversionApi.consumable` includes each item of the marker range and the consumable value is same as the event name.
*
* If the marker range is collapsed:
*
* * there is only one event,
* * `conversionApi.consumable` includes marker range with the event name.
*
* If the selection inside a marker is converted:
*
* * there is only one event,
* * `conversionApi.consumable` includes the selection instance with the event name.
*
* @event addMarker
* @param {Object} data Additional information about the change.
* @param {module:engine/model/item~Item|module:engine/model/selection~Selection} data.item Item inside the new marker or
* the selection that is being converted.
* @param {module:engine/model/range~Range} [data.range] Range spanning over converted item. Available only in marker conversion, if
* the marker range was not collapsed.
* @param {module:engine/model/range~Range} data.markerRange Marker range.
* @param {String} data.markerName Marker name.
* @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi Conversion interface
* to be used by callback, passed in `DowncastDispatcher` constructor.
*/
/**
* Fired when a marker is removed from the model.
*
* `removeMarker` is a namespace for a class of events. Names of actually called events follow this pattern:
* `removeMarker:markerName`. By specifying certain marker names, you can make the events even more gradual. For example,
* if markers are named `foo:abc`, `foo:bar`, then it is possible to listen to `removeMarker:foo` or `removeMarker:foo:abc` and
* `removeMarker:foo:bar` events.
*
* @event removeMarker
* @param {Object} data Additional information about the change.
* @param {module:engine/model/range~Range} data.markerRange Marker range.
* @param {String} data.markerName Marker name.
* @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi Conversion interface
* to be used by callback, passed in `DowncastDispatcher` constructor.
*/
}
(0,_ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_3__["default"])( DowncastDispatcher, _ckeditor_ckeditor5_utils_src_emittermixin__WEBPACK_IMPORTED_MODULE_2__["default"] );
// Helper function, checks whether change of `marker` at `modelPosition` should be converted. Marker changes are not
// converted if they happen inside an element with custom conversion method.
//
// @param {module:engine/model/position~Position} modelPosition
// @param {module:engine/model/markercollection~Marker} marker
// @param {module:engine/conversion/mapper~Mapper} mapper
// @returns {Boolean}
function shouldMarkerChangeBeConverted( modelPosition, marker, mapper ) {
const range = marker.getRange();
const ancestors = Array.from( modelPosition.getAncestors() );
ancestors.shift(); // Remove root element. It cannot be passed to `model.Range#containsItem`.
ancestors.reverse();
const hasCustomHandling = ancestors.some( element => {
if ( range.containsItem( element ) ) {
const viewElement = mapper.toViewElement( element );
return !!viewElement.getCustomProperty( 'addHighlight' );
}
} );
return !hasCustomHandling;
}
function getEventName( type, data ) {
const name = data.item.name || '$text';
return `${ type }:${ name }`;
}
function walkerValueToEventData( value ) {
const item = value.item;
const itemRange = _model_range__WEBPACK_IMPORTED_MODULE_1__["default"]._createFromPositionAndShift( value.previousPosition, value.length );
return {
item,
range: itemRange
};
}
/**
* Conversion interface that is registered for given {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher}
* and is passed as one of parameters when {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher dispatcher}
* fires its events.
*
* @interface module:engine/conversion/downcastdispatcher~DowncastConversionApi
*/
/**
* The {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher} instance.
*
* @member {module:engine/conversion/downcastdispatcher~DowncastDispatcher} #dispatcher
*/
/**
* Stores the information about what parts of a processed model item are still waiting to be handled. After a piece of a model item was
* converted, an appropriate consumable value should be {@link module:engine/conversion/modelconsumable~ModelConsumable#consume consumed}.
*
* @member {module:engine/conversion/modelconsumable~ModelConsumable} #consumable
*/
/**
* The {@link module:engine/conversion/mapper~Mapper} instance.
*
* @member {module:engine/conversion/mapper~Mapper} #mapper
*/
/**
* The {@link module:engine/model/schema~Schema} instance set for the model that is downcast.
*
* @member {module:engine/model/schema~Schema} #schema
*/
/**
* The {@link module:engine/view/downcastwriter~DowncastWriter} instance used to manipulate the data during conversion.
*
* @member {module:engine/view/downcastwriter~DowncastWriter} #writer
*/
/**
* Triggers conversion of a specified item.
* This conversion is triggered within (as a separate process of) the parent conversion.
*
* @method #convertItem
* @param {module:engine/model/item~Item} item The model item to trigger nested insert conversion on.
*/
/**
* Triggers conversion of children of a specified element.
*
* @method #convertChildren
* @param {module:engine/model/element~Element} element The model element to trigger children insert conversion on.
*/
/**
* Triggers conversion of attributes of a specified item.
*
* @method #convertAttributes
* @param {module:engine/model/item~Item} item The model item to trigger attribute conversion on.
*/
/**
* An object with an additional configuration which can be used during the conversion process. Available only for data downcast conversion.
*
* @member {Object} #options
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/conversion/downcasthelpers.js":
/*!***********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/conversion/downcasthelpers.js ***!
\***********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "clearAttributes": () => (/* binding */ clearAttributes),
/* harmony export */ "convertCollapsedSelection": () => (/* binding */ convertCollapsedSelection),
/* harmony export */ "convertRangeSelection": () => (/* binding */ convertRangeSelection),
/* harmony export */ "createViewElementFromHighlightDescriptor": () => (/* binding */ createViewElementFromHighlightDescriptor),
/* harmony export */ "default": () => (/* binding */ DowncastHelpers),
/* harmony export */ "insertAttributesAndChildren": () => (/* binding */ insertAttributesAndChildren),
/* harmony export */ "insertElement": () => (/* binding */ insertElement),
/* harmony export */ "insertStructure": () => (/* binding */ insertStructure),
/* harmony export */ "insertText": () => (/* binding */ insertText),
/* harmony export */ "insertUIElement": () => (/* binding */ insertUIElement),
/* harmony export */ "remove": () => (/* binding */ remove),
/* harmony export */ "wrap": () => (/* binding */ wrap)
/* harmony export */ });
/* harmony import */ var _model_range__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../model/range */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/range.js");
/* harmony import */ var _model_selection__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../model/selection */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/selection.js");
/* harmony import */ var _model_element__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../model/element */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/element.js");
/* harmony import */ var _model_position__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../model/position */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/position.js");
/* harmony import */ var _view_attributeelement__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../view/attributeelement */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/attributeelement.js");
/* harmony import */ var _model_documentselection__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../model/documentselection */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/documentselection.js");
/* harmony import */ var _conversionhelpers__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./conversionhelpers */ "./node_modules/@ckeditor/ckeditor5-engine/src/conversion/conversionhelpers.js");
/* harmony import */ var lodash_es__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! lodash-es */ "./node_modules/lodash-es/cloneDeep.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_toarray__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/toarray */ "./node_modules/@ckeditor/ckeditor5-utils/src/toarray.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* Contains downcast (model-to-view) converters for {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher}.
*
* @module engine/conversion/downcasthelpers
*/
/**
* Downcast conversion helper functions.
*
* Learn more about {@glink framework/guides/deep-dive/conversion/downcast downcast helpers}.
*
* @extends module:engine/conversion/conversionhelpers~ConversionHelpers
*/
class DowncastHelpers extends _conversionhelpers__WEBPACK_IMPORTED_MODULE_6__["default"] {
/**
* Model element to view element conversion helper.
*
* This conversion results in creating a view element. For example, model `<paragraph>Foo</paragraph>` becomes `<p>Foo</p>` in the view.
*
* editor.conversion.for( 'downcast' ).elementToElement( {
* model: 'paragraph',
* view: 'p'
* } );
*
* editor.conversion.for( 'downcast' ).elementToElement( {
* model: 'paragraph',
* view: 'div',
* converterPriority: 'high'
* } );
*
* editor.conversion.for( 'downcast' ).elementToElement( {
* model: 'fancyParagraph',
* view: {
* name: 'p',
* classes: 'fancy'
* }
* } );
*
* editor.conversion.for( 'downcast' ).elementToElement( {
* model: 'heading',
* view: ( modelElement, conversionApi ) => {
* const { writer } = conversionApi;
*
* return writer.createContainerElement( 'h' + modelElement.getAttribute( 'level' ) );
* }
* } );
*
* The element-to-element conversion supports the reconversion mechanism. It can be enabled by using either the `attributes` or
* the `children` props on a model description. You will find a couple examples below.
*
* In order to reconvert an element if any of its direct children have been added or removed, use the `children` property on a `model`
* description. For example, this model:
*
* <box>
* <paragraph>Some text.</paragraph>
* </box>
*
* will be converted into this structure in the view:
*
* <div class="box" data-type="single">
* <p>Some text.</p>
* </div>
*
* But if more items were inserted in the model:
*
* <box>
* <paragraph>Some text.</paragraph>
* <paragraph>Other item.</paragraph>
* </box>
*
* it will be converted into this structure in the view (note the element `data-type` change):
*
* <div class="box" data-type="multiple">
* <p>Some text.</p>
* <p>Other item.</p>
* </div>
*
* Such a converter would look like this (note that the `paragraph` elements are converted separately):
*
* editor.conversion.for( 'downcast' ).elementToElement( {
* model: {
* name: 'box',
* children: true
* },
* view: ( modelElement, conversionApi ) => {
* const { writer } = conversionApi;
*
* return writer.createContainerElement( 'div', {
* class: 'box',
* 'data-type': modelElement.childCount == 1 ? 'single' : 'multiple'
* } );
* }
* } );
*
* In order to reconvert element if any of its attributes have been updated, use the `attributes` property on a `model`
* description. For example, this model:
*
* <heading level="2">Some text.</heading>
*
* will be converted into this structure in the view:
*
* <h2>Some text.</h2>
*
* But if the `heading` element's `level` attribute has been updated to `3` for example, then
* it will be converted into this structure in the view:
*
* <h3>Some text.</h3>
*
* Such a converter would look as follows:
*
* editor.conversion.for( 'downcast' ).elementToElement( {
* model: {
* name: 'heading',
* attributes: 'level'
* },
* view: ( modelElement, conversionApi ) => {
* const { writer } = conversionApi;
*
* return writer.createContainerElement( 'h' + modelElement.getAttribute( 'level' ) );
* }
* } );
*
* See {@link module:engine/conversion/conversion~Conversion#for `conversion.for()`} to learn how to add a converter
* to the conversion process.
*
* You can read more about the element-to-element conversion in the
* {@glink framework/guides/deep-dive/conversion/downcast downcast conversion} guide.
*
* @method #elementToElement
* @param {Object} config Conversion configuration.
* @param {String|Object} config.model The description or a name of the model element to convert.
* @param {String|Array.<String>} [config.model.attributes] The list of attribute names that should be consumed while creating
* the view element. Note that the view will be reconverted if any of the listed attributes changes.
* @param {Boolean} [config.model.children] Specifies whether the view element requires reconversion if the list
* of the model child nodes changed.
* @param {module:engine/view/elementdefinition~ElementDefinition|Function} config.view A view element definition or a function
* that takes the model element and {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi downcast conversion API}
* as parameters and returns a view container element.
* @returns {module:engine/conversion/downcasthelpers~DowncastHelpers}
*/
elementToElement( config ) {
return this.add( downcastElementToElement( config ) );
}
/**
* The model element to view structure (several elements) conversion helper.
*
* This conversion results in creating a view structure with one or more slots defined for the child nodes.
* For example, a model `<table>` may become this structure in the view:
*
* <figure class="table">
* <table>
* <tbody>${ slot for table rows }</tbody>
* </table>
* </figure>
*
* The children of the model's `<table>` element will be inserted into the `<tbody>` element.
* If the `elementToElement()` helper was used, the children would be inserted into the `<figure>`.
*
* An example converter that converts the following model structure:
*
* <wrappedParagraph>Some text.</wrappedParagraph>
*
* into this structure in the view:
*
* <div class="wrapper">
* <p>Some text.</p>
* </div>
*
* would look like this:
*
* editor.conversion.for( 'downcast' ).elementToStructure( {
* model: 'wrappedParagraph',
* view: ( modelElement, conversionApi ) => {
* const { writer } = conversionApi;
*
* const wrapperViewElement = writer.createContainerElement( 'div', { class: 'wrapper' } );
* const paragraphViewElement = writer.createContainerElement( 'p' );
*
* writer.insert( writer.createPositionAt( wrapperViewElement, 0 ), paragraphViewElement );
* writer.insert( writer.createPositionAt( paragraphViewElement, 0 ), writer.createSlot() );
*
* return wrapperViewElement;
* }
* } );
*
* The `slorFor()` function can also take a callback that allows filtering which children of the model element
* should be converted into this slot.
*
* Imagine a table feature where for this model structure:
*
* <table headingRows="1">
* <tableRow> ... table cells 1 ... </tableRow>
* <tableRow> ... table cells 2 ... </tableRow>
* <tableRow> ... table cells 3 ... </tableRow>
* <caption>Caption text</caption>
* </table>
*
* we want to generate this view structure:
*
* <figure class="table">
* <table>
* <thead>
* <tr> ... table cells 1 ... </tr>
* </thead>
* <tbody>
* <tr> ... table cells 2 ... </tr>
* <tr> ... table cells 3 ... </tr>
* </tbody>
* </table>
* <figcaption>Caption text</figcaption>
* </figure>
*
* The converter has to take the `headingRows` attribute into consideration when allocating the `<tableRow>` elements
* into the `<tbody>` and `<thead>` elements. Hence, we need two slots and need to define proper filter callbacks for them.
*
* Additionally, all elements other than `<tableRow>` should be placed outside the `<table>` tag.
* In the example above, this will handle the table caption.
*
* Such a converter would look like this:
*
* editor.conversion.for( 'downcast' ).elementToStructure( {
* model: {
* name: 'table',
* attributes: [ 'headingRows' ]
* },
* view: ( modelElement, conversionApi ) => {
* const { writer } = conversionApi;
*
* const figureElement = writer.createContainerElement( 'figure', { class: 'table' } );
* const tableElement = writer.createContainerElement( 'table' );
*
* writer.insert( writer.createPositionAt( figureElement, 0 ), tableElement );
*
* const headingRows = modelElement.getAttribute( 'headingRows' ) || 0;
*
* if ( headingRows > 0 ) {
* const tableHead = writer.createContainerElement( 'thead' );
*
* const headSlot = writer.createSlot( node => node.is( 'element', 'tableRow' ) && node.index < headingRows );
*
* writer.insert( writer.createPositionAt( tableElement, 'end' ), tableHead );
* writer.insert( writer.createPositionAt( tableHead, 0 ), headSlot );
* }
*
* if ( headingRows < tableUtils.getRows( table ) ) {
* const tableBody = writer.createContainerElement( 'tbody' );
*
* const bodySlot = writer.createSlot( node => node.is( 'element', 'tableRow' ) && node.index >= headingRows );
*
* writer.insert( writer.createPositionAt( tableElement, 'end' ), tableBody );
* writer.insert( writer.createPositionAt( tableBody, 0 ), bodySlot );
* }
*
* const restSlot = writer.createSlot( node => !node.is( 'element', 'tableRow' ) );
*
* writer.insert( writer.createPositionAt( figureElement, 'end' ), restSlot );
*
* return figureElement;
* }
* } );
*
* Note: The children of a model element that's being converted must be allocated in the same order in the view
* in which they are placed in the model.
*
* See {@link module:engine/conversion/conversion~Conversion#for `conversion.for()`} to learn how to add a converter
* to the conversion process.
*
* @method #elementToStructure
* @param {Object} config Conversion configuration.
* @param {String|Object} config.model The description or a name of the model element to convert.
* @param {String} [config.model.name] The name of the model element to convert.
* @param {String|Array.<String>} [config.model.attributes] The list of attribute names that should be consumed while creating
* the view structure. Note that the view will be reconverted if any of the listed attributes will change.
* @param {module:engine/conversion/downcasthelpers~StructureCreatorFunction} config.view A function
* that takes the model element and {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi downcast
* conversion API} as parameters and returns a view container element with slots for model child nodes to be converted into.
* @returns {module:engine/conversion/downcasthelpers~DowncastHelpers}
*/
elementToStructure( config ) {
return this.add( downcastElementToStructure( config ) );
}
/**
* Model attribute to view element conversion helper.
*
* This conversion results in wrapping view nodes with a view attribute element. For example, a model text node with
* `"Foo"` as data and the `bold` attribute becomes `<strong>Foo</strong>` in the view.
*
* editor.conversion.for( 'downcast' ).attributeToElement( {
* model: 'bold',
* view: 'strong'
* } );
*
* editor.conversion.for( 'downcast' ).attributeToElement( {
* model: 'bold',
* view: 'b',
* converterPriority: 'high'
* } );
*
* editor.conversion.for( 'downcast' ).attributeToElement( {
* model: 'invert',
* view: {
* name: 'span',
* classes: [ 'font-light', 'bg-dark' ]
* }
* } );
*
* editor.conversion.for( 'downcast' ).attributeToElement( {
* model: {
* key: 'fontSize',
* values: [ 'big', 'small' ]
* },
* view: {
* big: {
* name: 'span',
* styles: {
* 'font-size': '1.2em'
* }
* },
* small: {
* name: 'span',
* styles: {
* 'font-size': '0.8em'
* }
* }
* }
* } );
*
* editor.conversion.for( 'downcast' ).attributeToElement( {
* model: 'bold',
* view: ( modelAttributeValue, conversionApi ) => {
* const { writer } = conversionApi;
*
* return writer.createAttributeElement( 'span', {
* style: 'font-weight:' + modelAttributeValue
* } );
* }
* } );
*
* editor.conversion.for( 'downcast' ).attributeToElement( {
* model: {
* key: 'color',
* name: '$text'
* },
* view: ( modelAttributeValue, conversionApi ) => {
* const { writer } = conversionApi;
*
* return writer.createAttributeElement( 'span', {
* style: 'color:' + modelAttributeValue
* } );
* }
* } );
*
* See {@link module:engine/conversion/conversion~Conversion#for `conversion.for()`} to learn how to add a converter
* to the conversion process.
*
* @method #attributeToElement
* @param {Object} config Conversion configuration.
* @param {String|Object} config.model The key of the attribute to convert from or a `{ key, values }` object. `values` is an array
* of `String`s with possible values if the model attribute is an enumerable.
* @param {module:engine/view/elementdefinition~ElementDefinition|Function|Object} config.view A view element definition or a function
* that takes the model attribute value and
* {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi downcast conversion API} as parameters and returns a view
* attribute element. If `config.model.values` is given, `config.view` should be an object assigning values from `config.model.values`
* to view element definitions or functions.
* @param {module:utils/priorities~PriorityString} [config.converterPriority='normal'] Converter priority.
* @returns {module:engine/conversion/downcasthelpers~DowncastHelpers}
*/
attributeToElement( config ) {
return this.add( downcastAttributeToElement( config ) );
}
/**
* Model attribute to view attribute conversion helper.
*
* This conversion results in adding an attribute to a view node, basing on an attribute from a model node. For example,
* `<imageInline src='foo.jpg'></imageInline>` is converted to `<img src='foo.jpg'></img>`.
*
* editor.conversion.for( 'downcast' ).attributeToAttribute( {
* model: 'source',
* view: 'src'
* } );
*
* editor.conversion.for( 'downcast' ).attributeToAttribute( {
* model: 'source',
* view: 'href',
* converterPriority: 'high'
* } );
*
* editor.conversion.for( 'downcast' ).attributeToAttribute( {
* model: {
* name: 'imageInline',
* key: 'source'
* },
* view: 'src'
* } );
*
* editor.conversion.for( 'downcast' ).attributeToAttribute( {
* model: {
* name: 'styled',
* values: [ 'dark', 'light' ]
* },
* view: {
* dark: {
* key: 'class',
* value: [ 'styled', 'styled-dark' ]
* },
* light: {
* key: 'class',
* value: [ 'styled', 'styled-light' ]
* }
* }
* } );
*
* editor.conversion.for( 'downcast' ).attributeToAttribute( {
* model: 'styled',
* view: modelAttributeValue => ( {
* key: 'class',
* value: 'styled-' + modelAttributeValue
* } )
* } );
*
* **Note**: Downcasting to a style property requires providing `value` as an object:
*
* editor.conversion.for( 'downcast' ).attributeToAttribute( {
* model: 'lineHeight',
* view: modelAttributeValue => ( {
* key: 'style',
* value: {
* 'line-height': modelAttributeValue,
* 'border-bottom': '1px dotted #ba2'
* }
* } )
* } );
*
* See {@link module:engine/conversion/conversion~Conversion#for `conversion.for()`} to learn how to add a converter
* to the conversion process.
*
* @method #attributeToAttribute
* @param {Object} config Conversion configuration.
* @param {String|Object} config.model The key of the attribute to convert from or a `{ key, values, [ name ] }` object describing
* the attribute key, possible values and, optionally, an element name to convert from.
* @param {String|Object|Function} config.view A view attribute key, or a `{ key, value }` object or a function that takes
* the model attribute value and {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi downcast conversion API}
* as parameters and returns a `{ key, value }` object. If the `key` is `'class'`, the `value` can be a `String` or an
* array of `String`s. If the `key` is `'style'`, the `value` is an object with key-value pairs. In other cases, `value` is a `String`.
* If `config.model.values` is set, `config.view` should be an object assigning values from `config.model.values` to
* `{ key, value }` objects or a functions.
* @param {module:utils/priorities~PriorityString} [config.converterPriority='normal'] Converter priority.
* @returns {module:engine/conversion/downcasthelpers~DowncastHelpers}
*/
attributeToAttribute( config ) {
return this.add( downcastAttributeToAttribute( config ) );
}
/**
* Model marker to view element conversion helper.
*
* **Note**: This method should be used mainly for editing the downcast and it is recommended
* to use the {@link #markerToData `#markerToData()`} helper instead.
*
* This helper may produce invalid HTML code (e.g. a span between table cells).
* It should only be used when you are sure that the produced HTML will be semantically correct.
*
* This conversion results in creating a view element on the boundaries of the converted marker. If the converted marker
* is collapsed, only one element is created. For example, a model marker set like this: `<paragraph>F[oo b]ar</paragraph>`
* becomes `<p>F<span data-marker="search"></span>oo b<span data-marker="search"></span>ar</p>` in the view.
*
* editor.conversion.for( 'editingDowncast' ).markerToElement( {
* model: 'search',
* view: 'marker-search'
* } );
*
* editor.conversion.for( 'editingDowncast' ).markerToElement( {
* model: 'search',
* view: 'search-result',
* converterPriority: 'high'
* } );
*
* editor.conversion.for( 'editingDowncast' ).markerToElement( {
* model: 'search',
* view: {
* name: 'span',
* attributes: {
* 'data-marker': 'search'
* }
* }
* } );
*
* editor.conversion.for( 'editingDowncast' ).markerToElement( {
* model: 'search',
* view: ( markerData, conversionApi ) => {
* const { writer } = conversionApi;
*
* return writer.createUIElement( 'span', {
* 'data-marker': 'search',
* 'data-start': markerData.isOpening
* } );
* }
* } );
*
* If a function is passed as the `config.view` parameter, it will be used to generate both boundary elements. The function
* receives the `data` object and {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi downcast conversion API}
* as a parameters and should return an instance of the
* {@link module:engine/view/uielement~UIElement view UI element}. The `data` object and
* {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi `conversionApi`} are passed from
* {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:addMarker}. Additionally,
* the `data.isOpening` parameter is passed, which is set to `true` for the marker start boundary element, and `false` for
* the marker end boundary element.
*
* See {@link module:engine/conversion/conversion~Conversion#for `conversion.for()`} to learn how to add a converter
* to the conversion process.
*
* @method #markerToElement
* @param {Object} config Conversion configuration.
* @param {String} config.model The name of the model marker (or model marker group) to convert.
* @param {module:engine/view/elementdefinition~ElementDefinition|Function} config.view A view element definition or a function that
* takes the model marker data and {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi downcast conversion API}
* as a parameters and returns a view UI element.
* @param {module:utils/priorities~PriorityString} [config.converterPriority='normal'] Converter priority.
* @returns {module:engine/conversion/downcasthelpers~DowncastHelpers}
*/
markerToElement( config ) {
return this.add( downcastMarkerToElement( config ) );
}
/**
* Model marker to highlight conversion helper.
*
* This conversion results in creating a highlight on view nodes. For this kind of conversion,
* the {@link module:engine/conversion/downcasthelpers~HighlightDescriptor} should be provided.
*
* For text nodes, a `<span>` {@link module:engine/view/attributeelement~AttributeElement} is created and it wraps all text nodes
* in the converted marker range. For example, a model marker set like this: `<paragraph>F[oo b]ar</paragraph>` becomes
* `<p>F<span class="comment">oo b</span>ar</p>` in the view.
*
* {@link module:engine/view/containerelement~ContainerElement} may provide a custom way of handling highlight. Most often,
* the element itself is given classes and attributes described in the highlight descriptor (instead of being wrapped in `<span>`).
* For example, a model marker set like this:
* `[<imageInline src="foo.jpg"></imageInline>]` becomes `<img src="foo.jpg" class="comment"></img>` in the view.
*
* For container elements, the conversion is two-step. While the converter processes the highlight descriptor and passes it
* to a container element, it is the container element instance itself that applies values from the highlight descriptor.
* So, in a sense, the converter takes care of stating what should be applied on what, while the element decides how to apply that.
*
* editor.conversion.for( 'downcast' ).markerToHighlight( { model: 'comment', view: { classes: 'comment' } } );
*
* editor.conversion.for( 'downcast' ).markerToHighlight( {
* model: 'comment',
* view: { classes: 'comment' },
* converterPriority: 'high'
* } );
*
* editor.conversion.for( 'downcast' ).markerToHighlight( {
* model: 'comment',
* view: ( data, conversionApi ) => {
* // Assuming that the marker name is in a form of comment:commentType:commentId.
* const [ , commentType, commentId ] = data.markerName.split( ':' );
*
* return {
* classes: [ 'comment', 'comment-' + commentType ],
* attributes: { 'data-comment-id': commentId }
* };
* }
* } );
*
* If a function is passed as the `config.view` parameter, it will be used to generate the highlight descriptor. The function
* receives the `data` object and {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi downcast conversion API}
* as the parameters and should return a
* {@link module:engine/conversion/downcasthelpers~HighlightDescriptor highlight descriptor}.
* The `data` object properties are passed from {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:addMarker}.
*
* See {@link module:engine/conversion/conversion~Conversion#for `conversion.for()`} to learn how to add a converter
* to the conversion process.
*
* @method #markerToHighlight
* @param {Object} config Conversion configuration.
* @param {String} config.model The name of the model marker (or model marker group) to convert.
* @param {module:engine/conversion/downcasthelpers~HighlightDescriptor|Function} config.view A highlight descriptor
* that will be used for highlighting or a function that takes the model marker data and
* {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi downcast conversion API} as a parameters
* and returns a highlight descriptor.
* @param {module:utils/priorities~PriorityString} [config.converterPriority='normal'] Converter priority.
* @returns {module:engine/conversion/downcasthelpers~DowncastHelpers}
*/
markerToHighlight( config ) {
return this.add( downcastMarkerToHighlight( config ) );
}
/**
* Model marker converter for data downcast.
*
* This conversion creates a representation for model marker boundaries in the view:
*
* * If the marker boundary is before or after a model element, a view attribute is set on a corresponding view element.
* * In other cases, a view element with the specified tag name is inserted at the corresponding view position.
*
* Typically, the marker names use the `group:uniqueId:otherData` convention. For example: `comment:e34zfk9k2n459df53sjl34:zx32c`.
* The default configuration for this conversion is that the first part is the `group` part and the rest of
* the marker name becomes the `name` part.
*
* Tag and attribute names and values are generated from the marker name:
*
* * The templates for attributes are `data-[group]-start-before="[name]"`, `data-[group]-start-after="[name]"`,
* `data-[group]-end-before="[name]"` and `data-[group]-end-after="[name]"`.
* * The templates for view elements are `<[group]-start name="[name]">` and `<[group]-end name="[name]">`.
*
* Attributes mark whether the given marker's start or end boundary is before or after the given element.
* The `data-[group]-start-before` and `data-[group]-end-after` attributes are favored.
* The other two are used when the former two cannot be used.
*
* The conversion configuration can take a function that will generate different group and name parts.
* If such a function is set as the `config.view` parameter, it is passed a marker name and it is expected to return an object with two
* properties: `group` and `name`. If the function returns a falsy value, the conversion will not take place.
*
* Basic usage:
*
* // Using the default conversion.
* // In this case, all markers with names starting with 'comment:' will be converted.
* // The `group` parameter will be set to `comment`.
* // The `name` parameter will be the rest of the marker name (without the `:`).
* editor.conversion.for( 'dataDowncast' ).markerToData( {
* model: 'comment'
* } );
*
* An example of a view that may be generated by this conversion (assuming a marker with the name `comment:commentId:uid` marked
* by `[]`):
*
* // Model:
* <paragraph>Foo[bar</paragraph>
* <imageBlock src="abc.jpg"></imageBlock>]
*
* // View:
* <p>Foo<comment-start name="commentId:uid"></comment-start>bar</p>
* <figure data-comment-end-after="commentId:uid" class="image"><img src="abc.jpg" /></figure>
*
* In the example above, the comment starts before "bar" and ends after the image.
*
* If the `name` part is empty, the following view may be generated:
*
* <p>Foo <myMarker-start></myMarker-start>bar</p>
* <figure data-myMarker-end-after="" class="image"><img src="abc.jpg" /></figure>
*
* **Note:** A situation where some markers have the `name` part and some do not, is incorrect and should be avoided.
*
* Examples where `data-group-start-after` and `data-group-end-before` are used:
*
* // Model:
* <blockQuote>[]<paragraph>Foo</paragraph></blockQuote>
*
* // View:
* <blockquote><p data-group-end-before="name" data-group-start-before="name">Foo</p></blockquote>
*
* Similarly, when a marker is collapsed after the last element:
*
* // Model:
* <blockQuote><paragraph>Foo</paragraph>[]</blockQuote>
*
* // View:
* <blockquote><p data-group-end-after="name" data-group-start-after="name">Foo</p></blockquote>
*
* When there are multiple markers from the same group stored in the same attribute of the same element, their
* name parts are put together in the attribute value, for example: `data-group-start-before="name1,name2,name3"`.
*
* Other examples of usage:
*
* // Using a custom function which is the same as the default conversion:
* editor.conversion.for( 'dataDowncast' ).markerToData( {
* model: 'comment'
* view: markerName => ( {
* group: 'comment',
* name: markerName.substr( 8 ) // Removes 'comment:' part.
* } )
* } );
*
* // Using the converter priority:
* editor.conversion.for( 'dataDowncast' ).markerToData( {
* model: 'comment'
* view: markerName => ( {
* group: 'comment',
* name: markerName.substr( 8 ) // Removes 'comment:' part.
* } ),
* converterPriority: 'high'
* } );
*
* This kind of conversion is useful for saving data into the database, so it should be used in the data conversion pipeline.
*
* See the {@link module:engine/conversion/conversion~Conversion#for `conversion.for()`} API guide to learn how to
* add a converter to the conversion process.
*
* @method #markerToData
* @param {Object} config Conversion configuration.
* @param {String} config.model The name of the model marker (or the model marker group) to convert.
* @param {Function} [config.view] A function that takes the model marker name and
* {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi downcast conversion API} as the parameters
* and returns an object with the `group` and `name` properties.
* @param {module:utils/priorities~PriorityString} [config.converterPriority='normal'] Converter priority.
* @returns {module:engine/conversion/downcasthelpers~DowncastHelpers}
*/
markerToData( config ) {
return this.add( downcastMarkerToData( config ) );
}
}
/**
* Function factory that creates a default downcast converter for text insertion changes.
*
* The converter automatically consumes the corresponding value from the consumables list and stops the event (see
* {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher}).
*
* modelDispatcher.on( 'insert:$text', insertText() );
*
* @returns {Function} Insert text event converter.
*/
function insertText() {
return ( evt, data, conversionApi ) => {
if ( !conversionApi.consumable.consume( data.item, evt.name ) ) {
return;
}
const viewWriter = conversionApi.writer;
const viewPosition = conversionApi.mapper.toViewPosition( data.range.start );
const viewText = viewWriter.createText( data.item.data );
viewWriter.insert( viewPosition, viewText );
};
}
/**
* Function factory that creates a default downcast converter for triggering attributes and children conversion.
*
* @returns {Function} The converter.
*/
function insertAttributesAndChildren() {
return ( evt, data, conversionApi ) => {
conversionApi.convertAttributes( data.item );
// Start converting children of the current item.
// In case of reconversion children were already re-inserted or converted separately.
if ( !data.reconversion && data.item.is( 'element' ) && !data.item.isEmpty ) {
conversionApi.convertChildren( data.item );
}
};
}
/**
* Function factory that creates a default downcast converter for node remove changes.
*
* modelDispatcher.on( 'remove', remove() );
*
* @returns {Function} Remove event converter.
*/
function remove() {
return ( evt, data, conversionApi ) => {
// Find the view range start position by mapping the model position at which the remove happened.
const viewStart = conversionApi.mapper.toViewPosition( data.position );
const modelEnd = data.position.getShiftedBy( data.length );
const viewEnd = conversionApi.mapper.toViewPosition( modelEnd, { isPhantom: true } );
const viewRange = conversionApi.writer.createRange( viewStart, viewEnd );
// Trim the range to remove in case some UI elements are on the view range boundaries.
const removed = conversionApi.writer.remove( viewRange.getTrimmed() );
// After the range is removed, unbind all view elements from the model.
// Range inside view document fragment is used to unbind deeply.
for ( const child of conversionApi.writer.createRangeIn( removed ).getItems() ) {
conversionApi.mapper.unbindViewElement( child, { defer: true } );
}
};
}
/**
* Creates a `<span>` {@link module:engine/view/attributeelement~AttributeElement view attribute element} from the information
* provided by the {@link module:engine/conversion/downcasthelpers~HighlightDescriptor highlight descriptor} object. If the priority
* is not provided in the descriptor, the default priority will be used.
*
* @param {module:engine/view/downcastwriter~DowncastWriter} writer
* @param {module:engine/conversion/downcasthelpers~HighlightDescriptor} descriptor
* @returns {module:engine/view/attributeelement~AttributeElement}
*/
function createViewElementFromHighlightDescriptor( writer, descriptor ) {
const viewElement = writer.createAttributeElement( 'span', descriptor.attributes );
if ( descriptor.classes ) {
viewElement._addClass( descriptor.classes );
}
if ( typeof descriptor.priority === 'number' ) {
viewElement._priority = descriptor.priority;
}
viewElement._id = descriptor.id;
return viewElement;
}
/**
* Function factory that creates a converter which converts a non-collapsed {@link module:engine/model/selection~Selection model selection}
* to a {@link module:engine/view/documentselection~DocumentSelection view selection}. The converter consumes appropriate
* value from the `consumable` object and maps model positions from the selection to view positions.
*
* modelDispatcher.on( 'selection', convertRangeSelection() );
*
* @returns {Function} Selection converter.
*/
function convertRangeSelection() {
return ( evt, data, conversionApi ) => {
const selection = data.selection;
if ( selection.isCollapsed ) {
return;
}
if ( !conversionApi.consumable.consume( selection, 'selection' ) ) {
return;
}
const viewRanges = [];
for ( const range of selection.getRanges() ) {
const viewRange = conversionApi.mapper.toViewRange( range );
viewRanges.push( viewRange );
}
conversionApi.writer.setSelection( viewRanges, { backward: selection.isBackward } );
};
}
/**
* Function factory that creates a converter which converts a collapsed {@link module:engine/model/selection~Selection model selection} to
* a {@link module:engine/view/documentselection~DocumentSelection view selection}. The converter consumes appropriate
* value from the `consumable` object, maps the model selection position to the view position and breaks
* {@link module:engine/view/attributeelement~AttributeElement attribute elements} at the selection position.
*
* modelDispatcher.on( 'selection', convertCollapsedSelection() );
*
* An example of the view state before and after converting the collapsed selection:
*
* <p><strong>f^oo<strong>bar</p>
* -> <p><strong>f</strong>^<strong>oo</strong>bar</p>
*
* By breaking attribute elements like `<strong>`, the selection is in a correct element. Then, when the selection attribute is
* converted, broken attributes might be merged again, or the position where the selection is may be wrapped
* with different, appropriate attribute elements.
*
* See also {@link module:engine/conversion/downcasthelpers~clearAttributes} which does a clean-up
* by merging attributes.
*
* @returns {Function} Selection converter.
*/
function convertCollapsedSelection() {
return ( evt, data, conversionApi ) => {
const selection = data.selection;
if ( !selection.isCollapsed ) {
return;
}
if ( !conversionApi.consumable.consume( selection, 'selection' ) ) {
return;
}
const viewWriter = conversionApi.writer;
const modelPosition = selection.getFirstPosition();
const viewPosition = conversionApi.mapper.toViewPosition( modelPosition );
const brokenPosition = viewWriter.breakAttributes( viewPosition );
viewWriter.setSelection( brokenPosition );
};
}
/**
* Function factory that creates a converter which clears artifacts after the previous
* {@link module:engine/model/selection~Selection model selection} conversion. It removes all empty
* {@link module:engine/view/attributeelement~AttributeElement view attribute elements} and merges sibling attributes at all start and end
* positions of all ranges.
*
* <p><strong>^</strong></p>
* -> <p>^</p>
*
* <p><strong>foo</strong>^<strong>bar</strong>bar</p>
* -> <p><strong>foo^bar<strong>bar</p>
*
* <p><strong>foo</strong><em>^</em><strong>bar</strong>bar</p>
* -> <p><strong>foo^bar<strong>bar</p>
*
* This listener should be assigned before any converter for the new selection:
*
* modelDispatcher.on( 'selection', clearAttributes() );
*
* See {@link module:engine/conversion/downcasthelpers~convertCollapsedSelection}
* which does the opposite by breaking attributes in the selection position.
*
* @returns {Function} Selection converter.
*/
function clearAttributes() {
return ( evt, data, conversionApi ) => {
const viewWriter = conversionApi.writer;
const viewSelection = viewWriter.document.selection;
for ( const range of viewSelection.getRanges() ) {
// Not collapsed selection should not have artifacts.
if ( range.isCollapsed ) {
// Position might be in the node removed by the view writer.
if ( range.end.parent.isAttached() ) {
conversionApi.writer.mergeAttributes( range.start );
}
}
}
viewWriter.setSelection( null );
};
}
/**
* Function factory that creates a converter which converts the set/change/remove attribute changes from the model to the view.
* It can also be used to convert selection attributes. In that case, an empty attribute element will be created and the
* selection will be put inside it.
*
* Attributes from the model are converted to a view element that will be wrapping these view nodes that are bound to
* model elements having the given attribute. This is useful for attributes like `bold` that may be set on text nodes in the model
* but are represented as an element in the view:
*
* [paragraph] MODEL ====> VIEW <p>
* |- a {bold: true} |- <b>
* |- b {bold: true} | |- ab
* |- c |- c
*
* Passed `Function` will be provided with the attribute value and then all the parameters of the
* {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:attribute `attribute` event}.
* It is expected that the function returns an {@link module:engine/view/element~Element}.
* The result of the function will be the wrapping element.
* When the provided `Function` does not return any element, no conversion will take place.
*
* The converter automatically consumes the corresponding value from the consumables list and stops the event (see
* {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher}).
*
* modelDispatcher.on( 'attribute:bold', wrap( ( modelAttributeValue, { writer } ) => {
* return writer.createAttributeElement( 'strong' );
* } );
*
* @protected
* @param {Function} elementCreator Function returning a view element that will be used for wrapping.
* @returns {Function} Set/change attribute converter.
*/
function wrap( elementCreator ) {
return ( evt, data, conversionApi ) => {
if ( !conversionApi.consumable.test( data.item, evt.name ) ) {
return;
}
// Recreate current wrapping node. It will be used to unwrap view range if the attribute value has changed
// or the attribute was removed.
const oldViewElement = elementCreator( data.attributeOldValue, conversionApi );
// Create node to wrap with.
const newViewElement = elementCreator( data.attributeNewValue, conversionApi );
if ( !oldViewElement && !newViewElement ) {
return;
}
conversionApi.consumable.consume( data.item, evt.name );
const viewWriter = conversionApi.writer;
const viewSelection = viewWriter.document.selection;
if ( data.item instanceof _model_selection__WEBPACK_IMPORTED_MODULE_1__["default"] || data.item instanceof _model_documentselection__WEBPACK_IMPORTED_MODULE_5__["default"] ) {
// Selection attribute conversion.
viewWriter.wrap( viewSelection.getFirstRange(), newViewElement );
} else {
// Node attribute conversion.
let viewRange = conversionApi.mapper.toViewRange( data.range );
// First, unwrap the range from current wrapper.
if ( data.attributeOldValue !== null && oldViewElement ) {
viewRange = viewWriter.unwrap( viewRange, oldViewElement );
}
if ( data.attributeNewValue !== null && newViewElement ) {
viewWriter.wrap( viewRange, newViewElement );
}
}
};
}
/**
* Function factory that creates a converter which converts node insertion changes from the model to the view.
* The function passed will be provided with all the parameters of the dispatcher's
* {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:insert `insert` event}.
* It is expected that the function returns an {@link module:engine/view/element~Element}.
* The result of the function will be inserted into the view.
*
* The converter automatically consumes the corresponding value from the consumables list and binds the model and view elements.
*
* downcastDispatcher.on(
* 'insert:myElem',
* insertElement( ( modelItem, { writer } ) => {
* const text = writer.createText( 'myText' );
* const myElem = writer.createElement( 'myElem', { myAttr: 'my-' + modelItem.getAttribute( 'myAttr' ) }, text );
*
* // Do something fancy with `myElem` using `modelItem` or other parameters.
*
* return myElem;
* }
* ) );
*
* @protected
* @param {Function} elementCreator Function returning a view element, which will be inserted.
* @param {module:engine/conversion/downcasthelpers~ConsumerFunction} [consumer] Function defining element consumption process.
* By default this function just consume passed item insertion.
* @returns {Function} Insert element event converter.
*/
function insertElement( elementCreator, consumer = defaultConsumer ) {
return ( evt, data, conversionApi ) => {
if ( !consumer( data.item, conversionApi.consumable, { preflight: true } ) ) {
return;
}
const viewElement = elementCreator( data.item, conversionApi );
if ( !viewElement ) {
return;
}
// Consume an element insertion and all present attributes that are specified as a reconversion triggers.
consumer( data.item, conversionApi.consumable );
const viewPosition = conversionApi.mapper.toViewPosition( data.range.start );
conversionApi.mapper.bindElements( data.item, viewElement );
conversionApi.writer.insert( viewPosition, viewElement );
// Convert attributes before converting children.
conversionApi.convertAttributes( data.item );
// Convert children or reinsert previous view elements.
reinsertOrConvertNodes( viewElement, data.item.getChildren(), conversionApi, { reconversion: data.reconversion } );
};
}
/**
* Function factory that creates a converter which converts a single model node insertion to a view structure.
*
* It is expected that the passed element creator function returns an {@link module:engine/view/element~Element} with attached slots
* created with `writer.createSlot()` to indicate where child nodes should be converted.
*
* @see module:engine/conversion/downcasthelpers~DowncastHelpers#elementToStructure
*
* @protected
* @param {module:engine/conversion/downcasthelpers~StructureCreatorFunction} elementCreator Function returning a view structure,
* which will be inserted.
* @param {module:engine/conversion/downcasthelpers~ConsumerFunction} consumer A callback that is expected to consume all the consumables
* that were used by the element creator.
* @returns {Function} Insert element event converter.
*/
function insertStructure( elementCreator, consumer ) {
return ( evt, data, conversionApi ) => {
if ( !consumer( data.item, conversionApi.consumable, { preflight: true } ) ) {
return;
}
const slotsMap = new Map();
conversionApi.writer._registerSlotFactory( createSlotFactory( data.item, slotsMap, conversionApi ) );
// View creation.
const viewElement = elementCreator( data.item, conversionApi );
conversionApi.writer._clearSlotFactory();
if ( !viewElement ) {
return;
}
// Check if all children are covered by slots and there is no child that landed in multiple slots.
validateSlotsChildren( data.item, slotsMap, conversionApi );
// Consume an element insertion and all present attributes that are specified as a reconversion triggers.
consumer( data.item, conversionApi.consumable );
const viewPosition = conversionApi.mapper.toViewPosition( data.range.start );
conversionApi.mapper.bindElements( data.item, viewElement );
conversionApi.writer.insert( viewPosition, viewElement );
// Convert attributes before converting children.
conversionApi.convertAttributes( data.item );
// Fill view slots with previous view elements or create new ones.
fillSlots( viewElement, slotsMap, conversionApi, { reconversion: data.reconversion } );
};
}
/**
* Function factory that creates a converter which converts marker adding change to the
* {@link module:engine/view/uielement~UIElement view UI element}.
*
* The view UI element that will be added to the view depends on the passed parameter. See {@link ~insertElement}.
* In case of a non-collapsed range, the UI element will not wrap nodes but separate elements will be placed at the beginning
* and at the end of the range.
*
* This converter binds created UI elements with the marker name using {@link module:engine/conversion/mapper~Mapper#bindElementToMarker}.
*
* @protected
* @param {module:engine/view/uielement~UIElement|Function} elementCreator A view UI element or a function returning the view element
* that will be inserted.
* @returns {Function} Insert element event converter.
*/
function insertUIElement( elementCreator ) {
return ( evt, data, conversionApi ) => {
// Create two view elements. One will be inserted at the beginning of marker, one at the end.
// If marker is collapsed, only "opening" element will be inserted.
data.isOpening = true;
const viewStartElement = elementCreator( data, conversionApi );
data.isOpening = false;
const viewEndElement = elementCreator( data, conversionApi );
if ( !viewStartElement || !viewEndElement ) {
return;
}
const markerRange = data.markerRange;
// Marker that is collapsed has consumable build differently that non-collapsed one.
// For more information see `addMarker` event description.
// If marker's range is collapsed - check if it can be consumed.
if ( markerRange.isCollapsed && !conversionApi.consumable.consume( markerRange, evt.name ) ) {
return;
}
// If marker's range is not collapsed - consume all items inside.
for ( const value of markerRange ) {
if ( !conversionApi.consumable.consume( value.item, evt.name ) ) {
return;
}
}
const mapper = conversionApi.mapper;
const viewWriter = conversionApi.writer;
// Add "opening" element.
viewWriter.insert( mapper.toViewPosition( markerRange.start ), viewStartElement );
conversionApi.mapper.bindElementToMarker( viewStartElement, data.markerName );
// Add "closing" element only if range is not collapsed.
if ( !markerRange.isCollapsed ) {
viewWriter.insert( mapper.toViewPosition( markerRange.end ), viewEndElement );
conversionApi.mapper.bindElementToMarker( viewEndElement, data.markerName );
}
evt.stop();
};
}
// Function factory that returns a default downcast converter for removing a {@link module:engine/view/uielement~UIElement UI element}
// based on marker remove change.
//
// This converter unbinds elements from the marker name.
//
// @returns {Function} Removed UI element converter.
function removeUIElement() {
return ( evt, data, conversionApi ) => {
const elements = conversionApi.mapper.markerNameToElements( data.markerName );
if ( !elements ) {
return;
}
for ( const element of elements ) {
conversionApi.mapper.unbindElementFromMarkerName( element, data.markerName );
conversionApi.writer.clear( conversionApi.writer.createRangeOn( element ), element );
}
conversionApi.writer.clearClonedElementsGroup( data.markerName );
evt.stop();
};
}
// Function factory that creates a default converter for model markers.
//
// See {@link DowncastHelpers#markerToData} for more information what type of view is generated.
//
// This converter binds created UI elements and affected view elements with the marker name
// using {@link module:engine/conversion/mapper~Mapper#bindElementToMarker}.
//
// @returns {Function} Add marker converter.
function insertMarkerData( viewCreator ) {
return ( evt, data, conversionApi ) => {
const viewMarkerData = viewCreator( data.markerName, conversionApi );
if ( !viewMarkerData ) {
return;
}
const markerRange = data.markerRange;
if ( !conversionApi.consumable.consume( markerRange, evt.name ) ) {
return;
}
// Adding closing data first to keep the proper order in the view.
handleMarkerBoundary( markerRange, false, conversionApi, data, viewMarkerData );
handleMarkerBoundary( markerRange, true, conversionApi, data, viewMarkerData );
evt.stop();
};
}
// Helper function for `insertMarkerData()` that marks a marker boundary at the beginning or end of given `range`.
function handleMarkerBoundary( range, isStart, conversionApi, data, viewMarkerData ) {
const modelPosition = isStart ? range.start : range.end;
const elementAfter = modelPosition.nodeAfter && modelPosition.nodeAfter.is( 'element' ) ? modelPosition.nodeAfter : null;
const elementBefore = modelPosition.nodeBefore && modelPosition.nodeBefore.is( 'element' ) ? modelPosition.nodeBefore : null;
if ( elementAfter || elementBefore ) {
let modelElement;
let isBefore;
// If possible, we want to add `data-group-start-before` and `data-group-end-after` attributes.
if ( isStart && elementAfter || !isStart && !elementBefore ) {
// [<elementAfter>...</elementAfter> -> <elementAfter data-group-start-before="...">...</elementAfter>
// <parent>]<elementAfter> -> <parent><elementAfter data-group-end-before="...">
modelElement = elementAfter;
isBefore = true;
} else {
// <elementBefore>...</elementBefore>] -> <elementBefore data-group-end-after="...">...</elementBefore>
// </elementBefore>[</parent> -> </elementBefore data-group-start-after="..."></parent>
modelElement = elementBefore;
isBefore = false;
}
const viewElement = conversionApi.mapper.toViewElement( modelElement );
// In rare circumstances, the model element may be not mapped to any view element and that would cause an error.
// One of those situations is a soft break inside code block.
if ( viewElement ) {
insertMarkerAsAttribute( viewElement, isStart, isBefore, conversionApi, data, viewMarkerData );
return;
}
}
const viewPosition = conversionApi.mapper.toViewPosition( modelPosition );
insertMarkerAsElement( viewPosition, isStart, conversionApi, data, viewMarkerData );
}
// Helper function for `insertMarkerData()` that marks a marker boundary in the view as an attribute on a view element.
function insertMarkerAsAttribute( viewElement, isStart, isBefore, conversionApi, data, viewMarkerData ) {
const attributeName = `data-${ viewMarkerData.group }-${ isStart ? 'start' : 'end' }-${ isBefore ? 'before' : 'after' }`;
const markerNames = viewElement.hasAttribute( attributeName ) ? viewElement.getAttribute( attributeName ).split( ',' ) : [];
// Adding marker name at the beginning to have the same order in the attribute as there is with marker elements.
markerNames.unshift( viewMarkerData.name );
conversionApi.writer.setAttribute( attributeName, markerNames.join( ',' ), viewElement );
conversionApi.mapper.bindElementToMarker( viewElement, data.markerName );
}
// Helper function for `insertMarkerData()` that marks a marker boundary in the view as a separate view ui element.
function insertMarkerAsElement( position, isStart, conversionApi, data, viewMarkerData ) {
const viewElementName = `${ viewMarkerData.group }-${ isStart ? 'start' : 'end' }`;
const attrs = viewMarkerData.name ? { 'name': viewMarkerData.name } : null;
const viewElement = conversionApi.writer.createUIElement( viewElementName, attrs );
conversionApi.writer.insert( position, viewElement );
conversionApi.mapper.bindElementToMarker( viewElement, data.markerName );
}
// Function factory that creates a converter for removing a model marker data added by the {@link #insertMarkerData} converter.
//
// @returns {Function} Remove marker converter.
function removeMarkerData( viewCreator ) {
return ( evt, data, conversionApi ) => {
const viewData = viewCreator( data.markerName, conversionApi );
if ( !viewData ) {
return;
}
const elements = conversionApi.mapper.markerNameToElements( data.markerName );
if ( !elements ) {
return;
}
for ( const element of elements ) {
conversionApi.mapper.unbindElementFromMarkerName( element, data.markerName );
if ( element.is( 'containerElement' ) ) {
removeMarkerFromAttribute( `data-${ viewData.group }-start-before`, element );
removeMarkerFromAttribute( `data-${ viewData.group }-start-after`, element );
removeMarkerFromAttribute( `data-${ viewData.group }-end-before`, element );
removeMarkerFromAttribute( `data-${ viewData.group }-end-after`, element );
} else {
conversionApi.writer.clear( conversionApi.writer.createRangeOn( element ), element );
}
}
conversionApi.writer.clearClonedElementsGroup( data.markerName );
evt.stop();
function removeMarkerFromAttribute( attributeName, element ) {
if ( element.hasAttribute( attributeName ) ) {
const markerNames = new Set( element.getAttribute( attributeName ).split( ',' ) );
markerNames.delete( viewData.name );
if ( markerNames.size == 0 ) {
conversionApi.writer.removeAttribute( attributeName, element );
} else {
conversionApi.writer.setAttribute( attributeName, Array.from( markerNames ).join( ',' ), element );
}
}
}
};
}
// Function factory that creates a converter which converts the set/change/remove attribute changes from the model to the view.
//
// Attributes from the model are converted to the view element attributes in the view. You may provide a custom function to generate
// a key-value attribute pair to add/change/remove. If not provided, model attributes will be converted to view element
// attributes on a one-to-one basis.
//
// *Note:** The provided attribute creator should always return the same `key` for a given attribute from the model.
//
// The converter automatically consumes the corresponding value from the consumables list and stops the event (see
// {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher}).
//
// modelDispatcher.on( 'attribute:customAttr:myElem', changeAttribute( ( value, data ) => {
// // Change attribute key from `customAttr` to `class` in the view.
// const key = 'class';
// let value = data.attributeNewValue;
//
// // Force attribute value to 'empty' if the model element is empty.
// if ( data.item.childCount === 0 ) {
// value = 'empty';
// }
//
// // Return the key-value pair.
// return { key, value };
// } ) );
//
// @param {Function} [attributeCreator] Function returning an object with two properties: `key` and `value`, which
// represent the attribute key and attribute value to be set on a {@link module:engine/view/element~Element view element}.
// The function is passed the model attribute value as the first parameter and additional data about the change as the second parameter.
// @returns {Function} Set/change attribute converter.
function changeAttribute( attributeCreator ) {
return ( evt, data, conversionApi ) => {
if ( !conversionApi.consumable.test( data.item, evt.name ) ) {
return;
}
const oldAttribute = attributeCreator( data.attributeOldValue, conversionApi );
const newAttribute = attributeCreator( data.attributeNewValue, conversionApi );
if ( !oldAttribute && !newAttribute ) {
return;
}
conversionApi.consumable.consume( data.item, evt.name );
const viewElement = conversionApi.mapper.toViewElement( data.item );
const viewWriter = conversionApi.writer;
// If model item cannot be mapped to a view element, it means item is not an `Element` instance but a `TextProxy` node.
// Only elements can have attributes in a view so do not proceed for anything else (#1587).
if ( !viewElement ) {
/**
* This error occurs when a {@link module:engine/model/textproxy~TextProxy text node's} attribute is to be downcasted
* by an {@link module:engine/conversion/conversion~Conversion#attributeToAttribute `Attribute to Attribute converter`}.
* In most cases it is caused by converters misconfiguration when only "generic" converter is defined:
*
* editor.conversion.for( 'downcast' ).attributeToAttribute( {
* model: 'attribute-name',
* view: 'attribute-name'
* } ) );
*
* and given attribute is used on text node, for example:
*
* model.change( writer => {
* writer.insertText( 'Foo', { 'attribute-name': 'bar' }, parent, 0 );
* } );
*
* In such cases, to convert the same attribute for both {@link module:engine/model/element~Element}
* and {@link module:engine/model/textproxy~TextProxy `Text`} nodes, text specific
* {@link module:engine/conversion/conversion~Conversion#attributeToElement `Attribute to Element converter`}
* with higher {@link module:utils/priorities~PriorityString priority} must also be defined:
*
* editor.conversion.for( 'downcast' ).attributeToElement( {
* model: {
* key: 'attribute-name',
* name: '$text'
* },
* view: ( value, { writer } ) => {
* return writer.createAttributeElement( 'span', { 'attribute-name': value } );
* },
* converterPriority: 'high'
* } ) );
*
* @error conversion-attribute-to-attribute-on-text
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_7__["default"]( 'conversion-attribute-to-attribute-on-text', conversionApi.dispatcher, data );
}
// First remove the old attribute if there was one.
if ( data.attributeOldValue !== null && oldAttribute ) {
if ( oldAttribute.key == 'class' ) {
const classes = (0,_ckeditor_ckeditor5_utils_src_toarray__WEBPACK_IMPORTED_MODULE_8__["default"])( oldAttribute.value );
for ( const className of classes ) {
viewWriter.removeClass( className, viewElement );
}
} else if ( oldAttribute.key == 'style' ) {
const keys = Object.keys( oldAttribute.value );
for ( const key of keys ) {
viewWriter.removeStyle( key, viewElement );
}
} else {
viewWriter.removeAttribute( oldAttribute.key, viewElement );
}
}
// Then set the new attribute.
if ( data.attributeNewValue !== null && newAttribute ) {
if ( newAttribute.key == 'class' ) {
const classes = (0,_ckeditor_ckeditor5_utils_src_toarray__WEBPACK_IMPORTED_MODULE_8__["default"])( newAttribute.value );
for ( const className of classes ) {
viewWriter.addClass( className, viewElement );
}
} else if ( newAttribute.key == 'style' ) {
const keys = Object.keys( newAttribute.value );
for ( const key of keys ) {
viewWriter.setStyle( key, newAttribute.value[ key ], viewElement );
}
} else {
viewWriter.setAttribute( newAttribute.key, newAttribute.value, viewElement );
}
}
};
}
// Function factory that creates a converter which converts the text inside marker's range. The converter wraps the text with
// {@link module:engine/view/attributeelement~AttributeElement} created from the provided descriptor.
// See {link module:engine/conversion/downcasthelpers~createViewElementFromHighlightDescriptor}.
//
// It can also be used to convert the selection that is inside a marker. In that case, an empty attribute element will be
// created and the selection will be put inside it.
//
// If the highlight descriptor does not provide the `priority` property, `10` will be used.
//
// If the highlight descriptor does not provide the `id` property, the name of the marker will be used.
//
// This converter binds the created {@link module:engine/view/attributeelement~AttributeElement attribute elemens} with the marker name
// using the {@link module:engine/conversion/mapper~Mapper#bindElementToMarker} method.
//
// @param {module:engine/conversion/downcasthelpers~HighlightDescriptor|Function} highlightDescriptor
// @returns {Function}
function highlightText( highlightDescriptor ) {
return ( evt, data, conversionApi ) => {
if ( !data.item ) {
return;
}
if ( !( data.item instanceof _model_selection__WEBPACK_IMPORTED_MODULE_1__["default"] || data.item instanceof _model_documentselection__WEBPACK_IMPORTED_MODULE_5__["default"] ) && !data.item.is( '$textProxy' ) ) {
return;
}
const descriptor = prepareDescriptor( highlightDescriptor, data, conversionApi );
if ( !descriptor ) {
return;
}
if ( !conversionApi.consumable.consume( data.item, evt.name ) ) {
return;
}
const viewWriter = conversionApi.writer;
const viewElement = createViewElementFromHighlightDescriptor( viewWriter, descriptor );
const viewSelection = viewWriter.document.selection;
if ( data.item instanceof _model_selection__WEBPACK_IMPORTED_MODULE_1__["default"] || data.item instanceof _model_documentselection__WEBPACK_IMPORTED_MODULE_5__["default"] ) {
viewWriter.wrap( viewSelection.getFirstRange(), viewElement, viewSelection );
} else {
const viewRange = conversionApi.mapper.toViewRange( data.range );
const rangeAfterWrap = viewWriter.wrap( viewRange, viewElement );
for ( const element of rangeAfterWrap.getItems() ) {
if ( element.is( 'attributeElement' ) && element.isSimilar( viewElement ) ) {
conversionApi.mapper.bindElementToMarker( element, data.markerName );
// One attribute element is enough, because all of them are bound together by the view writer.
// Mapper uses this binding to get all the elements no matter how many of them are registered in the mapper.
break;
}
}
}
};
}
// Converter function factory. It creates a function which applies the marker's highlight to an element inside the marker's range.
//
// The converter checks if an element has the `addHighlight` function stored as a
// {@link module:engine/view/element~Element#_setCustomProperty custom property} and, if so, uses it to apply the highlight.
// In such case the converter will consume all element's children, assuming that they were handled by the element itself.
//
// When the `addHighlight` custom property is not present, the element is not converted in any special way.
// This means that converters will proceed to convert the element's child nodes.
//
// If the highlight descriptor does not provide the `priority` property, `10` will be used.
//
// If the highlight descriptor does not provide the `id` property, the name of the marker will be used.
//
// This converter binds altered {@link module:engine/view/containerelement~ContainerElement container elements} with the marker name using
// the {@link module:engine/conversion/mapper~Mapper#bindElementToMarker} method.
//
// @param {module:engine/conversion/downcasthelpers~HighlightDescriptor|Function} highlightDescriptor
// @returns {Function}
function highlightElement( highlightDescriptor ) {
return ( evt, data, conversionApi ) => {
if ( !data.item ) {
return;
}
if ( !( data.item instanceof _model_element__WEBPACK_IMPORTED_MODULE_2__["default"] ) ) {
return;
}
const descriptor = prepareDescriptor( highlightDescriptor, data, conversionApi );
if ( !descriptor ) {
return;
}
if ( !conversionApi.consumable.test( data.item, evt.name ) ) {
return;
}
const viewElement = conversionApi.mapper.toViewElement( data.item );
if ( viewElement && viewElement.getCustomProperty( 'addHighlight' ) ) {
// Consume element itself.
conversionApi.consumable.consume( data.item, evt.name );
// Consume all children nodes.
for ( const value of _model_range__WEBPACK_IMPORTED_MODULE_0__["default"]._createIn( data.item ) ) {
conversionApi.consumable.consume( value.item, evt.name );
}
viewElement.getCustomProperty( 'addHighlight' )( viewElement, descriptor, conversionApi.writer );
conversionApi.mapper.bindElementToMarker( viewElement, data.markerName );
}
};
}
// Function factory that creates a converter which converts the removing model marker to the view.
//
// Both text nodes and elements are handled by this converter but they are handled a bit differently.
//
// Text nodes are unwrapped using the {@link module:engine/view/attributeelement~AttributeElement attribute element} created from the
// provided highlight descriptor. See {link module:engine/conversion/downcasthelpers~HighlightDescriptor}.
//
// For elements, the converter checks if an element has the `removeHighlight` function stored as a
// {@link module:engine/view/element~Element#_setCustomProperty custom property}. If so, it uses it to remove the highlight.
// In such case, the children of that element will not be converted.
//
// When `removeHighlight` is not present, the element is not converted in any special way.
// The converter will proceed to convert the element's child nodes instead.
//
// If the highlight descriptor does not provide the `priority` property, `10` will be used.
//
// If the highlight descriptor does not provide the `id` property, the name of the marker will be used.
//
// This converter unbinds elements from the marker name.
//
// @param {module:engine/conversion/downcasthelpers~HighlightDescriptor|Function} highlightDescriptor
// @returns {Function}
function removeHighlight( highlightDescriptor ) {
return ( evt, data, conversionApi ) => {
// This conversion makes sense only for non-collapsed range.
if ( data.markerRange.isCollapsed ) {
return;
}
const descriptor = prepareDescriptor( highlightDescriptor, data, conversionApi );
if ( !descriptor ) {
return;
}
// View element that will be used to unwrap `AttributeElement`s.
const viewHighlightElement = createViewElementFromHighlightDescriptor( conversionApi.writer, descriptor );
// Get all elements bound with given marker name.
const elements = conversionApi.mapper.markerNameToElements( data.markerName );
if ( !elements ) {
return;
}
for ( const element of elements ) {
conversionApi.mapper.unbindElementFromMarkerName( element, data.markerName );
if ( element.is( 'attributeElement' ) ) {
conversionApi.writer.unwrap( conversionApi.writer.createRangeOn( element ), viewHighlightElement );
} else {
// if element.is( 'containerElement' ).
element.getCustomProperty( 'removeHighlight' )( element, descriptor.id, conversionApi.writer );
}
}
conversionApi.writer.clearClonedElementsGroup( data.markerName );
evt.stop();
};
}
// Model element to view element conversion helper.
//
// See {@link ~DowncastHelpers#elementToElement `.elementToElement()` downcast helper} for examples and config params description.
//
// @param {Object} config Conversion configuration.
// @param {String|Object} config.model The description or a name of the model element to convert.
// @param {String|Array.<String>} [config.model.attributes] List of attributes triggering element reconversion.
// @param {Boolean} [config.model.children] Should reconvert element if the list of model child nodes changed.
// @param {module:engine/view/elementdefinition~ElementDefinition|Function} config.view
// @returns {Function} Conversion helper.
function downcastElementToElement( config ) {
config = (0,lodash_es__WEBPACK_IMPORTED_MODULE_9__["default"])( config );
config.model = normalizeModelElementConfig( config.model );
config.view = normalizeToElementConfig( config.view, 'container' );
// Trigger reconversion on children list change if element is a subject to any reconversion.
// This is required to be able to trigger Differ#refreshItem() on a direct child of the reconverted element.
if ( config.model.attributes.length ) {
config.model.children = true;
}
return dispatcher => {
dispatcher.on(
'insert:' + config.model.name,
insertElement( config.view, createConsumer( config.model ) ),
{ priority: config.converterPriority || 'normal' }
);
if ( config.model.children || config.model.attributes.length ) {
dispatcher.on( 'reduceChanges', createChangeReducer( config.model ), { priority: 'low' } );
}
};
}
// Model element to view structure conversion helper.
//
// See {@link ~DowncastHelpers#elementToStructure `.elementToStructure()` downcast helper} for examples and config params description.
//
// @param {Object} config Conversion configuration.
// @param {String|Object} config.model
// @param {String} [config.model.name]
// @param {Array.<String>} [config.model.attributes]
// @param {module:engine/conversion/downcasthelpers~StructureCreatorFunction} config.view
// @returns {Function} Conversion helper.
function downcastElementToStructure( config ) {
config = (0,lodash_es__WEBPACK_IMPORTED_MODULE_9__["default"])( config );
config.model = normalizeModelElementConfig( config.model );
config.view = normalizeToElementConfig( config.view, 'container' );
// Trigger reconversion on children list change because it always needs to use slots to put children in proper places.
// This is required to be able to trigger Differ#refreshItem() on a direct child of the reconverted element.
config.model.children = true;
return dispatcher => {
if ( dispatcher._conversionApi.schema.checkChild( config.model.name, '$text' ) ) {
/**
* This error occurs when a {@link module:engine/model/element~Element model element} is downcasted
* via {@link module:engine/conversion/downcasthelpers~DowncastHelpers#elementToStructure} helper but the element was
* allowed to host `$text` by the {@link module:engine/model/schema~Schema model schema}.
*
* For instance, this may be the result of `myElement` allowing the content of
* {@glink framework/guides/deep-dive/schema#generic-items `$block`} in its schema definition:
*
* // Element definition in schema.
* schema.register( 'myElement', {
* allowContentOf: '$block',
*
* // ...
* } );
*
* // ...
*
* // Conversion of myElement with the use of elementToStructure().
* editor.conversion.for( 'downcast' ).elementToStructure( {
* model: 'myElement',
* view: ( modelElement, { writer } ) => {
* // ...
* }
* } );
*
* In such case, {@link module:engine/conversion/downcasthelpers~DowncastHelpers#elementToElement `elementToElement()`} helper
* can be used instead to get around this problem:
*
* editor.conversion.for( 'downcast' ).elementToElement( {
* model: 'myElement',
* view: ( modelElement, { writer } ) => {
* // ...
* }
* } );
*
* @error conversion-element-to-structure-disallowed-text
* @param {String} elementName The name of the element the structure is to be created for.
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_7__["default"]( 'conversion-element-to-structure-disallowed-text', dispatcher, { elementName: config.model.name } );
}
dispatcher.on(
'insert:' + config.model.name,
insertStructure( config.view, createConsumer( config.model ) ),
{ priority: config.converterPriority || 'normal' }
);
dispatcher.on( 'reduceChanges', createChangeReducer( config.model ), { priority: 'low' } );
};
}
// Model attribute to view element conversion helper.
//
// See {@link ~DowncastHelpers#attributeToElement `.attributeToElement()` downcast helper} for examples.
//
// @param {Object} config Conversion configuration.
// @param {String|Object} config.model The key of the attribute to convert from or a `{ key, values }` object. `values` is an array
// of `String`s with possible values if the model attribute is an enumerable.
// @param {module:engine/view/elementdefinition~ElementDefinition|Function|Object} config.view A view element definition or a function
// that takes the model attribute value and {@link module:engine/view/downcastwriter~DowncastWriter view downcast writer}
// as parameters and returns a view attribute element. If `config.model.values` is
// given, `config.view` should be an object assigning values from `config.model.values` to view element definitions or functions.
// @param {module:utils/priorities~PriorityString} [config.converterPriority='normal'] Converter priority.
// @returns {Function} Conversion helper.
function downcastAttributeToElement( config ) {
config = (0,lodash_es__WEBPACK_IMPORTED_MODULE_9__["default"])( config );
const modelKey = config.model.key ? config.model.key : config.model;
let eventName = 'attribute:' + modelKey;
if ( config.model.name ) {
eventName += ':' + config.model.name;
}
if ( config.model.values ) {
for ( const modelValue of config.model.values ) {
config.view[ modelValue ] = normalizeToElementConfig( config.view[ modelValue ], 'attribute' );
}
} else {
config.view = normalizeToElementConfig( config.view, 'attribute' );
}
const elementCreator = getFromAttributeCreator( config );
return dispatcher => {
dispatcher.on( eventName, wrap( elementCreator ), { priority: config.converterPriority || 'normal' } );
};
}
// Model attribute to view attribute conversion helper.
//
// See {@link ~DowncastHelpers#attributeToAttribute `.attributeToAttribute()` downcast helper} for examples.
//
// @param {Object} config Conversion configuration.
// @param {String|Object} config.model The key of the attribute to convert from or a `{ key, values, [ name ] }` object describing
// the attribute key, possible values and, optionally, an element name to convert from.
// @param {String|Object|Function} config.view A view attribute key, or a `{ key, value }` object or a function that takes
// the model attribute value and returns a `{ key, value }` object. If `key` is `'class'`, `value` can be a `String` or an
// array of `String`s. If `key` is `'style'`, `value` is an object with key-value pairs. In other cases, `value` is a `String`.
// If `config.model.values` is set, `config.view` should be an object assigning values from `config.model.values` to
// `{ key, value }` objects or a functions.
// @param {module:utils/priorities~PriorityString} [config.converterPriority='normal'] Converter priority.
// @returns {Function} Conversion helper.
function downcastAttributeToAttribute( config ) {
config = (0,lodash_es__WEBPACK_IMPORTED_MODULE_9__["default"])( config );
const modelKey = config.model.key ? config.model.key : config.model;
let eventName = 'attribute:' + modelKey;
if ( config.model.name ) {
eventName += ':' + config.model.name;
}
if ( config.model.values ) {
for ( const modelValue of config.model.values ) {
config.view[ modelValue ] = normalizeToAttributeConfig( config.view[ modelValue ] );
}
} else {
config.view = normalizeToAttributeConfig( config.view );
}
const elementCreator = getFromAttributeCreator( config );
return dispatcher => {
dispatcher.on( eventName, changeAttribute( elementCreator ), { priority: config.converterPriority || 'normal' } );
};
}
// Model marker to view element conversion helper.
//
// See {@link ~DowncastHelpers#markerToElement `.markerToElement()` downcast helper} for examples.
//
// @param {Object} config Conversion configuration.
// @param {String} config.model The name of the model marker (or model marker group) to convert.
// @param {module:engine/view/elementdefinition~ElementDefinition|Function} config.view A view element definition or a function
// that takes the model marker data as a parameter and returns a view UI element.
// @param {module:utils/priorities~PriorityString} [config.converterPriority='normal'] Converter priority.
// @returns {Function} Conversion helper.
function downcastMarkerToElement( config ) {
config = (0,lodash_es__WEBPACK_IMPORTED_MODULE_9__["default"])( config );
config.view = normalizeToElementConfig( config.view, 'ui' );
return dispatcher => {
dispatcher.on( 'addMarker:' + config.model, insertUIElement( config.view ), { priority: config.converterPriority || 'normal' } );
dispatcher.on( 'removeMarker:' + config.model, removeUIElement( config.view ), { priority: config.converterPriority || 'normal' } );
};
}
// Model marker to view data conversion helper.
//
// See {@link ~DowncastHelpers#markerToData `markerToData()` downcast helper} to learn more.
//
// @param {Object} config
// @param {String} config.model
// @param {Function} [config.view]
// @param {module:utils/priorities~PriorityString} [config.converterPriority='normal']
// @returns {Function} Conversion helper.
function downcastMarkerToData( config ) {
config = (0,lodash_es__WEBPACK_IMPORTED_MODULE_9__["default"])( config );
const group = config.model;
// Default conversion.
if ( !config.view ) {
config.view = markerName => ( {
group,
name: markerName.substr( config.model.length + 1 )
} );
}
return dispatcher => {
dispatcher.on( 'addMarker:' + group, insertMarkerData( config.view ), { priority: config.converterPriority || 'normal' } );
dispatcher.on( 'removeMarker:' + group, removeMarkerData( config.view ), { priority: config.converterPriority || 'normal' } );
};
}
// Model marker to highlight conversion helper.
//
// See {@link ~DowncastHelpers#markerToElement `.markerToElement()` downcast helper} for examples.
//
// @param {Object} config Conversion configuration.
// @param {String} config.model The name of the model marker (or model marker group) to convert.
// @param {module:engine/conversion/downcasthelpers~HighlightDescriptor|Function} config.view A highlight descriptor
// that will be used for highlighting or a function that takes the model marker data as a parameter and returns a highlight descriptor.
// @param {module:utils/priorities~PriorityString} [config.converterPriority='normal'] Converter priority.
// @returns {Function} Conversion helper.
function downcastMarkerToHighlight( config ) {
return dispatcher => {
dispatcher.on( 'addMarker:' + config.model, highlightText( config.view ), { priority: config.converterPriority || 'normal' } );
dispatcher.on( 'addMarker:' + config.model, highlightElement( config.view ), { priority: config.converterPriority || 'normal' } );
dispatcher.on( 'removeMarker:' + config.model, removeHighlight( config.view ), { priority: config.converterPriority || 'normal' } );
};
}
// Takes `config.model`, and converts it to an object with normalized structure.
//
// @param {String|Object} model Model configuration or element name.
// @param {String} model.name
// @param {Array.<String>} [model.attributes]
// @param {Boolean} [model.children]
// @returns {Object}
function normalizeModelElementConfig( model ) {
if ( typeof model == 'string' ) {
model = { name: model };
}
// List of attributes that should trigger reconversion.
if ( !model.attributes ) {
model.attributes = [];
} else if ( !Array.isArray( model.attributes ) ) {
model.attributes = [ model.attributes ];
}
// Whether a children insertion/deletion should trigger reconversion.
model.children = !!model.children;
return model;
}
// Takes `config.view`, and if it is an {@link module:engine/view/elementdefinition~ElementDefinition}, converts it
// to a function (because lower level converters accept only element creator functions).
//
// @param {module:engine/view/elementdefinition~ElementDefinition|Function} view View configuration.
// @param {'container'|'attribute'|'ui'} viewElementType View element type to create.
// @returns {Function} Element creator function to use in lower level converters.
function normalizeToElementConfig( view, viewElementType ) {
if ( typeof view == 'function' ) {
// If `view` is already a function, don't do anything.
return view;
}
return ( modelData, conversionApi ) => createViewElementFromDefinition( view, conversionApi, viewElementType );
}
// Creates a view element instance from the provided {@link module:engine/view/elementdefinition~ElementDefinition} and class.
//
// @param {module:engine/view/elementdefinition~ElementDefinition} viewElementDefinition
// @param {module:engine/view/downcastwriter~DowncastWriter} viewWriter
// @param {'container'|'attribute'|'ui'} viewElementType
// @returns {module:engine/view/element~Element}
function createViewElementFromDefinition( viewElementDefinition, conversionApi, viewElementType ) {
if ( typeof viewElementDefinition == 'string' ) {
// If `viewElementDefinition` is given as a `String`, normalize it to an object with `name` property.
viewElementDefinition = { name: viewElementDefinition };
}
let element;
const viewWriter = conversionApi.writer;
const attributes = Object.assign( {}, viewElementDefinition.attributes );
if ( viewElementType == 'container' ) {
element = viewWriter.createContainerElement( viewElementDefinition.name, attributes );
} else if ( viewElementType == 'attribute' ) {
const options = {
priority: viewElementDefinition.priority || _view_attributeelement__WEBPACK_IMPORTED_MODULE_4__["default"].DEFAULT_PRIORITY
};
element = viewWriter.createAttributeElement( viewElementDefinition.name, attributes, options );
} else {
// 'ui'.
element = viewWriter.createUIElement( viewElementDefinition.name, attributes );
}
if ( viewElementDefinition.styles ) {
const keys = Object.keys( viewElementDefinition.styles );
for ( const key of keys ) {
viewWriter.setStyle( key, viewElementDefinition.styles[ key ], element );
}
}
if ( viewElementDefinition.classes ) {
const classes = viewElementDefinition.classes;
if ( typeof classes == 'string' ) {
viewWriter.addClass( classes, element );
} else {
for ( const className of classes ) {
viewWriter.addClass( className, element );
}
}
}
return element;
}
function getFromAttributeCreator( config ) {
if ( config.model.values ) {
return ( modelAttributeValue, conversionApi ) => {
const view = config.view[ modelAttributeValue ];
if ( view ) {
return view( modelAttributeValue, conversionApi );
}
return null;
};
} else {
return config.view;
}
}
// Takes the configuration, adds default parameters if they do not exist and normalizes other parameters to be used in downcast converters
// for generating a view attribute.
//
// @param {Object} view View configuration.
function normalizeToAttributeConfig( view ) {
if ( typeof view == 'string' ) {
return modelAttributeValue => ( { key: view, value: modelAttributeValue } );
} else if ( typeof view == 'object' ) {
// { key, value, ... }
if ( view.value ) {
return () => view;
}
// { key, ... }
else {
return modelAttributeValue => ( { key: view.key, value: modelAttributeValue } );
}
} else {
// function.
return view;
}
}
// Helper function for `highlight`. Prepares the actual descriptor object using value passed to the converter.
function prepareDescriptor( highlightDescriptor, data, conversionApi ) {
// If passed descriptor is a creator function, call it. If not, just use passed value.
const descriptor = typeof highlightDescriptor == 'function' ?
highlightDescriptor( data, conversionApi ) :
highlightDescriptor;
if ( !descriptor ) {
return null;
}
// Apply default descriptor priority.
if ( !descriptor.priority ) {
descriptor.priority = 10;
}
// Default descriptor id is marker name.
if ( !descriptor.id ) {
descriptor.id = data.markerName;
}
return descriptor;
}
// Creates a function that checks a single differ diff item whether it should trigger reconversion.
//
// @param {Object} model A normalized `config.model` converter configuration.
// @param {String} model.name The name of element.
// @param {Array.<String>} model.attributes The list of attribute names that should trigger reconversion.
// @param {Boolean} [model.children] Whether the child list change should trigger reconversion.
// @returns {Function}
function createChangeReducerCallback( model ) {
return ( node, change ) => {
if ( !node.is( 'element', model.name ) ) {
return false;
}
if ( change.type == 'attribute' ) {
if ( model.attributes.includes( change.attributeKey ) ) {
return true;
}
} else {
/* istanbul ignore else: This is always true because otherwise it would not register a reducer callback. */
if ( model.children ) {
return true;
}
}
return false;
};
}
// Creates a `reduceChanges` event handler for reconversion.
//
// @param {Object} model A normalized `config.model` converter configuration.
// @param {String} model.name The name of element.
// @param {Array.<String>} model.attributes The list of attribute names that should trigger reconversion.
// @param {Boolean} [model.children] Whether the child list change should trigger reconversion.
// @returns {Function}
function createChangeReducer( model ) {
const shouldReplace = createChangeReducerCallback( model );
return ( evt, data ) => {
const reducedChanges = [];
if ( !data.reconvertedElements ) {
data.reconvertedElements = new Set();
}
for ( const change of data.changes ) {
// For attribute use node affected by the change.
// For insert or remove use parent element because we need to check if it's added/removed child.
const node = change.position ? change.position.parent : change.range.start.nodeAfter;
if ( !node || !shouldReplace( node, change ) ) {
reducedChanges.push( change );
continue;
}
// If it's already marked for reconversion, so skip this change, otherwise add the diff items.
if ( !data.reconvertedElements.has( node ) ) {
data.reconvertedElements.add( node );
const position = _model_position__WEBPACK_IMPORTED_MODULE_3__["default"]._createBefore( node );
reducedChanges.push( {
type: 'remove',
name: node.name,
position,
length: 1
}, {
type: 'reinsert',
name: node.name,
position,
length: 1
} );
}
}
data.changes = reducedChanges;
};
}
// Creates a function that checks if an element and its watched attributes can be consumed and consumes them.
//
// @param {Object} model A normalized `config.model` converter configuration.
// @param {String} model.name The name of element.
// @param {Array.<String>} model.attributes The list of attribute names that should trigger reconversion.
// @param {Boolean} [model.children] Whether the child list change should trigger reconversion.
// @returns {module:engine/conversion/downcasthelpers~ConsumerFunction}
function createConsumer( model ) {
return ( node, consumable, options = {} ) => {
const events = [ 'insert' ];
// Collect all set attributes that are triggering conversion.
for ( const attributeName of model.attributes ) {
if ( node.hasAttribute( attributeName ) ) {
events.push( `attribute:${ attributeName }` );
}
}
if ( !events.every( event => consumable.test( node, event ) ) ) {
return false;
}
if ( !options.preflight ) {
events.forEach( event => consumable.consume( node, event ) );
}
return true;
};
}
// Creates a function that create view slots.
//
// @param {module:engine/model/element~Element} element
// @param {Map.<module:engine/view/element~Element,Array.<module:engine/model/node~Node>>} slotsMap
// @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi
// @returns {Function} Exposed by writer as createSlot().
function createSlotFactory( element, slotsMap, conversionApi ) {
return ( writer, modeOrFilter = 'children' ) => {
const slot = writer.createContainerElement( '$slot' );
let children = null;
if ( modeOrFilter === 'children' ) {
children = Array.from( element.getChildren() );
} else if ( typeof modeOrFilter == 'function' ) {
children = Array.from( element.getChildren() ).filter( element => modeOrFilter( element ) );
} else {
/**
* Unknown slot mode was provided to `writer.createSlot()` in downcast converter.
*
* @error conversion-slot-mode-unknown
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_7__["default"]( 'conversion-slot-mode-unknown', conversionApi.dispatcher, { modeOrFilter } );
}
slotsMap.set( slot, children );
return slot;
};
}
// Checks if all children are covered by slots and there is no child that landed in multiple slots.
//
// @param {module:engine/model/element~Element}
// @param {Map.<module:engine/view/element~Element,Array.<module:engine/model/node~Node>>} slotsMap
// @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi
function validateSlotsChildren( element, slotsMap, conversionApi ) {
const childrenInSlots = Array.from( slotsMap.values() ).flat();
const uniqueChildrenInSlots = new Set( childrenInSlots );
if ( uniqueChildrenInSlots.size != childrenInSlots.length ) {
/**
* Filters provided to `writer.createSlot()` overlap (at least two filters accept the same child element).
*
* @error conversion-slot-filter-overlap
* @param {module:engine/model/element~Element} element The element of which children would not be properly
* allocated to multiple slots.
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_7__["default"]( 'conversion-slot-filter-overlap', conversionApi.dispatcher, { element } );
}
if ( uniqueChildrenInSlots.size != element.childCount ) {
/**
* Filters provided to `writer.createSlot()` are incomplete and exclude at least one children element (one of
* the children elements would not be assigned to any of the slots).
*
* @error conversion-slot-filter-incomplete
* @param {module:engine/model/element~Element} element The element of which children would not be properly
* allocated to multiple slots.
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_7__["default"]( 'conversion-slot-filter-incomplete', conversionApi.dispatcher, { element } );
}
}
// Fill slots with appropriate view elements.
//
// @param {module:engine/view/element~Element} viewElement
// @param {Map.<module:engine/view/element~Element,Array.<module:engine/model/node~Node>>} slotsMap
// @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi
// @param {Object} options
// @param {Boolean} [options.reconversion]
function fillSlots( viewElement, slotsMap, conversionApi, options ) {
// Set temporary position mapping to redirect child view elements into a proper slots.
conversionApi.mapper.on( 'modelToViewPosition', toViewPositionMapping, { priority: 'highest' } );
let currentSlot = null;
let currentSlotNodes = null;
// Fill slots with nested view nodes.
for ( [ currentSlot, currentSlotNodes ] of slotsMap ) {
reinsertOrConvertNodes( viewElement, currentSlotNodes, conversionApi, options );
conversionApi.writer.move(
conversionApi.writer.createRangeIn( currentSlot ),
conversionApi.writer.createPositionBefore( currentSlot )
);
conversionApi.writer.remove( currentSlot );
}
conversionApi.mapper.off( 'modelToViewPosition', toViewPositionMapping );
function toViewPositionMapping( evt, data ) {
const element = data.modelPosition.nodeAfter;
// Find the proper offset within the slot.
const index = currentSlotNodes.indexOf( element );
if ( index < 0 ) {
return;
}
data.viewPosition = data.mapper.findPositionIn( currentSlot, index );
}
}
// Inserts view representation of `nodes` into the `viewElement` either by bringing back just removed view nodes
// or by triggering conversion for them.
//
// @param {module:engine/view/element~Element} viewElement
// @param {Iterable.<module:engine/model/element~Element>} modelNodes
// @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi
// @param {Object} options
// @param {Boolean} [options.reconversion]
function reinsertOrConvertNodes( viewElement, modelNodes, conversionApi, options ) {
// Fill with nested view nodes.
for ( const modelChildNode of modelNodes ) {
// Try reinserting the view node for the specified model node...
if ( !reinsertNode( viewElement.root, modelChildNode, conversionApi, options ) ) {
// ...or else convert the model element to the view.
conversionApi.convertItem( modelChildNode );
}
}
}
// Checks if the view for the given model element could be reused and reinserts it to the view.
//
// @param {module:engine/view/node~Node|module:engine/view/documentfragment~DocumentFragment} viewRoot
// @param {module:engine/model/element~Element} modelElement
// @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi
// @param {Object} options
// @param {Boolean} [options.reconversion]
// @returns {Boolean} `false` if view element can't be reused.
function reinsertNode( viewRoot, modelElement, conversionApi, options ) {
const { writer, mapper } = conversionApi;
// Don't reinsert if this is not a reconversion...
if ( !options.reconversion ) {
return false;
}
const viewChildNode = mapper.toViewElement( modelElement );
// ...or there is no view to reinsert or it was already inserted to the view structure...
if ( !viewChildNode || viewChildNode.root == viewRoot ) {
return false;
}
// ...or it was strictly marked as not to be reused.
if ( !conversionApi.canReuseView( viewChildNode ) ) {
return false;
}
// Otherwise reinsert the view node.
writer.move(
writer.createRangeOn( viewChildNode ),
mapper.toViewPosition( _model_position__WEBPACK_IMPORTED_MODULE_3__["default"]._createBefore( modelElement ) )
);
return true;
}
// The default consumer for insert events.
// @param {module:engine/model/item~Item} item Model item.
// @param {module:engine/conversion/modelconsumable~ModelConsumable} consumable The model consumable.
// @param {Object} [options]
// @param {Boolean} [options.preflight=false] Whether should consume or just check if can be consumed.
// @returns {Boolean}
function defaultConsumer( item, consumable, { preflight } = {} ) {
if ( preflight ) {
return consumable.test( item, 'insert' );
} else {
return consumable.consume( item, 'insert' );
}
}
/**
* An object describing how the marker highlight should be represented in the view.
*
* Each text node contained in a highlighted range will be wrapped in a `<span>`
* {@link module:engine/view/attributeelement~AttributeElement view attribute element} with CSS class(es), attributes and a priority
* described by this object.
*
* Additionally, each {@link module:engine/view/containerelement~ContainerElement container element} can handle displaying the highlight
* separately by providing the `addHighlight` and `removeHighlight` custom properties. In this case:
*
* * The `HighlightDescriptor` object is passed to the `addHighlight` function upon conversion and should be used to apply the highlight to
* the element.
* * The descriptor `id` is passed to the `removeHighlight` function upon conversion and should be used to remove the highlight with the
* given ID from the element.
*
* @typedef {Object} module:engine/conversion/downcasthelpers~HighlightDescriptor
*
* @property {String|Array.<String>} classes A CSS class or an array of classes to set. If the descriptor is used to
* create an {@link module:engine/view/attributeelement~AttributeElement attribute element} over text nodes, these classes will be set
* on that attribute element. If the descriptor is applied to an element, usually these classes will be set on that element, however,
* this depends on how the element converts the descriptor.
*
* @property {String} [id] Descriptor identifier. If not provided, it defaults to the converted marker's name.
*
* @property {Number} [priority] Descriptor priority. If not provided, it defaults to `10`. If the descriptor is used to create
* an {@link module:engine/view/attributeelement~AttributeElement attribute element}, it will be that element's
* {@link module:engine/view/attributeelement~AttributeElement#priority priority}. If the descriptor is applied to an element,
* the priority will be used to determine which descriptor is more important.
*
* @property {Object} [attributes] Attributes to set. If the descriptor is used to create
* an {@link module:engine/view/attributeelement~AttributeElement attribute element} over text nodes, these attributes will be set on that
* attribute element. If the descriptor is applied to an element, usually these attributes will be set on that element, however,
* this depends on how the element converts the descriptor.
*/
/**
* A filtering function used to choose model child nodes to be downcasted into the specific view
* {@link module:engine/view/downcastwriter~DowncastWriter#createSlot "slot"} while executing the
* {@link module:engine/conversion/downcasthelpers~DowncastHelpers#elementToStructure `elementToStructure()`} converter.
*
* @callback module:engine/conversion/downcasthelpers~SlotFilter
*
* @param {module:engine/model/node~Node} node A model node.
* @returns {Boolean} Whether the provided model node should be downcasted into this slot.
*
* @see module:engine/view/downcastwriter~DowncastWriter#createSlot
* @see module:engine/conversion/downcasthelpers~DowncastHelpers#elementToStructure
* @see module:engine/conversion/downcasthelpers~insertStructure
*/
/**
* A function that takes the model element and {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi downcast
* conversion API} as parameters and returns a view container element with slots for model child nodes to be converted into.
*
* @callback module:engine/conversion/downcasthelpers~StructureCreatorFunction
* @param {module:engine/model/element~Element} element The model element to be converted to the view structure.
* @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi The conversion interface.
* @returns {module:engine/view/element~Element} The view structure with slots for model child nodes.
*
* @see module:engine/conversion/downcasthelpers~DowncastHelpers#elementToStructure
* @see module:engine/conversion/downcasthelpers~insertStructure
*/
/**
* A function that is expected to consume all the consumables that were used by the element creator.
*
* @callback module:engine/conversion/downcasthelpers~ConsumerFunction
* @param {module:engine/model/element~Element} element The model element to be converted to the view structure.
* @param {module:engine/conversion/modelconsumable~ModelConsumable} consumable The `ModelConsumable` same as in
* {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi#consumable `DowncastConversionApi.consumable`}.
* @param {Object} [options]
* @param {Boolean} [options.preflight=false] Whether should consume or just check if can be consumed.
* @returns {Boolean} `true` if all consumable values were available and were consumed, `false` otherwise.
*
* @see module:engine/conversion/downcasthelpers~insertStructure
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/conversion/mapper.js":
/*!**************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/conversion/mapper.js ***!
\**************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ Mapper)
/* harmony export */ });
/* harmony import */ var _model_position__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../model/position */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/position.js");
/* harmony import */ var _model_range__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../model/range */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/range.js");
/* harmony import */ var _view_position__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../view/position */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/position.js");
/* harmony import */ var _view_range__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../view/range */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/range.js");
/* harmony import */ var _view_text__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../view/text */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/text.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_emittermixin__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/emittermixin */ "./node_modules/@ckeditor/ckeditor5-utils/src/emittermixin.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/mix */ "./node_modules/@ckeditor/ckeditor5-utils/src/mix.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/conversion/mapper
*/
/**
* Maps elements, positions and markers between the {@link module:engine/view/document~Document view} and
* the {@link module:engine/model/model model}.
*
* The instance of the Mapper used for the editing pipeline is available in
* {@link module:engine/controller/editingcontroller~EditingController#mapper `editor.editing.mapper`}.
*
* Mapper uses bound elements to find corresponding elements and positions, so, to get proper results,
* all model elements should be {@link module:engine/conversion/mapper~Mapper#bindElements bound}.
*
* To map the complex model to/from view relations, you may provide custom callbacks for the
* {@link module:engine/conversion/mapper~Mapper#event:modelToViewPosition modelToViewPosition event} and
* {@link module:engine/conversion/mapper~Mapper#event:viewToModelPosition viewToModelPosition event} that are fired whenever
* a position mapping request occurs.
* Those events are fired by the {@link module:engine/conversion/mapper~Mapper#toViewPosition toViewPosition}
* and {@link module:engine/conversion/mapper~Mapper#toModelPosition toModelPosition} methods. `Mapper` adds its own default callbacks
* with `'lowest'` priority. To override default `Mapper` mapping, add custom callback with higher priority and
* stop the event.
* @mixes module:utils/emittermixin~EmitterMixin
*/
class Mapper {
/**
* Creates an instance of the mapper.
*/
constructor() {
/**
* Model element to view element mapping.
*
* @private
* @member {WeakMap}
*/
this._modelToViewMapping = new WeakMap();
/**
* View element to model element mapping.
*
* @private
* @member {WeakMap}
*/
this._viewToModelMapping = new WeakMap();
/**
* A map containing callbacks between view element names and functions evaluating length of view elements
* in model.
*
* @private
* @member {Map}
*/
this._viewToModelLengthCallbacks = new Map();
/**
* Model marker name to view elements mapping.
*
* Keys are `String`s while values are `Set`s with {@link module:engine/view/element~Element view elements}.
* One marker (name) can be mapped to multiple elements.
*
* @private
* @member {Map}
*/
this._markerNameToElements = new Map();
/**
* View element to model marker names mapping.
*
* This is reverse to {@link ~Mapper#_markerNameToElements} map.
*
* @private
* @member {Map}
*/
this._elementToMarkerNames = new Map();
/**
* The map of removed view elements with their current root (used for deferred unbinding).
*
* @private
* @member {Map.<module:engine/view/element~Element,module:engine/view/documentfragment~DocumentFragment>}
*/
this._deferredBindingRemovals = new Map();
/**
* Stores marker names of markers which have changed due to unbinding a view element (so it is assumed that the view element
* has been removed, moved or renamed).
*
* @private
* @member {Set.<module:engine/model/markercollection~Marker>}
*/
this._unboundMarkerNames = new Set();
// Default mapper algorithm for mapping model position to view position.
this.on( 'modelToViewPosition', ( evt, data ) => {
if ( data.viewPosition ) {
return;
}
const viewContainer = this._modelToViewMapping.get( data.modelPosition.parent );
if ( !viewContainer ) {
/**
* A model position could not be mapped to the view because the parent of the model position
* does not have a mapped view element (might have not been converted yet or it has no converter).
*
* Make sure that the model element is correctly converted to the view.
*
* @error mapping-view-position-parent-not-found
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_6__["default"]( 'mapping-view-position-parent-not-found', this, { modelPosition: data.modelPosition } );
}
data.viewPosition = this.findPositionIn( viewContainer, data.modelPosition.offset );
}, { priority: 'low' } );
// Default mapper algorithm for mapping view position to model position.
this.on( 'viewToModelPosition', ( evt, data ) => {
if ( data.modelPosition ) {
return;
}
const viewBlock = this.findMappedViewAncestor( data.viewPosition );
const modelParent = this._viewToModelMapping.get( viewBlock );
const modelOffset = this._toModelOffset( data.viewPosition.parent, data.viewPosition.offset, viewBlock );
data.modelPosition = _model_position__WEBPACK_IMPORTED_MODULE_0__["default"]._createAt( modelParent, modelOffset );
}, { priority: 'low' } );
}
/**
* Marks model and view elements as corresponding. Corresponding elements can be retrieved by using
* the {@link module:engine/conversion/mapper~Mapper#toModelElement toModelElement} and
* {@link module:engine/conversion/mapper~Mapper#toViewElement toViewElement} methods.
* The information that elements are bound is also used to translate positions.
*
* @param {module:engine/model/element~Element} modelElement Model element.
* @param {module:engine/view/element~Element} viewElement View element.
*/
bindElements( modelElement, viewElement ) {
this._modelToViewMapping.set( modelElement, viewElement );
this._viewToModelMapping.set( viewElement, modelElement );
}
/**
* Unbinds the given {@link module:engine/view/element~Element view element} from the map.
*
* **Note:** view-to-model binding will be removed, if it existed. However, corresponding model-to-view binding
* will be removed only if model element is still bound to the passed `viewElement`.
*
* This behavior allows for re-binding model element to another view element without fear of losing the new binding
* when the previously bound view element is unbound.
*
* @param {module:engine/view/element~Element} viewElement View element to unbind.
* @param {Object} [options={}] The options object.
* @param {Boolean} [options.defer=false] Controls whether the binding should be removed immediately or deferred until a
* {@link #flushDeferredBindings `flushDeferredBindings()`} call.
*/
unbindViewElement( viewElement, options = {} ) {
const modelElement = this.toModelElement( viewElement );
if ( this._elementToMarkerNames.has( viewElement ) ) {
for ( const markerName of this._elementToMarkerNames.get( viewElement ) ) {
this._unboundMarkerNames.add( markerName );
}
}
if ( options.defer ) {
this._deferredBindingRemovals.set( viewElement, viewElement.root );
} else {
this._viewToModelMapping.delete( viewElement );
if ( this._modelToViewMapping.get( modelElement ) == viewElement ) {
this._modelToViewMapping.delete( modelElement );
}
}
}
/**
* Unbinds the given {@link module:engine/model/element~Element model element} from the map.
*
* **Note:** the model-to-view binding will be removed, if it existed. However, the corresponding view-to-model binding
* will be removed only if the view element is still bound to the passed `modelElement`.
*
* This behavior lets for re-binding view element to another model element without fear of losing the new binding
* when the previously bound model element is unbound.
*
* @param {module:engine/model/element~Element} modelElement Model element to unbind.
*/
unbindModelElement( modelElement ) {
const viewElement = this.toViewElement( modelElement );
this._modelToViewMapping.delete( modelElement );
if ( this._viewToModelMapping.get( viewElement ) == modelElement ) {
this._viewToModelMapping.delete( viewElement );
}
}
/**
* Binds the given marker name with the given {@link module:engine/view/element~Element view element}. The element
* will be added to the current set of elements bound with the given marker name.
*
* @param {module:engine/view/element~Element} element Element to bind.
* @param {String} name Marker name.
*/
bindElementToMarker( element, name ) {
const elements = this._markerNameToElements.get( name ) || new Set();
elements.add( element );
const names = this._elementToMarkerNames.get( element ) || new Set();
names.add( name );
this._markerNameToElements.set( name, elements );
this._elementToMarkerNames.set( element, names );
}
/**
* Unbinds an element from given marker name.
*
* @param {module:engine/view/element~Element} element Element to unbind.
* @param {String} name Marker name.
*/
unbindElementFromMarkerName( element, name ) {
const nameToElements = this._markerNameToElements.get( name );
if ( nameToElements ) {
nameToElements.delete( element );
if ( nameToElements.size == 0 ) {
this._markerNameToElements.delete( name );
}
}
const elementToNames = this._elementToMarkerNames.get( element );
if ( elementToNames ) {
elementToNames.delete( name );
if ( elementToNames.size == 0 ) {
this._elementToMarkerNames.delete( element );
}
}
}
/**
* Returns all marker names of markers which have changed due to unbinding a view element (so it is assumed that the view element
* has been removed, moved or renamed) since the last flush. After returning, the marker names list is cleared.
*
* @returns {Array.<String>}
*/
flushUnboundMarkerNames() {
const markerNames = Array.from( this._unboundMarkerNames );
this._unboundMarkerNames.clear();
return markerNames;
}
/**
* Unbinds all deferred binding removals of view elements that in the meantime were not re-attached to some root or document fragment.
*
* See: {@link #unbindViewElement `unbindViewElement()`}.
*/
flushDeferredBindings() {
for ( const [ viewElement, root ] of this._deferredBindingRemovals ) {
// Unbind it only if it wasn't re-attached to some root or document fragment.
if ( viewElement.root == root ) {
this.unbindViewElement( viewElement );
}
}
this._deferredBindingRemovals = new Map();
}
/**
* Removes all model to view and view to model bindings.
*/
clearBindings() {
this._modelToViewMapping = new WeakMap();
this._viewToModelMapping = new WeakMap();
this._markerNameToElements = new Map();
this._elementToMarkerNames = new Map();
this._unboundMarkerNames = new Set();
this._deferredBindingRemovals = new Map();
}
/**
* Gets the corresponding model element.
*
* **Note:** {@link module:engine/view/uielement~UIElement} does not have corresponding element in model.
*
* @param {module:engine/view/element~Element} viewElement View element.
* @returns {module:engine/model/element~Element|undefined} Corresponding model element or `undefined` if not found.
*/
toModelElement( viewElement ) {
return this._viewToModelMapping.get( viewElement );
}
/**
* Gets the corresponding view element.
*
* @param {module:engine/model/element~Element} modelElement Model element.
* @returns {module:engine/view/element~Element|undefined} Corresponding view element or `undefined` if not found.
*/
toViewElement( modelElement ) {
return this._modelToViewMapping.get( modelElement );
}
/**
* Gets the corresponding model range.
*
* @param {module:engine/view/range~Range} viewRange View range.
* @returns {module:engine/model/range~Range} Corresponding model range.
*/
toModelRange( viewRange ) {
return new _model_range__WEBPACK_IMPORTED_MODULE_1__["default"]( this.toModelPosition( viewRange.start ), this.toModelPosition( viewRange.end ) );
}
/**
* Gets the corresponding view range.
*
* @param {module:engine/model/range~Range} modelRange Model range.
* @returns {module:engine/view/range~Range} Corresponding view range.
*/
toViewRange( modelRange ) {
return new _view_range__WEBPACK_IMPORTED_MODULE_3__["default"]( this.toViewPosition( modelRange.start ), this.toViewPosition( modelRange.end ) );
}
/**
* Gets the corresponding model position.
*
* @fires viewToModelPosition
* @param {module:engine/view/position~Position} viewPosition View position.
* @returns {module:engine/model/position~Position} Corresponding model position.
*/
toModelPosition( viewPosition ) {
const data = {
viewPosition,
mapper: this
};
this.fire( 'viewToModelPosition', data );
return data.modelPosition;
}
/**
* Gets the corresponding view position.
*
* @fires modelToViewPosition
* @param {module:engine/model/position~Position} modelPosition Model position.
* @param {Object} [options] Additional options for position mapping process.
* @param {Boolean} [options.isPhantom=false] Should be set to `true` if the model position to map is pointing to a place
* in model tree which no longer exists. For example, it could be an end of a removed model range.
* @returns {module:engine/view/position~Position} Corresponding view position.
*/
toViewPosition( modelPosition, options = { isPhantom: false } ) {
const data = {
modelPosition,
mapper: this,
isPhantom: options.isPhantom
};
this.fire( 'modelToViewPosition', data );
return data.viewPosition;
}
/**
* Gets all view elements bound to the given marker name.
*
* @param {String} name Marker name.
* @returns {Set.<module:engine/view/element~Element>|null} View elements bound with the given marker name or `null`
* if no elements are bound to the given marker name.
*/
markerNameToElements( name ) {
const boundElements = this._markerNameToElements.get( name );
if ( !boundElements ) {
return null;
}
const elements = new Set();
for ( const element of boundElements ) {
if ( element.is( 'attributeElement' ) ) {
for ( const clone of element.getElementsWithSameId() ) {
elements.add( clone );
}
} else {
elements.add( element );
}
}
return elements;
}
/**
* Registers a callback that evaluates the length in the model of a view element with the given name.
*
* The callback is fired with one argument, which is a view element instance. The callback is expected to return
* a number representing the length of the view element in the model.
*
* // List item in view may contain nested list, which have other list items. In model though,
* // the lists are represented by flat structure. Because of those differences, length of list view element
* // may be greater than one. In the callback it's checked how many nested list items are in evaluated list item.
*
* function getViewListItemLength( element ) {
* let length = 1;
*
* for ( let child of element.getChildren() ) {
* if ( child.name == 'ul' || child.name == 'ol' ) {
* for ( let item of child.getChildren() ) {
* length += getViewListItemLength( item );
* }
* }
* }
*
* return length;
* }
*
* mapper.registerViewToModelLength( 'li', getViewListItemLength );
*
* @param {String} viewElementName Name of view element for which callback is registered.
* @param {Function} lengthCallback Function return a length of view element instance in model.
*/
registerViewToModelLength( viewElementName, lengthCallback ) {
this._viewToModelLengthCallbacks.set( viewElementName, lengthCallback );
}
/**
* For the given `viewPosition`, finds and returns the closest ancestor of this position that has a mapping to
* the model.
*
* @param {module:engine/view/position~Position} viewPosition Position for which a mapped ancestor should be found.
* @returns {module:engine/view/element~Element}
*/
findMappedViewAncestor( viewPosition ) {
let parent = viewPosition.parent;
while ( !this._viewToModelMapping.has( parent ) ) {
parent = parent.parent;
}
return parent;
}
/**
* Calculates model offset based on the view position and the block element.
*
* Example:
*
* <p>foo<b>ba|r</b></p> // _toModelOffset( b, 2, p ) -> 5
*
* Is a sum of:
*
* <p>foo|<b>bar</b></p> // _toModelOffset( p, 3, p ) -> 3
* <p>foo<b>ba|r</b></p> // _toModelOffset( b, 2, b ) -> 2
*
* @private
* @param {module:engine/view/element~Element} viewParent Position parent.
* @param {Number} viewOffset Position offset.
* @param {module:engine/view/element~Element} viewBlock Block used as a base to calculate offset.
* @returns {Number} Offset in the model.
*/
_toModelOffset( viewParent, viewOffset, viewBlock ) {
if ( viewBlock != viewParent ) {
// See example.
const offsetToParentStart = this._toModelOffset( viewParent.parent, viewParent.index, viewBlock );
const offsetInParent = this._toModelOffset( viewParent, viewOffset, viewParent );
return offsetToParentStart + offsetInParent;
}
// viewBlock == viewParent, so we need to calculate the offset in the parent element.
// If the position is a text it is simple ("ba|r" -> 2).
if ( viewParent.is( '$text' ) ) {
return viewOffset;
}
// If the position is in an element we need to sum lengths of siblings ( <b> bar </b> foo | -> 3 + 3 = 6 ).
let modelOffset = 0;
for ( let i = 0; i < viewOffset; i++ ) {
modelOffset += this.getModelLength( viewParent.getChild( i ) );
}
return modelOffset;
}
/**
* Gets the length of the view element in the model.
*
* The length is calculated as follows:
* * if a {@link #registerViewToModelLength length mapping callback} is provided for the given `viewNode`, it is used to
* evaluate the model length (`viewNode` is used as first and only parameter passed to the callback),
* * length of a {@link module:engine/view/text~Text text node} is equal to the length of its
* {@link module:engine/view/text~Text#data data},
* * length of a {@link module:engine/view/uielement~UIElement ui element} is equal to 0,
* * length of a mapped {@link module:engine/view/element~Element element} is equal to 1,
* * length of a non-mapped {@link module:engine/view/element~Element element} is equal to the length of its children.
*
* Examples:
*
* foo -> 3 // Text length is equal to its data length.
* <p>foo</p> -> 1 // Length of an element which is mapped is by default equal to 1.
* <b>foo</b> -> 3 // Length of an element which is not mapped is a length of its children.
* <div><p>x</p><p>y</p></div> -> 2 // Assuming that <div> is not mapped and <p> are mapped.
*
* @param {module:engine/view/element~Element} viewNode View node.
* @returns {Number} Length of the node in the tree model.
*/
getModelLength( viewNode ) {
if ( this._viewToModelLengthCallbacks.get( viewNode.name ) ) {
const callback = this._viewToModelLengthCallbacks.get( viewNode.name );
return callback( viewNode );
} else if ( this._viewToModelMapping.has( viewNode ) ) {
return 1;
} else if ( viewNode.is( '$text' ) ) {
return viewNode.data.length;
} else if ( viewNode.is( 'uiElement' ) ) {
return 0;
} else {
let len = 0;
for ( const child of viewNode.getChildren() ) {
len += this.getModelLength( child );
}
return len;
}
}
/**
* Finds the position in the view node (or in its children) with the expected model offset.
*
* Example:
*
* <p>fo<b>bar</b>bom</p> -> expected offset: 4
*
* findPositionIn( p, 4 ):
* <p>|fo<b>bar</b>bom</p> -> expected offset: 4, actual offset: 0
* <p>fo|<b>bar</b>bom</p> -> expected offset: 4, actual offset: 2
* <p>fo<b>bar</b>|bom</p> -> expected offset: 4, actual offset: 5 -> we are too far
*
* findPositionIn( b, 4 - ( 5 - 3 ) ):
* <p>fo<b>|bar</b>bom</p> -> expected offset: 2, actual offset: 0
* <p>fo<b>bar|</b>bom</p> -> expected offset: 2, actual offset: 3 -> we are too far
*
* findPositionIn( bar, 2 - ( 3 - 3 ) ):
* We are in the text node so we can simple find the offset.
* <p>fo<b>ba|r</b>bom</p> -> expected offset: 2, actual offset: 2 -> position found
*
* @param {module:engine/view/element~Element} viewParent Tree view element in which we are looking for the position.
* @param {Number} expectedOffset Expected offset.
* @returns {module:engine/view/position~Position} Found position.
*/
findPositionIn( viewParent, expectedOffset ) {
// Last scanned view node.
let viewNode;
// Length of the last scanned view node.
let lastLength = 0;
let modelOffset = 0;
let viewOffset = 0;
// In the text node it is simple: the offset in the model equals the offset in the text.
if ( viewParent.is( '$text' ) ) {
return new _view_position__WEBPACK_IMPORTED_MODULE_2__["default"]( viewParent, expectedOffset );
}
// In other cases we add lengths of child nodes to find the proper offset.
// If it is smaller we add the length.
while ( modelOffset < expectedOffset ) {
viewNode = viewParent.getChild( viewOffset );
lastLength = this.getModelLength( viewNode );
modelOffset += lastLength;
viewOffset++;
}
// If it equals we found the position.
if ( modelOffset == expectedOffset ) {
return this._moveViewPositionToTextNode( new _view_position__WEBPACK_IMPORTED_MODULE_2__["default"]( viewParent, viewOffset ) );
}
// If it is higher we need to enter last child.
else {
// ( modelOffset - lastLength ) is the offset to the child we enter,
// so we subtract it from the expected offset to fine the offset in the child.
return this.findPositionIn( viewNode, expectedOffset - ( modelOffset - lastLength ) );
}
}
/**
* Because we prefer positions in the text nodes over positions next to text nodes, if the view position was next to a text node,
* it moves it into the text node instead.
*
* <p>[]<b>foo</b></p> -> <p>[]<b>foo</b></p> // do not touch if position is not directly next to text
* <p>foo[]<b>foo</b></p> -> <p>foo{}<b>foo</b></p> // move to text node
* <p><b>[]foo</b></p> -> <p><b>{}foo</b></p> // move to text node
*
* @private
* @param {module:engine/view/position~Position} viewPosition Position potentially next to the text node.
* @returns {module:engine/view/position~Position} Position in the text node if possible.
*/
_moveViewPositionToTextNode( viewPosition ) {
// If the position is just after a text node, put it at the end of that text node.
// If the position is just before a text node, put it at the beginning of that text node.
const nodeBefore = viewPosition.nodeBefore;
const nodeAfter = viewPosition.nodeAfter;
if ( nodeBefore instanceof _view_text__WEBPACK_IMPORTED_MODULE_4__["default"] ) {
return new _view_position__WEBPACK_IMPORTED_MODULE_2__["default"]( nodeBefore, nodeBefore.data.length );
} else if ( nodeAfter instanceof _view_text__WEBPACK_IMPORTED_MODULE_4__["default"] ) {
return new _view_position__WEBPACK_IMPORTED_MODULE_2__["default"]( nodeAfter, 0 );
}
// Otherwise, just return the given position.
return viewPosition;
}
/**
* Fired for each model-to-view position mapping request. The purpose of this event is to enable custom model-to-view position
* mapping. Callbacks added to this event take {@link module:engine/model/position~Position model position} and are expected to
* calculate the {@link module:engine/view/position~Position view position}. The calculated view position should be added as
* a `viewPosition` value in the `data` object that is passed as one of parameters to the event callback.
*
* // Assume that "captionedImage" model element is converted to <img> and following <span> elements in view,
* // and the model element is bound to <img> element. Force mapping model positions inside "captionedImage" to that
* // <span> element.
* mapper.on( 'modelToViewPosition', ( evt, data ) => {
* const positionParent = modelPosition.parent;
*
* if ( positionParent.name == 'captionedImage' ) {
* const viewImg = data.mapper.toViewElement( positionParent );
* const viewCaption = viewImg.nextSibling; // The <span> element.
*
* data.viewPosition = new ViewPosition( viewCaption, modelPosition.offset );
*
* // Stop the event if other callbacks should not modify calculated value.
* evt.stop();
* }
* } );
*
* **Note:** keep in mind that sometimes a "phantom" model position is being converted. A "phantom" model position is
* a position that points to a nonexistent place in model. Such a position might still be valid for conversion, though
* (it would point to a correct place in the view when converted). One example of such a situation is when a range is
* removed from the model, there may be a need to map the range's end (which is no longer a valid model position). To
* handle such situations, check the `data.isPhantom` flag:
*
* // Assume that there is a "customElement" model element and whenever the position is before it,
* // we want to move it to the inside of the view element bound to "customElement".
* mapper.on( 'modelToViewPosition', ( evt, data ) => {
* if ( data.isPhantom ) {
* return;
* }
*
* // Below line might crash for phantom position that does not exist in model.
* const sibling = data.modelPosition.nodeBefore;
*
* // Check if this is the element we are interested in.
* if ( !sibling.is( 'element', 'customElement' ) ) {
* return;
* }
*
* const viewElement = data.mapper.toViewElement( sibling );
*
* data.viewPosition = new ViewPosition( sibling, 0 );
*
* evt.stop();
* } );
*
* **Note:** the default mapping callback is provided with a `low` priority setting and does not cancel the event, so it is possible to
* attach a custom callback after a default callback and also use `data.viewPosition` calculated by the default callback
* (for example to fix it).
*
* **Note:** the default mapping callback will not fire if `data.viewPosition` is already set.
*
* **Note:** these callbacks are called **very often**. For efficiency reasons, it is advised to use them only when position
* mapping between the given model and view elements is unsolvable by using just elements mapping and default algorithm.
* Also, the condition that checks if a special case scenario happened should be as simple as possible.
*
* @event modelToViewPosition
* @param {Object} data Data pipeline object that can store and pass data between callbacks. The callback should add
* the `viewPosition` value to that object with calculated the {@link module:engine/view/position~Position view position}.
* @param {module:engine/conversion/mapper~Mapper} data.mapper Mapper instance that fired the event.
*/
/**
* Fired for each view-to-model position mapping request. See {@link module:engine/conversion/mapper~Mapper#event:modelToViewPosition}.
*
* // See example in `modelToViewPosition` event description.
* // This custom mapping will map positions from <span> element next to <img> to the "captionedImage" element.
* mapper.on( 'viewToModelPosition', ( evt, data ) => {
* const positionParent = viewPosition.parent;
*
* if ( positionParent.hasClass( 'image-caption' ) ) {
* const viewImg = positionParent.previousSibling;
* const modelImg = data.mapper.toModelElement( viewImg );
*
* data.modelPosition = new ModelPosition( modelImg, viewPosition.offset );
* evt.stop();
* }
* } );
*
* **Note:** the default mapping callback is provided with a `low` priority setting and does not cancel the event, so it is possible to
* attach a custom callback after the default callback and also use `data.modelPosition` calculated by the default callback
* (for example to fix it).
*
* **Note:** the default mapping callback will not fire if `data.modelPosition` is already set.
*
* **Note:** these callbacks are called **very often**. For efficiency reasons, it is advised to use them only when position
* mapping between the given model and view elements is unsolvable by using just elements mapping and default algorithm.
* Also, the condition that checks if special case scenario happened should be as simple as possible.
*
* @event viewToModelPosition
* @param {Object} data Data pipeline object that can store and pass data between callbacks. The callback should add
* `modelPosition` value to that object with calculated {@link module:engine/model/position~Position model position}.
* @param {module:engine/conversion/mapper~Mapper} data.mapper Mapper instance that fired the event.
*/
}
(0,_ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_7__["default"])( Mapper, _ckeditor_ckeditor5_utils_src_emittermixin__WEBPACK_IMPORTED_MODULE_5__["default"] );
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/conversion/modelconsumable.js":
/*!***********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/conversion/modelconsumable.js ***!
\***********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ModelConsumable)
/* harmony export */ });
/* harmony import */ var _model_textproxy__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../model/textproxy */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/textproxy.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/conversion/modelconsumable
*/
/**
* Manages a list of consumable values for the {@link module:engine/model/item~Item model items}.
*
* Consumables are various aspects of the model. A model item can be broken down into separate, single properties that might be
* taken into consideration when converting that item.
*
* `ModelConsumable` is used by {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher} while analyzing the changed
* parts of {@link module:engine/model/document~Document the document}. The added / changed / removed model items are broken down
* into singular properties (the item itself and its attributes). All those parts are saved in `ModelConsumable`. Then,
* during conversion, when the given part of a model item is converted (i.e. the view element has been inserted into the view,
* but without attributes), the consumable value is removed from `ModelConsumable`.
*
* For model items, `ModelConsumable` stores consumable values of one of following types: `insert`, `addattribute:<attributeKey>`,
* `changeattributes:<attributeKey>`, `removeattributes:<attributeKey>`.
*
* In most cases, it is enough to let th {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher}
* gather consumable values, so there is no need to use
* the {@link module:engine/conversion/modelconsumable~ModelConsumable#add add method} directly.
* However, it is important to understand how consumable values can be
* {@link module:engine/conversion/modelconsumable~ModelConsumable#consume consumed}.
* See {@link module:engine/conversion/downcasthelpers default downcast converters} for more information.
*
* Keep in mind that one conversion event may have multiple callbacks (converters) attached to it. Each of those is
* able to convert one or more parts of the model. However, when one of those callbacks actually converts
* something, the others should not, because they would duplicate the results. Using `ModelConsumable` helps to avoid
* this situation, because callbacks should only convert these values that were not yet consumed from `ModelConsumable`.
*
* Consuming multiple values in a single callback:
*
* // Converter for custom `imageBlock` element that might have a `caption` element inside which changes
* // how the image is displayed in the view:
* //
* // Model:
* //
* // [imageBlock]
* // └─ [caption]
* // └─ foo
* //
* // View:
* //
* // <figure>
* // ├─ <img />
* // └─ <caption>
* // └─ foo
* modelConversionDispatcher.on( 'insert:imageBlock', ( evt, data, conversionApi ) => {
* // First, consume the `imageBlock` element.
* conversionApi.consumable.consume( data.item, 'insert' );
*
* // Just create normal image element for the view.
* // Maybe it will be "decorated" later.
* const viewImage = new ViewElement( 'img' );
* const insertPosition = conversionApi.mapper.toViewPosition( data.range.start );
* const viewWriter = conversionApi.writer;
*
* // Check if the `imageBlock` element has children.
* if ( data.item.childCount > 0 ) {
* const modelCaption = data.item.getChild( 0 );
*
* // `modelCaption` insertion change is consumed from consumable values.
* // It will not be converted by other converters, but it's children (probably some text) will be.
* // Through mapping, converters for text will know where to insert contents of `modelCaption`.
* if ( conversionApi.consumable.consume( modelCaption, 'insert' ) ) {
* const viewCaption = new ViewElement( 'figcaption' );
*
* const viewImageHolder = new ViewElement( 'figure', null, [ viewImage, viewCaption ] );
*
* conversionApi.mapper.bindElements( modelCaption, viewCaption );
* conversionApi.mapper.bindElements( data.item, viewImageHolder );
* viewWriter.insert( insertPosition, viewImageHolder );
* }
* } else {
* conversionApi.mapper.bindElements( data.item, viewImage );
* viewWriter.insert( insertPosition, viewImage );
* }
*
* evt.stop();
* } );
*/
class ModelConsumable {
/**
* Creates an empty consumables list.
*/
constructor() {
/**
* Contains list of consumable values.
*
* @private
* @member {Map} module:engine/conversion/modelconsumable~ModelConsumable#_consumable
*/
this._consumable = new Map();
/**
* For each {@link module:engine/model/textproxy~TextProxy} added to `ModelConsumable`, this registry holds a parent
* of that `TextProxy` and the start and end indices of that `TextProxy`. This allows identification of the `TextProxy`
* instances that point to the same part of the model but are different instances. Each distinct `TextProxy`
* is given a unique `Symbol` which is then registered as consumable. This process is transparent for the `ModelConsumable`
* API user because whenever `TextProxy` is added, tested, consumed or reverted, the internal mechanisms of
* `ModelConsumable` translate `TextProxy` to that unique `Symbol`.
*
* @private
* @member {Map} module:engine/conversion/modelconsumable~ModelConsumable#_textProxyRegistry
*/
this._textProxyRegistry = new Map();
}
/**
* Adds a consumable value to the consumables list and links it with a given model item.
*
* modelConsumable.add( modelElement, 'insert' ); // Add `modelElement` insertion change to consumable values.
* modelConsumable.add( modelElement, 'addAttribute:bold' ); // Add `bold` attribute insertion on `modelElement` change.
* modelConsumable.add( modelElement, 'removeAttribute:bold' ); // Add `bold` attribute removal on `modelElement` change.
* modelConsumable.add( modelSelection, 'selection' ); // Add `modelSelection` to consumable values.
* modelConsumable.add( modelRange, 'range' ); // Add `modelRange` to consumable values.
*
* @param {module:engine/model/item~Item|module:engine/model/selection~Selection|module:engine/model/range~Range} item
* Model item, range or selection that has the consumable.
* @param {String} type Consumable type. Will be normalized to a proper form, that is either `<word>` or `<part>:<part>`.
* Second colon and everything after will be cut. Passing event name is a safe and good practice.
*/
add( item, type ) {
type = _normalizeConsumableType( type );
if ( item instanceof _model_textproxy__WEBPACK_IMPORTED_MODULE_0__["default"] ) {
item = this._getSymbolForTextProxy( item );
}
if ( !this._consumable.has( item ) ) {
this._consumable.set( item, new Map() );
}
this._consumable.get( item ).set( type, true );
}
/**
* Removes a given consumable value from a given model item.
*
* modelConsumable.consume( modelElement, 'insert' ); // Remove `modelElement` insertion change from consumable values.
* modelConsumable.consume( modelElement, 'addAttribute:bold' ); // Remove `bold` attribute insertion on `modelElement` change.
* modelConsumable.consume( modelElement, 'removeAttribute:bold' ); // Remove `bold` attribute removal on `modelElement` change.
* modelConsumable.consume( modelSelection, 'selection' ); // Remove `modelSelection` from consumable values.
* modelConsumable.consume( modelRange, 'range' ); // Remove 'modelRange' from consumable values.
*
* @param {module:engine/model/item~Item|module:engine/model/selection~Selection|module:engine/model/range~Range} item
* Model item, range or selection from which consumable will be consumed.
* @param {String} type Consumable type. Will be normalized to a proper form, that is either `<word>` or `<part>:<part>`.
* Second colon and everything after will be cut. Passing event name is a safe and good practice.
* @returns {Boolean} `true` if consumable value was available and was consumed, `false` otherwise.
*/
consume( item, type ) {
type = _normalizeConsumableType( type );
if ( item instanceof _model_textproxy__WEBPACK_IMPORTED_MODULE_0__["default"] ) {
item = this._getSymbolForTextProxy( item );
}
if ( this.test( item, type ) ) {
this._consumable.get( item ).set( type, false );
return true;
} else {
return false;
}
}
/**
* Tests whether there is a consumable value of a given type connected with a given model item.
*
* modelConsumable.test( modelElement, 'insert' ); // Check for `modelElement` insertion change.
* modelConsumable.test( modelElement, 'addAttribute:bold' ); // Check for `bold` attribute insertion on `modelElement` change.
* modelConsumable.test( modelElement, 'removeAttribute:bold' ); // Check for `bold` attribute removal on `modelElement` change.
* modelConsumable.test( modelSelection, 'selection' ); // Check if `modelSelection` is consumable.
* modelConsumable.test( modelRange, 'range' ); // Check if `modelRange` is consumable.
*
* @param {module:engine/model/item~Item|module:engine/model/selection~Selection|module:engine/model/range~Range} item
* Model item, range or selection to be tested.
* @param {String} type Consumable type. Will be normalized to a proper form, that is either `<word>` or `<part>:<part>`.
* Second colon and everything after will be cut. Passing event name is a safe and good practice.
* @returns {null|Boolean} `null` if such consumable was never added, `false` if the consumable values was
* already consumed or `true` if it was added and not consumed yet.
*/
test( item, type ) {
type = _normalizeConsumableType( type );
if ( item instanceof _model_textproxy__WEBPACK_IMPORTED_MODULE_0__["default"] ) {
item = this._getSymbolForTextProxy( item );
}
const itemConsumables = this._consumable.get( item );
if ( itemConsumables === undefined ) {
return null;
}
const value = itemConsumables.get( type );
if ( value === undefined ) {
return null;
}
return value;
}
/**
* Reverts consuming of a consumable value.
*
* modelConsumable.revert( modelElement, 'insert' ); // Revert consuming `modelElement` insertion change.
* modelConsumable.revert( modelElement, 'addAttribute:bold' ); // Revert consuming `bold` attribute insert from `modelElement`.
* modelConsumable.revert( modelElement, 'removeAttribute:bold' ); // Revert consuming `bold` attribute remove from `modelElement`.
* modelConsumable.revert( modelSelection, 'selection' ); // Revert consuming `modelSelection`.
* modelConsumable.revert( modelRange, 'range' ); // Revert consuming `modelRange`.
*
* @param {module:engine/model/item~Item|module:engine/model/selection~Selection|module:engine/model/range~Range} item
* Model item, range or selection to be reverted.
* @param {String} type Consumable type.
* @returns {null|Boolean} `true` if consumable has been reversed, `false` otherwise. `null` if the consumable has
* never been added.
*/
revert( item, type ) {
type = _normalizeConsumableType( type );
if ( item instanceof _model_textproxy__WEBPACK_IMPORTED_MODULE_0__["default"] ) {
item = this._getSymbolForTextProxy( item );
}
const test = this.test( item, type );
if ( test === false ) {
this._consumable.get( item ).set( type, true );
return true;
} else if ( test === true ) {
return false;
}
return null;
}
/**
* Verifies if all events from the specified group were consumed.
*
* @param {String} eventGroup The events group to verify.
*/
verifyAllConsumed( eventGroup ) {
const items = [];
for ( const [ item, consumables ] of this._consumable ) {
for ( const [ event, canConsume ] of consumables ) {
const eventPrefix = event.split( ':' )[ 0 ];
if ( canConsume && eventGroup == eventPrefix ) {
items.push( {
event,
item: item.name || item.description
} );
}
}
}
if ( items.length ) {
/**
* Some of the {@link module:engine/model/item~Item model items} were not consumed while downcasting the model to view.
*
* This might be the effect of:
*
* * A missing converter for some model elements. Make sure that you registered downcast converters for all model elements.
* * A custom converter that does not consume converted items. Make sure that you
* {@link module:engine/conversion/modelconsumable~ModelConsumable#consume consumed} all model elements that you converted
* from the model to the view.
* * A custom converter that called `event.stop()`. When providing a custom converter, keep in mind that you should not stop
* the event. If you stop it then the default converter at the `lowest` priority will not trigger the conversion of this node's
* attributes and child nodes.
*
* @error conversion-model-consumable-not-consumed
* @param {Array.<module:engine/model/item~Item>} items Items that were not consumed.
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_1__["default"]( 'conversion-model-consumable-not-consumed', null, { items } );
}
}
/**
* Gets a unique symbol for the passed {@link module:engine/model/textproxy~TextProxy} instance. All `TextProxy` instances that
* have same parent, same start index and same end index will get the same symbol.
*
* Used internally to correctly consume `TextProxy` instances.
*
* @protected
* @param {module:engine/model/textproxy~TextProxy} textProxy `TextProxy` instance to get a symbol for.
* @returns {Symbol} Symbol representing all equal instances of `TextProxy`.
*/
_getSymbolForTextProxy( textProxy ) {
let symbol = null;
const startMap = this._textProxyRegistry.get( textProxy.startOffset );
if ( startMap ) {
const endMap = startMap.get( textProxy.endOffset );
if ( endMap ) {
symbol = endMap.get( textProxy.parent );
}
}
if ( !symbol ) {
symbol = this._addSymbolForTextProxy( textProxy );
}
return symbol;
}
/**
* Adds a symbol for the given {@link module:engine/model/textproxy~TextProxy} instance.
*
* Used internally to correctly consume `TextProxy` instances.
*
* @private
* @param {module:engine/model/textproxy~TextProxy} textProxy Text proxy instance.
* @returns {Symbol} Symbol generated for given `TextProxy`.
*/
_addSymbolForTextProxy( textProxy ) {
const start = textProxy.startOffset;
const end = textProxy.endOffset;
const parent = textProxy.parent;
const symbol = Symbol( '$textProxy:' + textProxy.data );
let startMap, endMap;
startMap = this._textProxyRegistry.get( start );
if ( !startMap ) {
startMap = new Map();
this._textProxyRegistry.set( start, startMap );
}
endMap = startMap.get( end );
if ( !endMap ) {
endMap = new Map();
startMap.set( end, endMap );
}
endMap.set( parent, symbol );
return symbol;
}
}
// Returns a normalized consumable type name from the given string. A normalized consumable type name is a string that has
// at most one colon, for example: `insert` or `addMarker:highlight`. If a string to normalize has more "parts" (more colons),
// the further parts are dropped, for example: `addattribute:bold:$text` -> `addattributes:bold`.
//
// @param {String} type Consumable type.
// @returns {String} Normalized consumable type.
function _normalizeConsumableType( type ) {
const parts = type.split( ':' );
// For inserts allow passing event name, it's stored in the context of a specified element so the element name is not needed.
if ( parts[ 0 ] == 'insert' ) {
return parts[ 0 ];
}
// Markers are identified by the whole name (otherwise we would consume the whole markers group).
if ( parts[ 0 ] == 'addMarker' || parts[ 0 ] == 'removeMarker' ) {
return type;
}
return parts.length > 1 ? parts[ 0 ] + ':' + parts[ 1 ] : parts[ 0 ];
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/conversion/upcastdispatcher.js":
/*!************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/conversion/upcastdispatcher.js ***!
\************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ UpcastDispatcher)
/* harmony export */ });
/* harmony import */ var _viewconsumable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./viewconsumable */ "./node_modules/@ckeditor/ckeditor5-engine/src/conversion/viewconsumable.js");
/* harmony import */ var _model_range__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../model/range */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/range.js");
/* harmony import */ var _model_position__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../model/position */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/position.js");
/* harmony import */ var _model_schema__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../model/schema */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/schema.js");
/* harmony import */ var _model_utils_autoparagraphing__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../model/utils/autoparagraphing */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/utils/autoparagraphing.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_emittermixin__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/emittermixin */ "./node_modules/@ckeditor/ckeditor5-utils/src/emittermixin.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/mix */ "./node_modules/@ckeditor/ckeditor5-utils/src/mix.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/conversion/upcastdispatcher
*/
/**
* Upcast dispatcher is a central point of the view-to-model conversion, which is a process of
* converting a given {@link module:engine/view/documentfragment~DocumentFragment view document fragment} or
* {@link module:engine/view/element~Element view element} into a correct model structure.
*
* During the conversion process, the dispatcher fires events for all {@link module:engine/view/node~Node view nodes}
* from the converted view document fragment.
* Special callbacks called "converters" should listen to these events in order to convert the view nodes.
*
* The second parameter of the callback is the `data` object with the following properties:
*
* * `data.viewItem` contains a {@link module:engine/view/node~Node view node} or a
* {@link module:engine/view/documentfragment~DocumentFragment view document fragment}
* that is converted at the moment and might be handled by the callback.
* * `data.modelRange` is used to point to the result
* of the current conversion (e.g. the element that is being inserted)
* and is always a {@link module:engine/model/range~Range} when the conversion succeeds.
* * `data.modelCursor` is a {@link module:engine/model/position~Position position} on which the converter should insert
* the newly created items.
*
* The third parameter of the callback is an instance of {@link module:engine/conversion/upcastdispatcher~UpcastConversionApi}
* which provides additional tools for converters.
*
* You can read more about conversion in the {@glink framework/guides/deep-dive/conversion/upcast Upcast conversion} guide.
*
* Examples of event-based converters:
*
* // A converter for links (<a>).
* editor.data.upcastDispatcher.on( 'element:a', ( evt, data, conversionApi ) => {
* if ( conversionApi.consumable.consume( data.viewItem, { name: true, attributes: [ 'href' ] } ) ) {
* // The <a> element is inline and is represented by an attribute in the model.
* // This is why you need to convert only children.
* const { modelRange } = conversionApi.convertChildren( data.viewItem, data.modelCursor );
*
* for ( let item of modelRange.getItems() ) {
* if ( conversionApi.schema.checkAttribute( item, 'linkHref' ) ) {
* conversionApi.writer.setAttribute( 'linkHref', data.viewItem.getAttribute( 'href' ), item );
* }
* }
* }
* } );
*
* // Convert <p> element's font-size style.
* // Note: You should use a low-priority observer in order to ensure that
* // it is executed after the element-to-element converter.
* editor.data.upcastDispatcher.on( 'element:p', ( evt, data, conversionApi ) => {
* const { consumable, schema, writer } = conversionApi;
*
* if ( !consumable.consume( data.viewItem, { style: 'font-size' } ) ) {
* return;
* }
*
* const fontSize = data.viewItem.getStyle( 'font-size' );
*
* // Do not go for the model element after data.modelCursor because it might happen
* // that a single view element was converted to multiple model elements. Get all of them.
* for ( const item of data.modelRange.getItems( { shallow: true } ) ) {
* if ( schema.checkAttribute( item, 'fontSize' ) ) {
* writer.setAttribute( 'fontSize', fontSize, item );
* }
* }
* }, { priority: 'low' } );
*
* // Convert all elements which have no custom converter into a paragraph (autoparagraphing).
* editor.data.upcastDispatcher.on( 'element', ( evt, data, conversionApi ) => {
* // Check if an element can be converted.
* if ( !conversionApi.consumable.test( data.viewItem, { name: data.viewItem.name } ) ) {
* // When an element is already consumed by higher priority converters, do nothing.
* return;
* }
*
* const paragraph = conversionApi.writer.createElement( 'paragraph' );
*
* // Try to safely insert a paragraph at the model cursor - it will find an allowed parent for the current element.
* if ( !conversionApi.safeInsert( paragraph, data.modelCursor ) ) {
* // When an element was not inserted, it means that you cannot insert a paragraph at this position.
* return;
* }
*
* // Consume the inserted element.
* conversionApi.consumable.consume( data.viewItem, { name: data.viewItem.name } ) );
*
* // Convert the children to a paragraph.
* const { modelRange } = conversionApi.convertChildren( data.viewItem, paragraph ) );
*
* // Update `modelRange` and `modelCursor` in the `data` as a conversion result.
* conversionApi.updateConversionResult( paragraph, data );
* }, { priority: 'low' } );
*
* @mixes module:utils/emittermixin~EmitterMixin
* @fires viewCleanup
* @fires element
* @fires text
* @fires documentFragment
*/
class UpcastDispatcher {
/**
* Creates an upcast dispatcher that operates using the passed API.
*
* @see module:engine/conversion/upcastdispatcher~UpcastConversionApi
* @param {Object} [conversionApi] Additional properties for an interface that will be passed to events fired
* by the upcast dispatcher.
*/
constructor( conversionApi = {} ) {
/**
* The list of elements that were created during splitting.
*
* After the conversion process, the list is cleared.
*
* @private
* @type {Map.<module:engine/model/element~Element,Array.<module:engine/model/element~Element>>}
*/
this._splitParts = new Map();
/**
* The list of cursor parent elements that were created during splitting.
*
* After the conversion process the list is cleared.
*
* @private
* @type {Map.<module:engine/model/element~Element,Array.<module:engine/model/element~Element>>}
*/
this._cursorParents = new Map();
/**
* The position in the temporary structure where the converted content is inserted. The structure reflects the context of
* the target position where the content will be inserted. This property is built based on the context parameter of the
* convert method.
*
* @private
* @type {module:engine/model/position~Position|null}
*/
this._modelCursor = null;
/**
* An interface passed by the dispatcher to the event callbacks.
*
* @member {module:engine/conversion/upcastdispatcher~UpcastConversionApi}
*/
this.conversionApi = Object.assign( {}, conversionApi );
// The below methods are bound to this `UpcastDispatcher` instance and set on `conversionApi`.
// This way only a part of `UpcastDispatcher` API is exposed.
this.conversionApi.convertItem = this._convertItem.bind( this );
this.conversionApi.convertChildren = this._convertChildren.bind( this );
this.conversionApi.safeInsert = this._safeInsert.bind( this );
this.conversionApi.updateConversionResult = this._updateConversionResult.bind( this );
// Advanced API - use only if custom position handling is needed.
this.conversionApi.splitToAllowedParent = this._splitToAllowedParent.bind( this );
this.conversionApi.getSplitParts = this._getSplitParts.bind( this );
}
/**
* Starts the conversion process. The entry point for the conversion.
*
* @fires element
* @fires text
* @fires documentFragment
* @param {module:engine/view/documentfragment~DocumentFragment|module:engine/view/element~Element} viewItem
* The part of the view to be converted.
* @param {module:engine/model/writer~Writer} writer An instance of the model writer.
* @param {module:engine/model/schema~SchemaContextDefinition} [context=['$root']] Elements will be converted according to this context.
* @returns {module:engine/model/documentfragment~DocumentFragment} Model data that is the result of the conversion process
* wrapped in `DocumentFragment`. Converted marker elements will be set as the document fragment's
* {@link module:engine/model/documentfragment~DocumentFragment#markers static markers map}.
*/
convert( viewItem, writer, context = [ '$root' ] ) {
this.fire( 'viewCleanup', viewItem );
// Create context tree and set position in the top element.
// Items will be converted according to this position.
this._modelCursor = createContextTree( context, writer );
// Store writer in conversion as a conversion API
// to be sure that conversion process will use the same batch.
this.conversionApi.writer = writer;
// Create consumable values list for conversion process.
this.conversionApi.consumable = _viewconsumable__WEBPACK_IMPORTED_MODULE_0__["default"].createFrom( viewItem );
// Custom data stored by converter for conversion process.
this.conversionApi.store = {};
// Do the conversion.
const { modelRange } = this._convertItem( viewItem, this._modelCursor );
// Conversion result is always a document fragment so let's create it.
const documentFragment = writer.createDocumentFragment();
// When there is a conversion result.
if ( modelRange ) {
// Remove all empty elements that were create while splitting.
this._removeEmptyElements();
// Move all items that were converted in context tree to the document fragment.
for ( const item of Array.from( this._modelCursor.parent.getChildren() ) ) {
writer.append( item, documentFragment );
}
// Extract temporary markers elements from model and set as static markers collection.
documentFragment.markers = extractMarkersFromModelFragment( documentFragment, writer );
}
// Clear context position.
this._modelCursor = null;
// Clear split elements & parents lists.
this._splitParts.clear();
this._cursorParents.clear();
// Clear conversion API.
this.conversionApi.writer = null;
this.conversionApi.store = null;
// Return fragment as conversion result.
return documentFragment;
}
/**
* @private
* @see module:engine/conversion/upcastdispatcher~UpcastConversionApi#convertItem
*/
_convertItem( viewItem, modelCursor ) {
const data = Object.assign( { viewItem, modelCursor, modelRange: null } );
if ( viewItem.is( 'element' ) ) {
this.fire( 'element:' + viewItem.name, data, this.conversionApi );
} else if ( viewItem.is( '$text' ) ) {
this.fire( 'text', data, this.conversionApi );
} else {
this.fire( 'documentFragment', data, this.conversionApi );
}
// Handle incorrect conversion result.
if ( data.modelRange && !( data.modelRange instanceof _model_range__WEBPACK_IMPORTED_MODULE_1__["default"] ) ) {
/**
* Incorrect conversion result was dropped.
*
* {@link module:engine/model/range~Range Model range} should be a conversion result.
*
* @error view-conversion-dispatcher-incorrect-result
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_5__["default"]( 'view-conversion-dispatcher-incorrect-result', this );
}
return { modelRange: data.modelRange, modelCursor: data.modelCursor };
}
/**
* @private
* @see module:engine/conversion/upcastdispatcher~UpcastConversionApi#convertChildren
*/
_convertChildren( viewItem, elementOrModelCursor ) {
let nextModelCursor = elementOrModelCursor.is( 'position' ) ?
elementOrModelCursor : _model_position__WEBPACK_IMPORTED_MODULE_2__["default"]._createAt( elementOrModelCursor, 0 );
const modelRange = new _model_range__WEBPACK_IMPORTED_MODULE_1__["default"]( nextModelCursor );
for ( const viewChild of Array.from( viewItem.getChildren() ) ) {
const result = this._convertItem( viewChild, nextModelCursor );
if ( result.modelRange instanceof _model_range__WEBPACK_IMPORTED_MODULE_1__["default"] ) {
modelRange.end = result.modelRange.end;
nextModelCursor = result.modelCursor;
}
}
return { modelRange, modelCursor: nextModelCursor };
}
/**
* @private
* @see module:engine/conversion/upcastdispatcher~UpcastConversionApi#safeInsert
*/
_safeInsert( modelElement, position ) {
// Find allowed parent for element that we are going to insert.
// If current parent does not allow to insert element but one of the ancestors does
// then split nodes to allowed parent.
const splitResult = this._splitToAllowedParent( modelElement, position );
// When there is no split result it means that we can't insert element to model tree, so let's skip it.
if ( !splitResult ) {
return false;
}
// Insert element on allowed position.
this.conversionApi.writer.insert( modelElement, splitResult.position );
return true;
}
/**
* @private
* @see module:engine/conversion/upcastdispatcher~UpcastConversionApi#updateConversionResult
*/
_updateConversionResult( modelElement, data ) {
const parts = this._getSplitParts( modelElement );
const writer = this.conversionApi.writer;
// Set conversion result range - only if not set already.
if ( !data.modelRange ) {
data.modelRange = writer.createRange(
writer.createPositionBefore( modelElement ),
writer.createPositionAfter( parts[ parts.length - 1 ] )
);
}
const savedCursorParent = this._cursorParents.get( modelElement );
// Now we need to check where the `modelCursor` should be.
if ( savedCursorParent ) {
// If we split parent to insert our element then we want to continue conversion in the new part of the split parent.
//
// before: <allowed><notAllowed>foo[]</notAllowed></allowed>
// after: <allowed><notAllowed>foo</notAllowed> <converted></converted> <notAllowed>[]</notAllowed></allowed>
data.modelCursor = writer.createPositionAt( savedCursorParent, 0 );
} else {
// Otherwise just continue after inserted element.
data.modelCursor = data.modelRange.end;
}
}
/**
* @private
* @see module:engine/conversion/upcastdispatcher~UpcastConversionApi#splitToAllowedParent
*/
_splitToAllowedParent( node, modelCursor ) {
const { schema, writer } = this.conversionApi;
// Try to find allowed parent.
let allowedParent = schema.findAllowedParent( modelCursor, node );
if ( allowedParent ) {
// When current position parent allows to insert node then return this position.
if ( allowedParent === modelCursor.parent ) {
return { position: modelCursor };
}
// When allowed parent is in context tree (it's outside the converted tree).
if ( this._modelCursor.parent.getAncestors().includes( allowedParent ) ) {
allowedParent = null;
}
}
if ( !allowedParent ) {
// Check if the node wrapped with a paragraph would be accepted by the schema.
if ( !(0,_model_utils_autoparagraphing__WEBPACK_IMPORTED_MODULE_4__.isParagraphable)( modelCursor, node, schema ) ) {
return null;
}
return {
position: (0,_model_utils_autoparagraphing__WEBPACK_IMPORTED_MODULE_4__.wrapInParagraph)( modelCursor, writer )
};
}
// Split element to allowed parent.
const splitResult = this.conversionApi.writer.split( modelCursor, allowedParent );
// Using the range returned by `model.Writer#split`, we will pair original elements with their split parts.
//
// The range returned from the writer spans "over the split" or, precisely saying, from the end of the original element (the one
// that got split) to the beginning of the other part of that element:
//
// <limit><a><b><c>X[]Y</c></b><a></limit> ->
// <limit><a><b><c>X[</c></b></a><a><b><c>]Y</c></b></a>
//
// After the split there cannot be any full node between the positions in `splitRange`. The positions are touching.
// Also, because of how splitting works, it is easy to notice, that "closing tags" are in the reverse order than "opening tags".
// Also, since we split all those elements, each of them has to have the other part.
//
// With those observations in mind, we will pair the original elements with their split parts by saving "closing tags" and matching
// them with "opening tags" in the reverse order. For that we can use a stack.
const stack = [];
for ( const treeWalkerValue of splitResult.range.getWalker() ) {
if ( treeWalkerValue.type == 'elementEnd' ) {
stack.push( treeWalkerValue.item );
} else {
// There should not be any text nodes after the element is split, so the only other value is `elementStart`.
const originalPart = stack.pop();
const splitPart = treeWalkerValue.item;
this._registerSplitPair( originalPart, splitPart );
}
}
const cursorParent = splitResult.range.end.parent;
this._cursorParents.set( node, cursorParent );
return {
position: splitResult.position,
cursorParent
};
}
/**
* Registers that a `splitPart` element is a split part of the `originalPart` element.
*
* The data set by this method is used by {@link #_getSplitParts} and {@link #_removeEmptyElements}.
*
* @private
* @param {module:engine/model/element~Element} originalPart
* @param {module:engine/model/element~Element} splitPart
*/
_registerSplitPair( originalPart, splitPart ) {
if ( !this._splitParts.has( originalPart ) ) {
this._splitParts.set( originalPart, [ originalPart ] );
}
const list = this._splitParts.get( originalPart );
this._splitParts.set( splitPart, list );
list.push( splitPart );
}
/**
* @private
* @see module:engine/conversion/upcastdispatcher~UpcastConversionApi#getSplitParts
*/
_getSplitParts( element ) {
let parts;
if ( !this._splitParts.has( element ) ) {
parts = [ element ];
} else {
parts = this._splitParts.get( element );
}
return parts;
}
/**
* Checks if there are any empty elements created while splitting and removes them.
*
* This method works recursively to re-check empty elements again after at least one element was removed in the initial call,
* as some elements might have become empty after other empty elements were removed from them.
*
* @private
*/
_removeEmptyElements() {
let anyRemoved = false;
for ( const element of this._splitParts.keys() ) {
if ( element.isEmpty ) {
this.conversionApi.writer.remove( element );
this._splitParts.delete( element );
anyRemoved = true;
}
}
if ( anyRemoved ) {
this._removeEmptyElements();
}
}
/**
* Fired before the first conversion event, at the beginning of the upcast (view-to-model conversion) process.
*
* @event viewCleanup
* @param {module:engine/view/documentfragment~DocumentFragment|module:engine/view/element~Element}
* viewItem A part of the view to be converted.
*/
/**
* Fired when an {@link module:engine/view/element~Element} is converted.
*
* `element` is a namespace event for a class of events. Names of actually called events follow the pattern of
* `element:<elementName>` where `elementName` is the name of the converted element. This way listeners may listen to
* a conversion of all or just specific elements.
*
* @event element
* @param {module:engine/conversion/upcastdispatcher~UpcastConversionData} data The conversion data. Keep in mind that this object is
* shared by reference between all callbacks that will be called. This means that callbacks can override values if needed, and these
* values will be available in other callbacks.
* @param {module:engine/conversion/upcastdispatcher~UpcastConversionApi} conversionApi Conversion utilities to be used by the
* callback.
*/
/**
* Fired when a {@link module:engine/view/text~Text} is converted.
*
* @event text
* @see #event:element
*/
/**
* Fired when a {@link module:engine/view/documentfragment~DocumentFragment} is converted.
*
* @event documentFragment
* @see #event:element
*/
}
(0,_ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_7__["default"])( UpcastDispatcher, _ckeditor_ckeditor5_utils_src_emittermixin__WEBPACK_IMPORTED_MODULE_6__["default"] );
// Traverses given model item and searches elements which marks marker range. Found element is removed from
// DocumentFragment but path of this element is stored in a Map which is then returned.
//
// @param {module:engine/view/documentfragment~DocumentFragment|module:engine/view/node~Node} modelItem Fragment of model.
// @returns {Map<String, module:engine/model/range~Range>} List of static markers.
function extractMarkersFromModelFragment( modelItem, writer ) {
const markerElements = new Set();
const markers = new Map();
// Create ModelTreeWalker.
const range = _model_range__WEBPACK_IMPORTED_MODULE_1__["default"]._createIn( modelItem ).getItems();
// Walk through DocumentFragment and collect marker elements.
for ( const item of range ) {
// Check if current element is a marker.
if ( item.name == '$marker' ) {
markerElements.add( item );
}
}
// Walk through collected marker elements store its path and remove its from the DocumentFragment.
for ( const markerElement of markerElements ) {
const markerName = markerElement.getAttribute( 'data-name' );
const currentPosition = writer.createPositionBefore( markerElement );
// When marker of given name is not stored it means that we have found the beginning of the range.
if ( !markers.has( markerName ) ) {
markers.set( markerName, new _model_range__WEBPACK_IMPORTED_MODULE_1__["default"]( currentPosition.clone() ) );
// Otherwise is means that we have found end of the marker range.
} else {
markers.get( markerName ).end = currentPosition.clone();
}
// Remove marker element from DocumentFragment.
writer.remove( markerElement );
}
return markers;
}
// Creates model fragment according to given context and returns position in the bottom (the deepest) element.
function createContextTree( contextDefinition, writer ) {
let position;
for ( const item of new _model_schema__WEBPACK_IMPORTED_MODULE_3__.SchemaContext( contextDefinition ) ) {
const attributes = {};
for ( const key of item.getAttributeKeys() ) {
attributes[ key ] = item.getAttribute( key );
}
const current = writer.createElement( item.name, attributes );
if ( position ) {
writer.append( current, position );
}
position = _model_position__WEBPACK_IMPORTED_MODULE_2__["default"]._createAt( current, 0 );
}
return position;
}
/**
* A set of conversion utilities available as the third parameter of the
* {@link module:engine/conversion/upcastdispatcher~UpcastDispatcher upcast dispatcher}'s events.
*
* @interface module:engine/conversion/upcastdispatcher~UpcastConversionApi
*/
/**
* Starts the conversion of a given item by firing an appropriate event.
*
* Every fired event is passed (as the first parameter) an object with the `modelRange` property. Every event may set and/or
* modify that property. When all callbacks are done, the final value of the `modelRange` property is returned by this method.
* The `modelRange` must be a {@link module:engine/model/range~Range model range} or `null` (as set by default).
*
* @method #convertItem
* @fires module:engine/conversion/upcastdispatcher~UpcastDispatcher#event:element
* @fires module:engine/conversion/upcastdispatcher~UpcastDispatcher#event:text
* @fires module:engine/conversion/upcastdispatcher~UpcastDispatcher#event:documentFragment
* @param {module:engine/view/item~Item} viewItem Item to convert.
* @param {module:engine/model/position~Position} modelCursor The conversion position.
* @returns {Object} result The conversion result.
* @returns {module:engine/model/range~Range|null} result.modelRange The model range containing the result of the item conversion,
* created and modified by callbacks attached to the fired event, or `null` if the conversion result was incorrect.
* @returns {module:engine/model/position~Position} result.modelCursor The position where the conversion should be continued.
*/
/**
* Starts the conversion of all children of a given item by firing appropriate events for all the children.
*
* @method #convertChildren
* @fires module:engine/conversion/upcastdispatcher~UpcastDispatcher#event:element
* @fires module:engine/conversion/upcastdispatcher~UpcastDispatcher#event:text
* @fires module:engine/conversion/upcastdispatcher~UpcastDispatcher#event:documentFragment
* @param {module:engine/view/item~Item} viewItem An element whose children should be converted.
* @param {module:engine/model/position~Position|module:engine/model/element~Element} positionOrElement A position or an element of
* the conversion.
* @returns {Object} result The conversion result.
* @returns {module:engine/model/range~Range} result.modelRange The model range containing the results of the conversion of all children
* of the given item. When no child was converted, the range is collapsed.
* @returns {module:engine/model/position~Position} result.modelCursor The position where the conversion should be continued.
*/
/**
* Safely inserts an element to the document, checking the {@link module:engine/model/schema~Schema schema} to find an allowed parent for
* an element that you are going to insert, starting from the given position. If the current parent does not allow to insert the element
* but one of the ancestors does, then splits the nodes to allowed parent.
*
* If the schema allows to insert the node in a given position, nothing is split.
*
* If it was not possible to find an allowed parent, `false` is returned and nothing is split.
*
* Otherwise, ancestors are split.
*
* For instance, if `<imageBlock>` is not allowed in `<paragraph>` but is allowed in `$root`:
*
* <paragraph>foo[]bar</paragraph>
*
* -> safe insert for `<imageBlock>` will split ->
*
* <paragraph>foo</paragraph>[]<paragraph>bar</paragraph>
*
* Example usage:
*
* const myElement = conversionApi.writer.createElement( 'myElement' );
*
* if ( !conversionApi.safeInsert( myElement, data.modelCursor ) ) {
* return;
* }
*
* The split result is saved and {@link #updateConversionResult} should be used to update the
* {@link module:engine/conversion/upcastdispatcher~UpcastConversionData conversion data}.
*
* @method #safeInsert
* @param {module:engine/model/node~Node} node The node to insert.
* @param {module:engine/model/position~Position} position The position where an element is going to be inserted.
* @returns {Boolean} The split result. If it was not possible to find an allowed position, `false` is returned.
*/
/**
* Updates the conversion result and sets a proper {@link module:engine/conversion/upcastdispatcher~UpcastConversionData#modelRange} and
* the next {@link module:engine/conversion/upcastdispatcher~UpcastConversionData#modelCursor} after the conversion.
* Used together with {@link #safeInsert}, it enables you to easily convert elements without worrying if the node was split
* during the conversion of its children.
*
* A usage example in converter code:
*
* const myElement = conversionApi.writer.createElement( 'myElement' );
*
* if ( !conversionApi.safeInsert( myElement, data.modelCursor ) ) {
* return;
* }
*
* // Children conversion may split `myElement`.
* conversionApi.convertChildren( data.viewItem, myElement );
*
* conversionApi.updateConversionResult( myElement, data );
*
* @method #updateConversionResult
* @param {module:engine/model/element~Element} element
* @param {module:engine/conversion/upcastdispatcher~UpcastConversionData} data Conversion data.
* @param {module:engine/conversion/upcastdispatcher~UpcastConversionApi} conversionApi Conversion utilities to be used by the callback.
*/
/**
* Checks the {@link module:engine/model/schema~Schema schema} to find an allowed parent for an element that is going to be inserted
* starting from the given position. If the current parent does not allow inserting an element but one of the ancestors does, the method
* splits nodes to allowed parent.
*
* If the schema allows inserting the node in the given position, nothing is split and an object with that position is returned.
*
* If it was not possible to find an allowed parent, `null` is returned and nothing is split.
*
* Otherwise, ancestors are split and an object with a position and the copy of the split element is returned.
*
* For instance, if `<imageBlock>` is not allowed in `<paragraph>` but is allowed in `$root`:
*
* <paragraph>foo[]bar</paragraph>
*
* -> split for `<imageBlock>` ->
*
* <paragraph>foo</paragraph>[]<paragraph>bar</paragraph>
*
* In the example above, the position between `<paragraph>` elements will be returned as `position` and the second `paragraph`
* as `cursorParent`.
*
* **Note:** This is an advanced method. For most cases {@link #safeInsert} and {@link #updateConversionResult} should be used.
*
* @method #splitToAllowedParent
* @param {module:engine/model/position~Position} position The position where the element is going to be inserted.
* @param {module:engine/model/node~Node} node The node to insert.
* @returns {Object|null} The split result. If it was not possible to find an allowed position, `null` is returned.
* @returns {module:engine/model/position~Position} The position between split elements.
* @returns {module:engine/model/element~Element} [cursorParent] The element inside which the cursor should be placed to
* continue the conversion. When the element is not defined it means that there was no split.
*/
/**
* Returns all the split parts of the given `element` that were created during upcasting through using {@link #splitToAllowedParent}.
* It enables you to easily track these elements and continue processing them after they are split during the conversion of their children.
*
* <paragraph>Foo<imageBlock />bar<imageBlock />baz</paragraph> ->
* <paragraph>Foo</paragraph><imageBlock /><paragraph>bar</paragraph><imageBlock /><paragraph>baz</paragraph>
*
* For a reference to any of above paragraphs, the function will return all three paragraphs (the original element included),
* sorted in the order of their creation (the original element is the first one).
*
* If the given `element` was not split, an array with a single element is returned.
*
* A usage example in the converter code:
*
* const myElement = conversionApi.writer.createElement( 'myElement' );
*
* // Children conversion may split `myElement`.
* conversionApi.convertChildren( data.viewItem, data.modelCursor );
*
* const splitParts = conversionApi.getSplitParts( myElement );
* const lastSplitPart = splitParts[ splitParts.length - 1 ];
*
* // Setting `data.modelRange` basing on split parts:
* data.modelRange = conversionApi.writer.createRange(
* conversionApi.writer.createPositionBefore( myElement ),
* conversionApi.writer.createPositionAfter( lastSplitPart )
* );
*
* // Setting `data.modelCursor` to continue after the last split element:
* data.modelCursor = conversionApi.writer.createPositionAfter( lastSplitPart );
*
* **Tip:** If you are unable to get a reference to the original element (for example because the code is split into multiple converters
* or even classes) but it has already been converted, you may want to check the first element in `data.modelRange`. This is a common
* situation if an attribute converter is separated from an element converter.
*
* **Note:** This is an advanced method. For most cases {@link #safeInsert} and {@link #updateConversionResult} should be used.
*
* @method #getSplitParts
* @param {module:engine/model/element~Element} element
* @returns {Array.<module:engine/model/element~Element>}
*/
/**
* Stores information about what parts of the processed view item are still waiting to be handled. After a piece of view item
* was converted, an appropriate consumable value should be
* {@link module:engine/conversion/viewconsumable~ViewConsumable#consume consumed}.
*
* @member {module:engine/conversion/viewconsumable~ViewConsumable} #consumable
*/
/**
* Custom data stored by converters for the conversion process. Custom properties of this object can be defined and use to
* pass parameters between converters.
*
* The difference between this property and the `data` parameter of
* {@link module:engine/conversion/upcastdispatcher~UpcastDispatcher#event:element} is that the `data` parameters allow you
* to pass parameters within a single event and `store` within the whole conversion.
*
* @member {Object} #store
*/
/**
* The model's schema instance.
*
* @member {module:engine/model/schema~Schema} #schema
*/
/**
* The {@link module:engine/model/writer~Writer} instance used to manipulate the data during conversion.
*
* @member {module:engine/model/writer~Writer} #writer
*/
/**
* Conversion data.
*
* **Note:** Keep in mind that this object is shared by reference between all conversion callbacks that will be called.
* This means that callbacks can override values if needed, and these values will be available in other callbacks.
*
* @typedef {Object} module:engine/conversion/upcastdispatcher~UpcastConversionData
*
* @property {module:engine/view/item~Item} viewItem The converted item.
* @property {module:engine/model/position~Position} modelCursor The position where the converter should start changes.
* Change this value for the next converter to tell where the conversion should continue.
* @property {module:engine/model/range~Range} [modelRange] The current state of conversion result. Every change to
* the converted element should be reflected by setting or modifying this property.
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/conversion/upcasthelpers.js":
/*!*********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/conversion/upcasthelpers.js ***!
\*********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "convertSelectionChange": () => (/* binding */ convertSelectionChange),
/* harmony export */ "convertText": () => (/* binding */ convertText),
/* harmony export */ "convertToModelFragment": () => (/* binding */ convertToModelFragment),
/* harmony export */ "default": () => (/* binding */ UpcastHelpers)
/* harmony export */ });
/* harmony import */ var _view_matcher__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../view/matcher */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/matcher.js");
/* harmony import */ var _conversionhelpers__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./conversionhelpers */ "./node_modules/@ckeditor/ckeditor5-engine/src/conversion/conversionhelpers.js");
/* harmony import */ var lodash_es__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! lodash-es */ "./node_modules/lodash-es/cloneDeep.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_priorities__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/priorities */ "./node_modules/@ckeditor/ckeditor5-utils/src/priorities.js");
/* harmony import */ var _model_utils_autoparagraphing__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../model/utils/autoparagraphing */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/utils/autoparagraphing.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* Contains the {@link module:engine/view/view view} to {@link module:engine/model/model model} converters for
* {@link module:engine/conversion/upcastdispatcher~UpcastDispatcher}.
*
* @module engine/conversion/upcasthelpers
*/
/**
* Upcast conversion helper functions.
*
* Learn more about {@glink framework/guides/deep-dive/conversion/upcast upcast helpers}.
*
* @extends module:engine/conversion/conversionhelpers~ConversionHelpers
*/
class UpcastHelpers extends _conversionhelpers__WEBPACK_IMPORTED_MODULE_1__["default"] {
/**
* View element to model element conversion helper.
*
* This conversion results in creating a model element. For example,
* view `<p>Foo</p>` becomes `<paragraph>Foo</paragraph>` in the model.
*
* Keep in mind that the element will be inserted only if it is allowed
* by {@link module:engine/model/schema~Schema schema} configuration.
*
* editor.conversion.for( 'upcast' ).elementToElement( {
* view: 'p',
* model: 'paragraph'
* } );
*
* editor.conversion.for( 'upcast' ).elementToElement( {
* view: 'p',
* model: 'paragraph',
* converterPriority: 'high'
* } );
*
* editor.conversion.for( 'upcast' ).elementToElement( {
* view: {
* name: 'p',
* classes: 'fancy'
* },
* model: 'fancyParagraph'
* } );
*
* editor.conversion.for( 'upcast' ).elementToElement( {
* view: {
* name: 'p',
* classes: 'heading'
* },
* model: ( viewElement, conversionApi ) => {
* const modelWriter = conversionApi.writer;
*
* return modelWriter.createElement( 'heading', { level: viewElement.getAttribute( 'data-level' ) } );
* }
* } );
*
* See {@link module:engine/conversion/conversion~Conversion#for `conversion.for()`} to learn how to add a converter
* to the conversion process.
*
* @method #elementToElement
* @param {Object} config Conversion configuration.
* @param {module:engine/view/matcher~MatcherPattern} [config.view] Pattern matching all view elements which should be converted. If not
* set, the converter will fire for every view element.
* @param {String|module:engine/model/element~Element|Function} config.model Name of the model element, a model element instance or a
* function that takes a view element and {@link module:engine/conversion/upcastdispatcher~UpcastConversionApi upcast conversion API}
* and returns a model element. The model element will be inserted in the model.
* @param {module:utils/priorities~PriorityString} [config.converterPriority='normal'] Converter priority.
* @returns {module:engine/conversion/upcasthelpers~UpcastHelpers}
*/
elementToElement( config ) {
return this.add( upcastElementToElement( config ) );
}
/**
* View element to model attribute conversion helper.
*
* This conversion results in setting an attribute on a model node. For example, view `<strong>Foo</strong>` becomes
* `Foo` {@link module:engine/model/text~Text model text node} with `bold` attribute set to `true`.
*
* This helper is meant to set a model attribute on all the elements that are inside the converted element:
*
* <strong>Foo</strong> --> <strong><p>Foo</p></strong> --> <paragraph><$text bold="true">Foo</$text></paragraph>
*
* Above is a sample of HTML code, that goes through autoparagraphing (first step) and then is converted (second step).
* Even though `<strong>` is over `<p>` element, `bold="true"` was added to the text. See
* {@link module:engine/conversion/upcasthelpers~UpcastHelpers#attributeToAttribute} for comparison.
*
* Keep in mind that the attribute will be set only if it is allowed by {@link module:engine/model/schema~Schema schema} configuration.
*
* editor.conversion.for( 'upcast' ).elementToAttribute( {
* view: 'strong',
* model: 'bold'
* } );
*
* editor.conversion.for( 'upcast' ).elementToAttribute( {
* view: 'strong',
* model: 'bold',
* converterPriority: 'high'
* } );
*
* editor.conversion.for( 'upcast' ).elementToAttribute( {
* view: {
* name: 'span',
* classes: 'bold'
* },
* model: 'bold'
* } );
*
* editor.conversion.for( 'upcast' ).elementToAttribute( {
* view: {
* name: 'span',
* classes: [ 'styled', 'styled-dark' ]
* },
* model: {
* key: 'styled',
* value: 'dark'
* }
* } );
*
* editor.conversion.for( 'upcast' ).elementToAttribute( {
* view: {
* name: 'span',
* styles: {
* 'font-size': /[\s\S]+/
* }
* },
* model: {
* key: 'fontSize',
* value: ( viewElement, conversionApi ) => {
* const fontSize = viewElement.getStyle( 'font-size' );
* const value = fontSize.substr( 0, fontSize.length - 2 );
*
* if ( value <= 10 ) {
* return 'small';
* } else if ( value > 12 ) {
* return 'big';
* }
*
* return null;
* }
* }
* } );
*
* See {@link module:engine/conversion/conversion~Conversion#for `conversion.for()`} to learn how to add a converter
* to the conversion process.
*
* @method #elementToAttribute
* @param {Object} config Conversion configuration.
* @param {module:engine/view/matcher~MatcherPattern} config.view Pattern matching all view elements which should be converted.
* @param {String|Object} config.model Model attribute key or an object with `key` and `value` properties, describing
* the model attribute. `value` property may be set as a function that takes a view element and
* {@link module:engine/conversion/upcastdispatcher~UpcastConversionApi upcast conversion API} and returns the value.
* If `String` is given, the model attribute value will be set to `true`.
* @param {module:utils/priorities~PriorityString} [config.converterPriority='low'] Converter priority.
* @returns {module:engine/conversion/upcasthelpers~UpcastHelpers}
*/
elementToAttribute( config ) {
return this.add( upcastElementToAttribute( config ) );
}
/**
* View attribute to model attribute conversion helper.
*
* This conversion results in setting an attribute on a model node. For example, view `<img src="foo.jpg"></img>` becomes
* `<imageBlock source="foo.jpg"></imageBlock>` in the model.
*
* This helper is meant to convert view attributes from view elements which got converted to the model, so the view attribute
* is set only on the corresponding model node:
*
* <div class="dark"><div>foo</div></div> --> <div dark="true"><div>foo</div></div>
*
* Above, `class="dark"` attribute is added only to the `<div>` elements that has it. This is in contrary to
* {@link module:engine/conversion/upcasthelpers~UpcastHelpers#elementToAttribute} which sets attributes for
* all the children in the model:
*
* <strong>Foo</strong> --> <strong><p>Foo</p></strong> --> <paragraph><$text bold="true">Foo</$text></paragraph>
*
* Above is a sample of HTML code, that goes through autoparagraphing (first step) and then is converted (second step).
* Even though `<strong>` is over `<p>` element, `bold="true"` was added to the text.
*
* Keep in mind that the attribute will be set only if it is allowed by {@link module:engine/model/schema~Schema schema} configuration.
*
* editor.conversion.for( 'upcast' ).attributeToAttribute( {
* view: 'src',
* model: 'source'
* } );
*
* editor.conversion.for( 'upcast' ).attributeToAttribute( {
* view: { key: 'src' },
* model: 'source'
* } );
*
* editor.conversion.for( 'upcast' ).attributeToAttribute( {
* view: { key: 'src' },
* model: 'source',
* converterPriority: 'normal'
* } );
*
* editor.conversion.for( 'upcast' ).attributeToAttribute( {
* view: {
* key: 'data-style',
* value: /[\s\S]+/
* },
* model: 'styled'
* } );
*
* editor.conversion.for( 'upcast' ).attributeToAttribute( {
* view: {
* name: 'img',
* key: 'class',
* value: 'styled-dark'
* },
* model: {
* key: 'styled',
* value: 'dark'
* }
* } );
*
* editor.conversion.for( 'upcast' ).attributeToAttribute( {
* view: {
* key: 'class',
* value: /styled-[\S]+/
* },
* model: {
* key: 'styled'
* value: ( viewElement, conversionApi ) => {
* const regexp = /styled-([\S]+)/;
* const match = viewElement.getAttribute( 'class' ).match( regexp );
*
* return match[ 1 ];
* }
* }
* } );
*
* Converting styles works a bit differently as it requires `view.styles` to be an object and by default
* a model attribute will be set to `true` by such a converter. You can set the model attribute to any value by providing the `value`
* callback that returns the desired value.
*
* // Default conversion of font-weight style will result in setting bold attribute to true.
* editor.conversion.for( 'upcast' ).attributeToAttribute( {
* view: {
* styles: {
* 'font-weight': 'bold'
* }
* },
* model: 'bold'
* } );
*
* // This converter will pass any style value to the `lineHeight` model attribute.
* editor.conversion.for( 'upcast' ).attributeToAttribute( {
* view: {
* styles: {
* 'line-height': /[\s\S]+/
* }
* },
* model: {
* key: 'lineHeight',
* value: ( viewElement, conversionApi ) => viewElement.getStyle( 'line-height' )
* }
* } );
*
* See {@link module:engine/conversion/conversion~Conversion#for `conversion.for()`} to learn how to add a converter
* to the conversion process.
*
* @method #attributeToAttribute
* @param {Object} config Conversion configuration.
* @param {String|Object} config.view Specifies which view attribute will be converted. If a `String` is passed,
* attributes with given key will be converted. If an `Object` is passed, it must have a required `key` property,
* specifying view attribute key, and may have an optional `value` property, specifying view attribute value and optional `name`
* property specifying a view element name from/on which the attribute should be converted. `value` can be given as a `String`,
* a `RegExp` or a function callback, that takes view attribute value as the only parameter and returns `Boolean`.
* @param {String|Object} config.model Model attribute key or an object with `key` and `value` properties, describing
* the model attribute. `value` property may be set as a function that takes a view element and
* {@link module:engine/conversion/upcastdispatcher~UpcastConversionApi upcast conversion API} and returns the value.
* If `String` is given, the model attribute value will be same as view attribute value.
* @param {module:utils/priorities~PriorityString} [config.converterPriority='low'] Converter priority.
* @returns {module:engine/conversion/upcasthelpers~UpcastHelpers}
*/
attributeToAttribute( config ) {
return this.add( upcastAttributeToAttribute( config ) );
}
/**
* View element to model marker conversion helper.
*
* This conversion results in creating a model marker. For example, if the marker was stored in a view as an element:
* `<p>Fo<span data-marker="comment" data-comment-id="7"></span>o</p><p>B<span data-marker="comment" data-comment-id="7"></span>ar</p>`,
* after the conversion is done, the marker will be available in
* {@link module:engine/model/model~Model#markers model document markers}.
*
* **Note**: When this helper is used in the data upcast in combination with
* {@link module:engine/conversion/downcasthelpers~DowncastHelpers#markerToData `#markerToData()`} in the data downcast,
* then invalid HTML code (e.g. a span between table cells) may be produced by the latter converter.
*
* In most of the cases, the {@link #dataToMarker} should be used instead.
*
* editor.conversion.for( 'upcast' ).elementToMarker( {
* view: 'marker-search',
* model: 'search'
* } );
*
* editor.conversion.for( 'upcast' ).elementToMarker( {
* view: 'marker-search',
* model: 'search',
* converterPriority: 'high'
* } );
*
* editor.conversion.for( 'upcast' ).elementToMarker( {
* view: 'marker-search',
* model: ( viewElement, conversionApi ) => 'comment:' + viewElement.getAttribute( 'data-comment-id' )
* } );
*
* editor.conversion.for( 'upcast' ).elementToMarker( {
* view: {
* name: 'span',
* attributes: {
* 'data-marker': 'search'
* }
* },
* model: 'search'
* } );
*
* See {@link module:engine/conversion/conversion~Conversion#for `conversion.for()`} to learn how to add a converter
* to the conversion process.
*
* @method #elementToMarker
* @param {Object} config Conversion configuration.
* @param {module:engine/view/matcher~MatcherPattern} config.view Pattern matching all view elements which should be converted.
* @param {String|Function} config.model Name of the model marker, or a function that takes a view element and returns
* a model marker name.
* @param {module:utils/priorities~PriorityString} [config.converterPriority='normal'] Converter priority.
* @returns {module:engine/conversion/upcasthelpers~UpcastHelpers}
*/
elementToMarker( config ) {
return this.add( upcastElementToMarker( config ) );
}
/**
* View-to-model marker conversion helper.
*
* Converts view data created by {@link module:engine/conversion/downcasthelpers~DowncastHelpers#markerToData `#markerToData()`}
* back to a model marker.
*
* This converter looks for specific view elements and view attributes that mark marker boundaries. See
* {@link module:engine/conversion/downcasthelpers~DowncastHelpers#markerToData `#markerToData()`} to learn what view data
* is expected by this converter.
*
* The `config.view` property is equal to the marker group name to convert.
*
* By default, this converter creates markers with the `group:name` name convention (to match the default `markerToData` conversion).
*
* The conversion configuration can take a function that will generate a marker name.
* If such function is set as the `config.model` parameter, it is passed the `name` part from the view element or attribute and it is
* expected to return a string with the marker name.
*
* Basic usage:
*
* // Using the default conversion.
* // In this case, all markers from the `comment` group will be converted.
* // The conversion will look for `<comment-start>` and `<comment-end>` tags and
* // `data-comment-start-before`, `data-comment-start-after`,
* // `data-comment-end-before` and `data-comment-end-after` attributes.
* editor.conversion.for( 'upcast' ).dataToMarker( {
* view: 'comment'
* } );
*
* An example of a model that may be generated by this conversion:
*
* // View:
* <p>Foo<comment-start name="commentId:uid"></comment-start>bar</p>
* <figure data-comment-end-after="commentId:uid" class="image"><img src="abc.jpg" /></figure>
*
* // Model:
* <paragraph>Foo[bar</paragraph>
* <imageBlock src="abc.jpg"></imageBlock>]
*
* Where `[]` are boundaries of a marker that will receive the `comment:commentId:uid` name.
*
* Other examples of usage:
*
* // Using a custom function which is the same as the default conversion:
* editor.conversion.for( 'upcast' ).dataToMarker( {
* view: 'comment',
* model: ( name, conversionApi ) => 'comment:' + name,
* } );
*
* // Using the converter priority:
* editor.conversion.for( 'upcast' ).dataToMarker( {
* view: 'comment',
* model: ( name, conversionApi ) => 'comment:' + name,
* converterPriority: 'high'
* } );
*
* See {@link module:engine/conversion/conversion~Conversion#for `conversion.for()`} to learn how to add a converter
* to the conversion process.
*
* @method #dataToMarker
* @param {Object} config Conversion configuration.
* @param {String} config.view The marker group name to convert.
* @param {Function} [config.model] A function that takes the `name` part from the view element or attribute and
* {@link module:engine/conversion/upcastdispatcher~UpcastConversionApi upcast conversion API} and returns the marker name.
* @param {module:utils/priorities~PriorityString} [config.converterPriority='normal'] Converter priority.
* @returns {module:engine/conversion/upcasthelpers~UpcastHelpers}
*/
dataToMarker( config ) {
return this.add( upcastDataToMarker( config ) );
}
}
/**
* Function factory, creates a converter that converts {@link module:engine/view/documentfragment~DocumentFragment view document fragment}
* or all children of {@link module:engine/view/element~Element} into
* {@link module:engine/model/documentfragment~DocumentFragment model document fragment}.
* This is the "entry-point" converter for upcast (view to model conversion). This converter starts the conversion of all children
* of passed view document fragment. Those children {@link module:engine/view/node~Node view nodes} are then handled by other converters.
*
* This also a "default", last resort converter for all view elements that has not been converted by other converters.
* When a view element is being converted to the model but it does not have converter specified, that view element
* will be converted to {@link module:engine/model/documentfragment~DocumentFragment model document fragment} and returned.
*
* @returns {Function} Universal converter for view {@link module:engine/view/documentfragment~DocumentFragment fragments} and
* {@link module:engine/view/element~Element elements} that returns
* {@link module:engine/model/documentfragment~DocumentFragment model fragment} with children of converted view item.
*/
function convertToModelFragment() {
return ( evt, data, conversionApi ) => {
// Second argument in `consumable.consume` is discarded for ViewDocumentFragment but is needed for ViewElement.
if ( !data.modelRange && conversionApi.consumable.consume( data.viewItem, { name: true } ) ) {
const { modelRange, modelCursor } = conversionApi.convertChildren( data.viewItem, data.modelCursor );
data.modelRange = modelRange;
data.modelCursor = modelCursor;
}
};
}
/**
* Function factory, creates a converter that converts {@link module:engine/view/text~Text} to {@link module:engine/model/text~Text}.
*
* @returns {Function} {@link module:engine/view/text~Text View text} converter.
*/
function convertText() {
return ( evt, data, { schema, consumable, writer } ) => {
let position = data.modelCursor;
// When node is already converted then do nothing.
if ( !consumable.test( data.viewItem ) ) {
return;
}
if ( !schema.checkChild( position, '$text' ) ) {
if ( !(0,_model_utils_autoparagraphing__WEBPACK_IMPORTED_MODULE_3__.isParagraphable)( position, '$text', schema ) ) {
return;
}
position = (0,_model_utils_autoparagraphing__WEBPACK_IMPORTED_MODULE_3__.wrapInParagraph)( position, writer );
}
consumable.consume( data.viewItem );
const text = writer.createText( data.viewItem.data );
writer.insert( text, position );
data.modelRange = writer.createRange(
position,
position.getShiftedBy( text.offsetSize )
);
data.modelCursor = data.modelRange.end;
};
}
/**
* Function factory, creates a callback function which converts a {@link module:engine/view/selection~Selection
* view selection} taken from the {@link module:engine/view/document~Document#event:selectionChange} event
* and sets in on the {@link module:engine/model/document~Document#selection model}.
*
* **Note**: because there is no view selection change dispatcher nor any other advanced view selection to model
* conversion mechanism, the callback should be set directly on view document.
*
* view.document.on( 'selectionChange', convertSelectionChange( modelDocument, mapper ) );
*
* @param {module:engine/model/model~Model} model Data model.
* @param {module:engine/conversion/mapper~Mapper} mapper Conversion mapper.
* @returns {Function} {@link module:engine/view/document~Document#event:selectionChange} callback function.
*/
function convertSelectionChange( model, mapper ) {
return ( evt, data ) => {
const viewSelection = data.newSelection;
const ranges = [];
for ( const viewRange of viewSelection.getRanges() ) {
ranges.push( mapper.toModelRange( viewRange ) );
}
const modelSelection = model.createSelection( ranges, { backward: viewSelection.isBackward } );
if ( !modelSelection.isEqual( model.document.selection ) ) {
model.change( writer => {
writer.setSelection( modelSelection );
} );
}
};
}
// View element to model element conversion helper.
//
// See {@link ~UpcastHelpers#elementToElement `.elementToElement()` upcast helper} for examples.
//
// @param {Object} config Conversion configuration.
// @param {module:engine/view/matcher~MatcherPattern} [config.view] Pattern matching all view elements which should be converted. If not
// set, the converter will fire for every view element.
// @param {String|module:engine/model/element~Element|Function} config.model Name of the model element, a model element
// instance or a function that takes a view element and returns a model element. The model element will be inserted in the model.
// @param {module:utils/priorities~PriorityString} [config.converterPriority='normal'] Converter priority.
// @returns {Function} Conversion helper.
function upcastElementToElement( config ) {
config = (0,lodash_es__WEBPACK_IMPORTED_MODULE_4__["default"])( config );
const converter = prepareToElementConverter( config );
const elementName = getViewElementNameFromConfig( config.view );
const eventName = elementName ? 'element:' + elementName : 'element';
return dispatcher => {
dispatcher.on( eventName, converter, { priority: config.converterPriority || 'normal' } );
};
}
// View element to model attribute conversion helper.
//
// See {@link ~UpcastHelpers#elementToAttribute `.elementToAttribute()` upcast helper} for examples.
//
// @param {Object} config Conversion configuration.
// @param {module:engine/view/matcher~MatcherPattern} config.view Pattern matching all view elements which should be converted.
// @param {String|Object} config.model Model attribute key or an object with `key` and `value` properties, describing
// the model attribute. `value` property may be set as a function that takes a view element and returns the value.
// If `String` is given, the model attribute value will be set to `true`.
// @param {module:utils/priorities~PriorityString} [config.converterPriority='low'] Converter priority.
// @returns {Function} Conversion helper.
function upcastElementToAttribute( config ) {
config = (0,lodash_es__WEBPACK_IMPORTED_MODULE_4__["default"])( config );
normalizeModelAttributeConfig( config );
const converter = prepareToAttributeConverter( config, false );
const elementName = getViewElementNameFromConfig( config.view );
const eventName = elementName ? 'element:' + elementName : 'element';
return dispatcher => {
dispatcher.on( eventName, converter, { priority: config.converterPriority || 'low' } );
};
}
// View attribute to model attribute conversion helper.
//
// See {@link ~UpcastHelpers#attributeToAttribute `.attributeToAttribute()` upcast helper} for examples.
//
// @param {Object} config Conversion configuration.
// @param {String|Object} config.view Specifies which view attribute will be converted. If a `String` is passed,
// attributes with given key will be converted. If an `Object` is passed, it must have a required `key` property,
// specifying view attribute key, and may have an optional `value` property, specifying view attribute value and optional `name`
// property specifying a view element name from/on which the attribute should be converted. `value` can be given as a `String`,
// a `RegExp` or a function callback, that takes view attribute value as the only parameter and returns `Boolean`.
// @param {String|Object} config.model Model attribute key or an object with `key` and `value` properties, describing
// the model attribute. `value` property may be set as a function that takes a view element and returns the value.
// If `String` is given, the model attribute value will be same as view attribute value.
// @param {module:utils/priorities~PriorityString} [config.converterPriority='low'] Converter priority.
// @returns {Function} Conversion helper.
function upcastAttributeToAttribute( config ) {
config = (0,lodash_es__WEBPACK_IMPORTED_MODULE_4__["default"])( config );
let viewKey = null;
if ( typeof config.view == 'string' || config.view.key ) {
viewKey = normalizeViewAttributeKeyValueConfig( config );
}
normalizeModelAttributeConfig( config, viewKey );
const converter = prepareToAttributeConverter( config, true );
return dispatcher => {
dispatcher.on( 'element', converter, { priority: config.converterPriority || 'low' } );
};
}
// View element to model marker conversion helper.
//
// See {@link ~UpcastHelpers#elementToMarker `.elementToMarker()` upcast helper} for examples.
//
// @param {Object} config Conversion configuration.
// @param {module:engine/view/matcher~MatcherPattern} config.view Pattern matching all view elements which should be converted.
// @param {String|Function} config.model Name of the model marker, or a function that takes a view element and returns
// a model marker name.
// @param {module:utils/priorities~PriorityString} [config.converterPriority='normal'] Converter priority.
// @returns {Function} Conversion helper.
function upcastElementToMarker( config ) {
config = (0,lodash_es__WEBPACK_IMPORTED_MODULE_4__["default"])( config );
normalizeElementToMarkerConfig( config );
return upcastElementToElement( config );
}
// View data to model marker conversion helper.
//
// See {@link ~UpcastHelpers#dataToMarker} to learn more.
//
// @param {Object} config
// @param {String} config.view
// @param {Function} [config.model]
// @param {module:utils/priorities~PriorityString} [config.converterPriority='normal']
// @returns {Function} Conversion helper.
function upcastDataToMarker( config ) {
config = (0,lodash_es__WEBPACK_IMPORTED_MODULE_4__["default"])( config );
// Default conversion.
if ( !config.model ) {
config.model = name => {
return name ? config.view + ':' + name : config.view;
};
}
const converterStart = prepareToElementConverter( normalizeDataToMarkerConfig( config, 'start' ) );
const converterEnd = prepareToElementConverter( normalizeDataToMarkerConfig( config, 'end' ) );
return dispatcher => {
dispatcher.on( 'element:' + config.view + '-start', converterStart, { priority: config.converterPriority || 'normal' } );
dispatcher.on( 'element:' + config.view + '-end', converterEnd, { priority: config.converterPriority || 'normal' } );
// Below is a hack that is needed to properly handle `converterPriority` for both elements and attributes.
// Attribute conversion needs to be performed *after* element conversion.
// This converter handles both element conversion and attribute conversion, which means that if a single
// `config.converterPriority` is used, it will lead to problems. For example, if the `'high'` priority is used,
// the attribute conversion will be performed before a lot of element upcast converters.
// On the other hand, we want to support `config.converterPriority` and converter overwriting.
//
// To make it work, we need to do some extra processing for priority for attribute converter.
// Priority `'low'` value should be the base value and then we will change it depending on `config.converterPriority` value.
//
// This hack probably would not be needed if attributes are upcasted separately.
//
const basePriority = _ckeditor_ckeditor5_utils_src_priorities__WEBPACK_IMPORTED_MODULE_2__["default"].get( 'low' );
const maxPriority = _ckeditor_ckeditor5_utils_src_priorities__WEBPACK_IMPORTED_MODULE_2__["default"].get( 'highest' );
const priorityFactor = _ckeditor_ckeditor5_utils_src_priorities__WEBPACK_IMPORTED_MODULE_2__["default"].get( config.converterPriority ) / maxPriority; // Number in range [ -1, 1 ].
dispatcher.on( 'element', upcastAttributeToMarker( config ), { priority: basePriority + priorityFactor } );
};
}
// Function factory, returns a callback function which converts view attributes to a model marker.
//
// The converter looks for elements with `data-group-start-before`, `data-group-start-after`, `data-group-end-before`
// and `data-group-end-after` attributes and inserts `$marker` model elements before/after those elements.
// `group` part is specified in `config.view`.
//
// @param {Object} config
// @param {String} config.view
// @param {Function} [config.model]
// @returns {Function} Marker converter.
function upcastAttributeToMarker( config ) {
return ( evt, data, conversionApi ) => {
const attrName = `data-${ config.view }`;
// Check if any attribute for the given view item can be consumed before changing the conversion data
// and consuming view items with these attributes.
if (
!conversionApi.consumable.test( data.viewItem, { attributes: attrName + '-end-after' } ) &&
!conversionApi.consumable.test( data.viewItem, { attributes: attrName + '-start-after' } ) &&
!conversionApi.consumable.test( data.viewItem, { attributes: attrName + '-end-before' } ) &&
!conversionApi.consumable.test( data.viewItem, { attributes: attrName + '-start-before' } )
) {
return;
}
// This converter wants to add a model element, marking a marker, before/after an element (or maybe even group of elements).
// To do that, we can use `data.modelRange` which is set on an element (or a group of elements) that has been upcasted.
// But, if the processed view element has not been upcasted yet (it does not have been converted), we need to
// fire conversion for its children first, then we will have `data.modelRange` available.
if ( !data.modelRange ) {
Object.assign( data, conversionApi.convertChildren( data.viewItem, data.modelCursor ) );
}
if ( conversionApi.consumable.consume( data.viewItem, { attributes: attrName + '-end-after' } ) ) {
addMarkerElements( data.modelRange.end, data.viewItem.getAttribute( attrName + '-end-after' ).split( ',' ) );
}
if ( conversionApi.consumable.consume( data.viewItem, { attributes: attrName + '-start-after' } ) ) {
addMarkerElements( data.modelRange.end, data.viewItem.getAttribute( attrName + '-start-after' ).split( ',' ) );
}
if ( conversionApi.consumable.consume( data.viewItem, { attributes: attrName + '-end-before' } ) ) {
addMarkerElements( data.modelRange.start, data.viewItem.getAttribute( attrName + '-end-before' ).split( ',' ) );
}
if ( conversionApi.consumable.consume( data.viewItem, { attributes: attrName + '-start-before' } ) ) {
addMarkerElements( data.modelRange.start, data.viewItem.getAttribute( attrName + '-start-before' ).split( ',' ) );
}
function addMarkerElements( position, markerViewNames ) {
for ( const markerViewName of markerViewNames ) {
const markerName = config.model( markerViewName, conversionApi );
const element = conversionApi.writer.createElement( '$marker', { 'data-name': markerName } );
conversionApi.writer.insert( element, position );
if ( data.modelCursor.isEqual( position ) ) {
data.modelCursor = data.modelCursor.getShiftedBy( 1 );
} else {
data.modelCursor = data.modelCursor._getTransformedByInsertion( position, 1 );
}
data.modelRange = data.modelRange._getTransformedByInsertion( position, 1 )[ 0 ];
}
}
};
}
// Helper function for from-view-element conversion. Checks if `config.view` directly specifies converted view element's name
// and if so, returns it.
//
// @param {Object} config Conversion view config.
// @returns {String|null} View element name or `null` if name is not directly set.
function getViewElementNameFromConfig( viewConfig ) {
if ( typeof viewConfig == 'string' ) {
return viewConfig;
}
if ( typeof viewConfig == 'object' && typeof viewConfig.name == 'string' ) {
return viewConfig.name;
}
return null;
}
// Helper for to-model-element conversion. Takes a config object and returns a proper converter function.
//
// @param {Object} config Conversion configuration.
// @returns {Function} View to model converter.
function prepareToElementConverter( config ) {
const matcher = new _view_matcher__WEBPACK_IMPORTED_MODULE_0__["default"]( config.view );
return ( evt, data, conversionApi ) => {
const matcherResult = matcher.match( data.viewItem );
if ( !matcherResult ) {
return;
}
const match = matcherResult.match;
// Force consuming element's name.
match.name = true;
if ( !conversionApi.consumable.test( data.viewItem, match ) ) {
return;
}
const modelElement = getModelElement( config.model, data.viewItem, conversionApi );
if ( !modelElement ) {
return;
}
if ( !conversionApi.safeInsert( modelElement, data.modelCursor ) ) {
return;
}
conversionApi.consumable.consume( data.viewItem, match );
conversionApi.convertChildren( data.viewItem, modelElement );
conversionApi.updateConversionResult( modelElement, data );
};
}
// Helper function for upcasting-to-element converter. Takes the model configuration, the converted view element
// and a writer instance and returns a model element instance to be inserted in the model.
//
// @param {String|Function|module:engine/model/element~Element} model Model conversion configuration.
// @param {module:engine/view/node~Node} input The converted view node.
// @param {module:engine/conversion/upcastdispatcher~UpcastConversionApi} conversionApi The upcast conversion API.
function getModelElement( model, input, conversionApi ) {
if ( model instanceof Function ) {
return model( input, conversionApi );
} else {
return conversionApi.writer.createElement( model );
}
}
// Helper function view-attribute-to-model-attribute helper. Normalizes `config.view` which was set as `String` or
// as an `Object` with `key`, `value` and `name` properties. Normalized `config.view` has is compatible with
// {@link module:engine/view/matcher~MatcherPattern}.
//
// @param {Object} config Conversion config.
// @returns {String} Key of the converted view attribute.
function normalizeViewAttributeKeyValueConfig( config ) {
if ( typeof config.view == 'string' ) {
config.view = { key: config.view };
}
const key = config.view.key;
let normalized;
if ( key == 'class' || key == 'style' ) {
const keyName = key == 'class' ? 'classes' : 'styles';
normalized = {
[ keyName ]: config.view.value
};
} else {
const value = typeof config.view.value == 'undefined' ? /[\s\S]*/ : config.view.value;
normalized = {
attributes: {
[ key ]: value
}
};
}
if ( config.view.name ) {
normalized.name = config.view.name;
}
config.view = normalized;
return key;
}
// Helper function that normalizes `config.model` in from-model-attribute conversion. `config.model` can be set
// as a `String`, an `Object` with only `key` property or an `Object` with `key` and `value` properties. Normalized
// `config.model` is an `Object` with `key` and `value` properties.
//
// @param {Object} config Conversion config.
// @param {String} viewAttributeKeyToCopy Key of the converted view attribute. If it is set, model attribute value
// will be equal to view attribute value.
function normalizeModelAttributeConfig( config, viewAttributeKeyToCopy = null ) {
const defaultModelValue = viewAttributeKeyToCopy === null ? true : viewElement => viewElement.getAttribute( viewAttributeKeyToCopy );
const key = typeof config.model != 'object' ? config.model : config.model.key;
const value = typeof config.model != 'object' || typeof config.model.value == 'undefined' ? defaultModelValue : config.model.value;
config.model = { key, value };
}
// Helper for to-model-attribute conversion. Takes the model attribute name and conversion configuration and returns
// a proper converter function.
//
// @param {String} modelAttributeKey The key of the model attribute to set on a model node.
// @param {Object|Array.<Object>} config Conversion configuration. It is possible to provide multiple configurations in an array.
// @param {Boolean} shallow If set to `true` the attribute will be set only on top-level nodes. Otherwise, it will be set
// on all elements in the range.
function prepareToAttributeConverter( config, shallow ) {
const matcher = new _view_matcher__WEBPACK_IMPORTED_MODULE_0__["default"]( config.view );
return ( evt, data, conversionApi ) => {
const match = matcher.match( data.viewItem );
// If there is no match, this callback should not do anything.
if ( !match ) {
return;
}
if ( onlyViewNameIsDefined( config.view, data.viewItem ) ) {
match.match.name = true;
} else {
// Do not test or consume `name` consumable.
delete match.match.name;
}
// Try to consume appropriate values from consumable values list.
if ( !conversionApi.consumable.test( data.viewItem, match.match ) ) {
return;
}
const modelKey = config.model.key;
const modelValue = typeof config.model.value == 'function' ?
config.model.value( data.viewItem, conversionApi ) : config.model.value;
// Do not convert if attribute building function returned falsy value.
if ( modelValue === null ) {
return;
}
// Since we are converting to attribute we need a range on which we will set the attribute.
// If the range is not created yet, let's create it by converting children of the current node first.
if ( !data.modelRange ) {
// Convert children and set conversion result as a current data.
Object.assign( data, conversionApi.convertChildren( data.viewItem, data.modelCursor ) );
}
// Set attribute on current `output`. `Schema` is checked inside this helper function.
const attributeWasSet = setAttributeOn( data.modelRange, { key: modelKey, value: modelValue }, shallow, conversionApi );
// It may happen that a converter will try to set an attribute that is not allowed in the given context.
// In such a situation we cannot consume the attribute. See: https://github.com/ckeditor/ckeditor5/pull/9249#issuecomment-815658459.
if ( attributeWasSet ) {
conversionApi.consumable.consume( data.viewItem, match.match );
}
};
}
// Helper function that checks if element name should be consumed in attribute converters.
//
// @param {Object} config Conversion view config.
// @returns {Boolean}
function onlyViewNameIsDefined( viewConfig, viewItem ) {
// https://github.com/ckeditor/ckeditor5-engine/issues/1786
const configToTest = typeof viewConfig == 'function' ? viewConfig( viewItem ) : viewConfig;
if ( typeof configToTest == 'object' && !getViewElementNameFromConfig( configToTest ) ) {
return false;
}
return !configToTest.classes && !configToTest.attributes && !configToTest.styles;
}
// Helper function for to-model-attribute converter. Sets model attribute on given range. Checks {@link module:engine/model/schema~Schema}
// to ensure proper model structure.
//
// If any node on the given range has already defined an attribute with the same name, its value will not be updated.
//
// @param {module:engine/model/range~Range} modelRange Model range on which attribute should be set.
// @param {Object} modelAttribute Model attribute to set.
// @param {module:engine/conversion/upcastdispatcher~UpcastConversionApi} conversionApi Conversion API.
// @param {Boolean} shallow If set to `true` the attribute will be set only on top-level nodes. Otherwise, it will be set
// on all elements in the range.
// @returns {Boolean} `true` if attribute was set on at least one node from given `modelRange`.
function setAttributeOn( modelRange, modelAttribute, shallow, conversionApi ) {
let result = false;
// Set attribute on each item in range according to Schema.
for ( const node of Array.from( modelRange.getItems( { shallow } ) ) ) {
// Skip if not allowed.
if ( !conversionApi.schema.checkAttribute( node, modelAttribute.key ) ) {
continue;
}
// Mark the node as consumed even if the attribute will not be updated because it's in a valid context (schema)
// and would be converted if the attribute wouldn't be present. See #8921.
result = true;
// Do not override the attribute if it's already present.
if ( node.hasAttribute( modelAttribute.key ) ) {
continue;
}
conversionApi.writer.setAttribute( modelAttribute.key, modelAttribute.value, node );
}
return result;
}
// Helper function for upcasting-to-marker conversion. Takes the config in a format requested by `upcastElementToMarker()`
// function and converts it to a format that is supported by `upcastElementToElement()` function.
//
// @param {Object} config Conversion configuration.
function normalizeElementToMarkerConfig( config ) {
const oldModel = config.model;
config.model = ( viewElement, conversionApi ) => {
const markerName = typeof oldModel == 'string' ? oldModel : oldModel( viewElement, conversionApi );
return conversionApi.writer.createElement( '$marker', { 'data-name': markerName } );
};
}
// Helper function for upcasting-to-marker conversion. Takes the config in a format requested by `upcastDataToMarker()`
// function and converts it to a format that is supported by `upcastElementToElement()` function.
//
// @param {Object} config Conversion configuration.
function normalizeDataToMarkerConfig( config, type ) {
const configForElements = {};
// Upcast <markerGroup-start> and <markerGroup-end> elements.
configForElements.view = config.view + '-' + type;
configForElements.model = ( viewElement, conversionApi ) => {
const viewName = viewElement.getAttribute( 'name' );
const markerName = config.model( viewName, conversionApi );
return conversionApi.writer.createElement( '$marker', { 'data-name': markerName } );
};
return configForElements;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/conversion/viewconsumable.js":
/*!**********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/conversion/viewconsumable.js ***!
\**********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ViewConsumable)
/* harmony export */ });
/* harmony import */ var lodash_es__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! lodash-es */ "./node_modules/lodash-es/isArray.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/conversion/viewconsumable
*/
/**
* Class used for handling consumption of view {@link module:engine/view/element~Element elements},
* {@link module:engine/view/text~Text text nodes} and {@link module:engine/view/documentfragment~DocumentFragment document fragments}.
* Element's name and its parts (attributes, classes and styles) can be consumed separately. Consuming an element's name
* does not consume its attributes, classes and styles.
* To add items for consumption use {@link module:engine/conversion/viewconsumable~ViewConsumable#add add method}.
* To test items use {@link module:engine/conversion/viewconsumable~ViewConsumable#test test method}.
* To consume items use {@link module:engine/conversion/viewconsumable~ViewConsumable#consume consume method}.
* To revert already consumed items use {@link module:engine/conversion/viewconsumable~ViewConsumable#revert revert method}.
*
* viewConsumable.add( element, { name: true } ); // Adds element's name as ready to be consumed.
* viewConsumable.add( textNode ); // Adds text node for consumption.
* viewConsumable.add( docFragment ); // Adds document fragment for consumption.
* viewConsumable.test( element, { name: true } ); // Tests if element's name can be consumed.
* viewConsumable.test( textNode ); // Tests if text node can be consumed.
* viewConsumable.test( docFragment ); // Tests if document fragment can be consumed.
* viewConsumable.consume( element, { name: true } ); // Consume element's name.
* viewConsumable.consume( textNode ); // Consume text node.
* viewConsumable.consume( docFragment ); // Consume document fragment.
* viewConsumable.revert( element, { name: true } ); // Revert already consumed element's name.
* viewConsumable.revert( textNode ); // Revert already consumed text node.
* viewConsumable.revert( docFragment ); // Revert already consumed document fragment.
*/
class ViewConsumable {
/**
* Creates new ViewConsumable.
*/
constructor() {
/**
* Map of consumable elements. If {@link module:engine/view/element~Element element} is used as a key,
* {@link module:engine/conversion/viewconsumable~ViewElementConsumables ViewElementConsumables} instance is stored as value.
* For {@link module:engine/view/text~Text text nodes} and
* {@link module:engine/view/documentfragment~DocumentFragment document fragments} boolean value is stored as value.
*
* @protected
* @member {Map.<module:engine/conversion/viewconsumable~ViewElementConsumables|Boolean>}
*/
this._consumables = new Map();
}
/**
* Adds {@link module:engine/view/element~Element view element}, {@link module:engine/view/text~Text text node} or
* {@link module:engine/view/documentfragment~DocumentFragment document fragment} as ready to be consumed.
*
* viewConsumable.add( p, { name: true } ); // Adds element's name to consume.
* viewConsumable.add( p, { attributes: 'name' } ); // Adds element's attribute.
* viewConsumable.add( p, { classes: 'foobar' } ); // Adds element's class.
* viewConsumable.add( p, { styles: 'color' } ); // Adds element's style
* viewConsumable.add( p, { attributes: 'name', styles: 'color' } ); // Adds attribute and style.
* viewConsumable.add( p, { classes: [ 'baz', 'bar' ] } ); // Multiple consumables can be provided.
* viewConsumable.add( textNode ); // Adds text node to consume.
* viewConsumable.add( docFragment ); // Adds document fragment to consume.
*
* Throws {@link module:utils/ckeditorerror~CKEditorError CKEditorError} `viewconsumable-invalid-attribute` when `class` or `style`
* attribute is provided - it should be handled separately by providing actual style/class.
*
* viewConsumable.add( p, { attributes: 'style' } ); // This call will throw an exception.
* viewConsumable.add( p, { styles: 'color' } ); // This is properly handled style.
*
* @param {module:engine/view/element~Element|module:engine/view/text~Text|module:engine/view/documentfragment~DocumentFragment} element
* @param {Object} [consumables] Used only if first parameter is {@link module:engine/view/element~Element view element} instance.
* @param {Boolean} consumables.name If set to true element's name will be included.
* @param {String|Array.<String>} consumables.attributes Attribute name or array of attribute names.
* @param {String|Array.<String>} consumables.classes Class name or array of class names.
* @param {String|Array.<String>} consumables.styles Style name or array of style names.
*/
add( element, consumables ) {
let elementConsumables;
// For text nodes and document fragments just mark them as consumable.
if ( element.is( '$text' ) || element.is( 'documentFragment' ) ) {
this._consumables.set( element, true );
return;
}
// For elements create new ViewElementConsumables or update already existing one.
if ( !this._consumables.has( element ) ) {
elementConsumables = new ViewElementConsumables( element );
this._consumables.set( element, elementConsumables );
} else {
elementConsumables = this._consumables.get( element );
}
elementConsumables.add( consumables );
}
/**
* Tests if {@link module:engine/view/element~Element view element}, {@link module:engine/view/text~Text text node} or
* {@link module:engine/view/documentfragment~DocumentFragment document fragment} can be consumed.
* It returns `true` when all items included in method's call can be consumed. Returns `false` when
* first already consumed item is found and `null` when first non-consumable item is found.
*
* viewConsumable.test( p, { name: true } ); // Tests element's name.
* viewConsumable.test( p, { attributes: 'name' } ); // Tests attribute.
* viewConsumable.test( p, { classes: 'foobar' } ); // Tests class.
* viewConsumable.test( p, { styles: 'color' } ); // Tests style.
* viewConsumable.test( p, { attributes: 'name', styles: 'color' } ); // Tests attribute and style.
* viewConsumable.test( p, { classes: [ 'baz', 'bar' ] } ); // Multiple consumables can be tested.
* viewConsumable.test( textNode ); // Tests text node.
* viewConsumable.test( docFragment ); // Tests document fragment.
*
* Testing classes and styles as attribute will test if all added classes/styles can be consumed.
*
* viewConsumable.test( p, { attributes: 'class' } ); // Tests if all added classes can be consumed.
* viewConsumable.test( p, { attributes: 'style' } ); // Tests if all added styles can be consumed.
*
* @param {module:engine/view/element~Element|module:engine/view/text~Text|module:engine/view/documentfragment~DocumentFragment} element
* @param {Object} [consumables] Used only if first parameter is {@link module:engine/view/element~Element view element} instance.
* @param {Boolean} consumables.name If set to true element's name will be included.
* @param {String|Array.<String>} consumables.attributes Attribute name or array of attribute names.
* @param {String|Array.<String>} consumables.classes Class name or array of class names.
* @param {String|Array.<String>} consumables.styles Style name or array of style names.
* @returns {Boolean|null} Returns `true` when all items included in method's call can be consumed. Returns `false`
* when first already consumed item is found and `null` when first non-consumable item is found.
*/
test( element, consumables ) {
const elementConsumables = this._consumables.get( element );
if ( elementConsumables === undefined ) {
return null;
}
// For text nodes and document fragments return stored boolean value.
if ( element.is( '$text' ) || element.is( 'documentFragment' ) ) {
return elementConsumables;
}
// For elements test consumables object.
return elementConsumables.test( consumables );
}
/**
* Consumes {@link module:engine/view/element~Element view element}, {@link module:engine/view/text~Text text node} or
* {@link module:engine/view/documentfragment~DocumentFragment document fragment}.
* It returns `true` when all items included in method's call can be consumed, otherwise returns `false`.
*
* viewConsumable.consume( p, { name: true } ); // Consumes element's name.
* viewConsumable.consume( p, { attributes: 'name' } ); // Consumes element's attribute.
* viewConsumable.consume( p, { classes: 'foobar' } ); // Consumes element's class.
* viewConsumable.consume( p, { styles: 'color' } ); // Consumes element's style.
* viewConsumable.consume( p, { attributes: 'name', styles: 'color' } ); // Consumes attribute and style.
* viewConsumable.consume( p, { classes: [ 'baz', 'bar' ] } ); // Multiple consumables can be consumed.
* viewConsumable.consume( textNode ); // Consumes text node.
* viewConsumable.consume( docFragment ); // Consumes document fragment.
*
* Consuming classes and styles as attribute will test if all added classes/styles can be consumed.
*
* viewConsumable.consume( p, { attributes: 'class' } ); // Consume only if all added classes can be consumed.
* viewConsumable.consume( p, { attributes: 'style' } ); // Consume only if all added styles can be consumed.
*
* @param {module:engine/view/element~Element|module:engine/view/text~Text|module:engine/view/documentfragment~DocumentFragment} element
* @param {Object} [consumables] Used only if first parameter is {@link module:engine/view/element~Element view element} instance.
* @param {Boolean} consumables.name If set to true element's name will be included.
* @param {String|Array.<String>} consumables.attributes Attribute name or array of attribute names.
* @param {String|Array.<String>} consumables.classes Class name or array of class names.
* @param {String|Array.<String>} consumables.styles Style name or array of style names.
* @returns {Boolean} Returns `true` when all items included in method's call can be consumed,
* otherwise returns `false`.
*/
consume( element, consumables ) {
if ( this.test( element, consumables ) ) {
if ( element.is( '$text' ) || element.is( 'documentFragment' ) ) {
// For text nodes and document fragments set value to false.
this._consumables.set( element, false );
} else {
// For elements - consume consumables object.
this._consumables.get( element ).consume( consumables );
}
return true;
}
return false;
}
/**
* Reverts {@link module:engine/view/element~Element view element}, {@link module:engine/view/text~Text text node} or
* {@link module:engine/view/documentfragment~DocumentFragment document fragment} so they can be consumed once again.
* Method does not revert items that were never previously added for consumption, even if they are included in
* method's call.
*
* viewConsumable.revert( p, { name: true } ); // Reverts element's name.
* viewConsumable.revert( p, { attributes: 'name' } ); // Reverts element's attribute.
* viewConsumable.revert( p, { classes: 'foobar' } ); // Reverts element's class.
* viewConsumable.revert( p, { styles: 'color' } ); // Reverts element's style.
* viewConsumable.revert( p, { attributes: 'name', styles: 'color' } ); // Reverts attribute and style.
* viewConsumable.revert( p, { classes: [ 'baz', 'bar' ] } ); // Multiple names can be reverted.
* viewConsumable.revert( textNode ); // Reverts text node.
* viewConsumable.revert( docFragment ); // Reverts document fragment.
*
* Reverting classes and styles as attribute will revert all classes/styles that were previously added for
* consumption.
*
* viewConsumable.revert( p, { attributes: 'class' } ); // Reverts all classes added for consumption.
* viewConsumable.revert( p, { attributes: 'style' } ); // Reverts all styles added for consumption.
*
* @param {module:engine/view/element~Element|module:engine/view/text~Text|module:engine/view/documentfragment~DocumentFragment} element
* @param {Object} [consumables] Used only if first parameter is {@link module:engine/view/element~Element view element} instance.
* @param {Boolean} consumables.name If set to true element's name will be included.
* @param {String|Array.<String>} consumables.attributes Attribute name or array of attribute names.
* @param {String|Array.<String>} consumables.classes Class name or array of class names.
* @param {String|Array.<String>} consumables.styles Style name or array of style names.
*/
revert( element, consumables ) {
const elementConsumables = this._consumables.get( element );
if ( elementConsumables !== undefined ) {
if ( element.is( '$text' ) || element.is( 'documentFragment' ) ) {
// For text nodes and document fragments - set consumable to true.
this._consumables.set( element, true );
} else {
// For elements - revert items from consumables object.
elementConsumables.revert( consumables );
}
}
}
/**
* Creates consumable object from {@link module:engine/view/element~Element view element}. Consumable object will include
* element's name and all its attributes, classes and styles.
*
* @static
* @param {module:engine/view/element~Element} element
* @returns {Object} consumables
*/
static consumablesFromElement( element ) {
const consumables = {
element,
name: true,
attributes: [],
classes: [],
styles: []
};
const attributes = element.getAttributeKeys();
for ( const attribute of attributes ) {
// Skip classes and styles - will be added separately.
if ( attribute == 'style' || attribute == 'class' ) {
continue;
}
consumables.attributes.push( attribute );
}
const classes = element.getClassNames();
for ( const className of classes ) {
consumables.classes.push( className );
}
const styles = element.getStyleNames();
for ( const style of styles ) {
consumables.styles.push( style );
}
return consumables;
}
/**
* Creates {@link module:engine/conversion/viewconsumable~ViewConsumable ViewConsumable} instance from
* {@link module:engine/view/node~Node node} or {@link module:engine/view/documentfragment~DocumentFragment document fragment}.
* Instance will contain all elements, child nodes, attributes, styles and classes added for consumption.
*
* @static
* @param {module:engine/view/node~Node|module:engine/view/documentfragment~DocumentFragment} from View node or document fragment
* from which `ViewConsumable` will be created.
* @param {module:engine/conversion/viewconsumable~ViewConsumable} [instance] If provided, given `ViewConsumable` instance will be used
* to add all consumables. It will be returned instead of a new instance.
*/
static createFrom( from, instance ) {
if ( !instance ) {
instance = new ViewConsumable( from );
}
if ( from.is( '$text' ) ) {
instance.add( from );
return instance;
}
// Add `from` itself, if it is an element.
if ( from.is( 'element' ) ) {
instance.add( from, ViewConsumable.consumablesFromElement( from ) );
}
if ( from.is( 'documentFragment' ) ) {
instance.add( from );
}
for ( const child of from.getChildren() ) {
instance = ViewConsumable.createFrom( child, instance );
}
return instance;
}
}
/**
* This is a private helper-class for {@link module:engine/conversion/viewconsumable~ViewConsumable}.
* It represents and manipulates consumable parts of a single {@link module:engine/view/element~Element}.
*
* @private
*/
class ViewElementConsumables {
/**
* Creates ViewElementConsumables instance.
*
* @param {module:engine/view/node~Node|module:engine/view/documentfragment~DocumentFragment} from View node or document fragment
* from which `ViewElementConsumables` is being created.
*/
constructor( from ) {
/**
* @readonly
* @member {module:engine/view/node~Node|module:engine/view/documentfragment~DocumentFragment}
*/
this.element = from;
/**
* Flag indicating if name of the element can be consumed.
*
* @private
* @member {Boolean}
*/
this._canConsumeName = null;
/**
* Contains maps of element's consumables: attributes, classes and styles.
*
* @private
* @member {Object}
*/
this._consumables = {
attributes: new Map(),
styles: new Map(),
classes: new Map()
};
}
/**
* Adds consumable parts of the {@link module:engine/view/element~Element view element}.
* Element's name itself can be marked to be consumed (when element's name is consumed its attributes, classes and
* styles still could be consumed):
*
* consumables.add( { name: true } );
*
* Attributes classes and styles:
*
* consumables.add( { attributes: 'title', classes: 'foo', styles: 'color' } );
* consumables.add( { attributes: [ 'title', 'name' ], classes: [ 'foo', 'bar' ] );
*
* Throws {@link module:utils/ckeditorerror~CKEditorError CKEditorError} `viewconsumable-invalid-attribute` when `class` or `style`
* attribute is provided - it should be handled separately by providing `style` and `class` in consumables object.
*
* @param {Object} consumables Object describing which parts of the element can be consumed.
* @param {Boolean} consumables.name If set to `true` element's name will be added as consumable.
* @param {String|Array.<String>} consumables.attributes Attribute name or array of attribute names to add as consumable.
* @param {String|Array.<String>} consumables.classes Class name or array of class names to add as consumable.
* @param {String|Array.<String>} consumables.styles Style name or array of style names to add as consumable.
*/
add( consumables ) {
if ( consumables.name ) {
this._canConsumeName = true;
}
for ( const type in this._consumables ) {
if ( type in consumables ) {
this._add( type, consumables[ type ] );
}
}
}
/**
* Tests if parts of the {@link module:engine/view/node~Node view node} can be consumed.
*
* Element's name can be tested:
*
* consumables.test( { name: true } );
*
* Attributes classes and styles:
*
* consumables.test( { attributes: 'title', classes: 'foo', styles: 'color' } );
* consumables.test( { attributes: [ 'title', 'name' ], classes: [ 'foo', 'bar' ] );
*
* @param {Object} consumables Object describing which parts of the element should be tested.
* @param {Boolean} consumables.name If set to `true` element's name will be tested.
* @param {String|Array.<String>} consumables.attributes Attribute name or array of attribute names to test.
* @param {String|Array.<String>} consumables.classes Class name or array of class names to test.
* @param {String|Array.<String>} consumables.styles Style name or array of style names to test.
* @returns {Boolean|null} `true` when all tested items can be consumed, `null` when even one of the items
* was never marked for consumption and `false` when even one of the items was already consumed.
*/
test( consumables ) {
// Check if name can be consumed.
if ( consumables.name && !this._canConsumeName ) {
return this._canConsumeName;
}
for ( const type in this._consumables ) {
if ( type in consumables ) {
const value = this._test( type, consumables[ type ] );
if ( value !== true ) {
return value;
}
}
}
// Return true only if all can be consumed.
return true;
}
/**
* Consumes parts of {@link module:engine/view/element~Element view element}. This function does not check if consumable item
* is already consumed - it consumes all consumable items provided.
* Element's name can be consumed:
*
* consumables.consume( { name: true } );
*
* Attributes classes and styles:
*
* consumables.consume( { attributes: 'title', classes: 'foo', styles: 'color' } );
* consumables.consume( { attributes: [ 'title', 'name' ], classes: [ 'foo', 'bar' ] );
*
* @param {Object} consumables Object describing which parts of the element should be consumed.
* @param {Boolean} consumables.name If set to `true` element's name will be consumed.
* @param {String|Array.<String>} consumables.attributes Attribute name or array of attribute names to consume.
* @param {String|Array.<String>} consumables.classes Class name or array of class names to consume.
* @param {String|Array.<String>} consumables.styles Style name or array of style names to consume.
*/
consume( consumables ) {
if ( consumables.name ) {
this._canConsumeName = false;
}
for ( const type in this._consumables ) {
if ( type in consumables ) {
this._consume( type, consumables[ type ] );
}
}
}
/**
* Revert already consumed parts of {@link module:engine/view/element~Element view Element}, so they can be consumed once again.
* Element's name can be reverted:
*
* consumables.revert( { name: true } );
*
* Attributes classes and styles:
*
* consumables.revert( { attributes: 'title', classes: 'foo', styles: 'color' } );
* consumables.revert( { attributes: [ 'title', 'name' ], classes: [ 'foo', 'bar' ] );
*
* @param {Object} consumables Object describing which parts of the element should be reverted.
* @param {Boolean} consumables.name If set to `true` element's name will be reverted.
* @param {String|Array.<String>} consumables.attributes Attribute name or array of attribute names to revert.
* @param {String|Array.<String>} consumables.classes Class name or array of class names to revert.
* @param {String|Array.<String>} consumables.styles Style name or array of style names to revert.
*/
revert( consumables ) {
if ( consumables.name ) {
this._canConsumeName = true;
}
for ( const type in this._consumables ) {
if ( type in consumables ) {
this._revert( type, consumables[ type ] );
}
}
}
/**
* Helper method that adds consumables of a given type: attribute, class or style.
*
* Throws {@link module:utils/ckeditorerror~CKEditorError CKEditorError} `viewconsumable-invalid-attribute` when `class` or `style`
* type is provided - it should be handled separately by providing actual style/class type.
*
* @private
* @param {String} type Type of the consumable item: `attributes`, `classes` or `styles`.
* @param {String|Array.<String>} item Consumable item or array of items.
*/
_add( type, item ) {
const items = (0,lodash_es__WEBPACK_IMPORTED_MODULE_1__["default"])( item ) ? item : [ item ];
const consumables = this._consumables[ type ];
for ( const name of items ) {
if ( type === 'attributes' && ( name === 'class' || name === 'style' ) ) {
/**
* Class and style attributes should be handled separately in
* {@link module:engine/conversion/viewconsumable~ViewConsumable#add `ViewConsumable#add()`}.
*
* What you have done is trying to use:
*
* consumables.add( { attributes: [ 'class', 'style' ] } );
*
* While each class and style should be registered separately:
*
* consumables.add( { classes: 'some-class', styles: 'font-weight' } );
*
* @error viewconsumable-invalid-attribute
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_0__["default"]( 'viewconsumable-invalid-attribute', this );
}
consumables.set( name, true );
if ( type === 'styles' ) {
for ( const alsoName of this.element.document.stylesProcessor.getRelatedStyles( name ) ) {
consumables.set( alsoName, true );
}
}
}
}
/**
* Helper method that tests consumables of a given type: attribute, class or style.
*
* @private
* @param {String} type Type of the consumable item: `attributes`, `classes` or `styles`.
* @param {String|Array.<String>} item Consumable item or array of items.
* @returns {Boolean|null} Returns `true` if all items can be consumed, `null` when one of the items cannot be
* consumed and `false` when one of the items is already consumed.
*/
_test( type, item ) {
const items = (0,lodash_es__WEBPACK_IMPORTED_MODULE_1__["default"])( item ) ? item : [ item ];
const consumables = this._consumables[ type ];
for ( const name of items ) {
if ( type === 'attributes' && ( name === 'class' || name === 'style' ) ) {
const consumableName = name == 'class' ? 'classes' : 'styles';
// Check all classes/styles if class/style attribute is tested.
const value = this._test( consumableName, [ ...this._consumables[ consumableName ].keys() ] );
if ( value !== true ) {
return value;
}
} else {
const value = consumables.get( name );
// Return null if attribute is not found.
if ( value === undefined ) {
return null;
}
if ( !value ) {
return false;
}
}
}
return true;
}
/**
* Helper method that consumes items of a given type: attribute, class or style.
*
* @private
* @param {String} type Type of the consumable item: `attributes`, `classes` or `styles`.
* @param {String|Array.<String>} item Consumable item or array of items.
*/
_consume( type, item ) {
const items = (0,lodash_es__WEBPACK_IMPORTED_MODULE_1__["default"])( item ) ? item : [ item ];
const consumables = this._consumables[ type ];
for ( const name of items ) {
if ( type === 'attributes' && ( name === 'class' || name === 'style' ) ) {
const consumableName = name == 'class' ? 'classes' : 'styles';
// If class or style is provided for consumption - consume them all.
this._consume( consumableName, [ ...this._consumables[ consumableName ].keys() ] );
} else {
consumables.set( name, false );
if ( type == 'styles' ) {
for ( const toConsume of this.element.document.stylesProcessor.getRelatedStyles( name ) ) {
consumables.set( toConsume, false );
}
}
}
}
}
/**
* Helper method that reverts items of a given type: attribute, class or style.
*
* @private
* @param {String} type Type of the consumable item: `attributes`, `classes` or , `styles`.
* @param {String|Array.<String>} item Consumable item or array of items.
*/
_revert( type, item ) {
const items = (0,lodash_es__WEBPACK_IMPORTED_MODULE_1__["default"])( item ) ? item : [ item ];
const consumables = this._consumables[ type ];
for ( const name of items ) {
if ( type === 'attributes' && ( name === 'class' || name === 'style' ) ) {
const consumableName = name == 'class' ? 'classes' : 'styles';
// If class or style is provided for reverting - revert them all.
this._revert( consumableName, [ ...this._consumables[ consumableName ].keys() ] );
} else {
const value = consumables.get( name );
if ( value === false ) {
consumables.set( name, true );
}
}
}
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/dataprocessor/basichtmlwriter.js":
/*!**************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/dataprocessor/basichtmlwriter.js ***!
\**************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ BasicHtmlWriter)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/dataprocessor/basichtmlwriter
*/
/* globals document */
/**
* Basic HTML writer. It uses the native `innerHTML` property for basic conversion
* from a document fragment to an HTML string.
*
* @implements module:engine/dataprocessor/htmlwriter~HtmlWriter
*/
class BasicHtmlWriter {
/**
* Returns an HTML string created from the document fragment.
*
* @param {DocumentFragment} fragment
* @returns {String}
*/
getHtml( fragment ) {
const doc = document.implementation.createHTMLDocument( '' );
const container = doc.createElement( 'div' );
container.appendChild( fragment );
return container.innerHTML;
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/dataprocessor/htmldataprocessor.js":
/*!****************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/dataprocessor/htmldataprocessor.js ***!
\****************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ HtmlDataProcessor)
/* harmony export */ });
/* harmony import */ var _basichtmlwriter__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./basichtmlwriter */ "./node_modules/@ckeditor/ckeditor5-engine/src/dataprocessor/basichtmlwriter.js");
/* harmony import */ var _view_domconverter__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../view/domconverter */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/domconverter.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/dataprocessor/htmldataprocessor
*/
/* globals document, DOMParser */
/**
* The HTML data processor class.
* This data processor implementation uses HTML as input and output data.
*
* @implements module:engine/dataprocessor/dataprocessor~DataProcessor
*/
class HtmlDataProcessor {
/**
* Creates a new instance of the HTML data processor class.
*
* @param {module:engine/view/document~Document} document The view document instance.
*/
constructor( document ) {
/**
* A DOM parser instance used to parse an HTML string to an HTML document.
*
* @member {DOMParser}
*/
this.domParser = new DOMParser();
/**
* A DOM converter used to convert DOM elements to view elements.
*
* @member {module:engine/view/domconverter~DomConverter}
*/
this.domConverter = new _view_domconverter__WEBPACK_IMPORTED_MODULE_1__["default"]( document, { renderingMode: 'data' } );
/**
* A basic HTML writer instance used to convert DOM elements to an HTML string.
*
* @member {module:engine/dataprocessor/htmlwriter~HtmlWriter}
*/
this.htmlWriter = new _basichtmlwriter__WEBPACK_IMPORTED_MODULE_0__["default"]();
}
/**
* Converts a provided {@link module:engine/view/documentfragment~DocumentFragment document fragment}
* to data format — in this case to an HTML string.
*
* @param {module:engine/view/documentfragment~DocumentFragment} viewFragment
* @returns {String} HTML string.
*/
toData( viewFragment ) {
// Convert view DocumentFragment to DOM DocumentFragment.
const domFragment = this.domConverter.viewToDom( viewFragment, document );
// Convert DOM DocumentFragment to HTML output.
return this.htmlWriter.getHtml( domFragment );
}
/**
* Converts the provided HTML string to a view tree.
*
* @param {String} data An HTML string.
* @returns {module:engine/view/node~Node|module:engine/view/documentfragment~DocumentFragment|null} A converted view element.
*/
toView( data ) {
// Convert input HTML data to DOM DocumentFragment.
const domFragment = this._toDom( data );
// Convert DOM DocumentFragment to view DocumentFragment.
return this.domConverter.domToView( domFragment );
}
/**
* Registers a {@link module:engine/view/matcher~MatcherPattern} for view elements whose content should be treated as raw data
* and not processed during the conversion from the DOM to the view elements.
*
* The raw data can be later accessed by a
* {@link module:engine/view/element~Element#getCustomProperty custom property of a view element} called `"$rawContent"`.
*
* @param {module:engine/view/matcher~MatcherPattern} pattern Pattern matching all view elements whose content should
* be treated as raw data.
*/
registerRawContentMatcher( pattern ) {
this.domConverter.registerRawContentMatcher( pattern );
}
/**
* If the processor is set to use marked fillers, it will insert ` ` fillers wrapped in `<span>` elements
* (`<span data-cke-filler="true"> </span>`) instead of regular ` ` characters.
*
* This mode allows for a more precise handling of the block fillers (so they do not leak into the editor content) but
* bloats the editor data with additional markup.
*
* This mode may be required by some features and will be turned on by them automatically.
*
* @param {'default'|'marked'} type Whether to use the default or the marked ` ` block fillers.
*/
useFillerType( type ) {
this.domConverter.blockFillerMode = type == 'marked' ? 'markedNbsp' : 'nbsp';
}
/**
* Converts an HTML string to its DOM representation. Returns a document fragment containing nodes parsed from
* the provided data.
*
* @private
* @param {String} data
* @returns {DocumentFragment}
*/
_toDom( data ) {
// Wrap data with a <body> tag so leading non-layout nodes (like <script>, <style>, HTML comment)
// will be preserved in the body collection.
// Do it only for data that is not a full HTML document.
if ( !data.match( /<(?:html|body|head|meta)(?:\s[^>]*)?>/i ) ) {
data = `<body>${ data }</body>`;
}
const document = this.domParser.parseFromString( data, 'text/html' );
const fragment = document.createDocumentFragment();
const bodyChildNodes = document.body.childNodes;
while ( bodyChildNodes.length > 0 ) {
fragment.appendChild( bodyChildNodes[ 0 ] );
}
return fragment;
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/index.js":
/*!**************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/index.js ***!
\**************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "ClickObserver": () => (/* reexport safe */ _view_observer_clickobserver__WEBPACK_IMPORTED_MODULE_22__["default"]),
/* harmony export */ "Conversion": () => (/* reexport safe */ _conversion_conversion__WEBPACK_IMPORTED_MODULE_3__["default"]),
/* harmony export */ "DataController": () => (/* reexport safe */ _controller_datacontroller__WEBPACK_IMPORTED_MODULE_2__["default"]),
/* harmony export */ "DocumentSelection": () => (/* reexport safe */ _model_documentselection__WEBPACK_IMPORTED_MODULE_9__["default"]),
/* harmony export */ "DomConverter": () => (/* reexport safe */ _view_domconverter__WEBPACK_IMPORTED_MODULE_17__["default"]),
/* harmony export */ "DomEventData": () => (/* reexport safe */ _view_observer_domeventdata__WEBPACK_IMPORTED_MODULE_28__["default"]),
/* harmony export */ "DomEventObserver": () => (/* reexport safe */ _view_observer_domeventobserver__WEBPACK_IMPORTED_MODULE_23__["default"]),
/* harmony export */ "DowncastWriter": () => (/* reexport safe */ _view_downcastwriter__WEBPACK_IMPORTED_MODULE_25__["default"]),
/* harmony export */ "EditingController": () => (/* reexport safe */ _controller_editingcontroller__WEBPACK_IMPORTED_MODULE_1__["default"]),
/* harmony export */ "Element": () => (/* reexport safe */ _model_element__WEBPACK_IMPORTED_MODULE_15__["default"]),
/* harmony export */ "History": () => (/* reexport safe */ _model_history__WEBPACK_IMPORTED_MODULE_16__["default"]),
/* harmony export */ "HtmlDataProcessor": () => (/* reexport safe */ _dataprocessor_htmldataprocessor__WEBPACK_IMPORTED_MODULE_4__["default"]),
/* harmony export */ "InsertOperation": () => (/* reexport safe */ _model_operation_insertoperation__WEBPACK_IMPORTED_MODULE_5__["default"]),
/* harmony export */ "LivePosition": () => (/* reexport safe */ _model_liveposition__WEBPACK_IMPORTED_MODULE_12__["default"]),
/* harmony export */ "LiveRange": () => (/* reexport safe */ _model_liverange__WEBPACK_IMPORTED_MODULE_11__["default"]),
/* harmony export */ "MarkerOperation": () => (/* reexport safe */ _model_operation_markeroperation__WEBPACK_IMPORTED_MODULE_6__["default"]),
/* harmony export */ "Matcher": () => (/* reexport safe */ _view_matcher__WEBPACK_IMPORTED_MODULE_27__["default"]),
/* harmony export */ "Model": () => (/* reexport safe */ _model_model__WEBPACK_IMPORTED_MODULE_13__["default"]),
/* harmony export */ "MouseObserver": () => (/* reexport safe */ _view_observer_mouseobserver__WEBPACK_IMPORTED_MODULE_24__["default"]),
/* harmony export */ "Observer": () => (/* reexport safe */ _view_observer_observer__WEBPACK_IMPORTED_MODULE_21__["default"]),
/* harmony export */ "OperationFactory": () => (/* reexport safe */ _model_operation_operationfactory__WEBPACK_IMPORTED_MODULE_7__["default"]),
/* harmony export */ "Range": () => (/* reexport safe */ _model_range__WEBPACK_IMPORTED_MODULE_10__["default"]),
/* harmony export */ "Renderer": () => (/* reexport safe */ _view_renderer__WEBPACK_IMPORTED_MODULE_18__["default"]),
/* harmony export */ "StylesProcessor": () => (/* reexport safe */ _view_stylesmap__WEBPACK_IMPORTED_MODULE_29__.StylesProcessor),
/* harmony export */ "TreeWalker": () => (/* reexport safe */ _model_treewalker__WEBPACK_IMPORTED_MODULE_14__["default"]),
/* harmony export */ "UpcastWriter": () => (/* reexport safe */ _view_upcastwriter__WEBPACK_IMPORTED_MODULE_26__["default"]),
/* harmony export */ "ViewDocument": () => (/* reexport safe */ _view_document__WEBPACK_IMPORTED_MODULE_19__["default"]),
/* harmony export */ "addBackgroundRules": () => (/* reexport safe */ _view_styles_background__WEBPACK_IMPORTED_MODULE_30__.addBackgroundRules),
/* harmony export */ "addBorderRules": () => (/* reexport safe */ _view_styles_border__WEBPACK_IMPORTED_MODULE_31__.addBorderRules),
/* harmony export */ "addMarginRules": () => (/* reexport safe */ _view_styles_margin__WEBPACK_IMPORTED_MODULE_32__.addMarginRules),
/* harmony export */ "addPaddingRules": () => (/* reexport safe */ _view_styles_padding__WEBPACK_IMPORTED_MODULE_33__.addPaddingRules),
/* harmony export */ "disablePlaceholder": () => (/* reexport safe */ _view_placeholder__WEBPACK_IMPORTED_MODULE_0__.disablePlaceholder),
/* harmony export */ "enablePlaceholder": () => (/* reexport safe */ _view_placeholder__WEBPACK_IMPORTED_MODULE_0__.enablePlaceholder),
/* harmony export */ "getBoxSidesShorthandValue": () => (/* reexport safe */ _view_styles_utils__WEBPACK_IMPORTED_MODULE_34__.getBoxSidesShorthandValue),
/* harmony export */ "getBoxSidesValueReducer": () => (/* reexport safe */ _view_styles_utils__WEBPACK_IMPORTED_MODULE_34__.getBoxSidesValueReducer),
/* harmony export */ "getBoxSidesValues": () => (/* reexport safe */ _view_styles_utils__WEBPACK_IMPORTED_MODULE_34__.getBoxSidesValues),
/* harmony export */ "getFillerOffset": () => (/* reexport safe */ _view_containerelement__WEBPACK_IMPORTED_MODULE_20__.getFillerOffset),
/* harmony export */ "getPositionShorthandNormalizer": () => (/* reexport safe */ _view_styles_utils__WEBPACK_IMPORTED_MODULE_34__.getPositionShorthandNormalizer),
/* harmony export */ "getShorthandValues": () => (/* reexport safe */ _view_styles_utils__WEBPACK_IMPORTED_MODULE_34__.getShorthandValues),
/* harmony export */ "hidePlaceholder": () => (/* reexport safe */ _view_placeholder__WEBPACK_IMPORTED_MODULE_0__.hidePlaceholder),
/* harmony export */ "isAttachment": () => (/* reexport safe */ _view_styles_utils__WEBPACK_IMPORTED_MODULE_34__.isAttachment),
/* harmony export */ "isColor": () => (/* reexport safe */ _view_styles_utils__WEBPACK_IMPORTED_MODULE_34__.isColor),
/* harmony export */ "isLength": () => (/* reexport safe */ _view_styles_utils__WEBPACK_IMPORTED_MODULE_34__.isLength),
/* harmony export */ "isLineStyle": () => (/* reexport safe */ _view_styles_utils__WEBPACK_IMPORTED_MODULE_34__.isLineStyle),
/* harmony export */ "isPercentage": () => (/* reexport safe */ _view_styles_utils__WEBPACK_IMPORTED_MODULE_34__.isPercentage),
/* harmony export */ "isPosition": () => (/* reexport safe */ _view_styles_utils__WEBPACK_IMPORTED_MODULE_34__.isPosition),
/* harmony export */ "isRepeat": () => (/* reexport safe */ _view_styles_utils__WEBPACK_IMPORTED_MODULE_34__.isRepeat),
/* harmony export */ "isURL": () => (/* reexport safe */ _view_styles_utils__WEBPACK_IMPORTED_MODULE_34__.isURL),
/* harmony export */ "needsPlaceholder": () => (/* reexport safe */ _view_placeholder__WEBPACK_IMPORTED_MODULE_0__.needsPlaceholder),
/* harmony export */ "showPlaceholder": () => (/* reexport safe */ _view_placeholder__WEBPACK_IMPORTED_MODULE_0__.showPlaceholder),
/* harmony export */ "transformSets": () => (/* reexport safe */ _model_operation_transform__WEBPACK_IMPORTED_MODULE_8__.transformSets)
/* harmony export */ });
/* harmony import */ var _view_placeholder__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./view/placeholder */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/placeholder.js");
/* harmony import */ var _controller_editingcontroller__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./controller/editingcontroller */ "./node_modules/@ckeditor/ckeditor5-engine/src/controller/editingcontroller.js");
/* harmony import */ var _controller_datacontroller__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./controller/datacontroller */ "./node_modules/@ckeditor/ckeditor5-engine/src/controller/datacontroller.js");
/* harmony import */ var _conversion_conversion__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./conversion/conversion */ "./node_modules/@ckeditor/ckeditor5-engine/src/conversion/conversion.js");
/* harmony import */ var _dataprocessor_htmldataprocessor__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./dataprocessor/htmldataprocessor */ "./node_modules/@ckeditor/ckeditor5-engine/src/dataprocessor/htmldataprocessor.js");
/* harmony import */ var _model_operation_insertoperation__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./model/operation/insertoperation */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/insertoperation.js");
/* harmony import */ var _model_operation_markeroperation__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./model/operation/markeroperation */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/markeroperation.js");
/* harmony import */ var _model_operation_operationfactory__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./model/operation/operationfactory */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/operationfactory.js");
/* harmony import */ var _model_operation_transform__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./model/operation/transform */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/transform.js");
/* harmony import */ var _model_documentselection__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./model/documentselection */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/documentselection.js");
/* harmony import */ var _model_range__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./model/range */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/range.js");
/* harmony import */ var _model_liverange__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ./model/liverange */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/liverange.js");
/* harmony import */ var _model_liveposition__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! ./model/liveposition */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/liveposition.js");
/* harmony import */ var _model_model__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(/*! ./model/model */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/model.js");
/* harmony import */ var _model_treewalker__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(/*! ./model/treewalker */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/treewalker.js");
/* harmony import */ var _model_element__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(/*! ./model/element */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/element.js");
/* harmony import */ var _model_history__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(/*! ./model/history */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/history.js");
/* harmony import */ var _view_domconverter__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(/*! ./view/domconverter */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/domconverter.js");
/* harmony import */ var _view_renderer__WEBPACK_IMPORTED_MODULE_18__ = __webpack_require__(/*! ./view/renderer */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/renderer.js");
/* harmony import */ var _view_document__WEBPACK_IMPORTED_MODULE_19__ = __webpack_require__(/*! ./view/document */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/document.js");
/* harmony import */ var _view_containerelement__WEBPACK_IMPORTED_MODULE_20__ = __webpack_require__(/*! ./view/containerelement */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/containerelement.js");
/* harmony import */ var _view_observer_observer__WEBPACK_IMPORTED_MODULE_21__ = __webpack_require__(/*! ./view/observer/observer */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/observer.js");
/* harmony import */ var _view_observer_clickobserver__WEBPACK_IMPORTED_MODULE_22__ = __webpack_require__(/*! ./view/observer/clickobserver */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/clickobserver.js");
/* harmony import */ var _view_observer_domeventobserver__WEBPACK_IMPORTED_MODULE_23__ = __webpack_require__(/*! ./view/observer/domeventobserver */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/domeventobserver.js");
/* harmony import */ var _view_observer_mouseobserver__WEBPACK_IMPORTED_MODULE_24__ = __webpack_require__(/*! ./view/observer/mouseobserver */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/mouseobserver.js");
/* harmony import */ var _view_downcastwriter__WEBPACK_IMPORTED_MODULE_25__ = __webpack_require__(/*! ./view/downcastwriter */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/downcastwriter.js");
/* harmony import */ var _view_upcastwriter__WEBPACK_IMPORTED_MODULE_26__ = __webpack_require__(/*! ./view/upcastwriter */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/upcastwriter.js");
/* harmony import */ var _view_matcher__WEBPACK_IMPORTED_MODULE_27__ = __webpack_require__(/*! ./view/matcher */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/matcher.js");
/* harmony import */ var _view_observer_domeventdata__WEBPACK_IMPORTED_MODULE_28__ = __webpack_require__(/*! ./view/observer/domeventdata */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/domeventdata.js");
/* harmony import */ var _view_stylesmap__WEBPACK_IMPORTED_MODULE_29__ = __webpack_require__(/*! ./view/stylesmap */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/stylesmap.js");
/* harmony import */ var _view_styles_background__WEBPACK_IMPORTED_MODULE_30__ = __webpack_require__(/*! ./view/styles/background */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/styles/background.js");
/* harmony import */ var _view_styles_border__WEBPACK_IMPORTED_MODULE_31__ = __webpack_require__(/*! ./view/styles/border */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/styles/border.js");
/* harmony import */ var _view_styles_margin__WEBPACK_IMPORTED_MODULE_32__ = __webpack_require__(/*! ./view/styles/margin */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/styles/margin.js");
/* harmony import */ var _view_styles_padding__WEBPACK_IMPORTED_MODULE_33__ = __webpack_require__(/*! ./view/styles/padding */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/styles/padding.js");
/* harmony import */ var _view_styles_utils__WEBPACK_IMPORTED_MODULE_34__ = __webpack_require__(/*! ./view/styles/utils */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/styles/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/model/batch.js":
/*!********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/model/batch.js ***!
\********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ Batch)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/model/batch
*/
/**
* A batch instance groups model changes ({@link module:engine/model/operation/operation~Operation operations}). All operations
* grouped in a single batch can be reverted together, so you can also think about a batch as of a single undo step. If you want
* to extend a given undo step, you can add more changes to the batch using {@link module:engine/model/model~Model#enqueueChange}:
*
* model.enqueueChange( batch, writer => {
* writer.insertText( 'foo', paragraph, 'end' );
* } );
*
* @see module:engine/model/model~Model#enqueueChange
* @see module:engine/model/model~Model#change
*/
class Batch {
/**
* Creates a batch instance.
*
* @see module:engine/model/model~Model#enqueueChange
* @see module:engine/model/model~Model#change
* @param {Object} [type] A set of flags that specify the type of the batch. Batch type can alter how some of the features work
* when encountering a given `Batch` instance (for example, when a feature listens to applied operations).
* @param {Boolean} [type.isUndoable=true] Whether a batch can be undone through undo feature.
* @param {Boolean} [type.isLocal=true] Whether a batch includes operations created locally (`true`) or operations created on
* other, remote editors (`false`).
* @param {Boolean} [type.isUndo=false] Whether a batch was created by the undo feature and undoes other operations.
* @param {Boolean} [type.isTyping=false] Whether a batch includes operations connected with a typing action.
*/
constructor( type = {} ) {
if ( typeof type === 'string' ) {
type = type === 'transparent' ? { isUndoable: false } : {};
/**
* The string value for a `type` property of the `Batch` constructor has been deprecated and will be removed in the near future.
* Please refer to the {@link module:engine/model/batch~Batch#constructor `Batch` constructor API documentation} for more
* information.
*
* @error batch-constructor-deprecated-string-type
*/
(0,_ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_0__.logWarning)( 'batch-constructor-deprecated-string-type' );
}
const { isUndoable = true, isLocal = true, isUndo = false, isTyping = false } = type;
/**
* An array of operations that compose this batch.
*
* @readonly
* @type {Array.<module:engine/model/operation/operation~Operation>}
*/
this.operations = [];
/**
* Whether the batch can be undone through the undo feature.
*
* @readonly
* @type {Boolean}
*/
this.isUndoable = isUndoable;
/**
* Whether the batch includes operations created locally (`true`) or operations created on other, remote editors (`false`).
*
* @readonly
* @type {Boolean}
*/
this.isLocal = isLocal;
/**
* Whether the batch was created by the undo feature and undoes other operations.
*
* @readonly
* @type {Boolean}
*/
this.isUndo = isUndo;
/**
* Whether the batch includes operations connected with typing.
*
* @readonly
* @type {Boolean}
*/
this.isTyping = isTyping;
}
/**
* The type of the batch.
*
* **This property has been deprecated and is always set to the `'default'` value.**
*
* It can be one of the following values:
* * `'default'` – All "normal" batches. This is the most commonly used type.
* * `'transparent'` – A batch that should be ignored by other features, i.e. an initial batch or collaborative editing
* changes.
*
* @deprecated
* @type {'default'}
*/
get type() {
/**
* The {@link module:engine/model/batch~Batch#type `Batch#type` } property has been deprecated and will be removed in the near
* future. Use `Batch#isLocal`, `Batch#isUndoable`, `Batch#isUndo` and `Batch#isTyping` instead.
*
* @error batch-type-deprecated
*/
(0,_ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_0__.logWarning)( 'batch-type-deprecated' );
return 'default';
}
/**
* Returns the base version of this batch, which is equal to the base version of the first operation in the batch.
* If there are no operations in the batch or neither operation has the base version set, it returns `null`.
*
* @readonly
* @type {Number|null}
*/
get baseVersion() {
for ( const op of this.operations ) {
if ( op.baseVersion !== null ) {
return op.baseVersion;
}
}
return null;
}
/**
* Adds an operation to the batch instance.
*
* @param {module:engine/model/operation/operation~Operation} operation An operation to add.
* @returns {module:engine/model/operation/operation~Operation} The added operation.
*/
addOperation( operation ) {
operation.batch = this;
this.operations.push( operation );
return operation;
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/model/differ.js":
/*!*********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/model/differ.js ***!
\*********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ Differ)
/* harmony export */ });
/* harmony import */ var _position__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./position */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/position.js");
/* harmony import */ var _range__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./range */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/range.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/model/differ
*/
/**
* Calculates the difference between two model states.
*
* Receives operations that are to be applied on the model document. Marks parts of the model document tree which
* are changed and saves the state of these elements before the change. Then, it compares saved elements with the
* changed elements, after all changes are applied on the model document. Calculates the diff between saved
* elements and new ones and returns a change set.
*/
class Differ {
/**
* Creates a `Differ` instance.
*
* @param {module:engine/model/markercollection~MarkerCollection} markerCollection Model's marker collection.
*/
constructor( markerCollection ) {
/**
* Reference to the model's marker collection.
*
* @private
* @type {module:engine/model/markercollection~MarkerCollection}
*/
this._markerCollection = markerCollection;
/**
* A map that stores changes that happened in a given element.
*
* The keys of the map are references to the model elements.
* The values of the map are arrays with changes that were done on this element.
*
* @private
* @type {Map}
*/
this._changesInElement = new Map();
/**
* A map that stores "element's children snapshots". A snapshot is representing children of a given element before
* the first change was applied on that element. Snapshot items are objects with two properties: `name`,
* containing the element name (or `'$text'` for a text node) and `attributes` which is a map of the node's attributes.
*
* @private
* @type {Map}
*/
this._elementSnapshots = new Map();
/**
* A map that stores all changed markers.
*
* The keys of the map are marker names.
* The values of the map are objects with the following properties:
* - `oldMarkerData`,
* - `newMarkerData`.
*
* @private
* @type {Map.<String, Object>}
*/
this._changedMarkers = new Map();
/**
* Stores the number of changes that were processed. Used to order the changes chronologically. It is important
* when changes are sorted.
*
* @private
* @type {Number}
*/
this._changeCount = 0;
/**
* For efficiency purposes, `Differ` stores the change set returned by the differ after {@link #getChanges} call.
* Cache is reset each time a new operation is buffered. If the cache has not been reset, {@link #getChanges} will
* return the cached value instead of calculating it again.
*
* This property stores those changes that did not take place in graveyard root.
*
* @private
* @type {Array.<Object>|null}
*/
this._cachedChanges = null;
/**
* For efficiency purposes, `Differ` stores the change set returned by the differ after the {@link #getChanges} call.
* The cache is reset each time a new operation is buffered. If the cache has not been reset, {@link #getChanges} will
* return the cached value instead of calculating it again.
*
* This property stores all changes evaluated by `Differ`, including those that took place in the graveyard.
*
* @private
* @type {Array.<Object>|null}
*/
this._cachedChangesWithGraveyard = null;
/**
* Set of model items that were marked to get refreshed in {@link #_refreshItem}.
*
* @private
* @type {Set.<module:engine/model/item~Item>}
*/
this._refreshedItems = new Set();
}
/**
* Informs whether there are any changes buffered in `Differ`.
*
* @readonly
* @type {Boolean}
*/
get isEmpty() {
return this._changesInElement.size == 0 && this._changedMarkers.size == 0;
}
/**
* Buffers the given operation. An operation has to be buffered before it is executed.
*
* Operation type is checked and it is checked which nodes it will affect. These nodes are then stored in `Differ`
* in the state before the operation is executed.
*
* @param {module:engine/model/operation/operation~Operation} operation An operation to buffer.
*/
bufferOperation( operation ) {
// Below we take an operation, check its type, then use its parameters in marking (private) methods.
// The general rule is to not mark elements inside inserted element. All inserted elements are re-rendered.
// Marking changes in them would cause a "double" changing then.
//
switch ( operation.type ) {
case 'insert': {
if ( this._isInInsertedElement( operation.position.parent ) ) {
return;
}
this._markInsert( operation.position.parent, operation.position.offset, operation.nodes.maxOffset );
break;
}
case 'addAttribute':
case 'removeAttribute':
case 'changeAttribute': {
for ( const item of operation.range.getItems( { shallow: true } ) ) {
if ( this._isInInsertedElement( item.parent ) ) {
continue;
}
this._markAttribute( item );
}
break;
}
case 'remove':
case 'move':
case 'reinsert': {
// When range is moved to the same position then not mark it as a change.
// See: https://github.com/ckeditor/ckeditor5-engine/issues/1664.
if (
operation.sourcePosition.isEqual( operation.targetPosition ) ||
operation.sourcePosition.getShiftedBy( operation.howMany ).isEqual( operation.targetPosition )
) {
return;
}
const sourceParentInserted = this._isInInsertedElement( operation.sourcePosition.parent );
const targetParentInserted = this._isInInsertedElement( operation.targetPosition.parent );
if ( !sourceParentInserted ) {
this._markRemove( operation.sourcePosition.parent, operation.sourcePosition.offset, operation.howMany );
}
if ( !targetParentInserted ) {
this._markInsert( operation.targetPosition.parent, operation.getMovedRangeStart().offset, operation.howMany );
}
break;
}
case 'rename': {
if ( this._isInInsertedElement( operation.position.parent ) ) {
return;
}
this._markRemove( operation.position.parent, operation.position.offset, 1 );
this._markInsert( operation.position.parent, operation.position.offset, 1 );
const range = _range__WEBPACK_IMPORTED_MODULE_1__["default"]._createFromPositionAndShift( operation.position, 1 );
for ( const marker of this._markerCollection.getMarkersIntersectingRange( range ) ) {
const markerData = marker.getData();
this.bufferMarkerChange( marker.name, markerData, markerData );
}
break;
}
case 'split': {
const splitElement = operation.splitPosition.parent;
// Mark that children of the split element were removed.
if ( !this._isInInsertedElement( splitElement ) ) {
this._markRemove( splitElement, operation.splitPosition.offset, operation.howMany );
}
// Mark that the new element (split copy) was inserted.
if ( !this._isInInsertedElement( operation.insertionPosition.parent ) ) {
this._markInsert( operation.insertionPosition.parent, operation.insertionPosition.offset, 1 );
}
// If the split took the element from the graveyard, mark that the element from the graveyard was removed.
if ( operation.graveyardPosition ) {
this._markRemove( operation.graveyardPosition.parent, operation.graveyardPosition.offset, 1 );
}
break;
}
case 'merge': {
// Mark that the merged element was removed.
const mergedElement = operation.sourcePosition.parent;
if ( !this._isInInsertedElement( mergedElement.parent ) ) {
this._markRemove( mergedElement.parent, mergedElement.startOffset, 1 );
}
// Mark that the merged element was inserted into graveyard.
const graveyardParent = operation.graveyardPosition.parent;
this._markInsert( graveyardParent, operation.graveyardPosition.offset, 1 );
// Mark that children of merged element were inserted at new parent.
const mergedIntoElement = operation.targetPosition.parent;
if ( !this._isInInsertedElement( mergedIntoElement ) ) {
this._markInsert( mergedIntoElement, operation.targetPosition.offset, mergedElement.maxOffset );
}
break;
}
}
// Clear cache after each buffered operation as it is no longer valid.
this._cachedChanges = null;
}
/**
* Buffers a marker change.
*
* @param {String} markerName The name of the marker that changed.
* @param {module:engine/model/markercollection~MarkerData} oldMarkerData Marker data before the change.
* @param {module:engine/model/markercollection~MarkerData} newMarkerData Marker data after the change.
*/
bufferMarkerChange( markerName, oldMarkerData, newMarkerData ) {
const buffered = this._changedMarkers.get( markerName );
if ( !buffered ) {
this._changedMarkers.set( markerName, {
newMarkerData,
oldMarkerData
} );
} else {
buffered.newMarkerData = newMarkerData;
if ( buffered.oldMarkerData.range == null && newMarkerData.range == null ) {
// The marker is going to be removed (`newMarkerData.range == null`) but it did not exist before the first buffered change
// (`buffered.oldMarkerData.range == null`). In this case, do not keep the marker in buffer at all.
this._changedMarkers.delete( markerName );
}
}
}
/**
* Returns all markers that should be removed as a result of buffered changes.
*
* @returns {Array.<Object>} Markers to remove. Each array item is an object containing the `name` and `range` properties.
*/
getMarkersToRemove() {
const result = [];
for ( const [ name, change ] of this._changedMarkers ) {
if ( change.oldMarkerData.range != null ) {
result.push( { name, range: change.oldMarkerData.range } );
}
}
return result;
}
/**
* Returns all markers which should be added as a result of buffered changes.
*
* @returns {Array.<Object>} Markers to add. Each array item is an object containing the `name` and `range` properties.
*/
getMarkersToAdd() {
const result = [];
for ( const [ name, change ] of this._changedMarkers ) {
if ( change.newMarkerData.range != null ) {
result.push( { name, range: change.newMarkerData.range } );
}
}
return result;
}
/**
* Returns all markers which changed.
*
* @returns {Array.<Object>}
*/
getChangedMarkers() {
return Array.from( this._changedMarkers ).map( ( [ name, change ] ) => (
{
name,
data: {
oldRange: change.oldMarkerData.range,
newRange: change.newMarkerData.range
}
}
) );
}
/**
* Checks whether some of the buffered changes affect the editor data.
*
* Types of changes which affect the editor data:
*
* * model structure changes,
* * attribute changes,
* * changes of markers which were defined as `affectsData`,
* * changes of markers' `affectsData` property.
*
* @returns {Boolean}
*/
hasDataChanges() {
for ( const { newMarkerData, oldMarkerData } of this._changedMarkers.values() ) {
if ( newMarkerData.affectsData !== oldMarkerData.affectsData ) {
return true;
}
if ( newMarkerData.affectsData ) {
// Skip markers, which ranges have not changed.
if ( newMarkerData.range && oldMarkerData.range && newMarkerData.range.isEqual( oldMarkerData.range ) ) {
return false;
}
return true;
}
}
// If markers do not affect the data, check whether there are some changes in elements.
return this._changesInElement.size > 0;
}
/**
* Calculates the diff between the old model tree state (the state before the first buffered operations since the last {@link #reset}
* call) and the new model tree state (actual one). It should be called after all buffered operations are executed.
*
* The diff set is returned as an array of {@link module:engine/model/differ~DiffItem diff items}, each describing a change done
* on the model. The items are sorted by the position on which the change happened. If a position
* {@link module:engine/model/position~Position#isBefore is before} another one, it will be on an earlier index in the diff set.
*
* **Note**: Elements inside inserted element will not have a separate diff item, only the top most element change will be reported.
*
* Because calculating the diff is a costly operation, the result is cached. If no new operation was buffered since the
* previous {@link #getChanges} call, the next call will return the cached value.
*
* @param {Object} options Additional options.
* @param {Boolean} [options.includeChangesInGraveyard=false] If set to `true`, also changes that happened
* in the graveyard root will be returned. By default, changes in the graveyard root are not returned.
* @returns {Array.<module:engine/model/differ~DiffItem>} Diff between the old and the new model tree state.
*/
getChanges( options = { includeChangesInGraveyard: false } ) {
// If there are cached changes, just return them instead of calculating changes again.
if ( this._cachedChanges ) {
if ( options.includeChangesInGraveyard ) {
return this._cachedChangesWithGraveyard.slice();
} else {
return this._cachedChanges.slice();
}
}
// Will contain returned results.
let diffSet = [];
// Check all changed elements.
for ( const element of this._changesInElement.keys() ) {
// Get changes for this element and sort them.
const changes = this._changesInElement.get( element ).sort( ( a, b ) => {
if ( a.offset === b.offset ) {
if ( a.type != b.type ) {
// If there are multiple changes at the same position, "remove" change should be first.
// If the order is different, for example, we would first add some nodes and then removed them
// (instead of the nodes that we should remove).
return a.type == 'remove' ? -1 : 1;
}
return 0;
}
return a.offset < b.offset ? -1 : 1;
} );
// Get children of this element before any change was applied on it.
const snapshotChildren = this._elementSnapshots.get( element );
// Get snapshot of current element's children.
const elementChildren = _getChildrenSnapshot( element.getChildren() );
// Generate actions basing on changes done on element.
const actions = _generateActionsFromChanges( snapshotChildren.length, changes );
let i = 0; // Iterator in `elementChildren` array -- iterates through current children of element.
let j = 0; // Iterator in `snapshotChildren` array -- iterates through old children of element.
// Process every action.
for ( const action of actions ) {
if ( action === 'i' ) {
// Generate diff item for this element and insert it into the diff set.
diffSet.push( this._getInsertDiff( element, i, elementChildren[ i ].name ) );
i++;
} else if ( action === 'r' ) {
// Generate diff item for this element and insert it into the diff set.
diffSet.push( this._getRemoveDiff( element, i, snapshotChildren[ j ].name ) );
j++;
} else if ( action === 'a' ) {
// Take attributes from saved and current children.
const elementAttributes = elementChildren[ i ].attributes;
const snapshotAttributes = snapshotChildren[ j ].attributes;
let range;
if ( elementChildren[ i ].name == '$text' ) {
range = new _range__WEBPACK_IMPORTED_MODULE_1__["default"]( _position__WEBPACK_IMPORTED_MODULE_0__["default"]._createAt( element, i ), _position__WEBPACK_IMPORTED_MODULE_0__["default"]._createAt( element, i + 1 ) );
} else {
const index = element.offsetToIndex( i );
range = new _range__WEBPACK_IMPORTED_MODULE_1__["default"]( _position__WEBPACK_IMPORTED_MODULE_0__["default"]._createAt( element, i ), _position__WEBPACK_IMPORTED_MODULE_0__["default"]._createAt( element.getChild( index ), 0 ) );
}
// Generate diff items for this change (there might be multiple attributes changed and
// there is a single diff for each of them) and insert them into the diff set.
diffSet.push( ...this._getAttributesDiff( range, snapshotAttributes, elementAttributes ) );
i++;
j++;
} else {
// `action` is 'equal'. Child not changed.
i++;
j++;
}
}
}
// Then, sort the changes by the position (change at position before other changes is first).
diffSet.sort( ( a, b ) => {
// If the change is in different root, we don't care much, but we'd like to have all changes in given
// root "together" in the array. So let's just sort them by the root name. It does not matter which root
// will be processed first.
if ( a.position.root != b.position.root ) {
return a.position.root.rootName < b.position.root.rootName ? -1 : 1;
}
// If change happens at the same position...
if ( a.position.isEqual( b.position ) ) {
// Keep chronological order of operations.
return a.changeCount - b.changeCount;
}
// If positions differ, position "on the left" should be earlier in the result.
return a.position.isBefore( b.position ) ? -1 : 1;
} );
// Glue together multiple changes (mostly on text nodes).
for ( let i = 1, prevIndex = 0; i < diffSet.length; i++ ) {
const prevDiff = diffSet[ prevIndex ];
const thisDiff = diffSet[ i ];
// Glue remove changes if they happen on text on same position.
const isConsecutiveTextRemove =
prevDiff.type == 'remove' && thisDiff.type == 'remove' &&
prevDiff.name == '$text' && thisDiff.name == '$text' &&
prevDiff.position.isEqual( thisDiff.position );
// Glue insert changes if they happen on text on consecutive fragments.
const isConsecutiveTextAdd =
prevDiff.type == 'insert' && thisDiff.type == 'insert' &&
prevDiff.name == '$text' && thisDiff.name == '$text' &&
prevDiff.position.parent == thisDiff.position.parent &&
prevDiff.position.offset + prevDiff.length == thisDiff.position.offset;
// Glue attribute changes if they happen on consecutive fragments and have same key, old value and new value.
const isConsecutiveAttributeChange =
prevDiff.type == 'attribute' && thisDiff.type == 'attribute' &&
prevDiff.position.parent == thisDiff.position.parent &&
prevDiff.range.isFlat && thisDiff.range.isFlat &&
prevDiff.position.offset + prevDiff.length == thisDiff.position.offset &&
prevDiff.attributeKey == thisDiff.attributeKey &&
prevDiff.attributeOldValue == thisDiff.attributeOldValue &&
prevDiff.attributeNewValue == thisDiff.attributeNewValue;
if ( isConsecutiveTextRemove || isConsecutiveTextAdd || isConsecutiveAttributeChange ) {
prevDiff.length++;
if ( isConsecutiveAttributeChange ) {
prevDiff.range.end = prevDiff.range.end.getShiftedBy( 1 );
}
diffSet[ i ] = null;
} else {
prevIndex = i;
}
}
diffSet = diffSet.filter( v => v );
// Remove `changeCount` property from diff items. It is used only for sorting and is internal thing.
for ( const item of diffSet ) {
delete item.changeCount;
if ( item.type == 'attribute' ) {
delete item.position;
delete item.length;
}
}
this._changeCount = 0;
// Cache changes.
this._cachedChangesWithGraveyard = diffSet;
this._cachedChanges = diffSet.filter( _changesInGraveyardFilter );
if ( options.includeChangesInGraveyard ) {
return this._cachedChangesWithGraveyard.slice();
} else {
return this._cachedChanges.slice();
}
}
/**
* Returns a set of model items that were marked to get refreshed.
*
* @return {Set.<module:engine/model/item~Item>}
*/
getRefreshedItems() {
return new Set( this._refreshedItems );
}
/**
* Resets `Differ`. Removes all buffered changes.
*/
reset() {
this._changesInElement.clear();
this._elementSnapshots.clear();
this._changedMarkers.clear();
this._refreshedItems = new Set();
this._cachedChanges = null;
}
/**
* Marks the given `item` in differ to be "refreshed". It means that the item will be marked as removed and inserted
* in the differ changes set, so it will be effectively re-converted when the differ changes are handled by a dispatcher.
*
* @protected
* @param {module:engine/model/item~Item} item Item to refresh.
*/
_refreshItem( item ) {
if ( this._isInInsertedElement( item.parent ) ) {
return;
}
this._markRemove( item.parent, item.startOffset, item.offsetSize );
this._markInsert( item.parent, item.startOffset, item.offsetSize );
this._refreshedItems.add( item );
const range = _range__WEBPACK_IMPORTED_MODULE_1__["default"]._createOn( item );
for ( const marker of this._markerCollection.getMarkersIntersectingRange( range ) ) {
const markerData = marker.getData();
this.bufferMarkerChange( marker.name, markerData, markerData );
}
// Clear cache after each buffered operation as it is no longer valid.
this._cachedChanges = null;
}
/**
* Saves and handles an insert change.
*
* @private
* @param {module:engine/model/element~Element} parent
* @param {Number} offset
* @param {Number} howMany
*/
_markInsert( parent, offset, howMany ) {
const changeItem = { type: 'insert', offset, howMany, count: this._changeCount++ };
this._markChange( parent, changeItem );
}
/**
* Saves and handles a remove change.
*
* @private
* @param {module:engine/model/element~Element} parent
* @param {Number} offset
* @param {Number} howMany
*/
_markRemove( parent, offset, howMany ) {
const changeItem = { type: 'remove', offset, howMany, count: this._changeCount++ };
this._markChange( parent, changeItem );
this._removeAllNestedChanges( parent, offset, howMany );
}
/**
* Saves and handles an attribute change.
*
* @private
* @param {module:engine/model/item~Item} item
*/
_markAttribute( item ) {
const changeItem = { type: 'attribute', offset: item.startOffset, howMany: item.offsetSize, count: this._changeCount++ };
this._markChange( item.parent, changeItem );
}
/**
* Saves and handles a model change.
*
* @private
* @param {module:engine/model/element~Element} parent
* @param {Object} changeItem
*/
_markChange( parent, changeItem ) {
// First, make a snapshot of this parent's children (it will be made only if it was not made before).
this._makeSnapshot( parent );
// Then, get all changes that already were done on the element (empty array if this is the first change).
const changes = this._getChangesForElement( parent );
// Then, look through all the changes, and transform them or the new change.
this._handleChange( changeItem, changes );
// Add the new change.
changes.push( changeItem );
// Remove incorrect changes. During transformation some change might be, for example, included in another.
// In that case, the change will have `howMany` property set to `0` or less. We need to remove those changes.
for ( let i = 0; i < changes.length; i++ ) {
if ( changes[ i ].howMany < 1 ) {
changes.splice( i, 1 );
i--;
}
}
}
/**
* Gets an array of changes that have already been saved for a given element.
*
* @private
* @param {module:engine/model/element~Element} element
* @returns {Array.<Object>}
*/
_getChangesForElement( element ) {
let changes;
if ( this._changesInElement.has( element ) ) {
changes = this._changesInElement.get( element );
} else {
changes = [];
this._changesInElement.set( element, changes );
}
return changes;
}
/**
* Saves a children snapshot for a given element.
*
* @private
* @param {module:engine/model/element~Element} element
*/
_makeSnapshot( element ) {
if ( !this._elementSnapshots.has( element ) ) {
this._elementSnapshots.set( element, _getChildrenSnapshot( element.getChildren() ) );
}
}
/**
* For a given newly saved change, compares it with a change already done on the element and modifies the incoming
* change and/or the old change.
*
* @private
* @param {Object} inc Incoming (new) change.
* @param {Array.<Object>} changes An array containing all the changes done on that element.
*/
_handleChange( inc, changes ) {
// We need a helper variable that will store how many nodes are to be still handled for this change item.
// `nodesToHandle` (how many nodes still need to be handled) and `howMany` (how many nodes were affected)
// needs to be differentiated.
//
// This comes up when there are multiple changes that are affected by `inc` change item.
//
// For example: assume two insert changes: `{ offset: 2, howMany: 1 }` and `{ offset: 5, howMany: 1 }`.
// Assume that `inc` change is remove `{ offset: 2, howMany: 2, nodesToHandle: 2 }`.
//
// Then, we:
// - "forget" about first insert change (it is "eaten" by remove),
// - because of that, at the end we will want to remove only one node (`nodesToHandle = 1`),
// - but still we have to change offset of the second insert change from `5` to `3`!
//
// So, `howMany` does not change throughout items transformation and keeps information about how many nodes were affected,
// while `nodesToHandle` means how many nodes need to be handled after the change item is transformed by other changes.
inc.nodesToHandle = inc.howMany;
for ( const old of changes ) {
const incEnd = inc.offset + inc.howMany;
const oldEnd = old.offset + old.howMany;
if ( inc.type == 'insert' ) {
if ( old.type == 'insert' ) {
if ( inc.offset <= old.offset ) {
old.offset += inc.howMany;
} else if ( inc.offset < oldEnd ) {
old.howMany += inc.nodesToHandle;
inc.nodesToHandle = 0;
}
}
if ( old.type == 'remove' ) {
if ( inc.offset < old.offset ) {
old.offset += inc.howMany;
}
}
if ( old.type == 'attribute' ) {
if ( inc.offset <= old.offset ) {
old.offset += inc.howMany;
} else if ( inc.offset < oldEnd ) {
// This case is more complicated, because attribute change has to be split into two.
// Example (assume that uppercase and lowercase letters mean different attributes):
//
// initial state: abcxyz
// attribute change: aBCXYz
// incoming insert: aBCfooXYz
//
// Change ranges cannot intersect because each item has to be described exactly (it was either
// not changed, inserted, removed, or its attribute was changed). That's why old attribute
// change has to be split and both parts has to be handled separately from now on.
const howMany = old.howMany;
old.howMany = inc.offset - old.offset;
// Add the second part of attribute change to the beginning of processed array so it won't
// be processed again in this loop.
changes.unshift( {
type: 'attribute',
offset: incEnd,
howMany: howMany - old.howMany,
count: this._changeCount++
} );
}
}
}
if ( inc.type == 'remove' ) {
if ( old.type == 'insert' ) {
if ( incEnd <= old.offset ) {
old.offset -= inc.howMany;
} else if ( incEnd <= oldEnd ) {
if ( inc.offset < old.offset ) {
const intersectionLength = incEnd - old.offset;
old.offset = inc.offset;
old.howMany -= intersectionLength;
inc.nodesToHandle -= intersectionLength;
} else {
old.howMany -= inc.nodesToHandle;
inc.nodesToHandle = 0;
}
} else {
if ( inc.offset <= old.offset ) {
inc.nodesToHandle -= old.howMany;
old.howMany = 0;
} else if ( inc.offset < oldEnd ) {
const intersectionLength = oldEnd - inc.offset;
old.howMany -= intersectionLength;
inc.nodesToHandle -= intersectionLength;
}
}
}
if ( old.type == 'remove' ) {
if ( incEnd <= old.offset ) {
old.offset -= inc.howMany;
} else if ( inc.offset < old.offset ) {
inc.nodesToHandle += old.howMany;
old.howMany = 0;
}
}
if ( old.type == 'attribute' ) {
if ( incEnd <= old.offset ) {
old.offset -= inc.howMany;
} else if ( inc.offset < old.offset ) {
const intersectionLength = incEnd - old.offset;
old.offset = inc.offset;
old.howMany -= intersectionLength;
} else if ( inc.offset < oldEnd ) {
if ( incEnd <= oldEnd ) {
// On first sight in this case we don't need to split attribute operation into two.
// However the changes set is later converted to actions (see `_generateActionsFromChanges`).
// For that reason, no two changes may intersect.
// So we cannot have an attribute change that "contains" remove change.
// Attribute change needs to be split.
const howMany = old.howMany;
old.howMany = inc.offset - old.offset;
const howManyAfter = howMany - old.howMany - inc.nodesToHandle;
// Add the second part of attribute change to the beginning of processed array so it won't
// be processed again in this loop.
changes.unshift( {
type: 'attribute',
offset: inc.offset,
howMany: howManyAfter,
count: this._changeCount++
} );
} else {
old.howMany -= oldEnd - inc.offset;
}
}
}
}
if ( inc.type == 'attribute' ) {
// In case of attribute change, `howMany` should be kept same as `nodesToHandle`. It's not an error.
if ( old.type == 'insert' ) {
if ( inc.offset < old.offset && incEnd > old.offset ) {
if ( incEnd > oldEnd ) {
// This case is similar to a case described when incoming change was insert and old change was attribute.
// See comment above.
//
// This time incoming change is attribute. We need to split incoming change in this case too.
// However this time, the second part of the attribute change needs to be processed further
// because there might be other changes that it collides with.
const attributePart = {
type: 'attribute',
offset: oldEnd,
howMany: incEnd - oldEnd,
count: this._changeCount++
};
this._handleChange( attributePart, changes );
changes.push( attributePart );
}
inc.nodesToHandle = old.offset - inc.offset;
inc.howMany = inc.nodesToHandle;
} else if ( inc.offset >= old.offset && inc.offset < oldEnd ) {
if ( incEnd > oldEnd ) {
inc.nodesToHandle = incEnd - oldEnd;
inc.offset = oldEnd;
} else {
inc.nodesToHandle = 0;
}
}
}
if ( old.type == 'remove' ) {
// This is a case when attribute change "contains" remove change.
// The attribute change needs to be split into two because changes cannot intersect.
if ( inc.offset < old.offset && incEnd > old.offset ) {
const attributePart = {
type: 'attribute',
offset: old.offset,
howMany: incEnd - old.offset,
count: this._changeCount++
};
this._handleChange( attributePart, changes );
changes.push( attributePart );
inc.nodesToHandle = old.offset - inc.offset;
inc.howMany = inc.nodesToHandle;
}
}
if ( old.type == 'attribute' ) {
// There are only two conflicting scenarios possible here:
if ( inc.offset >= old.offset && incEnd <= oldEnd ) {
// `old` change includes `inc` change, or they are the same.
inc.nodesToHandle = 0;
inc.howMany = 0;
inc.offset = 0;
} else if ( inc.offset <= old.offset && incEnd >= oldEnd ) {
// `inc` change includes `old` change.
old.howMany = 0;
}
}
}
}
inc.howMany = inc.nodesToHandle;
delete inc.nodesToHandle;
}
/**
* Returns an object with a single insert change description.
*
* @private
* @param {module:engine/model/element~Element} parent The element in which the change happened.
* @param {Number} offset The offset at which change happened.
* @param {String} name The name of the removed element or `'$text'` for a character.
* @returns {Object} The diff item.
*/
_getInsertDiff( parent, offset, name ) {
return {
type: 'insert',
position: _position__WEBPACK_IMPORTED_MODULE_0__["default"]._createAt( parent, offset ),
name,
length: 1,
changeCount: this._changeCount++
};
}
/**
* Returns an object with a single remove change description.
*
* @private
* @param {module:engine/model/element~Element} parent The element in which change happened.
* @param {Number} offset The offset at which change happened.
* @param {String} name The name of the removed element or `'$text'` for a character.
* @returns {Object} The diff item.
*/
_getRemoveDiff( parent, offset, name ) {
return {
type: 'remove',
position: _position__WEBPACK_IMPORTED_MODULE_0__["default"]._createAt( parent, offset ),
name,
length: 1,
changeCount: this._changeCount++
};
}
/**
* Returns an array of objects where each one is a single attribute change description.
*
* @private
* @param {module:engine/model/range~Range} range The range where the change happened.
* @param {Map} oldAttributes A map, map iterator or compatible object that contains attributes before the change.
* @param {Map} newAttributes A map, map iterator or compatible object that contains attributes after the change.
* @returns {Array.<Object>} An array containing one or more diff items.
*/
_getAttributesDiff( range, oldAttributes, newAttributes ) {
// Results holder.
const diffs = [];
// Clone new attributes as we will be performing changes on this object.
newAttributes = new Map( newAttributes );
// Look through old attributes.
for ( const [ key, oldValue ] of oldAttributes ) {
// Check what is the new value of the attribute (or if it was removed).
const newValue = newAttributes.has( key ) ? newAttributes.get( key ) : null;
// If values are different (or attribute was removed)...
if ( newValue !== oldValue ) {
// Add diff item.
diffs.push( {
type: 'attribute',
position: range.start,
range: range.clone(),
length: 1,
attributeKey: key,
attributeOldValue: oldValue,
attributeNewValue: newValue,
changeCount: this._changeCount++
} );
}
// Prevent returning two diff items for the same change.
newAttributes.delete( key );
}
// Look through new attributes that weren't handled above.
for ( const [ key, newValue ] of newAttributes ) {
// Each of them is a new attribute. Add diff item.
diffs.push( {
type: 'attribute',
position: range.start,
range: range.clone(),
length: 1,
attributeKey: key,
attributeOldValue: null,
attributeNewValue: newValue,
changeCount: this._changeCount++
} );
}
return diffs;
}
/**
* Checks whether given element or any of its parents is an element that is buffered as an inserted element.
*
* @private
* @param {module:engine/model/element~Element} element Element to check.
* @returns {Boolean}
*/
_isInInsertedElement( element ) {
const parent = element.parent;
if ( !parent ) {
return false;
}
const changes = this._changesInElement.get( parent );
const offset = element.startOffset;
if ( changes ) {
for ( const change of changes ) {
if ( change.type == 'insert' && offset >= change.offset && offset < change.offset + change.howMany ) {
return true;
}
}
}
return this._isInInsertedElement( parent );
}
/**
* Removes deeply all buffered changes that are registered in elements from range specified by `parent`, `offset`
* and `howMany`.
*
* @private
* @param {module:engine/model/element~Element} parent
* @param {Number} offset
* @param {Number} howMany
*/
_removeAllNestedChanges( parent, offset, howMany ) {
const range = new _range__WEBPACK_IMPORTED_MODULE_1__["default"]( _position__WEBPACK_IMPORTED_MODULE_0__["default"]._createAt( parent, offset ), _position__WEBPACK_IMPORTED_MODULE_0__["default"]._createAt( parent, offset + howMany ) );
for ( const item of range.getItems( { shallow: true } ) ) {
if ( item.is( 'element' ) ) {
this._elementSnapshots.delete( item );
this._changesInElement.delete( item );
this._removeAllNestedChanges( item, 0, item.maxOffset );
}
}
}
}
// Returns an array that is a copy of passed child list with the exception that text nodes are split to one or more
// objects, each representing one character and attributes set on that character.
function _getChildrenSnapshot( children ) {
const snapshot = [];
for ( const child of children ) {
if ( child.is( '$text' ) ) {
for ( let i = 0; i < child.data.length; i++ ) {
snapshot.push( {
name: '$text',
attributes: new Map( child.getAttributes() )
} );
}
} else {
snapshot.push( {
name: child.name,
attributes: new Map( child.getAttributes() )
} );
}
}
return snapshot;
}
// Generates array of actions for given changes set.
// It simulates what `diff` function does.
// Generated actions are:
// - 'e' for 'equal' - when item at that position did not change,
// - 'i' for 'insert' - when item at that position was inserted,
// - 'r' for 'remove' - when item at that position was removed,
// - 'a' for 'attribute' - when item at that position has it attributes changed.
//
// Example (assume that uppercase letters have bold attribute, compare with function code):
//
// children before: fooBAR
// children after: foxybAR
//
// changes: type: remove, offset: 1, howMany: 1
// type: insert, offset: 2, howMany: 2
// type: attribute, offset: 4, howMany: 1
//
// expected actions: equal (f), remove (o), equal (o), insert (x), insert (y), attribute (b), equal (A), equal (R)
//
// steps taken by th script:
//
// 1. change = "type: remove, offset: 1, howMany: 1"; offset = 0; oldChildrenHandled = 0
// 1.1 between this change and the beginning is one not-changed node, fill with one equal action, one old child has been handled
// 1.2 this change removes one node, add one remove action
// 1.3 change last visited `offset` to 1
// 1.4 since an old child has been removed, one more old child has been handled
// 1.5 actions at this point are: equal, remove
//
// 2. change = "type: insert, offset: 2, howMany: 2"; offset = 1; oldChildrenHandled = 2
// 2.1 between this change and previous change is one not-changed node, add equal action, another one old children has been handled
// 2.2 this change inserts two nodes, add two insert actions
// 2.3 change last visited offset to the end of the inserted range, that is 4
// 2.4 actions at this point are: equal, remove, equal, insert, insert
//
// 3. change = "type: attribute, offset: 4, howMany: 1"; offset = 4, oldChildrenHandled = 3
// 3.1 between this change and previous change are no not-changed nodes
// 3.2 this change changes one node, add one attribute action
// 3.3 change last visited `offset` to the end of change range, that is 5
// 3.4 since an old child has been changed, one more old child has been handled
// 3.5 actions at this point are: equal, remove, equal, insert, insert, attribute
//
// 4. after loop oldChildrenHandled = 4, oldChildrenLength = 6 (fooBAR is 6 characters)
// 4.1 fill up with two equal actions
//
// The result actions are: equal, remove, equal, insert, insert, attribute, equal, equal.
function _generateActionsFromChanges( oldChildrenLength, changes ) {
const actions = [];
let offset = 0;
let oldChildrenHandled = 0;
// Go through all buffered changes.
for ( const change of changes ) {
// First, fill "holes" between changes with "equal" actions.
if ( change.offset > offset ) {
for ( let i = 0; i < change.offset - offset; i++ ) {
actions.push( 'e' );
}
oldChildrenHandled += change.offset - offset;
}
// Then, fill up actions accordingly to change type.
if ( change.type == 'insert' ) {
for ( let i = 0; i < change.howMany; i++ ) {
actions.push( 'i' );
}
// The last handled offset is after inserted range.
offset = change.offset + change.howMany;
} else if ( change.type == 'remove' ) {
for ( let i = 0; i < change.howMany; i++ ) {
actions.push( 'r' );
}
// The last handled offset is at the position where the nodes were removed.
offset = change.offset;
// We removed `howMany` old nodes, update `oldChildrenHandled`.
oldChildrenHandled += change.howMany;
} else {
actions.push( ...'a'.repeat( change.howMany ).split( '' ) );
// The last handled offset is at the position after the changed range.
offset = change.offset + change.howMany;
// We changed `howMany` old nodes, update `oldChildrenHandled`.
oldChildrenHandled += change.howMany;
}
}
// Fill "equal" actions at the end of actions set. Use `oldChildrenHandled` to see how many children
// has not been changed / removed at the end of their parent.
if ( oldChildrenHandled < oldChildrenLength ) {
for ( let i = 0; i < oldChildrenLength - oldChildrenHandled - offset; i++ ) {
actions.push( 'e' );
}
}
return actions;
}
// Filter callback for Array.filter that filters out change entries that are in graveyard.
function _changesInGraveyardFilter( entry ) {
const posInGy = entry.position && entry.position.root.rootName == '$graveyard';
const rangeInGy = entry.range && entry.range.root.rootName == '$graveyard';
return !posInGy && !rangeInGy;
}
/**
* The single diff item.
*
* Could be one of:
*
* * {@link module:engine/model/differ~DiffItemInsert `DiffItemInsert`},
* * {@link module:engine/model/differ~DiffItemRemove `DiffItemRemove`},
* * {@link module:engine/model/differ~DiffItemAttribute `DiffItemAttribute`}.
*
* @interface DiffItem
*/
/**
* The single diff item for inserted nodes.
*
* @class DiffItemInsert
* @implements module:engine/model/differ~DiffItem
*/
/**
* The type of diff item.
*
* @member {'insert'} module:engine/model/differ~DiffItemInsert#type
*/
/**
* The name of the inserted elements or `'$text'` for a text node.
*
* @member {String} module:engine/model/differ~DiffItemInsert#name
*/
/**
* The position where the node was inserted.
*
* @member {module:engine/model/position~Position} module:engine/model/differ~DiffItemInsert#position
*/
/**
* The length of an inserted text node. For elements it is always 1 as each inserted element is counted as a one.
*
* @member {Number} module:engine/model/differ~DiffItemInsert#length
*/
/**
* The single diff item for removed nodes.
*
* @class DiffItemRemove
* @implements module:engine/model/differ~DiffItem
*/
/**
* The type of diff item.
*
* @member {'remove'} module:engine/model/differ~DiffItemRemove#type
*/
/**
* The name of the removed element or `'$text'` for a text node.
*
* @member {String} module:engine/model/differ~DiffItemRemove#name
*/
/**
* The position where the node was removed.
*
* @member {module:engine/model/position~Position} module:engine/model/differ~DiffItemRemove#position
*/
/**
* The length of a removed text node. For elements it is always 1 as each removed element is counted as a one.
*
* @member {Number} module:engine/model/differ~DiffItemRemove#length
*/
/**
* The single diff item for attribute change.
*
* @class DiffItemAttribute
* @implements module:engine/model/differ~DiffItem
*/
/**
* The type of diff item.
*
* @member {'attribute'} module:engine/model/differ~DiffItemAttribute#type
*/
/**
* The name of the changed attribute.
*
* @member {String} module:engine/model/differ~DiffItemAttribute#attributeKey
*/
/**
* An attribute previous value (before change).
*
* @member {String} module:engine/model/differ~DiffItemAttribute#attributeOldValue
*/
/**
* An attribute new value (after change).
*
* @member {String} module:engine/model/differ~DiffItemAttribute#attributeNewValue
*/
/**
* The range where the change happened.
*
* @member {module:engine/model/range~Range} module:engine/model/differ~DiffItemAttribute#range
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/model/document.js":
/*!***********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/model/document.js ***!
\***********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ Document)
/* harmony export */ });
/* harmony import */ var _differ__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./differ */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/differ.js");
/* harmony import */ var _rootelement__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./rootelement */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/rootelement.js");
/* harmony import */ var _history__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./history */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/history.js");
/* harmony import */ var _documentselection__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./documentselection */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/documentselection.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_collection__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/collection */ "./node_modules/@ckeditor/ckeditor5-utils/src/collection.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_emittermixin__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/emittermixin */ "./node_modules/@ckeditor/ckeditor5-utils/src/emittermixin.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/mix */ "./node_modules/@ckeditor/ckeditor5-utils/src/mix.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_unicode__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/unicode */ "./node_modules/@ckeditor/ckeditor5-utils/src/unicode.js");
/* harmony import */ var lodash_es__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! lodash-es */ "./node_modules/lodash-es/clone.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/model/document
*/
// @if CK_DEBUG_ENGINE // const { logDocument } = require( '../dev-utils/utils' );
const graveyardName = '$graveyard';
/**
* Data model's document. It contains the model's structure, its selection and the history of changes.
*
* Read more about working with the model in
* {@glink framework/guides/architecture/editing-engine#model introduction to the the editing engine's architecture}.
*
* Usually, the document contains just one {@link module:engine/model/document~Document#roots root element}, so
* you can retrieve it by just calling {@link module:engine/model/document~Document#getRoot} without specifying its name:
*
* model.document.getRoot(); // -> returns the main root
*
* However, the document may contain multiple roots – e.g. when the editor has multiple editable areas
* (e.g. a title and a body of a message).
*
* @mixes module:utils/emittermixin~EmitterMixin
*/
class Document {
/**
* Creates an empty document instance with no {@link #roots} (other than
* the {@link #graveyard graveyard root}).
*/
constructor( model ) {
/**
* The {@link module:engine/model/model~Model model} that the document is a part of.
*
* @readonly
* @type {module:engine/model/model~Model}
*/
this.model = model;
/**
* The document version. It starts from `0` and every operation increases the version number. It is used to ensure that
* operations are applied on a proper document version.
*
* If the {@link module:engine/model/operation/operation~Operation#baseVersion base version} does not match the document version,
* a {@link module:utils/ckeditorerror~CKEditorError model-document-applyoperation-wrong-version} error is thrown.
*
* @type {Number}
*/
this.version = 0;
/**
* The document's history.
*
* @readonly
* @type {module:engine/model/history~History}
*/
this.history = new _history__WEBPACK_IMPORTED_MODULE_2__["default"]( this );
/**
* The selection in this document.
*
* @readonly
* @type {module:engine/model/documentselection~DocumentSelection}
*/
this.selection = new _documentselection__WEBPACK_IMPORTED_MODULE_3__["default"]( this );
/**
* A list of roots that are owned and managed by this document. Use {@link #createRoot} and
* {@link #getRoot} to manipulate it.
*
* @readonly
* @type {module:utils/collection~Collection}
*/
this.roots = new _ckeditor_ckeditor5_utils_src_collection__WEBPACK_IMPORTED_MODULE_4__["default"]( { idProperty: 'rootName' } );
/**
* The model differ object. Its role is to buffer changes done on the model document and then calculate a diff of those changes.
*
* @readonly
* @type {module:engine/model/differ~Differ}
*/
this.differ = new _differ__WEBPACK_IMPORTED_MODULE_0__["default"]( model.markers );
/**
* Post-fixer callbacks registered to the model document.
*
* @private
* @type {Set.<Function>}
*/
this._postFixers = new Set();
/**
* A boolean indicates whether the selection has changed until
*
* @private
* @type {Boolean}
*/
this._hasSelectionChangedFromTheLastChangeBlock = false;
// Graveyard tree root. Document always have a graveyard root, which stores removed nodes.
this.createRoot( '$root', graveyardName );
// First, if the operation is a document operation check if it's base version is correct.
this.listenTo( model, 'applyOperation', ( evt, args ) => {
const operation = args[ 0 ];
if ( operation.isDocumentOperation && operation.baseVersion !== this.version ) {
/**
* Only operations with matching versions can be applied.
*
* @error model-document-applyoperation-wrong-version
* @param {module:engine/model/operation/operation~Operation} operation
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_6__["default"]( 'model-document-applyoperation-wrong-version', this, { operation } );
}
}, { priority: 'highest' } );
// Then, still before an operation is applied on model, buffer the change in differ.
this.listenTo( model, 'applyOperation', ( evt, args ) => {
const operation = args[ 0 ];
if ( operation.isDocumentOperation ) {
this.differ.bufferOperation( operation );
}
}, { priority: 'high' } );
// After the operation is applied, bump document's version and add the operation to the history.
this.listenTo( model, 'applyOperation', ( evt, args ) => {
const operation = args[ 0 ];
if ( operation.isDocumentOperation ) {
this.version++;
this.history.addOperation( operation );
}
}, { priority: 'low' } );
// Listen to selection changes. If selection changed, mark it.
this.listenTo( this.selection, 'change', () => {
this._hasSelectionChangedFromTheLastChangeBlock = true;
} );
// Buffer marker changes.
// This is not covered in buffering operations because markers may change outside of them (when they
// are modified using `model.markers` collection, not through `MarkerOperation`).
this.listenTo( model.markers, 'update', ( evt, marker, oldRange, newRange, oldMarkerData ) => {
// Copy the `newRange` to the new marker data as during the marker removal the range is not updated.
const newMarkerData = { ...marker.getData(), range: newRange };
// Whenever marker is updated, buffer that change.
this.differ.bufferMarkerChange( marker.name, oldMarkerData, newMarkerData );
if ( oldRange === null ) {
// If this is a new marker, add a listener that will buffer change whenever marker changes.
marker.on( 'change', ( evt, oldRange ) => {
const markerData = marker.getData();
this.differ.bufferMarkerChange(
marker.name,
{ ...markerData, range: oldRange },
markerData
);
} );
}
} );
}
/**
* The graveyard tree root. A document always has a graveyard root that stores removed nodes.
*
* @readonly
* @member {module:engine/model/rootelement~RootElement}
*/
get graveyard() {
return this.getRoot( graveyardName );
}
/**
* Creates a new root.
*
* @param {String} [elementName='$root'] The element name. Defaults to `'$root'` which also has some basic schema defined
* (`$block`s are allowed inside the `$root`). Make sure to define a proper schema if you use a different name.
* @param {String} [rootName='main'] A unique root name.
* @returns {module:engine/model/rootelement~RootElement} The created root.
*/
createRoot( elementName = '$root', rootName = 'main' ) {
if ( this.roots.get( rootName ) ) {
/**
* A root with the specified name already exists.
*
* @error model-document-createroot-name-exists
* @param {module:engine/model/document~Document} doc
* @param {String} name
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_6__["default"]( 'model-document-createroot-name-exists', this, { name: rootName } );
}
const root = new _rootelement__WEBPACK_IMPORTED_MODULE_1__["default"]( this, elementName, rootName );
this.roots.add( root );
return root;
}
/**
* Removes all event listeners set by the document instance.
*/
destroy() {
this.selection.destroy();
this.stopListening();
}
/**
* Returns a root by its name.
*
* @param {String} [name='main'] A unique root name.
* @returns {module:engine/model/rootelement~RootElement|null} The root registered under a given name or `null` when
* there is no root with the given name.
*/
getRoot( name = 'main' ) {
return this.roots.get( name );
}
/**
* Returns an array with names of all roots (without the {@link #graveyard}) added to the document.
*
* @returns {Array.<String>} Roots names.
*/
getRootNames() {
return Array.from( this.roots, root => root.rootName ).filter( name => name != graveyardName );
}
/**
* Used to register a post-fixer callback. A post-fixer mechanism guarantees that the features
* will operate on a correct model state.
*
* An execution of a feature may lead to an incorrect document tree state. The callbacks are used to fix the document tree after
* it has changed. Post-fixers are fired just after all changes from the outermost change block were applied but
* before the {@link module:engine/model/document~Document#event:change change event} is fired. If a post-fixer callback made
* a change, it should return `true`. When this happens, all post-fixers are fired again to check if something else should
* not be fixed in the new document tree state.
*
* As a parameter, a post-fixer callback receives a {@link module:engine/model/writer~Writer writer} instance connected with the
* executed changes block. Thanks to that, all changes done by the callback will be added to the same
* {@link module:engine/model/batch~Batch batch} (and undo step) as the original changes. This makes post-fixer changes transparent
* for the user.
*
* An example of a post-fixer is a callback that checks if all the data were removed from the editor. If so, the
* callback should add an empty paragraph so that the editor is never empty:
*
* document.registerPostFixer( writer => {
* const changes = document.differ.getChanges();
*
* // Check if the changes lead to an empty root in the editor.
* for ( const entry of changes ) {
* if ( entry.type == 'remove' && entry.position.root.isEmpty ) {
* writer.insertElement( 'paragraph', entry.position.root, 0 );
*
* // It is fine to return early, even if multiple roots would need to be fixed.
* // All post-fixers will be fired again, so if there are more empty roots, those will be fixed, too.
* return true;
* }
* }
* } );
*
* @param {Function} postFixer
*/
registerPostFixer( postFixer ) {
this._postFixers.add( postFixer );
}
/**
* A custom `toJSON()` method to solve child-parent circular dependencies.
*
* @returns {Object} A clone of this object with the document property changed to a string.
*/
toJSON() {
const json = (0,lodash_es__WEBPACK_IMPORTED_MODULE_9__["default"])( this );
// Due to circular references we need to remove parent reference.
json.selection = '[engine.model.DocumentSelection]';
json.model = '[engine.model.Model]';
return json;
}
/**
* Check if there were any changes done on document, and if so, call post-fixers,
* fire `change` event for features and conversion and then reset the differ.
* Fire `change:data` event when at least one operation or buffered marker changes the data.
*
* @protected
* @fires change
* @fires change:data
* @param {module:engine/model/writer~Writer} writer The writer on which post-fixers will be called.
*/
_handleChangeBlock( writer ) {
if ( this._hasDocumentChangedFromTheLastChangeBlock() ) {
this._callPostFixers( writer );
// Refresh selection attributes according to the final position in the model after the change.
this.selection.refresh();
if ( this.differ.hasDataChanges() ) {
this.fire( 'change:data', writer.batch );
} else {
this.fire( 'change', writer.batch );
}
// Theoretically, it is not necessary to refresh selection after change event because
// post-fixers are the last who should change the model, but just in case...
this.selection.refresh();
this.differ.reset();
}
this._hasSelectionChangedFromTheLastChangeBlock = false;
}
/**
* Returns whether there is a buffered change or if the selection has changed from the last
* {@link module:engine/model/model~Model#enqueueChange `enqueueChange()` block}
* or {@link module:engine/model/model~Model#change `change()` block}.
*
* @protected
* @returns {Boolean} Returns `true` if document has changed from the last `change()` or `enqueueChange()` block.
*/
_hasDocumentChangedFromTheLastChangeBlock() {
return !this.differ.isEmpty || this._hasSelectionChangedFromTheLastChangeBlock;
}
/**
* Returns the default root for this document which is either the first root that was added to the document using
* {@link #createRoot} or the {@link #graveyard graveyard root} if no other roots were created.
*
* @protected
* @returns {module:engine/model/rootelement~RootElement} The default root for this document.
*/
_getDefaultRoot() {
for ( const root of this.roots ) {
if ( root !== this.graveyard ) {
return root;
}
}
return this.graveyard;
}
/**
* Returns the default range for this selection. The default range is a collapsed range that starts and ends
* at the beginning of this selection's document {@link #_getDefaultRoot default root}.
*
* @protected
* @returns {module:engine/model/range~Range}
*/
_getDefaultRange() {
const defaultRoot = this._getDefaultRoot();
const model = this.model;
const schema = model.schema;
// Find the first position where the selection can be put.
const position = model.createPositionFromPath( defaultRoot, [ 0 ] );
const nearestRange = schema.getNearestSelectionRange( position );
// If valid selection range is not found - return range collapsed at the beginning of the root.
return nearestRange || model.createRange( position );
}
/**
* Checks whether a given {@link module:engine/model/range~Range range} is a valid range for
* the {@link #selection document's selection}.
*
* @private
* @param {module:engine/model/range~Range} range A range to check.
* @returns {Boolean} `true` if `range` is valid, `false` otherwise.
*/
_validateSelectionRange( range ) {
return validateTextNodePosition( range.start ) && validateTextNodePosition( range.end );
}
/**
* Performs post-fixer loops. Executes post-fixer callbacks as long as none of them has done any changes to the model.
*
* @private
* @param {module:engine/model/writer~Writer} writer The writer on which post-fixer callbacks will be called.
*/
_callPostFixers( writer ) {
let wasFixed = false;
do {
for ( const callback of this._postFixers ) {
// Ensure selection attributes are up to date before each post-fixer.
// https://github.com/ckeditor/ckeditor5-engine/issues/1673.
//
// It might be good to refresh the selection after each operation but at the moment it leads
// to losing attributes for composition or and spell checking
// https://github.com/ckeditor/ckeditor5-typing/issues/188
this.selection.refresh();
wasFixed = callback( writer );
if ( wasFixed ) {
break;
}
}
} while ( wasFixed );
}
/**
* Fired after each {@link module:engine/model/model~Model#enqueueChange `enqueueChange()` block} or the outermost
* {@link module:engine/model/model~Model#change `change()` block} was executed and the document was changed
* during that block's execution.
*
* The changes which this event will cover include:
*
* * document structure changes,
* * selection changes,
* * marker changes.
*
* If you want to be notified about all these changes, then simply listen to this event like this:
*
* model.document.on( 'change', () => {
* console.log( 'The document has changed!' );
* } );
*
* If, however, you only want to be notified about the data changes, then use the
* {@link module:engine/model/document~Document#event:change:data change:data} event,
* which is fired for document structure changes and marker changes (which affects the data).
*
* model.document.on( 'change:data', () => {
* console.log( 'The data has changed!' );
* } );
*
* @event change
* @param {module:engine/model/batch~Batch} batch The batch that was used in the executed changes block.
*/
/**
* It is a narrower version of the {@link #event:change} event. It is fired for changes which
* affect the editor data. This is:
*
* * document structure changes,
* * marker changes (which affects the data).
*
* If you want to be notified about the data changes, then listen to this event:
*
* model.document.on( 'change:data', () => {
* console.log( 'The data has changed!' );
* } );
*
* If you would like to listen to all document changes, then check out the
* {@link module:engine/model/document~Document#event:change change} event.
*
* @event change:data
* @param {module:engine/model/batch~Batch} batch The batch that was used in the executed changes block.
*/
// @if CK_DEBUG_ENGINE // log( version = null ) {
// @if CK_DEBUG_ENGINE // version = version === null ? this.version : version;
// @if CK_DEBUG_ENGINE // logDocument( this, version );
// @if CK_DEBUG_ENGINE // }
}
(0,_ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_7__["default"])( Document, _ckeditor_ckeditor5_utils_src_emittermixin__WEBPACK_IMPORTED_MODULE_5__["default"] );
// Checks whether given range boundary position is valid for document selection, meaning that is not between
// unicode surrogate pairs or base character and combining marks.
function validateTextNodePosition( rangeBoundary ) {
const textNode = rangeBoundary.textNode;
if ( textNode ) {
const data = textNode.data;
const offset = rangeBoundary.offset - textNode.startOffset;
return !(0,_ckeditor_ckeditor5_utils_src_unicode__WEBPACK_IMPORTED_MODULE_8__.isInsideSurrogatePair)( data, offset ) && !(0,_ckeditor_ckeditor5_utils_src_unicode__WEBPACK_IMPORTED_MODULE_8__.isInsideCombinedSymbol)( data, offset );
}
return true;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/model/documentfragment.js":
/*!*******************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/model/documentfragment.js ***!
\*******************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ DocumentFragment)
/* harmony export */ });
/* harmony import */ var _nodelist__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./nodelist */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/nodelist.js");
/* harmony import */ var _element__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./element */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/element.js");
/* harmony import */ var _text__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./text */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/text.js");
/* harmony import */ var _textproxy__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./textproxy */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/textproxy.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_isiterable__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/isiterable */ "./node_modules/@ckeditor/ckeditor5-utils/src/isiterable.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module module:engine/model/documentfragment
*/
// @if CK_DEBUG_ENGINE // const { stringifyMap } = require( '../dev-utils/utils' );
/**
* DocumentFragment represents a part of model which does not have a common root but its top-level nodes
* can be seen as siblings. In other words, it is a detached part of model tree, without a root.
*
* DocumentFragment has own {@link module:engine/model/markercollection~MarkerCollection}. Markers from this collection
* will be set to the {@link module:engine/model/model~Model#markers model markers} by a
* {@link module:engine/model/writer~Writer#insert} function.
*/
class DocumentFragment {
/**
* Creates an empty `DocumentFragment`.
*
* **Note:** Constructor of this class shouldn't be used directly in the code.
* Use the {@link module:engine/model/writer~Writer#createDocumentFragment} method instead.
*
* @protected
* @param {module:engine/model/node~Node|Iterable.<module:engine/model/node~Node>} [children]
* Nodes to be contained inside the `DocumentFragment`.
*/
constructor( children ) {
/**
* DocumentFragment static markers map. This is a list of names and {@link module:engine/model/range~Range ranges}
* which will be set as Markers to {@link module:engine/model/model~Model#markers model markers collection}
* when DocumentFragment will be inserted to the document.
*
* @readonly
* @member {Map<String,module:engine/model/range~Range>} module:engine/model/documentfragment~DocumentFragment#markers
*/
this.markers = new Map();
/**
* List of nodes contained inside the document fragment.
*
* @private
* @member {module:engine/model/nodelist~NodeList} module:engine/model/documentfragment~DocumentFragment#_children
*/
this._children = new _nodelist__WEBPACK_IMPORTED_MODULE_0__["default"]();
if ( children ) {
this._insertChild( 0, children );
}
}
/**
* Returns an iterator that iterates over all nodes contained inside this document fragment.
*
* @returns {Iterable.<module:engine/model/node~Node>}
*/
[ Symbol.iterator ]() {
return this.getChildren();
}
/**
* Number of this document fragment's children.
*
* @readonly
* @type {Number}
*/
get childCount() {
return this._children.length;
}
/**
* Sum of {@link module:engine/model/node~Node#offsetSize offset sizes} of all of this document fragment's children.
*
* @readonly
* @type {Number}
*/
get maxOffset() {
return this._children.maxOffset;
}
/**
* Is `true` if there are no nodes inside this document fragment, `false` otherwise.
*
* @readonly
* @type {Boolean}
*/
get isEmpty() {
return this.childCount === 0;
}
/**
* Artificial root of `DocumentFragment`. Returns itself. Added for compatibility reasons.
*
* @readonly
* @type {module:engine/model/documentfragment~DocumentFragment}
*/
get root() {
return this;
}
/**
* Artificial parent of `DocumentFragment`. Returns `null`. Added for compatibility reasons.
*
* @readonly
* @type {null}
*/
get parent() {
return null;
}
/**
* Checks whether this object is of the given type.
*
* docFrag.is( 'documentFragment' ); // -> true
* docFrag.is( 'model:documentFragment' ); // -> true
*
* docFrag.is( 'view:documentFragment' ); // -> false
* docFrag.is( 'element' ); // -> false
* docFrag.is( 'node' ); // -> false
*
* {@link module:engine/model/node~Node#is Check the entire list of model objects} which implement the `is()` method.
*
* @param {String} type
* @returns {Boolean}
*/
is( type ) {
return type === 'documentFragment' || type === 'model:documentFragment';
}
/**
* Gets the child at the given index. Returns `null` if incorrect index was passed.
*
* @param {Number} index Index of child.
* @returns {module:engine/model/node~Node|null} Child node.
*/
getChild( index ) {
return this._children.getNode( index );
}
/**
* Returns an iterator that iterates over all of this document fragment's children.
*
* @returns {Iterable.<module:engine/model/node~Node>}
*/
getChildren() {
return this._children[ Symbol.iterator ]();
}
/**
* Returns an index of the given child node. Returns `null` if given node is not a child of this document fragment.
*
* @param {module:engine/model/node~Node} node Child node to look for.
* @returns {Number|null} Child node's index.
*/
getChildIndex( node ) {
return this._children.getNodeIndex( node );
}
/**
* Returns the starting offset of given child. Starting offset is equal to the sum of
* {@link module:engine/model/node~Node#offsetSize offset sizes} of all node's siblings that are before it. Returns `null` if
* given node is not a child of this document fragment.
*
* @param {module:engine/model/node~Node} node Child node to look for.
* @returns {Number|null} Child node's starting offset.
*/
getChildStartOffset( node ) {
return this._children.getNodeStartOffset( node );
}
/**
* Returns path to a `DocumentFragment`, which is an empty array. Added for compatibility reasons.
*
* @returns {Array}
*/
getPath() {
return [];
}
/**
* Returns a descendant node by its path relative to this element.
*
* // <this>a<b>c</b></this>
* this.getNodeByPath( [ 0 ] ); // -> "a"
* this.getNodeByPath( [ 1 ] ); // -> <b>
* this.getNodeByPath( [ 1, 0 ] ); // -> "c"
*
* @param {Array.<Number>} relativePath Path of the node to find, relative to this element.
* @returns {module:engine/model/node~Node|module:engine/model/documentfragment~DocumentFragment}
*/
getNodeByPath( relativePath ) {
let node = this; // eslint-disable-line consistent-this
for ( const index of relativePath ) {
node = node.getChild( node.offsetToIndex( index ) );
}
return node;
}
/**
* Converts offset "position" to index "position".
*
* Returns index of a node that occupies given offset. If given offset is too low, returns `0`. If given offset is
* too high, returns index after last child}.
*
* const textNode = new Text( 'foo' );
* const pElement = new Element( 'p' );
* const docFrag = new DocumentFragment( [ textNode, pElement ] );
* docFrag.offsetToIndex( -1 ); // Returns 0, because offset is too low.
* docFrag.offsetToIndex( 0 ); // Returns 0, because offset 0 is taken by `textNode` which is at index 0.
* docFrag.offsetToIndex( 1 ); // Returns 0, because `textNode` has `offsetSize` equal to 3, so it occupies offset 1 too.
* docFrag.offsetToIndex( 2 ); // Returns 0.
* docFrag.offsetToIndex( 3 ); // Returns 1.
* docFrag.offsetToIndex( 4 ); // Returns 2. There are no nodes at offset 4, so last available index is returned.
*
* @param {Number} offset Offset to look for.
* @returns {Number} Index of a node that occupies given offset.
*/
offsetToIndex( offset ) {
return this._children.offsetToIndex( offset );
}
/**
* Converts `DocumentFragment` instance to plain object and returns it.
* Takes care of converting all of this document fragment's children.
*
* @returns {Object} `DocumentFragment` instance converted to plain object.
*/
toJSON() {
const json = [];
for ( const node of this._children ) {
json.push( node.toJSON() );
}
return json;
}
/**
* Creates a `DocumentFragment` instance from given plain object (i.e. parsed JSON string).
* Converts `DocumentFragment` children to proper nodes.
*
* @param {Object} json Plain object to be converted to `DocumentFragment`.
* @returns {module:engine/model/documentfragment~DocumentFragment} `DocumentFragment` instance created using given plain object.
*/
static fromJSON( json ) {
const children = [];
for ( const child of json ) {
if ( child.name ) {
// If child has name property, it is an Element.
children.push( _element__WEBPACK_IMPORTED_MODULE_1__["default"].fromJSON( child ) );
} else {
// Otherwise, it is a Text node.
children.push( _text__WEBPACK_IMPORTED_MODULE_2__["default"].fromJSON( child ) );
}
}
return new DocumentFragment( children );
}
/**
* {@link #_insertChild Inserts} one or more nodes at the end of this document fragment.
*
* @protected
* @param {module:engine/model/item~Item|Iterable.<module:engine/model/item~Item>} items Items to be inserted.
*/
_appendChild( items ) {
this._insertChild( this.childCount, items );
}
/**
* Inserts one or more nodes at the given index and sets {@link module:engine/model/node~Node#parent parent} of these nodes
* to this document fragment.
*
* @protected
* @param {Number} index Index at which nodes should be inserted.
* @param {module:engine/model/item~Item|Iterable.<module:engine/model/item~Item>} items Items to be inserted.
*/
_insertChild( index, items ) {
const nodes = normalize( items );
for ( const node of nodes ) {
// If node that is being added to this element is already inside another element, first remove it from the old parent.
if ( node.parent !== null ) {
node._remove();
}
node.parent = this;
}
this._children._insertNodes( index, nodes );
}
/**
* Removes one or more nodes starting at the given index
* and sets {@link module:engine/model/node~Node#parent parent} of these nodes to `null`.
*
* @protected
* @param {Number} index Index of the first node to remove.
* @param {Number} [howMany=1] Number of nodes to remove.
* @returns {Array.<module:engine/model/node~Node>} Array containing removed nodes.
*/
_removeChildren( index, howMany = 1 ) {
const nodes = this._children._removeNodes( index, howMany );
for ( const node of nodes ) {
node.parent = null;
}
return nodes;
}
// @if CK_DEBUG_ENGINE // toString() {
// @if CK_DEBUG_ENGINE // return 'documentFragment';
// @if CK_DEBUG_ENGINE // }
// @if CK_DEBUG_ENGINE // log() {
// @if CK_DEBUG_ENGINE // console.log( 'ModelDocumentFragment: ' + this );
// @if CK_DEBUG_ENGINE // }
// @if CK_DEBUG_ENGINE // printTree() {
// @if CK_DEBUG_ENGINE // let string = 'ModelDocumentFragment: [';
// @if CK_DEBUG_ENGINE // for ( const child of this.getChildren() ) {
// @if CK_DEBUG_ENGINE // string += '\n';
// @if CK_DEBUG_ENGINE // if ( child.is( '$text' ) ) {
// @if CK_DEBUG_ENGINE // const textAttrs = stringifyMap( child._attrs );
// @if CK_DEBUG_ENGINE // string += '\t'.repeat( 1 );
// @if CK_DEBUG_ENGINE // if ( textAttrs !== '' ) {
// @if CK_DEBUG_ENGINE // string += `<$text${ textAttrs }>` + child.data + '</$text>';
// @if CK_DEBUG_ENGINE // } else {
// @if CK_DEBUG_ENGINE // string += child.data;
// @if CK_DEBUG_ENGINE // }
// @if CK_DEBUG_ENGINE // } else {
// @if CK_DEBUG_ENGINE // string += child.printTree( 1 );
// @if CK_DEBUG_ENGINE // }
// @if CK_DEBUG_ENGINE // }
// @if CK_DEBUG_ENGINE // string += '\n]';
// @if CK_DEBUG_ENGINE // return string;
// @if CK_DEBUG_ENGINE // }
// @if CK_DEBUG_ENGINE // logTree() {
// @if CK_DEBUG_ENGINE // console.log( this.printTree() );
// @if CK_DEBUG_ENGINE // }
}
// Converts strings to Text and non-iterables to arrays.
//
// @param {String|module:engine/model/item~Item|Iterable.<module:engine/model/item~Item>}
// @returns {Iterable.<module:engine/model/node~Node>}
function normalize( nodes ) {
// Separate condition because string is iterable.
if ( typeof nodes == 'string' ) {
return [ new _text__WEBPACK_IMPORTED_MODULE_2__["default"]( nodes ) ];
}
if ( !(0,_ckeditor_ckeditor5_utils_src_isiterable__WEBPACK_IMPORTED_MODULE_4__["default"])( nodes ) ) {
nodes = [ nodes ];
}
// Array.from to enable .map() on non-arrays.
return Array.from( nodes )
.map( node => {
if ( typeof node == 'string' ) {
return new _text__WEBPACK_IMPORTED_MODULE_2__["default"]( node );
}
if ( node instanceof _textproxy__WEBPACK_IMPORTED_MODULE_3__["default"] ) {
return new _text__WEBPACK_IMPORTED_MODULE_2__["default"]( node.data, node.getAttributes() );
}
return node;
} );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/model/documentselection.js":
/*!********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/model/documentselection.js ***!
\********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ DocumentSelection)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/mix */ "./node_modules/@ckeditor/ckeditor5-utils/src/mix.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_emittermixin__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/emittermixin */ "./node_modules/@ckeditor/ckeditor5-utils/src/emittermixin.js");
/* harmony import */ var _selection__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./selection */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/selection.js");
/* harmony import */ var _liverange__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./liverange */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/liverange.js");
/* harmony import */ var _text__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./text */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/text.js");
/* harmony import */ var _textproxy__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./textproxy */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/textproxy.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_tomap__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/tomap */ "./node_modules/@ckeditor/ckeditor5-utils/src/tomap.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_collection__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/collection */ "./node_modules/@ckeditor/ckeditor5-utils/src/collection.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_uid__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/uid */ "./node_modules/@ckeditor/ckeditor5-utils/src/uid.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/model/documentselection
*/
const storePrefix = 'selection:';
/**
* `DocumentSelection` is a special selection which is used as the
* {@link module:engine/model/document~Document#selection document's selection}.
* There can be only one instance of `DocumentSelection` per document.
*
* Document selection can only be changed by using the {@link module:engine/model/writer~Writer} instance
* inside the {@link module:engine/model/model~Model#change `change()`} block, as it provides a secure way to modify model.
*
* `DocumentSelection` is automatically updated upon changes in the {@link module:engine/model/document~Document document}
* to always contain valid ranges. Its attributes are inherited from the text unless set explicitly.
*
* Differences between {@link module:engine/model/selection~Selection} and `DocumentSelection` are:
* * there is always a range in `DocumentSelection` - even if no ranges were added there is a "default range"
* present in the selection,
* * ranges added to this selection updates automatically when the document changes,
* * attributes of `DocumentSelection` are updated automatically according to selection ranges.
*
* Since `DocumentSelection` uses {@link module:engine/model/liverange~LiveRange live ranges}
* and is updated when {@link module:engine/model/document~Document document}
* changes, it cannot be set on {@link module:engine/model/node~Node nodes}
* that are inside {@link module:engine/model/documentfragment~DocumentFragment document fragment}.
* If you need to represent a selection in document fragment,
* use {@link module:engine/model/selection~Selection Selection class} instead.
*
* @mixes module:utils/emittermixin~EmitterMixin
*/
class DocumentSelection {
/**
* Creates an empty live selection for given {@link module:engine/model/document~Document}.
*
* @param {module:engine/model/document~Document} doc Document which owns this selection.
*/
constructor( doc ) {
/**
* Selection used internally by that class (`DocumentSelection` is a proxy to that selection).
*
* @protected
*/
this._selection = new LiveSelection( doc );
this._selection.delegate( 'change:range' ).to( this );
this._selection.delegate( 'change:attribute' ).to( this );
this._selection.delegate( 'change:marker' ).to( this );
}
/**
* Returns whether the selection is collapsed. Selection is collapsed when there is exactly one range which is
* collapsed.
*
* @readonly
* @type {Boolean}
*/
get isCollapsed() {
return this._selection.isCollapsed;
}
/**
* Selection anchor. Anchor may be described as a position where the most recent part of the selection starts.
* Together with {@link #focus} they define the direction of selection, which is important
* when expanding/shrinking selection. Anchor is always {@link module:engine/model/range~Range#start start} or
* {@link module:engine/model/range~Range#end end} position of the most recently added range.
*
* Is set to `null` if there are no ranges in selection.
*
* @see #focus
* @readonly
* @type {module:engine/model/position~Position|null}
*/
get anchor() {
return this._selection.anchor;
}
/**
* Selection focus. Focus is a position where the selection ends.
*
* Is set to `null` if there are no ranges in selection.
*
* @see #anchor
* @readonly
* @type {module:engine/model/position~Position|null}
*/
get focus() {
return this._selection.focus;
}
/**
* Returns number of ranges in selection.
*
* @readonly
* @type {Number}
*/
get rangeCount() {
return this._selection.rangeCount;
}
/**
* Describes whether `Documentselection` has own range(s) set, or if it is defaulted to
* {@link module:engine/model/document~Document#_getDefaultRange document's default range}.
*
* @readonly
* @type {Boolean}
*/
get hasOwnRange() {
return this._selection.hasOwnRange;
}
/**
* Specifies whether the {@link #focus}
* precedes {@link #anchor}.
*
* @readonly
* @type {Boolean}
*/
get isBackward() {
return this._selection.isBackward;
}
/**
* Describes whether the gravity is overridden (using {@link module:engine/model/writer~Writer#overrideSelectionGravity}) or not.
*
* Note that the gravity remains overridden as long as will not be restored the same number of times as it was overridden.
*
* @readonly
* @returns {Boolean}
*/
get isGravityOverridden() {
return this._selection.isGravityOverridden;
}
/**
* A collection of selection {@link module:engine/model/markercollection~Marker markers}.
* Marker is a selection marker when selection range is inside the marker range.
*
* **Note**: Only markers from {@link ~DocumentSelection#observeMarkers observed markers groups} are collected.
*
* @readonly
* @type {module:utils/collection~Collection}
*/
get markers() {
return this._selection.markers;
}
/**
* Used for the compatibility with the {@link module:engine/model/selection~Selection#isEqual} method.
*
* @protected
*/
get _ranges() {
return this._selection._ranges;
}
/**
* Returns an iterable that iterates over copies of selection ranges.
*
* @returns {Iterable.<module:engine/model/range~Range>}
*/
getRanges() {
return this._selection.getRanges();
}
/**
* Returns the first position in the selection.
* First position is the position that {@link module:engine/model/position~Position#isBefore is before}
* any other position in the selection.
*
* Returns `null` if there are no ranges in selection.
*
* @returns {module:engine/model/position~Position|null}
*/
getFirstPosition() {
return this._selection.getFirstPosition();
}
/**
* Returns the last position in the selection.
* Last position is the position that {@link module:engine/model/position~Position#isAfter is after}
* any other position in the selection.
*
* Returns `null` if there are no ranges in selection.
*
* @returns {module:engine/model/position~Position|null}
*/
getLastPosition() {
return this._selection.getLastPosition();
}
/**
* Returns a copy of the first range in the selection.
* First range is the one which {@link module:engine/model/range~Range#start start} position
* {@link module:engine/model/position~Position#isBefore is before} start position of all other ranges
* (not to confuse with the first range added to the selection).
*
* Returns `null` if there are no ranges in selection.
*
* @returns {module:engine/model/range~Range|null}
*/
getFirstRange() {
return this._selection.getFirstRange();
}
/**
* Returns a copy of the last range in the selection.
* Last range is the one which {@link module:engine/model/range~Range#end end} position
* {@link module:engine/model/position~Position#isAfter is after} end position of all other ranges (not to confuse with the range most
* recently added to the selection).
*
* Returns `null` if there are no ranges in selection.
*
* @returns {module:engine/model/range~Range|null}
*/
getLastRange() {
return this._selection.getLastRange();
}
/**
* Gets elements of type {@link module:engine/model/schema~Schema#isBlock "block"} touched by the selection.
*
* This method's result can be used for example to apply block styling to all blocks covered by this selection.
*
* **Note:** `getSelectedBlocks()` returns blocks that are nested in other non-block elements
* but will not return blocks nested in other blocks.
*
* In this case the function will return exactly all 3 paragraphs (note: `<blockQuote>` is not a block itself):
*
* <paragraph>[a</paragraph>
* <blockQuote>
* <paragraph>b</paragraph>
* </blockQuote>
* <paragraph>c]d</paragraph>
*
* In this case the paragraph will also be returned, despite the collapsed selection:
*
* <paragraph>[]a</paragraph>
*
* In such a scenario, however, only blocks A, B & E will be returned as blocks C & D are nested in block B:
*
* [<blockA></blockA>
* <blockB>
* <blockC></blockC>
* <blockD></blockD>
* </blockB>
* <blockE></blockE>]
*
* If the selection is inside a block all the inner blocks (A & B) are returned:
*
* <block>
* <blockA>[a</blockA>
* <blockB>b]</blockB>
* </block>
*
* **Special case**: If a selection ends at the beginning of a block, that block is not returned as from user perspective
* this block wasn't selected. See [#984](https://github.com/ckeditor/ckeditor5-engine/issues/984) for more details.
*
* <paragraph>[a</paragraph>
* <paragraph>b</paragraph>
* <paragraph>]c</paragraph> // this block will not be returned
*
* @returns {Iterable.<module:engine/model/element~Element>}
*/
getSelectedBlocks() {
return this._selection.getSelectedBlocks();
}
/**
* Returns the selected element. {@link module:engine/model/element~Element Element} is considered as selected if there is only
* one range in the selection, and that range contains exactly one element.
* Returns `null` if there is no selected element.
*
* @returns {module:engine/model/element~Element|null}
*/
getSelectedElement() {
return this._selection.getSelectedElement();
}
/**
* Checks whether the selection contains the entire content of the given element. This means that selection must start
* at a position {@link module:engine/model/position~Position#isTouching touching} the element's start and ends at position
* touching the element's end.
*
* By default, this method will check whether the entire content of the selection's current root is selected.
* Useful to check if e.g. the user has just pressed <kbd>Ctrl</kbd> + <kbd>A</kbd>.
*
* @param {module:engine/model/element~Element} [element=this.anchor.root]
* @returns {Boolean}
*/
containsEntireContent( element ) {
return this._selection.containsEntireContent( element );
}
/**
* Unbinds all events previously bound by document selection.
*/
destroy() {
this._selection.destroy();
}
/**
* Returns iterable that iterates over this selection's attribute keys.
*
* @returns {Iterable.<String>}
*/
getAttributeKeys() {
return this._selection.getAttributeKeys();
}
/**
* Returns iterable that iterates over this selection's attributes.
*
* Attributes are returned as arrays containing two items. First one is attribute key and second is attribute value.
* This format is accepted by native `Map` object and also can be passed in `Node` constructor.
*
* @returns {Iterable.<*>}
*/
getAttributes() {
return this._selection.getAttributes();
}
/**
* Gets an attribute value for given key or `undefined` if that attribute is not set on the selection.
*
* @param {String} key Key of attribute to look for.
* @returns {*} Attribute value or `undefined`.
*/
getAttribute( key ) {
return this._selection.getAttribute( key );
}
/**
* Checks if the selection has an attribute for given key.
*
* @param {String} key Key of attribute to check.
* @returns {Boolean} `true` if attribute with given key is set on selection, `false` otherwise.
*/
hasAttribute( key ) {
return this._selection.hasAttribute( key );
}
/**
* Refreshes selection attributes and markers according to the current position in the model.
*/
refresh() {
this._selection._updateMarkers();
this._selection._updateAttributes( false );
}
/**
* Registers a marker group prefix or a marker name to be collected in the
* {@link ~DocumentSelection#markers selection markers collection}.
*
* See also {@link module:engine/model/markercollection~MarkerCollection#getMarkersGroup `MarkerCollection#getMarkersGroup()`}.
*
* @param {String} prefixOrName The marker group prefix or marker name.
*/
observeMarkers( prefixOrName ) {
this._selection.observeMarkers( prefixOrName );
}
/**
* Checks whether this object is of the given type.
*
* selection.is( 'selection' ); // -> true
* selection.is( 'documentSelection' ); // -> true
* selection.is( 'model:selection' ); // -> true
* selection.is( 'model:documentSelection' ); // -> true
*
* selection.is( 'view:selection' ); // -> false
* selection.is( 'element' ); // -> false
* selection.is( 'node' ); // -> false
*
* {@link module:engine/model/node~Node#is Check the entire list of model objects} which implement the `is()` method.
*
* @param {String} type
* @returns {Boolean}
*/
is( type ) {
return type === 'selection' ||
type == 'model:selection' ||
type == 'documentSelection' ||
type == 'model:documentSelection';
}
/**
* Moves {@link module:engine/model/documentselection~DocumentSelection#focus} to the specified location.
* Should be used only within the {@link module:engine/model/writer~Writer#setSelectionFocus} method.
*
* The location can be specified in the same form as
* {@link module:engine/model/writer~Writer#createPositionAt writer.createPositionAt()} parameters.
*
* @see module:engine/model/writer~Writer#setSelectionFocus
* @protected
* @param {module:engine/model/item~Item|module:engine/model/position~Position} itemOrPosition
* @param {Number|'end'|'before'|'after'} [offset] Offset or one of the flags. Used only when
* first parameter is a {@link module:engine/model/item~Item model item}.
*/
_setFocus( itemOrPosition, offset ) {
this._selection.setFocus( itemOrPosition, offset );
}
/**
* Sets this selection's ranges and direction to the specified location based on the given
* {@link module:engine/model/selection~Selectable selectable}.
* Should be used only within the {@link module:engine/model/writer~Writer#setSelection} method.
*
* @see module:engine/model/writer~Writer#setSelection
* @protected
* @param {module:engine/model/selection~Selectable} selectable
* @param {Number|'before'|'end'|'after'|'on'|'in'} [placeOrOffset] Sets place or offset of the selection.
* @param {Object} [options]
* @param {Boolean} [options.backward] Sets this selection instance to be backward.
*/
_setTo( selectable, placeOrOffset, options ) {
this._selection.setTo( selectable, placeOrOffset, options );
}
/**
* Sets attribute on the selection. If attribute with the same key already is set, it's value is overwritten.
* Should be used only within the {@link module:engine/model/writer~Writer#setSelectionAttribute} method.
*
* @see module:engine/model/writer~Writer#setSelectionAttribute
* @protected
* @param {String} key Key of the attribute to set.
* @param {*} value Attribute value.
*/
_setAttribute( key, value ) {
this._selection.setAttribute( key, value );
}
/**
* Removes an attribute with given key from the selection.
* If the given attribute was set on the selection, fires the {@link module:engine/model/selection~Selection#event:change:range}
* event with removed attribute key.
* Should be used only within the {@link module:engine/model/writer~Writer#removeSelectionAttribute} method.
*
* @see module:engine/model/writer~Writer#removeSelectionAttribute
* @protected
* @param {String} key Key of the attribute to remove.
*/
_removeAttribute( key ) {
this._selection.removeAttribute( key );
}
/**
* Returns an iterable that iterates through all selection attributes stored in current selection's parent.
*
* @protected
* @returns {Iterable.<*>}
*/
_getStoredAttributes() {
return this._selection._getStoredAttributes();
}
/**
* Temporarily changes the gravity of the selection from the left to the right.
*
* The gravity defines from which direction the selection inherits its attributes. If it's the default left
* gravity, the selection (after being moved by the the user) inherits attributes from its left hand side.
* This method allows to temporarily override this behavior by forcing the gravity to the right.
*
* It returns an unique identifier which is required to restore the gravity. It guarantees the symmetry
* of the process.
*
* @see module:engine/model/writer~Writer#overrideSelectionGravity
* @protected
* @returns {String} The unique id which allows restoring the gravity.
*/
_overrideGravity() {
return this._selection.overrideGravity();
}
/**
* Restores the {@link ~DocumentSelection#_overrideGravity overridden gravity}.
*
* Restoring the gravity is only possible using the unique identifier returned by
* {@link ~DocumentSelection#_overrideGravity}. Note that the gravity remains overridden as long as won't be restored
* the same number of times it was overridden.
*
* @see module:engine/model/writer~Writer#restoreSelectionGravity
* @protected
* @param {String} uid The unique id returned by {@link #_overrideGravity}.
*/
_restoreGravity( uid ) {
this._selection.restoreGravity( uid );
}
/**
* Generates and returns an attribute key for selection attributes store, basing on original attribute key.
*
* @protected
* @param {String} key Attribute key to convert.
* @returns {String} Converted attribute key, applicable for selection store.
*/
static _getStoreAttributeKey( key ) {
return storePrefix + key;
}
/**
* Checks whether the given attribute key is an attribute stored on an element.
*
* @protected
* @param {String} key
* @returns {Boolean}
*/
static _isStoreAttributeKey( key ) {
return key.startsWith( storePrefix );
}
}
(0,_ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_0__["default"])( DocumentSelection, _ckeditor_ckeditor5_utils_src_emittermixin__WEBPACK_IMPORTED_MODULE_1__["default"] );
/**
* Fired when selection range(s) changed.
*
* @event change:range
* @param {Boolean} directChange In case of {@link module:engine/model/selection~Selection} class it is always set
* to `true` which indicates that the selection change was caused by a direct use of selection's API.
* The {@link module:engine/model/documentselection~DocumentSelection}, however, may change because its position
* was directly changed through the {@link module:engine/model/writer~Writer writer} or because its position was
* changed because the structure of the model has been changed (which means an indirect change).
* The indirect change does not occur in case of normal (detached) selections because they are "static" (as "not live")
* which mean that they are not updated once the document changes.
*/
/**
* Fired when selection attribute changed.
*
* @event change:attribute
* @param {Boolean} directChange In case of {@link module:engine/model/selection~Selection} class it is always set
* to `true` which indicates that the selection change was caused by a direct use of selection's API.
* The {@link module:engine/model/documentselection~DocumentSelection}, however, may change because its attributes
* were directly changed through the {@link module:engine/model/writer~Writer writer} or because its position was
* changed in the model and its attributes were refreshed (which means an indirect change).
* The indirect change does not occur in case of normal (detached) selections because they are "static" (as "not live")
* which mean that they are not updated once the document changes.
* @param {Array.<String>} attributeKeys Array containing keys of attributes that changed.
*/
/**
* Fired when selection marker(s) changed.
*
* @event change:marker
* @param {Boolean} directChange This is always set to `false` in case of `change:marker` event as there is no possibility
* to change markers directly through {@link module:engine/model/documentselection~DocumentSelection} API.
* See also {@link module:engine/model/documentselection~DocumentSelection#event:change:range} and
* {@link module:engine/model/documentselection~DocumentSelection#event:change:attribute}.
* @param {Array.<module:engine/model/markercollection~Marker>} oldMarkers Markers in which the selection was before the change.
*/
// `LiveSelection` is used internally by {@link module:engine/model/documentselection~DocumentSelection} and shouldn't be used directly.
//
// LiveSelection` is automatically updated upon changes in the {@link module:engine/model/document~Document document}
// to always contain valid ranges. Its attributes are inherited from the text unless set explicitly.
//
// Differences between {@link module:engine/model/selection~Selection} and `LiveSelection` are:
// * there is always a range in `LiveSelection` - even if no ranges were added there is a "default range"
// present in the selection,
// * ranges added to this selection updates automatically when the document changes,
// * attributes of `LiveSelection` are updated automatically according to selection ranges.
//
// @extends module:engine/model/selection~Selection
//
class LiveSelection extends _selection__WEBPACK_IMPORTED_MODULE_2__["default"] {
// Creates an empty live selection for given {@link module:engine/model/document~Document}.
// @param {module:engine/model/document~Document} doc Document which owns this selection.
constructor( doc ) {
super();
// List of selection markers.
// Marker is a selection marker when selection range is inside the marker range.
//
// @type {module:utils/collection~Collection}
this.markers = new _ckeditor_ckeditor5_utils_src_collection__WEBPACK_IMPORTED_MODULE_7__["default"]( { idProperty: 'name' } );
// Document which owns this selection.
//
// @protected
// @member {module:engine/model/model~Model}
this._model = doc.model;
// Document which owns this selection.
//
// @protected
// @member {module:engine/model/document~Document}
this._document = doc;
// Keeps mapping of attribute name to priority with which the attribute got modified (added/changed/removed)
// last time. Possible values of priority are: `'low'` and `'normal'`.
//
// Priorities are used by internal `LiveSelection` mechanisms. All attributes set using `LiveSelection`
// attributes API are set with `'normal'` priority.
//
// @private
// @member {Map} module:engine/model/liveselection~LiveSelection#_attributePriority
this._attributePriority = new Map();
// Position to which the selection should be set if the last selection range was moved to the graveyard.
// @private
// @member {module:engine/model/position~Position} module:engine/model/liveselection~LiveSelection#_selectionRestorePosition
this._selectionRestorePosition = null;
// Flag that informs whether the selection ranges have changed. It is changed on true when `LiveRange#change:range` event is fired.
// @private
// @member {Array} module:engine/model/liveselection~LiveSelection#_hasChangedRange
this._hasChangedRange = false;
// Each overriding gravity adds an UID to the set and each removal removes it.
// Gravity is overridden when there's at least one UID in the set.
// Gravity is restored when the set is empty.
// This is to prevent conflicts when gravity is overridden by more than one feature at the same time.
// @private
// @type {Set}
this._overriddenGravityRegister = new Set();
// Prefixes of marker names that should affect `LiveSelection#markers` collection.
// @private
// @type {Set}
this._observedMarkers = new Set();
// Ensure selection is correct after each operation.
this.listenTo( this._model, 'applyOperation', ( evt, args ) => {
const operation = args[ 0 ];
if ( !operation.isDocumentOperation || operation.type == 'marker' || operation.type == 'rename' || operation.type == 'noop' ) {
return;
}
// Fix selection if the last range was removed from it and we have a position to which we can restore the selection.
if ( this._ranges.length == 0 && this._selectionRestorePosition ) {
this._fixGraveyardSelection( this._selectionRestorePosition );
}
// "Forget" the restore position even if it was not "used".
this._selectionRestorePosition = null;
if ( this._hasChangedRange ) {
this._hasChangedRange = false;
this.fire( 'change:range', { directChange: false } );
}
}, { priority: 'lowest' } );
// Ensure selection is correct and up to date after each range change.
this.on( 'change:range', () => {
for ( const range of this.getRanges() ) {
if ( !this._document._validateSelectionRange( range ) ) {
/**
* Range from {@link module:engine/model/documentselection~DocumentSelection document selection}
* starts or ends at incorrect position.
*
* @error document-selection-wrong-position
* @param {module:engine/model/range~Range} range
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_8__["default"](
'document-selection-wrong-position',
this,
{ range }
);
}
}
} );
// Update markers data stored by the selection after each marker change.
// This handles only marker changes done through marker operations (not model tree changes).
this.listenTo( this._model.markers, 'update', ( evt, marker, oldRange, newRange ) => {
this._updateMarker( marker, newRange );
} );
// Ensure selection is up to date after each change block.
this.listenTo( this._document, 'change', ( evt, batch ) => {
clearAttributesStoredInElement( this._model, batch );
} );
}
get isCollapsed() {
const length = this._ranges.length;
return length === 0 ? this._document._getDefaultRange().isCollapsed : super.isCollapsed;
}
get anchor() {
return super.anchor || this._document._getDefaultRange().start;
}
get focus() {
return super.focus || this._document._getDefaultRange().end;
}
get rangeCount() {
return this._ranges.length ? this._ranges.length : 1;
}
// Describes whether `LiveSelection` has own range(s) set, or if it is defaulted to
// {@link module:engine/model/document~Document#_getDefaultRange document's default range}.
//
// @readonly
// @type {Boolean}
get hasOwnRange() {
return this._ranges.length > 0;
}
// When set to `true` then selection attributes on node before the caret won't be taken
// into consideration while updating selection attributes.
//
// @protected
// @type {Boolean}
get isGravityOverridden() {
return !!this._overriddenGravityRegister.size;
}
// Unbinds all events previously bound by live selection.
destroy() {
for ( let i = 0; i < this._ranges.length; i++ ) {
this._ranges[ i ].detach();
}
this.stopListening();
}
* getRanges() {
if ( this._ranges.length ) {
yield* super.getRanges();
} else {
yield this._document._getDefaultRange();
}
}
getFirstRange() {
return super.getFirstRange() || this._document._getDefaultRange();
}
getLastRange() {
return super.getLastRange() || this._document._getDefaultRange();
}
setTo( selectable, optionsOrPlaceOrOffset, options ) {
super.setTo( selectable, optionsOrPlaceOrOffset, options );
this._updateAttributes( true );
this._updateMarkers();
}
setFocus( itemOrPosition, offset ) {
super.setFocus( itemOrPosition, offset );
this._updateAttributes( true );
this._updateMarkers();
}
setAttribute( key, value ) {
if ( this._setAttribute( key, value ) ) {
// Fire event with exact data.
const attributeKeys = [ key ];
this.fire( 'change:attribute', { attributeKeys, directChange: true } );
}
}
removeAttribute( key ) {
if ( this._removeAttribute( key ) ) {
// Fire event with exact data.
const attributeKeys = [ key ];
this.fire( 'change:attribute', { attributeKeys, directChange: true } );
}
}
overrideGravity() {
const overrideUid = (0,_ckeditor_ckeditor5_utils_src_uid__WEBPACK_IMPORTED_MODULE_9__["default"])();
// Remember that another overriding has been requested. It will need to be removed
// before the gravity is to be restored.
this._overriddenGravityRegister.add( overrideUid );
if ( this._overriddenGravityRegister.size === 1 ) {
this._updateAttributes( true );
}
return overrideUid;
}
restoreGravity( uid ) {
if ( !this._overriddenGravityRegister.has( uid ) ) {
/**
* Restoring gravity for an unknown UID is not possible. Make sure you are using a correct
* UID obtained from the {@link module:engine/model/writer~Writer#overrideSelectionGravity} to restore.
*
* @error document-selection-gravity-wrong-restore
* @param {String} uid The unique identifier returned by
* {@link module:engine/model/documentselection~DocumentSelection#_overrideGravity}.
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_8__["default"](
'document-selection-gravity-wrong-restore',
this,
{ uid }
);
}
this._overriddenGravityRegister.delete( uid );
// Restore gravity only when all overriding have been restored.
if ( !this.isGravityOverridden ) {
this._updateAttributes( true );
}
}
observeMarkers( prefixOrName ) {
this._observedMarkers.add( prefixOrName );
this._updateMarkers();
}
_popRange() {
this._ranges.pop().detach();
}
_pushRange( range ) {
const liveRange = this._prepareRange( range );
// `undefined` is returned when given `range` is in graveyard root.
if ( liveRange ) {
this._ranges.push( liveRange );
}
}
// Prepares given range to be added to selection. Checks if it is correct,
// converts it to {@link module:engine/model/liverange~LiveRange LiveRange}
// and sets listeners listening to the range's change event.
//
// @private
// @param {module:engine/model/range~Range} range
_prepareRange( range ) {
this._checkRange( range );
if ( range.root == this._document.graveyard ) {
// @if CK_DEBUG // console.warn( 'Trying to add a Range that is in the graveyard root. Range rejected.' );
return;
}
const liveRange = _liverange__WEBPACK_IMPORTED_MODULE_3__["default"].fromRange( range );
// If selection range is moved to the graveyard remove it from the selection object.
// Also, save some data that can be used to restore selection later, on `Model#applyOperation` event.
liveRange.on( 'change:range', ( evt, oldRange, data ) => {
this._hasChangedRange = true;
if ( liveRange.root == this._document.graveyard ) {
this._selectionRestorePosition = data.deletionPosition;
const index = this._ranges.indexOf( liveRange );
this._ranges.splice( index, 1 );
liveRange.detach();
}
} );
return liveRange;
}
_updateMarkers() {
if ( !this._observedMarkers.size ) {
return;
}
const markers = [];
let changed = false;
for ( const marker of this._model.markers ) {
const markerGroup = marker.name.split( ':', 1 )[ 0 ];
if ( !this._observedMarkers.has( markerGroup ) ) {
continue;
}
const markerRange = marker.getRange();
for ( const selectionRange of this.getRanges() ) {
if ( markerRange.containsRange( selectionRange, !selectionRange.isCollapsed ) ) {
markers.push( marker );
}
}
}
const oldMarkers = Array.from( this.markers );
for ( const marker of markers ) {
if ( !this.markers.has( marker ) ) {
this.markers.add( marker );
changed = true;
}
}
for ( const marker of Array.from( this.markers ) ) {
if ( !markers.includes( marker ) ) {
this.markers.remove( marker );
changed = true;
}
}
if ( changed ) {
this.fire( 'change:marker', { oldMarkers, directChange: false } );
}
}
_updateMarker( marker, markerRange ) {
const markerGroup = marker.name.split( ':', 1 )[ 0 ];
if ( !this._observedMarkers.has( markerGroup ) ) {
return;
}
let changed = false;
const oldMarkers = Array.from( this.markers );
const hasMarker = this.markers.has( marker );
if ( !markerRange ) {
if ( hasMarker ) {
this.markers.remove( marker );
changed = true;
}
} else {
let contained = false;
for ( const selectionRange of this.getRanges() ) {
if ( markerRange.containsRange( selectionRange, !selectionRange.isCollapsed ) ) {
contained = true;
break;
}
}
if ( contained && !hasMarker ) {
this.markers.add( marker );
changed = true;
} else if ( !contained && hasMarker ) {
this.markers.remove( marker );
changed = true;
}
}
if ( changed ) {
this.fire( 'change:marker', { oldMarkers, directChange: false } );
}
}
// Updates this selection attributes according to its ranges and the {@link module:engine/model/document~Document model document}.
//
// @protected
// @param {Boolean} clearAll
// @fires change:attribute
_updateAttributes( clearAll ) {
const newAttributes = (0,_ckeditor_ckeditor5_utils_src_tomap__WEBPACK_IMPORTED_MODULE_6__["default"])( this._getSurroundingAttributes() );
const oldAttributes = (0,_ckeditor_ckeditor5_utils_src_tomap__WEBPACK_IMPORTED_MODULE_6__["default"])( this.getAttributes() );
if ( clearAll ) {
// If `clearAll` remove all attributes and reset priorities.
this._attributePriority = new Map();
this._attrs = new Map();
} else {
// If not, remove only attributes added with `low` priority.
for ( const [ key, priority ] of this._attributePriority ) {
if ( priority == 'low' ) {
this._attrs.delete( key );
this._attributePriority.delete( key );
}
}
}
this._setAttributesTo( newAttributes );
// Let's evaluate which attributes really changed.
const changed = [];
// First, loop through all attributes that are set on selection right now.
// Check which of them are different than old attributes.
for ( const [ newKey, newValue ] of this.getAttributes() ) {
if ( !oldAttributes.has( newKey ) || oldAttributes.get( newKey ) !== newValue ) {
changed.push( newKey );
}
}
// Then, check which of old attributes got removed.
for ( const [ oldKey ] of oldAttributes ) {
if ( !this.hasAttribute( oldKey ) ) {
changed.push( oldKey );
}
}
// Fire event with exact data (fire only if anything changed).
if ( changed.length > 0 ) {
this.fire( 'change:attribute', { attributeKeys: changed, directChange: false } );
}
}
// Internal method for setting `LiveSelection` attribute. Supports attribute priorities (through `directChange`
// parameter).
//
// @private
// @param {String} key Attribute key.
// @param {*} value Attribute value.
// @param {Boolean} [directChange=true] `true` if the change is caused by `Selection` API, `false` if change
// is caused by `Batch` API.
// @returns {Boolean} Whether value has changed.
_setAttribute( key, value, directChange = true ) {
const priority = directChange ? 'normal' : 'low';
if ( priority == 'low' && this._attributePriority.get( key ) == 'normal' ) {
// Priority too low.
return false;
}
const oldValue = super.getAttribute( key );
// Don't do anything if value has not changed.
if ( oldValue === value ) {
return false;
}
this._attrs.set( key, value );
// Update priorities map.
this._attributePriority.set( key, priority );
return true;
}
// Internal method for removing `LiveSelection` attribute. Supports attribute priorities (through `directChange`
// parameter).
//
// NOTE: Even if attribute is not present in the selection but is provided to this method, it's priority will
// be changed according to `directChange` parameter.
//
// @private
// @param {String} key Attribute key.
// @param {Boolean} [directChange=true] `true` if the change is caused by `Selection` API, `false` if change
// is caused by `Batch` API.
// @returns {Boolean} Whether attribute was removed. May not be true if such attributes didn't exist or the
// existing attribute had higher priority.
_removeAttribute( key, directChange = true ) {
const priority = directChange ? 'normal' : 'low';
if ( priority == 'low' && this._attributePriority.get( key ) == 'normal' ) {
// Priority too low.
return false;
}
// Update priorities map.
this._attributePriority.set( key, priority );
// Don't do anything if value has not changed.
if ( !super.hasAttribute( key ) ) {
return false;
}
this._attrs.delete( key );
return true;
}
// Internal method for setting multiple `LiveSelection` attributes. Supports attribute priorities (through
// `directChange` parameter).
//
// @private
// @param {Map.<String,*>} attrs Iterable object containing attributes to be set.
// @returns {Set.<String>} Changed attribute keys.
_setAttributesTo( attrs ) {
const changed = new Set();
for ( const [ oldKey, oldValue ] of this.getAttributes() ) {
// Do not remove attribute if attribute with same key and value is about to be set.
if ( attrs.get( oldKey ) === oldValue ) {
continue;
}
// All rest attributes will be removed so changed attributes won't change .
this._removeAttribute( oldKey, false );
}
for ( const [ key, value ] of attrs ) {
// Attribute may not be set because of attributes or because same key/value is already added.
const gotAdded = this._setAttribute( key, value, false );
if ( gotAdded ) {
changed.add( key );
}
}
return changed;
}
// Returns an iterable that iterates through all selection attributes stored in current selection's parent.
//
// @protected
// @returns {Iterable.<*>}
* _getStoredAttributes() {
const selectionParent = this.getFirstPosition().parent;
if ( this.isCollapsed && selectionParent.isEmpty ) {
for ( const key of selectionParent.getAttributeKeys() ) {
if ( key.startsWith( storePrefix ) ) {
const realKey = key.substr( storePrefix.length );
yield [ realKey, selectionParent.getAttribute( key ) ];
}
}
}
}
// Checks model text nodes that are closest to the selection's first position and returns attributes of first
// found element. If there are no text nodes in selection's first position parent, it returns selection
// attributes stored in that parent.
//
// @private
// @returns {Iterable.<*>} Collection of attributes.
_getSurroundingAttributes() {
const position = this.getFirstPosition();
const schema = this._model.schema;
let attrs = null;
if ( !this.isCollapsed ) {
// 1. If selection is a range...
const range = this.getFirstRange();
// ...look for a first character node in that range and take attributes from it.
for ( const value of range ) {
// If the item is an object, we don't want to get attributes from its children.
if ( value.item.is( 'element' ) && schema.isObject( value.item ) ) {
break;
}
if ( value.type == 'text' ) {
attrs = value.item.getAttributes();
break;
}
}
} else {
// 2. If the selection is a caret or the range does not contain a character node...
const nodeBefore = position.textNode ? position.textNode : position.nodeBefore;
const nodeAfter = position.textNode ? position.textNode : position.nodeAfter;
// When gravity is overridden then don't take node before into consideration.
if ( !this.isGravityOverridden ) {
// ...look at the node before caret and take attributes from it if it is a character node.
attrs = getAttrsIfCharacter( nodeBefore );
}
// 3. If not, look at the node after caret...
if ( !attrs ) {
attrs = getAttrsIfCharacter( nodeAfter );
}
// 4. If not, try to find the first character on the left, that is in the same node.
// When gravity is overridden then don't take node before into consideration.
if ( !this.isGravityOverridden && !attrs ) {
let node = nodeBefore;
while ( node && !schema.isInline( node ) && !attrs ) {
node = node.previousSibling;
attrs = getAttrsIfCharacter( node );
}
}
// 5. If not found, try to find the first character on the right, that is in the same node.
if ( !attrs ) {
let node = nodeAfter;
while ( node && !schema.isInline( node ) && !attrs ) {
node = node.nextSibling;
attrs = getAttrsIfCharacter( node );
}
}
// 6. If not found, selection should retrieve attributes from parent.
if ( !attrs ) {
attrs = this._getStoredAttributes();
}
}
return attrs;
}
// Fixes the selection after all its ranges got removed.
//
// @private
// @param {module:engine/model/position~Position} deletionPosition Position where the deletion happened.
_fixGraveyardSelection( deletionPosition ) {
// Find a range that is a correct selection range and is closest to the position where the deletion happened.
const selectionRange = this._model.schema.getNearestSelectionRange( deletionPosition );
// If nearest valid selection range has been found - add it in the place of old range.
if ( selectionRange ) {
// Check the range, convert it to live range, bind events, etc.
this._pushRange( selectionRange );
}
// If nearest valid selection range cannot be found don't add any range. Selection will be set to the default range.
}
}
// Helper function for {@link module:engine/model/liveselection~LiveSelection#_updateAttributes}.
//
// It takes model item, checks whether it is a text node (or text proxy) and, if so, returns it's attributes. If not, returns `null`.
//
// @param {module:engine/model/item~Item|null} node
// @returns {Boolean}
function getAttrsIfCharacter( node ) {
if ( node instanceof _textproxy__WEBPACK_IMPORTED_MODULE_5__["default"] || node instanceof _text__WEBPACK_IMPORTED_MODULE_4__["default"] ) {
return node.getAttributes();
}
return null;
}
// Removes selection attributes from element which is not empty anymore.
//
// @param {module:engine/model/model~Model} model
// @param {module:engine/model/batch~Batch} batch
function clearAttributesStoredInElement( model, batch ) {
const differ = model.document.differ;
for ( const entry of differ.getChanges() ) {
if ( entry.type != 'insert' ) {
continue;
}
const changeParent = entry.position.parent;
const isNoLongerEmpty = entry.length === changeParent.maxOffset;
if ( isNoLongerEmpty ) {
model.enqueueChange( batch, writer => {
const storedAttributes = Array.from( changeParent.getAttributeKeys() )
.filter( key => key.startsWith( storePrefix ) );
for ( const key of storedAttributes ) {
writer.removeAttribute( key, changeParent );
}
} );
}
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/model/element.js":
/*!**********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/model/element.js ***!
\**********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ Element)
/* harmony export */ });
/* harmony import */ var _node__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./node */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/node.js");
/* harmony import */ var _nodelist__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./nodelist */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/nodelist.js");
/* harmony import */ var _text__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./text */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/text.js");
/* harmony import */ var _textproxy__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./textproxy */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/textproxy.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_isiterable__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/isiterable */ "./node_modules/@ckeditor/ckeditor5-utils/src/isiterable.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/model/element
*/
// @if CK_DEBUG_ENGINE // const { stringifyMap, convertMapToStringifiedObject, convertMapToTags } = require( '../dev-utils/utils' );
/**
* Model element. Type of {@link module:engine/model/node~Node node} that has a {@link module:engine/model/element~Element#name name} and
* {@link module:engine/model/element~Element#getChildren child nodes}.
*
* **Important**: see {@link module:engine/model/node~Node} to read about restrictions using `Element` and `Node` API.
*
* @extends module:engine/model/node~Node
*/
class Element extends _node__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* Creates a model element.
*
* **Note:** Constructor of this class shouldn't be used directly in the code.
* Use the {@link module:engine/model/writer~Writer#createElement} method instead.
*
* @protected
* @param {String} name Element's name.
* @param {Object} [attrs] Element's attributes. See {@link module:utils/tomap~toMap} for a list of accepted values.
* @param {module:engine/model/node~Node|Iterable.<module:engine/model/node~Node>} [children]
* One or more nodes to be inserted as children of created element.
*/
constructor( name, attrs, children ) {
super( attrs );
/**
* Element name.
*
* @readonly
* @member {String} module:engine/model/element~Element#name
*/
this.name = name;
/**
* List of children nodes.
*
* @private
* @member {module:engine/model/nodelist~NodeList} module:engine/model/element~Element#_children
*/
this._children = new _nodelist__WEBPACK_IMPORTED_MODULE_1__["default"]();
if ( children ) {
this._insertChild( 0, children );
}
}
/**
* Number of this element's children.
*
* @readonly
* @type {Number}
*/
get childCount() {
return this._children.length;
}
/**
* Sum of {@link module:engine/model/node~Node#offsetSize offset sizes} of all of this element's children.
*
* @readonly
* @type {Number}
*/
get maxOffset() {
return this._children.maxOffset;
}
/**
* Is `true` if there are no nodes inside this element, `false` otherwise.
*
* @readonly
* @type {Boolean}
*/
get isEmpty() {
return this.childCount === 0;
}
/**
* Checks whether this object is of the given.
*
* element.is( 'element' ); // -> true
* element.is( 'node' ); // -> true
* element.is( 'model:element' ); // -> true
* element.is( 'model:node' ); // -> true
*
* element.is( 'view:element' ); // -> false
* element.is( 'documentSelection' ); // -> false
*
* Assuming that the object being checked is an element, you can also check its
* {@link module:engine/model/element~Element#name name}:
*
* element.is( 'element', 'imageBlock' ); // -> true if this is an <imageBlock> element
* element.is( 'element', 'imageBlock' ); // -> same as above
* text.is( 'element', 'imageBlock' ); -> false
*
* {@link module:engine/model/node~Node#is Check the entire list of model objects} which implement the `is()` method.
*
* @param {String} type Type to check.
* @param {String} [name] Element name.
* @returns {Boolean}
*/
is( type, name = null ) {
if ( !name ) {
return type === 'element' || type === 'model:element' ||
// From super.is(). This is highly utilised method and cannot call super. See ckeditor/ckeditor5#6529.
type === 'node' || type === 'model:node';
}
return name === this.name && ( type === 'element' || type === 'model:element' );
}
/**
* Gets the child at the given index.
*
* @param {Number} index Index of child.
* @returns {module:engine/model/node~Node} Child node.
*/
getChild( index ) {
return this._children.getNode( index );
}
/**
* Returns an iterator that iterates over all of this element's children.
*
* @returns {Iterable.<module:engine/model/node~Node>}
*/
getChildren() {
return this._children[ Symbol.iterator ]();
}
/**
* Returns an index of the given child node. Returns `null` if given node is not a child of this element.
*
* @param {module:engine/model/node~Node} node Child node to look for.
* @returns {Number} Child node's index in this element.
*/
getChildIndex( node ) {
return this._children.getNodeIndex( node );
}
/**
* Returns the starting offset of given child. Starting offset is equal to the sum of
* {@link module:engine/model/node~Node#offsetSize offset sizes} of all node's siblings that are before it. Returns `null` if
* given node is not a child of this element.
*
* @param {module:engine/model/node~Node} node Child node to look for.
* @returns {Number} Child node's starting offset.
*/
getChildStartOffset( node ) {
return this._children.getNodeStartOffset( node );
}
/**
* Returns index of a node that occupies given offset. If given offset is too low, returns `0`. If given offset is
* too high, returns {@link module:engine/model/element~Element#getChildIndex index after last child}.
*
* const textNode = new Text( 'foo' );
* const pElement = new Element( 'p' );
* const divElement = new Element( [ textNode, pElement ] );
* divElement.offsetToIndex( -1 ); // Returns 0, because offset is too low.
* divElement.offsetToIndex( 0 ); // Returns 0, because offset 0 is taken by `textNode` which is at index 0.
* divElement.offsetToIndex( 1 ); // Returns 0, because `textNode` has `offsetSize` equal to 3, so it occupies offset 1 too.
* divElement.offsetToIndex( 2 ); // Returns 0.
* divElement.offsetToIndex( 3 ); // Returns 1.
* divElement.offsetToIndex( 4 ); // Returns 2. There are no nodes at offset 4, so last available index is returned.
*
* @param {Number} offset Offset to look for.
* @returns {Number}
*/
offsetToIndex( offset ) {
return this._children.offsetToIndex( offset );
}
/**
* Returns a descendant node by its path relative to this element.
*
* // <this>a<b>c</b></this>
* this.getNodeByPath( [ 0 ] ); // -> "a"
* this.getNodeByPath( [ 1 ] ); // -> <b>
* this.getNodeByPath( [ 1, 0 ] ); // -> "c"
*
* @param {Array.<Number>} relativePath Path of the node to find, relative to this element.
* @returns {module:engine/model/node~Node}
*/
getNodeByPath( relativePath ) {
let node = this; // eslint-disable-line consistent-this
for ( const index of relativePath ) {
node = node.getChild( node.offsetToIndex( index ) );
}
return node;
}
/**
* Returns the parent element of the given name. Returns null if the element is not inside the desired parent.
*
* @param {String} parentName The name of the parent element to find.
* @param {Object} [options] Options object.
* @param {Boolean} [options.includeSelf=false] When set to `true` this node will be also included while searching.
* @returns {module:engine/model/element~Element|null}
*/
findAncestor( parentName, options = { includeSelf: false } ) {
let parent = options.includeSelf ? this : this.parent;
while ( parent ) {
if ( parent.name === parentName ) {
return parent;
}
parent = parent.parent;
}
return null;
}
/**
* Converts `Element` instance to plain object and returns it. Takes care of converting all of this element's children.
*
* @returns {Object} `Element` instance converted to plain object.
*/
toJSON() {
const json = super.toJSON();
json.name = this.name;
if ( this._children.length > 0 ) {
json.children = [];
for ( const node of this._children ) {
json.children.push( node.toJSON() );
}
}
return json;
}
/**
* Creates a copy of this element and returns it. Created element has the same name and attributes as the original element.
* If clone is deep, the original element's children are also cloned. If not, then empty element is returned.
*
* @protected
* @param {Boolean} [deep=false] If set to `true` clones element and all its children recursively. When set to `false`,
* element will be cloned without any child.
*/
_clone( deep = false ) {
const children = deep ? Array.from( this._children ).map( node => node._clone( true ) ) : null;
return new Element( this.name, this.getAttributes(), children );
}
/**
* {@link module:engine/model/element~Element#_insertChild Inserts} one or more nodes at the end of this element.
*
* @see module:engine/model/writer~Writer#append
* @protected
* @param {module:engine/model/item~Item|Iterable.<module:engine/model/item~Item>} nodes Nodes to be inserted.
*/
_appendChild( nodes ) {
this._insertChild( this.childCount, nodes );
}
/**
* Inserts one or more nodes at the given index and sets {@link module:engine/model/node~Node#parent parent} of these nodes
* to this element.
*
* @see module:engine/model/writer~Writer#insert
* @protected
* @param {Number} index Index at which nodes should be inserted.
* @param {module:engine/model/item~Item|Iterable.<module:engine/model/item~Item>} items Items to be inserted.
*/
_insertChild( index, items ) {
const nodes = normalize( items );
for ( const node of nodes ) {
// If node that is being added to this element is already inside another element, first remove it from the old parent.
if ( node.parent !== null ) {
node._remove();
}
node.parent = this;
}
this._children._insertNodes( index, nodes );
}
/**
* Removes one or more nodes starting at the given index and sets
* {@link module:engine/model/node~Node#parent parent} of these nodes to `null`.
*
* @see module:engine/model/writer~Writer#remove
* @protected
* @param {Number} index Index of the first node to remove.
* @param {Number} [howMany=1] Number of nodes to remove.
* @returns {Array.<module:engine/model/node~Node>} Array containing removed nodes.
*/
_removeChildren( index, howMany = 1 ) {
const nodes = this._children._removeNodes( index, howMany );
for ( const node of nodes ) {
node.parent = null;
}
return nodes;
}
/**
* Creates an `Element` instance from given plain object (i.e. parsed JSON string).
* Converts `Element` children to proper nodes.
*
* @param {Object} json Plain object to be converted to `Element`.
* @returns {module:engine/model/element~Element} `Element` instance created using given plain object.
*/
static fromJSON( json ) {
let children = null;
if ( json.children ) {
children = [];
for ( const child of json.children ) {
if ( child.name ) {
// If child has name property, it is an Element.
children.push( Element.fromJSON( child ) );
} else {
// Otherwise, it is a Text node.
children.push( _text__WEBPACK_IMPORTED_MODULE_2__["default"].fromJSON( child ) );
}
}
}
return new Element( json.name, json.attributes, children );
}
// @if CK_DEBUG_ENGINE // toString() {
// @if CK_DEBUG_ENGINE // return `<${ this.rootName || this.name }>`;
// @if CK_DEBUG_ENGINE // }
// @if CK_DEBUG_ENGINE // log() {
// @if CK_DEBUG_ENGINE // console.log( 'ModelElement: ' + this );
// @if CK_DEBUG_ENGINE // }
// @if CK_DEBUG_ENGINE // logExtended() {
// @if CK_DEBUG_ENGINE // console.log( `ModelElement: ${ this }, ${ this.childCount } children,
// @if CK_DEBUG_ENGINE // attrs: ${ convertMapToStringifiedObject( this.getAttributes() ) }` );
// @if CK_DEBUG_ENGINE // }
// @if CK_DEBUG_ENGINE // logAll() {
// @if CK_DEBUG_ENGINE // console.log( '--------------------' );
// @if CK_DEBUG_ENGINE //
// @if CK_DEBUG_ENGINE // this.logExtended();
// @if CK_DEBUG_ENGINE // console.log( 'List of children:' );
// @if CK_DEBUG_ENGINE //
// @if CK_DEBUG_ENGINE // for ( const child of this.getChildren() ) {
// @if CK_DEBUG_ENGINE // child.log();
// @if CK_DEBUG_ENGINE // }
// @if CK_DEBUG_ENGINE // }
// @if CK_DEBUG_ENGINE // printTree( level = 0) {
// @if CK_DEBUG_ENGINE // let string = '';
// @if CK_DEBUG_ENGINE // string += '\t'.repeat( level );
// @if CK_DEBUG_ENGINE // string += `<${ this.rootName || this.name }${ convertMapToTags( this.getAttributes() ) }>`;
// @if CK_DEBUG_ENGINE // for ( const child of this.getChildren() ) {
// @if CK_DEBUG_ENGINE // string += '\n';
// @if CK_DEBUG_ENGINE // if ( child.is( '$text' ) ) {
// @if CK_DEBUG_ENGINE // const textAttrs = convertMapToTags( child._attrs );
// @if CK_DEBUG_ENGINE // string += '\t'.repeat( level + 1 );
// @if CK_DEBUG_ENGINE // if ( textAttrs !== '' ) {
// @if CK_DEBUG_ENGINE // string += `<$text${ textAttrs }>` + child.data + '</$text>';
// @if CK_DEBUG_ENGINE // } else {
// @if CK_DEBUG_ENGINE // string += child.data;
// @if CK_DEBUG_ENGINE // }
// @if CK_DEBUG_ENGINE // } else {
// @if CK_DEBUG_ENGINE // string += child.printTree( level + 1 );
// @if CK_DEBUG_ENGINE // }
// @if CK_DEBUG_ENGINE // }
// @if CK_DEBUG_ENGINE // if ( this.childCount ) {
// @if CK_DEBUG_ENGINE // string += '\n' + '\t'.repeat( level );
// @if CK_DEBUG_ENGINE // }
// @if CK_DEBUG_ENGINE // string += `</${ this.rootName || this.name }>`;
// @if CK_DEBUG_ENGINE // return string;
// @if CK_DEBUG_ENGINE // }
// @if CK_DEBUG_ENGINE // logTree() {
// @if CK_DEBUG_ENGINE // console.log( this.printTree() );
// @if CK_DEBUG_ENGINE // }
}
// Converts strings to Text and non-iterables to arrays.
//
// @param {String|module:engine/model/item~Item|Iterable.<String|module:engine/model/item~Item>}
// @returns {Iterable.<module:engine/model/node~Node>}
function normalize( nodes ) {
// Separate condition because string is iterable.
if ( typeof nodes == 'string' ) {
return [ new _text__WEBPACK_IMPORTED_MODULE_2__["default"]( nodes ) ];
}
if ( !(0,_ckeditor_ckeditor5_utils_src_isiterable__WEBPACK_IMPORTED_MODULE_4__["default"])( nodes ) ) {
nodes = [ nodes ];
}
// Array.from to enable .map() on non-arrays.
return Array.from( nodes )
.map( node => {
if ( typeof node == 'string' ) {
return new _text__WEBPACK_IMPORTED_MODULE_2__["default"]( node );
}
if ( node instanceof _textproxy__WEBPACK_IMPORTED_MODULE_3__["default"] ) {
return new _text__WEBPACK_IMPORTED_MODULE_2__["default"]( node.data, node.getAttributes() );
}
return node;
} );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/model/history.js":
/*!**********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/model/history.js ***!
\**********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ History)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/model/history
*/
/**
* `History` keeps the track of all the operations applied to the {@link module:engine/model/document~Document document}.
*/
class History {
/**
* Creates an empty History instance.
*/
constructor() {
/**
* Operations added to the history.
*
* @protected
* @member {Array.<module:engine/model/operation/operation~Operation>} module:engine/model/history~History#_operations
*/
this._operations = [];
/**
* Holds an information which {@link module:engine/model/operation/operation~Operation operation} undoes which
* {@link module:engine/model/operation/operation~Operation operation}.
*
* Keys of the map are "undoing operations", that is operations that undone some other operations. For each key, the
* value is an operation that has been undone by the "undoing operation".
*
* @private
* @member {Map} module:engine/model/history~History#_undoPairs
*/
this._undoPairs = new Map();
/**
* Holds all undone operations.
*
* @private
* @member {Set.<module:engine/model/operation/operation~Operation>} module:engine/model/history~History#_undoneOperations
*/
this._undoneOperations = new Set();
}
/**
* Adds an operation to the history.
*
* @param {module:engine/model/operation/operation~Operation} operation Operation to add.
*/
addOperation( operation ) {
if ( this._operations.includes( operation ) ) {
return;
}
this._operations.push( operation );
}
/**
* Returns operations added to the history.
*
* @param {Number} [from=Number.NEGATIVE_INFINITY] Base version from which operations should be returned (inclusive).
* Defaults to `Number.NEGATIVE_INFINITY`, which means that operations from the first one will be returned.
* @param {Number} [to=Number.POSITIVE_INFINITY] Base version up to which operations should be returned (exclusive).
* Defaults to `Number.POSITIVE_INFINITY` which means that operations up to the last one will be returned.
* @returns {Array.<module:engine/model/operation/operation~Operation>} Operations added to the history.
*/
getOperations( from = Number.NEGATIVE_INFINITY, to = Number.POSITIVE_INFINITY ) {
const operations = [];
for ( const operation of this._operations ) {
if ( operation.baseVersion >= from && operation.baseVersion < to ) {
operations.push( operation );
}
}
return operations;
}
/**
* Returns operation from the history that bases on given `baseVersion`.
*
* @param {Number} baseVersion Base version of the operation to get.
* @returns {module:engine/model/operation/operation~Operation|undefined} Operation with given base version or `undefined` if
* there is no such operation in history.
*/
getOperation( baseVersion ) {
for ( const operation of this._operations ) {
if ( operation.baseVersion == baseVersion ) {
return operation;
}
}
}
/**
* Marks in history that one operation is an operation that is undoing the other operation. By marking operation this way,
* history is keeping more context information about operations, which helps in operational transformation.
*
* @param {module:engine/model/operation/operation~Operation} undoneOperation Operation which is undone by `undoingOperation`.
* @param {module:engine/model/operation/operation~Operation} undoingOperation Operation which undoes `undoneOperation`.
*/
setOperationAsUndone( undoneOperation, undoingOperation ) {
this._undoPairs.set( undoingOperation, undoneOperation );
this._undoneOperations.add( undoneOperation );
}
/**
* Checks whether given `operation` is undoing any other operation.
*
* @param {module:engine/model/operation/operation~Operation} operation Operation to check.
* @returns {Boolean} `true` if given `operation` is undoing any other operation, `false` otherwise.
*/
isUndoingOperation( operation ) {
return this._undoPairs.has( operation );
}
/**
* Checks whether given `operation` has been undone by any other operation.
*
* @param {module:engine/model/operation/operation~Operation} operation Operation to check.
* @returns {Boolean} `true` if given `operation` has been undone any other operation, `false` otherwise.
*/
isUndoneOperation( operation ) {
return this._undoneOperations.has( operation );
}
/**
* For given `undoingOperation`, returns the operation which has been undone by it.
*
* @param {module:engine/model/operation/operation~Operation} undoingOperation
* @returns {module:engine/model/operation/operation~Operation|undefined} Operation that has been undone by given
* `undoingOperation` or `undefined` if given `undoingOperation` is not undoing any other operation.
*/
getUndoneOperation( undoingOperation ) {
return this._undoPairs.get( undoingOperation );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/model/liveposition.js":
/*!***************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/model/liveposition.js ***!
\***************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ LivePosition)
/* harmony export */ });
/* harmony import */ var _position__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./position */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/position.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_emittermixin__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/emittermixin */ "./node_modules/@ckeditor/ckeditor5-utils/src/emittermixin.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/mix */ "./node_modules/@ckeditor/ckeditor5-utils/src/mix.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/model/liveposition
*/
/**
* `LivePosition` is a type of {@link module:engine/model/position~Position Position}
* that updates itself as {@link module:engine/model/document~Document document}
* is changed through operations. It may be used as a bookmark.
*
* **Note:** Contrary to {@link module:engine/model/position~Position}, `LivePosition` works only in roots that are
* {@link module:engine/model/rootelement~RootElement}.
* If {@link module:engine/model/documentfragment~DocumentFragment} is passed, error will be thrown.
*
* **Note:** Be very careful when dealing with `LivePosition`. Each `LivePosition` instance bind events that might
* have to be unbound.
* Use {@link module:engine/model/liveposition~LivePosition#detach} whenever you don't need `LivePosition` anymore.
*
* @extends module:engine/model/position~Position
*/
class LivePosition extends _position__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* Creates a live position.
*
* @see module:engine/model/position~Position
* @param {module:engine/model/rootelement~RootElement} root
* @param {Array.<Number>} path
* @param {module:engine/model/position~PositionStickiness} [stickiness]
*/
constructor( root, path, stickiness = 'toNone' ) {
super( root, path, stickiness );
if ( !this.root.is( 'rootElement' ) ) {
/**
* LivePosition's root has to be an instance of RootElement.
*
* @error model-liveposition-root-not-rootelement
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_3__["default"]( 'model-liveposition-root-not-rootelement', root );
}
bindWithDocument.call( this );
}
/**
* Unbinds all events previously bound by `LivePosition`. Use it whenever you don't need `LivePosition` instance
* anymore (i.e. when leaving scope in which it was declared or before re-assigning variable that was
* referring to it).
*/
detach() {
this.stopListening();
}
/**
* Checks whether this object is of the given.
*
* livePosition.is( 'position' ); // -> true
* livePosition.is( 'model:position' ); // -> true
* livePosition.is( 'liveposition' ); // -> true
* livePosition.is( 'model:livePosition' ); // -> true
*
* livePosition.is( 'view:position' ); // -> false
* livePosition.is( 'documentSelection' ); // -> false
*
* {@link module:engine/model/node~Node#is Check the entire list of model objects} which implement the `is()` method.
*
* @param {String} type
* @returns {Boolean}
*/
is( type ) {
return type === 'livePosition' || type === 'model:livePosition' ||
// From super.is(). This is highly utilised method and cannot call super. See ckeditor/ckeditor5#6529.
type == 'position' || type === 'model:position';
}
/**
* Creates a {@link module:engine/model/position~Position position instance}, which is equal to this live position.
*
* @returns {module:engine/model/position~Position}
*/
toPosition() {
return new _position__WEBPACK_IMPORTED_MODULE_0__["default"]( this.root, this.path.slice(), this.stickiness );
}
/**
* Creates a `LivePosition` instance that is equal to position.
*
* @param {module:engine/model/position~Position} position
* @param {module:engine/model/position~PositionStickiness} [stickiness]
* @returns {module:engine/model/liveposition~LivePosition}
*/
static fromPosition( position, stickiness ) {
return new this( position.root, position.path.slice(), stickiness ? stickiness : position.stickiness );
}
/**
* @static
* @protected
* @method module:engine/model/liveposition~LivePosition._createAfter
* @see module:engine/model/position~Position._createAfter
* @param {module:engine/model/node~Node} node
* @param {module:engine/model/position~PositionStickiness} [stickiness='toNone']
* @returns {module:engine/model/liveposition~LivePosition}
*/
/**
* @static
* @protected
* @method module:engine/model/liveposition~LivePosition._createBefore
* @see module:engine/model/position~Position._createBefore
* @param {module:engine/model/node~Node} node
* @param {module:engine/model/position~PositionStickiness} [stickiness='toNone']
* @returns {module:engine/model/liveposition~LivePosition}
*/
/**
* @static
* @protected
* @method module:engine/model/liveposition~LivePosition._createAt
* @see module:engine/model/position~Position._createAt
* @param {module:engine/model/item~Item|module:engine/model/position~Position} itemOrPosition
* @param {Number|'end'|'before'|'after'} [offset]
* @param {module:engine/model/position~PositionStickiness} [stickiness='toNone']
* @returns {module:engine/model/liveposition~LivePosition}
*/
/**
* Fired when `LivePosition` instance is changed due to changes on {@link module:engine/model/document~Document}.
*
* @event module:engine/model/liveposition~LivePosition#change
* @param {module:engine/model/position~Position} oldPosition Position equal to this live position before it got changed.
*/
}
// Binds this `LivePosition` to the {@link module:engine/model/document~Document document} that owns
// this position's {@link module:engine/model/position~Position#root root}.
//
// @private
function bindWithDocument() {
this.listenTo(
this.root.document.model,
'applyOperation',
( event, args ) => {
const operation = args[ 0 ];
if ( !operation.isDocumentOperation ) {
return;
}
transform.call( this, operation );
},
{ priority: 'low' }
);
}
// Updates this position accordingly to the updates applied to the model. Bases on change events.
//
// @private
// @param {module:engine/model/operation/operation~Operation} operation Executed operation.
function transform( operation ) {
const result = this.getTransformedByOperation( operation );
if ( !this.isEqual( result ) ) {
const oldPosition = this.toPosition();
this.path = result.path;
this.root = result.root;
this.fire( 'change', oldPosition );
}
}
(0,_ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_2__["default"])( LivePosition, _ckeditor_ckeditor5_utils_src_emittermixin__WEBPACK_IMPORTED_MODULE_1__["default"] );
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/model/liverange.js":
/*!************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/model/liverange.js ***!
\************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ LiveRange)
/* harmony export */ });
/* harmony import */ var _range__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./range */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/range.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_emittermixin__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/emittermixin */ "./node_modules/@ckeditor/ckeditor5-utils/src/emittermixin.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/mix */ "./node_modules/@ckeditor/ckeditor5-utils/src/mix.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/model/liverange
*/
/**
* `LiveRange` is a type of {@link module:engine/model/range~Range Range}
* that updates itself as {@link module:engine/model/document~Document document}
* is changed through operations. It may be used as a bookmark.
*
* **Note:** Be very careful when dealing with `LiveRange`. Each `LiveRange` instance bind events that might
* have to be unbound. Use {@link module:engine/model/liverange~LiveRange#detach detach} whenever you don't need `LiveRange` anymore.
*/
class LiveRange extends _range__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* Creates a live range.
*
* @see module:engine/model/range~Range
*/
constructor( start, end ) {
super( start, end );
bindWithDocument.call( this );
}
/**
* Unbinds all events previously bound by `LiveRange`. Use it whenever you don't need `LiveRange` instance
* anymore (i.e. when leaving scope in which it was declared or before re-assigning variable that was
* referring to it).
*/
detach() {
this.stopListening();
}
/**
* Checks whether this object is of the given.
*
* liveRange.is( 'range' ); // -> true
* liveRange.is( 'model:range' ); // -> true
* liveRange.is( 'liveRange' ); // -> true
* liveRange.is( 'model:liveRange' ); // -> true
*
* liveRange.is( 'view:range' ); // -> false
* liveRange.is( 'documentSelection' ); // -> false
*
* {@link module:engine/model/node~Node#is Check the entire list of model objects} which implement the `is()` method.
*
* @param {String} type
* @returns {Boolean}
*/
is( type ) {
return type === 'liveRange' || type === 'model:liveRange' ||
// From super.is(). This is highly utilised method and cannot call super. See ckeditor/ckeditor5#6529.
type == 'range' || type === 'model:range';
}
/**
* Creates a {@link module:engine/model/range~Range range instance} that is equal to this live range.
*
* @returns {module:engine/model/range~Range}
*/
toRange() {
return new _range__WEBPACK_IMPORTED_MODULE_0__["default"]( this.start, this.end );
}
/**
* Creates a `LiveRange` instance that is equal to the given range.
*
* @param {module:engine/model/range~Range} range
* @returns {module:engine/model/liverange~LiveRange}
*/
static fromRange( range ) {
return new LiveRange( range.start, range.end );
}
/**
* @see module:engine/model/range~Range._createIn
* @static
* @protected
* @method module:engine/model/liverange~LiveRange._createIn
* @param {module:engine/model/element~Element} element
* @returns {module:engine/model/liverange~LiveRange}
*/
/**
* @see module:engine/model/range~Range._createOn
* @static
* @protected
* @method module:engine/model/liverange~LiveRange._createOn
* @param {module:engine/model/element~Element} element
* @returns {module:engine/model/liverange~LiveRange}
*/
/**
* @see module:engine/model/range~Range._createFromPositionAndShift
* @static
* @protected
* @method module:engine/model/liverange~LiveRange._createFromPositionAndShift
* @param {module:engine/model/position~Position} position
* @param {Number} shift
* @returns {module:engine/model/liverange~LiveRange}
*/
/**
* Fired when `LiveRange` instance boundaries have changed due to changes in the
* {@link module:engine/model/document~Document document}.
*
* @event change:range
* @param {module:engine/model/range~Range} oldRange Range with start and end position equal to start and end position of this live
* range before it got changed.
* @param {Object} data Object with additional information about the change.
* @param {module:engine/model/position~Position|null} data.deletionPosition Source position for remove and merge changes.
* Available if the range was moved to the graveyard root, `null` otherwise.
*/
/**
* Fired when `LiveRange` instance boundaries have not changed after a change in {@link module:engine/model/document~Document document}
* but the change took place inside the range, effectively changing its content.
*
* @event change:content
* @param {module:engine/model/range~Range} range Range with start and end position equal to start and end position of
* change range.
* @param {Object} data Object with additional information about the change.
* @param {null} data.deletionPosition Due to the nature of this event, this property is always set to `null`. It is passed
* for compatibility with the {@link module:engine/model/liverange~LiveRange#event:change:range} event.
*/
}
// Binds this `LiveRange` to the {@link module:engine/model/document~Document document}
// that owns this range's {@link module:engine/model/range~Range#root root}.
//
// @private
function bindWithDocument() {
this.listenTo(
this.root.document.model,
'applyOperation',
( event, args ) => {
const operation = args[ 0 ];
if ( !operation.isDocumentOperation ) {
return;
}
transform.call( this, operation );
},
{ priority: 'low' }
);
}
// Updates this range accordingly to the updates applied to the model. Bases on change events.
//
// @private
// @param {module:engine/model/operation/operation~Operation} operation Executed operation.
function transform( operation ) {
// Transform the range by the operation. Join the result ranges if needed.
const ranges = this.getTransformedByOperation( operation );
const result = _range__WEBPACK_IMPORTED_MODULE_0__["default"]._createFromRanges( ranges );
const boundariesChanged = !result.isEqual( this );
const contentChanged = doesOperationChangeRangeContent( this, operation );
let deletionPosition = null;
if ( boundariesChanged ) {
// If range boundaries have changed, fire `change:range` event.
//
if ( result.root.rootName == '$graveyard' ) {
// If the range was moved to the graveyard root, set `deletionPosition`.
if ( operation.type == 'remove' ) {
deletionPosition = operation.sourcePosition;
} else {
// Merge operation.
deletionPosition = operation.deletionPosition;
}
}
const oldRange = this.toRange();
this.start = result.start;
this.end = result.end;
this.fire( 'change:range', oldRange, { deletionPosition } );
} else if ( contentChanged ) {
// If range boundaries have not changed, but there was change inside the range, fire `change:content` event.
this.fire( 'change:content', this.toRange(), { deletionPosition } );
}
}
// Checks whether given operation changes something inside the range (even if it does not change boundaries).
//
// @private
// @param {module:engine/model/range~Range} range Range to check.
// @param {module:engine/model/operation/operation~Operation} operation Executed operation.
// @returns {Boolean}
function doesOperationChangeRangeContent( range, operation ) {
switch ( operation.type ) {
case 'insert':
return range.containsPosition( operation.position );
case 'move':
case 'remove':
case 'reinsert':
case 'merge':
return range.containsPosition( operation.sourcePosition ) ||
range.start.isEqual( operation.sourcePosition ) ||
range.containsPosition( operation.targetPosition );
case 'split':
return range.containsPosition( operation.splitPosition ) || range.containsPosition( operation.insertionPosition );
}
return false;
}
(0,_ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_2__["default"])( LiveRange, _ckeditor_ckeditor5_utils_src_emittermixin__WEBPACK_IMPORTED_MODULE_1__["default"] );
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/model/markercollection.js":
/*!*******************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/model/markercollection.js ***!
\*******************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ MarkerCollection)
/* harmony export */ });
/* harmony import */ var _liverange__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./liverange */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/liverange.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_emittermixin__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/emittermixin */ "./node_modules/@ckeditor/ckeditor5-utils/src/emittermixin.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/mix */ "./node_modules/@ckeditor/ckeditor5-utils/src/mix.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/model/markercollection
*/
/**
* The collection of all {@link module:engine/model/markercollection~Marker markers} attached to the document.
* It lets you {@link module:engine/model/markercollection~MarkerCollection#get get} markers or track them using
* {@link module:engine/model/markercollection~MarkerCollection#event:update} event.
*
* To create, change or remove makers use {@link module:engine/model/writer~Writer model writers'} methods:
* {@link module:engine/model/writer~Writer#addMarker} or {@link module:engine/model/writer~Writer#removeMarker}. Since
* the writer is the only proper way to change the data model it is not possible to change markers directly using this
* collection. All markers created by the writer will be automatically added to this collection.
*
* By default there is one marker collection available as {@link module:engine/model/model~Model#markers model property}.
*
* @see module:engine/model/markercollection~Marker
*/
class MarkerCollection {
/**
* Creates a markers collection.
*/
constructor() {
/**
* Stores {@link ~Marker markers} added to the collection.
*
* @private
* @member {Map} #_markers
*/
this._markers = new Map();
}
/**
* Iterable interface.
*
* Iterates over all {@link ~Marker markers} added to the collection.
*
* @returns {Iterable}
*/
[ Symbol.iterator ]() {
return this._markers.values();
}
/**
* Checks if given {@link ~Marker marker} or marker name is in the collection.
*
* @param {String|module:engine/model/markercollection~Marker} markerOrName Name of marker or marker instance to check.
* @returns {Boolean} `true` if marker is in the collection, `false` otherwise.
*/
has( markerOrName ) {
const markerName = markerOrName instanceof Marker ? markerOrName.name : markerOrName;
return this._markers.has( markerName );
}
/**
* Returns {@link ~Marker marker} with given `markerName`.
*
* @param {String} markerName Name of marker to get.
* @returns {module:engine/model/markercollection~Marker|null} Marker with given name or `null` if such marker was
* not added to the collection.
*/
get( markerName ) {
return this._markers.get( markerName ) || null;
}
/**
* Creates and adds a {@link ~Marker marker} to the `MarkerCollection` with given name on given
* {@link module:engine/model/range~Range range}.
*
* If `MarkerCollection` already had a marker with given name (or {@link ~Marker marker} was passed), the marker in
* collection is updated and {@link module:engine/model/markercollection~MarkerCollection#event:update} event is fired
* but only if there was a change (marker range or {@link module:engine/model/markercollection~Marker#managedUsingOperations}
* flag has changed.
*
* @protected
* @fires module:engine/model/markercollection~MarkerCollection#event:update
* @param {String|module:engine/model/markercollection~Marker} markerOrName Name of marker to set or marker instance to update.
* @param {module:engine/model/range~Range} range Marker range.
* @param {Boolean} [managedUsingOperations=false] Specifies whether the marker is managed using operations.
* @param {Boolean} [affectsData=false] Specifies whether the marker affects the data produced by the data pipeline
* (is persisted in the editor's data).
* @returns {module:engine/model/markercollection~Marker} `Marker` instance which was added or updated.
*/
_set( markerOrName, range, managedUsingOperations = false, affectsData = false ) {
const markerName = markerOrName instanceof Marker ? markerOrName.name : markerOrName;
if ( markerName.includes( ',' ) ) {
/**
* Marker name cannot contain the "," character.
*
* @error markercollection-incorrect-marker-name
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_2__["default"]( 'markercollection-incorrect-marker-name', this );
}
const oldMarker = this._markers.get( markerName );
if ( oldMarker ) {
const oldMarkerData = oldMarker.getData();
const oldRange = oldMarker.getRange();
let hasChanged = false;
if ( !oldRange.isEqual( range ) ) {
oldMarker._attachLiveRange( _liverange__WEBPACK_IMPORTED_MODULE_0__["default"].fromRange( range ) );
hasChanged = true;
}
if ( managedUsingOperations != oldMarker.managedUsingOperations ) {
oldMarker._managedUsingOperations = managedUsingOperations;
hasChanged = true;
}
if ( typeof affectsData === 'boolean' && affectsData != oldMarker.affectsData ) {
oldMarker._affectsData = affectsData;
hasChanged = true;
}
if ( hasChanged ) {
this.fire( 'update:' + markerName, oldMarker, oldRange, range, oldMarkerData );
}
return oldMarker;
}
const liveRange = _liverange__WEBPACK_IMPORTED_MODULE_0__["default"].fromRange( range );
const marker = new Marker( markerName, liveRange, managedUsingOperations, affectsData );
this._markers.set( markerName, marker );
this.fire( 'update:' + markerName, marker, null, range, { ...marker.getData(), range: null } );
return marker;
}
/**
* Removes given {@link ~Marker marker} or a marker with given name from the `MarkerCollection`.
*
* @protected
* @fires module:engine/model/markercollection~MarkerCollection#event:update
* @param {String} markerOrName Marker or name of a marker to remove.
* @returns {Boolean} `true` if marker was found and removed, `false` otherwise.
*/
_remove( markerOrName ) {
const markerName = markerOrName instanceof Marker ? markerOrName.name : markerOrName;
const oldMarker = this._markers.get( markerName );
if ( oldMarker ) {
this._markers.delete( markerName );
this.fire( 'update:' + markerName, oldMarker, oldMarker.getRange(), null, oldMarker.getData() );
this._destroyMarker( oldMarker );
return true;
}
return false;
}
/**
* Fires an {@link module:engine/model/markercollection~MarkerCollection#event:update} event for the given {@link ~Marker marker}
* but does not change the marker. Useful to force {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher downcast
* conversion} for the marker.
*
* @protected
* @fires module:engine/model/markercollection~MarkerCollection#event:update
* @param {String} markerOrName Marker or name of a marker to refresh.
*/
_refresh( markerOrName ) {
const markerName = markerOrName instanceof Marker ? markerOrName.name : markerOrName;
const marker = this._markers.get( markerName );
if ( !marker ) {
/**
* Marker with provided name does not exists.
*
* @error markercollection-refresh-marker-not-exists
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_2__["default"]( 'markercollection-refresh-marker-not-exists', this );
}
const range = marker.getRange();
this.fire( 'update:' + markerName, marker, range, range, marker.getData() );
}
/**
* Returns iterator that iterates over all markers, which ranges contain given {@link module:engine/model/position~Position position}.
*
* @param {module:engine/model/position~Position} position
* @returns {Iterable.<module:engine/model/markercollection~Marker>}
*/
* getMarkersAtPosition( position ) {
for ( const marker of this ) {
if ( marker.getRange().containsPosition( position ) ) {
yield marker;
}
}
}
/**
* Returns iterator that iterates over all markers, which intersects with given {@link module:engine/model/range~Range range}.
*
* @param {module:engine/model/range~Range} range
* @returns {Iterable.<module:engine/model/markercollection~Marker>}
*/
* getMarkersIntersectingRange( range ) {
for ( const marker of this ) {
if ( marker.getRange().getIntersection( range ) !== null ) {
yield marker;
}
}
}
/**
* Destroys marker collection and all markers inside it.
*/
destroy() {
for ( const marker of this._markers.values() ) {
this._destroyMarker( marker );
}
this._markers = null;
this.stopListening();
}
/**
* Iterates over all markers that starts with given `prefix`.
*
* const markerFooA = markersCollection.set( 'foo:a', rangeFooA );
* const markerFooB = markersCollection.set( 'foo:b', rangeFooB );
* const markerBarA = markersCollection.set( 'bar:a', rangeBarA );
* const markerFooBarA = markersCollection.set( 'foobar:a', rangeFooBarA );
* Array.from( markersCollection.getMarkersGroup( 'foo' ) ); // [ markerFooA, markerFooB ]
* Array.from( markersCollection.getMarkersGroup( 'a' ) ); // []
*
* @param prefix
* @returns {Iterable.<module:engine/model/markercollection~Marker>}
*/
* getMarkersGroup( prefix ) {
for ( const marker of this._markers.values() ) {
if ( marker.name.startsWith( prefix + ':' ) ) {
yield marker;
}
}
}
/**
* Destroys the marker.
*
* @private
* @param {module:engine/model/markercollection~Marker} marker Marker to destroy.
*/
_destroyMarker( marker ) {
marker.stopListening();
marker._detachLiveRange();
}
/**
* Fired whenever marker is added, updated or removed from `MarkerCollection`.
*
* @event update
* @param {module:engine/model/markercollection~Marker} marker Updated Marker.
* @param {module:engine/model/range~Range|null} oldRange Marker range before the update. When is not defined it
* means that marker is just added.
* @param {module:engine/model/range~Range|null} newRange Marker range after update. When is not defined it
* means that marker is just removed.
* @param {module:engine/model/markercollection~MarkerData} oldMarkerData Data of the marker before the change.
*/
}
(0,_ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_3__["default"])( MarkerCollection, _ckeditor_ckeditor5_utils_src_emittermixin__WEBPACK_IMPORTED_MODULE_1__["default"] );
/**
* @typedef {Object} module:engine/model/markercollection~MarkerData
*
* @property {module:engine/model/range~Range|null} range Marker range. `null` if the marker was removed.
* @property {Boolean} affectsData A property defining if the marker affects data.
* @property {Boolean} managedUsingOperations A property defining if the marker is managed using operations.
*/
/**
* `Marker` is a continuous parts of model (like a range), is named and represent some kind of information about marked
* part of model document. In contrary to {@link module:engine/model/node~Node nodes}, which are building blocks of
* model document tree, markers are not stored directly in document tree but in
* {@link module:engine/model/model~Model#markers model markers' collection}. Still, they are document data, by giving
* additional meaning to the part of a model document between marker start and marker end.
*
* In this sense, markers are similar to adding and converting attributes on nodes. The difference is that attribute is
* connected with a given node (e.g. a character is bold no matter if it gets moved or content around it changes).
* Markers on the other hand are continuous ranges and are characterized by their start and end position. This means that
* any character in the marker is marked by the marker. For example, if a character is moved outside of marker it stops being
* "special" and the marker is shrunk. Similarly, when a character is moved into the marker from other place in document
* model, it starts being "special" and the marker is enlarged.
*
* Another upside of markers is that finding marked part of document is fast and easy. Using attributes to mark some nodes
* and then trying to find that part of document would require traversing whole document tree. Marker gives instant access
* to the range which it is marking at the moment.
*
* Markers are built from a name and a range.
*
* Range of the marker is updated automatically when document changes, using
* {@link module:engine/model/liverange~LiveRange live range} mechanism.
*
* Name is used to group and identify markers. Names have to be unique, but markers can be grouped by
* using common prefixes, separated with `:`, for example: `user:john` or `search:3`. That's useful in term of creating
* namespaces for custom elements (e.g. comments, highlights). You can use this prefixes in
* {@link module:engine/model/markercollection~MarkerCollection#event:update} listeners to listen on changes in a group of markers.
* For instance: `model.markers.on( 'update:user', callback );` will be called whenever any `user:*` markers changes.
*
* There are two types of markers.
*
* 1. Markers managed directly, without using operations. They are added directly by {@link module:engine/model/writer~Writer}
* to the {@link module:engine/model/markercollection~MarkerCollection} without any additional mechanism. They can be used
* as bookmarks or visual markers. They are great for showing results of the find, or select link when the focus is in the input.
*
* 1. Markers managed using operations. These markers are also stored in {@link module:engine/model/markercollection~MarkerCollection}
* but changes in these markers is managed the same way all other changes in the model structure - using operations.
* Therefore, they are handled in the undo stack and synchronized between clients if the collaboration plugin is enabled.
* This type of markers is useful for solutions like spell checking or comments.
*
* Both type of them should be added / updated by {@link module:engine/model/writer~Writer#addMarker}
* and removed by {@link module:engine/model/writer~Writer#removeMarker} methods.
*
* model.change( ( writer ) => {
* const marker = writer.addMarker( name, { range, usingOperation: true } );
*
* // ...
*
* writer.removeMarker( marker );
* } );
*
* See {@link module:engine/model/writer~Writer} to find more examples.
*
* Since markers need to track change in the document, for efficiency reasons, it is best to create and keep as little
* markers as possible and remove them as soon as they are not needed anymore.
*
* Markers can be downcasted and upcasted.
*
* Markers downcast happens on {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:addMarker} and
* {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:removeMarker} events.
* Use {@link module:engine/conversion/downcasthelpers downcast converters} or attach a custom converter to mentioned events.
* For {@link module:engine/controller/datacontroller~DataController data pipeline}, marker should be downcasted to an element.
* Then, it can be upcasted back to a marker. Again, use {@link module:engine/conversion/upcasthelpers upcast converters} or
* attach a custom converter to {@link module:engine/conversion/upcastdispatcher~UpcastDispatcher#event:element}.
*
* `Marker` instances are created and destroyed only by {@link ~MarkerCollection MarkerCollection}.
*/
class Marker {
/**
* Creates a marker instance.
*
* @param {String} name Marker name.
* @param {module:engine/model/liverange~LiveRange} liveRange Range marked by the marker.
* @param {Boolean} managedUsingOperations Specifies whether the marker is managed using operations.
* @param {Boolean} affectsData Specifies whether the marker affects the data produced by the data pipeline
* (is persisted in the editor's data).
*/
constructor( name, liveRange, managedUsingOperations, affectsData ) {
/**
* Marker's name.
*
* @readonly
* @type {String}
*/
this.name = name;
/**
* Range marked by the marker.
*
* @protected
* @member {module:engine/model/liverange~LiveRange}
*/
this._liveRange = this._attachLiveRange( liveRange );
/**
* Flag indicates if the marker is managed using operations or not.
*
* @private
* @member {Boolean}
*/
this._managedUsingOperations = managedUsingOperations;
/**
* Specifies whether the marker affects the data produced by the data pipeline
* (is persisted in the editor's data).
*
* @private
* @member {Boolean}
*/
this._affectsData = affectsData;
}
/**
* A value indicating if the marker is managed using operations.
* See {@link ~Marker marker class description} to learn more about marker types.
* See {@link module:engine/model/writer~Writer#addMarker}.
*
* @returns {Boolean}
*/
get managedUsingOperations() {
if ( !this._liveRange ) {
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_2__["default"]( 'marker-destroyed', this );
}
return this._managedUsingOperations;
}
/**
* A value indicating if the marker changes the data.
*
* @returns {Boolean}
*/
get affectsData() {
if ( !this._liveRange ) {
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_2__["default"]( 'marker-destroyed', this );
}
return this._affectsData;
}
/**
* Returns the marker data (properties defining the marker).
*
* @returns {module:engine/model/markercollection~MarkerData}
*/
getData() {
return {
range: this.getRange(),
affectsData: this.affectsData,
managedUsingOperations: this.managedUsingOperations
};
}
/**
* Returns current marker start position.
*
* @returns {module:engine/model/position~Position}
*/
getStart() {
if ( !this._liveRange ) {
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_2__["default"]( 'marker-destroyed', this );
}
return this._liveRange.start.clone();
}
/**
* Returns current marker end position.
*
* @returns {module:engine/model/position~Position}
*/
getEnd() {
if ( !this._liveRange ) {
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_2__["default"]( 'marker-destroyed', this );
}
return this._liveRange.end.clone();
}
/**
* Returns a range that represents the current state of the marker.
*
* Keep in mind that returned value is a {@link module:engine/model/range~Range Range}, not a
* {@link module:engine/model/liverange~LiveRange LiveRange}. This means that it is up-to-date and relevant only
* until next model document change. Do not store values returned by this method. Instead, store {@link ~Marker#name}
* and get `Marker` instance from {@link module:engine/model/markercollection~MarkerCollection MarkerCollection} every
* time there is a need to read marker properties. This will guarantee that the marker has not been removed and
* that it's data is up-to-date.
*
* @returns {module:engine/model/range~Range}
*/
getRange() {
if ( !this._liveRange ) {
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_2__["default"]( 'marker-destroyed', this );
}
return this._liveRange.toRange();
}
/**
* Checks whether this object is of the given.
*
* marker.is( 'marker' ); // -> true
* marker.is( 'model:marker' ); // -> true
*
* marker.is( 'view:element' ); // -> false
* marker.is( 'documentSelection' ); // -> false
*
* {@link module:engine/model/node~Node#is Check the entire list of model objects} which implement the `is()` method.
*
* @param {String} type
* @returns {Boolean}
*/
is( type ) {
return type === 'marker' || type === 'model:marker';
}
/**
* Binds new live range to the marker and detach the old one if is attached.
*
* @protected
* @param {module:engine/model/liverange~LiveRange} liveRange Live range to attach
* @returns {module:engine/model/liverange~LiveRange} Attached live range.
*/
_attachLiveRange( liveRange ) {
if ( this._liveRange ) {
this._detachLiveRange();
}
// Delegating does not work with namespaces. Alternatively, we could delegate all events (using `*`).
liveRange.delegate( 'change:range' ).to( this );
liveRange.delegate( 'change:content' ).to( this );
this._liveRange = liveRange;
return liveRange;
}
/**
* Unbinds and destroys currently attached live range.
*
* @protected
*/
_detachLiveRange() {
this._liveRange.stopDelegating( 'change:range', this );
this._liveRange.stopDelegating( 'change:content', this );
this._liveRange.detach();
this._liveRange = null;
}
/**
* Fired whenever {@link ~Marker#_liveRange marker range} is changed due to changes on {@link module:engine/model/document~Document}.
* This is a delegated {@link module:engine/model/liverange~LiveRange#event:change:range LiveRange change:range event}.
*
* When marker is removed from {@link module:engine/model/markercollection~MarkerCollection MarkerCollection},
* all event listeners listening to it should be removed. It is best to do it on
* {@link module:engine/model/markercollection~MarkerCollection#event:update MarkerCollection update event}.
*
* @see module:engine/model/liverange~LiveRange#event:change:range
* @event change:range
* @param {module:engine/model/range~Range} oldRange
* @param {Object} data
*/
/**
* Fired whenever change on {@link module:engine/model/document~Document} is done inside {@link ~Marker#_liveRange marker range}.
* This is a delegated {@link module:engine/model/liverange~LiveRange#event:change:content LiveRange change:content event}.
*
* When marker is removed from {@link module:engine/model/markercollection~MarkerCollection MarkerCollection},
* all event listeners listening to it should be removed. It is best to do it on
* {@link module:engine/model/markercollection~MarkerCollection#event:update MarkerCollection update event}.
*
* @see module:engine/model/liverange~LiveRange#event:change:content
* @event change:content
* @param {module:engine/model/range~Range} oldRange
* @param {Object} data
*/
}
(0,_ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_3__["default"])( Marker, _ckeditor_ckeditor5_utils_src_emittermixin__WEBPACK_IMPORTED_MODULE_1__["default"] );
/**
* Cannot use a {@link module:engine/model/markercollection~MarkerCollection#destroy destroyed marker} instance.
*
* @error marker-destroyed
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/model/model.js":
/*!********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/model/model.js ***!
\********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ Model)
/* harmony export */ });
/* harmony import */ var _batch__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./batch */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/batch.js");
/* harmony import */ var _writer__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./writer */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/writer.js");
/* harmony import */ var _schema__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./schema */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/schema.js");
/* harmony import */ var _document__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./document */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/document.js");
/* harmony import */ var _markercollection__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./markercollection */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/markercollection.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_observablemixin__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/observablemixin */ "./node_modules/@ckeditor/ckeditor5-utils/src/observablemixin.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/mix */ "./node_modules/@ckeditor/ckeditor5-utils/src/mix.js");
/* harmony import */ var _element__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./element */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/element.js");
/* harmony import */ var _range__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./range */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/range.js");
/* harmony import */ var _position__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./position */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/position.js");
/* harmony import */ var _selection__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./selection */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/selection.js");
/* harmony import */ var _operation_operationfactory__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ./operation/operationfactory */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/operationfactory.js");
/* harmony import */ var _utils_insertcontent__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! ./utils/insertcontent */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/utils/insertcontent.js");
/* harmony import */ var _utils_deletecontent__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(/*! ./utils/deletecontent */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/utils/deletecontent.js");
/* harmony import */ var _utils_modifyselection__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(/*! ./utils/modifyselection */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/utils/modifyselection.js");
/* harmony import */ var _utils_getselectedcontent__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(/*! ./utils/getselectedcontent */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/utils/getselectedcontent.js");
/* harmony import */ var _utils_selection_post_fixer__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(/*! ./utils/selection-post-fixer */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/utils/selection-post-fixer.js");
/* harmony import */ var _utils_autoparagraphing__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(/*! ./utils/autoparagraphing */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/utils/autoparagraphing.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_18__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/model/model
*/
// @if CK_DEBUG_ENGINE // const { dumpTrees } = require( '../dev-utils/utils' );
// @if CK_DEBUG_ENGINE // const { OperationReplayer } = require( '../dev-utils/operationreplayer' ).default;
/**
* Editor's data model. Read about the model in the
* {@glink framework/guides/architecture/editing-engine engine architecture guide}.
*
* @mixes module:utils/observablemixin~ObservableMixin
*/
class Model {
constructor() {
/**
* Model's marker collection.
*
* @readonly
* @member {module:engine/model/markercollection~MarkerCollection}
*/
this.markers = new _markercollection__WEBPACK_IMPORTED_MODULE_4__["default"]();
/**
* Model's document.
*
* @readonly
* @member {module:engine/model/document~Document}
*/
this.document = new _document__WEBPACK_IMPORTED_MODULE_3__["default"]( this );
/**
* Model's schema.
*
* @readonly
* @member {module:engine/model/schema~Schema}
*/
this.schema = new _schema__WEBPACK_IMPORTED_MODULE_2__["default"]();
/**
* All callbacks added by {@link module:engine/model/model~Model#change} or
* {@link module:engine/model/model~Model#enqueueChange} methods waiting to be executed.
*
* @private
* @type {Array.<Function>}
*/
this._pendingChanges = [];
/**
* The last created and currently used writer instance.
*
* @private
* @member {module:engine/model/writer~Writer}
*/
this._currentWriter = null;
[ 'insertContent', 'deleteContent', 'modifySelection', 'getSelectedContent', 'applyOperation' ]
.forEach( methodName => this.decorate( methodName ) );
// Adding operation validation with `highest` priority, so it is called before any other feature would like
// to do anything with the operation. If the operation has incorrect parameters it should throw on the earliest occasion.
this.on( 'applyOperation', ( evt, args ) => {
const operation = args[ 0 ];
operation._validate();
}, { priority: 'highest' } );
// Register some default abstract entities.
this.schema.register( '$root', {
isLimit: true
} );
this.schema.register( '$block', {
allowIn: '$root',
isBlock: true
} );
this.schema.register( '$text', {
allowIn: '$block',
isInline: true,
isContent: true
} );
this.schema.register( '$clipboardHolder', {
allowContentOf: '$root',
allowChildren: '$text',
isLimit: true
} );
this.schema.register( '$documentFragment', {
allowContentOf: '$root',
allowChildren: '$text',
isLimit: true
} );
// An element needed by the `upcastElementToMarker` converter.
// This element temporarily represents a marker boundary during the conversion process and is removed
// at the end of the conversion. `UpcastDispatcher` or at least `Conversion` class looks like a
// better place for this registration but both know nothing about `Schema`.
this.schema.register( '$marker' );
this.schema.addChildCheck( ( context, childDefinition ) => {
if ( childDefinition.name === '$marker' ) {
return true;
}
} );
(0,_utils_selection_post_fixer__WEBPACK_IMPORTED_MODULE_16__.injectSelectionPostFixer)( this );
// Post-fixer which takes care of adding empty paragraph elements to the empty roots.
this.document.registerPostFixer( _utils_autoparagraphing__WEBPACK_IMPORTED_MODULE_17__.autoParagraphEmptyRoots );
// @if CK_DEBUG_ENGINE // this.on( 'applyOperation', () => {
// @if CK_DEBUG_ENGINE // dumpTrees( this.document, this.document.version );
// @if CK_DEBUG_ENGINE // }, { priority: 'lowest' } );
}
/**
* The `change()` method is the primary way of changing the model. You should use it to modify all document nodes
* (including detached nodes – i.e. nodes not added to the {@link module:engine/model/model~Model#document model document}),
* the {@link module:engine/model/document~Document#selection document's selection}, and
* {@link module:engine/model/model~Model#markers model markers}.
*
* model.change( writer => {
* writer.insertText( 'foo', paragraph, 'end' );
* } );
*
* All changes inside the change block use the same {@link module:engine/model/batch~Batch} so they are combined
* into a single undo step.
*
* model.change( writer => {
* writer.insertText( 'foo', paragraph, 'end' ); // foo.
*
* model.change( writer => {
* writer.insertText( 'bar', paragraph, 'end' ); // foobar.
* } );
*
* writer.insertText( 'bom', paragraph, 'end' ); // foobarbom.
* } );
*
* The callback of the `change()` block is executed synchronously.
*
* You can also return a value from the change block.
*
* const img = model.change( writer => {
* return writer.createElement( 'img' );
* } );
*
* @see #enqueueChange
* @param {Function} callback Callback function which may modify the model.
* @returns {*} Value returned by the callback.
*/
change( callback ) {
try {
if ( this._pendingChanges.length === 0 ) {
// If this is the outermost block, create a new batch and start `_runPendingChanges` execution flow.
this._pendingChanges.push( { batch: new _batch__WEBPACK_IMPORTED_MODULE_0__["default"](), callback } );
return this._runPendingChanges()[ 0 ];
} else {
// If this is not the outermost block, just execute the callback.
return callback( this._currentWriter );
}
} catch ( err ) {
// @if CK_DEBUG // throw err;
/* istanbul ignore next */
_ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_18__["default"].rethrowUnexpectedError( err, this );
}
}
/**
* The `enqueueChange()` method performs similar task as the {@link #change `change()` method}, with two major differences.
*
* First, the callback of `enqueueChange()` is executed when all other enqueued changes are done. It might be executed
* immediately if it is not nested in any other change block, but if it is nested in another (enqueue)change block,
* it will be delayed and executed after the outermost block.
*
* model.change( writer => {
* console.log( 1 );
*
* model.enqueueChange( writer => {
* console.log( 2 );
* } );
*
* console.log( 3 );
* } ); // Will log: 1, 3, 2.
*
* In addition to that, the changes enqueued with `enqueueChange()` will be converted separately from the changes
* done in the outer `change()` block.
*
* Second, it lets you define the {@link module:engine/model/batch~Batch} into which you want to add your changes.
* By default, a new batch with the default {@link module:engine/model/batch~Batch#constructor batch type} is created.
* In the sample above, the `change` and `enqueueChange` blocks will use a different batch (and a different
* {@link module:engine/model/writer~Writer} instance since each of them operates on a separate batch).
*
* model.enqueueChange( { isUndoable: false }, writer => {
* writer.insertText( 'foo', paragraph, 'end' );
* } );
*
* When using the `enqueueChange()` block you can also add some changes to the batch you used before.
*
* model.enqueueChange( batch, writer => {
* writer.insertText( 'foo', paragraph, 'end' );
* } );
*
* In order to make a nested `enqueueChange()` create a single undo step together with the changes done in the outer `change()`
* block, you can obtain the batch instance from the {@link module:engine/model/writer~Writer#batch writer} of the outer block.
*
* @param {module:engine/model/batch~Batch|Object} [batchOrType] A batch or a
* {@link module:engine/model/batch~Batch#constructor batch type} that should be used in the callback. If not defined, a new batch with
* the default type will be created.
* @param {Function} callback Callback function which may modify the model.
*/
enqueueChange( batchOrType, callback ) {
try {
if ( !batchOrType ) {
batchOrType = new _batch__WEBPACK_IMPORTED_MODULE_0__["default"]();
} else if ( typeof batchOrType === 'function' ) {
callback = batchOrType;
batchOrType = new _batch__WEBPACK_IMPORTED_MODULE_0__["default"]();
} else if ( !( batchOrType instanceof _batch__WEBPACK_IMPORTED_MODULE_0__["default"] ) ) {
batchOrType = new _batch__WEBPACK_IMPORTED_MODULE_0__["default"]( batchOrType );
}
this._pendingChanges.push( { batch: batchOrType, callback } );
if ( this._pendingChanges.length == 1 ) {
this._runPendingChanges();
}
} catch ( err ) {
// @if CK_DEBUG // throw err;
/* istanbul ignore next */
_ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_18__["default"].rethrowUnexpectedError( err, this );
}
}
/**
* {@link module:utils/observablemixin~ObservableMixin#decorate Decorated} function for applying
* {@link module:engine/model/operation/operation~Operation operations} to the model.
*
* This is a low-level way of changing the model. It is exposed for very specific use cases (like the undo feature).
* Normally, to modify the model, you will want to use {@link module:engine/model/writer~Writer `Writer`}.
* See also {@glink framework/guides/architecture/editing-engine#changing-the-model Changing the model} section
* of the {@glink framework/guides/architecture/editing-engine Editing architecture} guide.
*
* @param {module:engine/model/operation/operation~Operation} operation The operation to apply.
*/
applyOperation( operation ) {
// @if CK_DEBUG_ENGINE // console.log( 'Applying ' + operation );
// @if CK_DEBUG_ENGINE // if ( !this._operationLogs ) {
// @if CK_DEBUG_ENGINE // this._operationLogs = [];
// @if CK_DEBUG_ENGINE // }
// @if CK_DEBUG_ENGINE // this._operationLogs.push( JSON.stringify( operation ) );
// @if CK_DEBUG_ENGINE //if ( !this._appliedOperations ) {
// @if CK_DEBUG_ENGINE // this._appliedOperations = [];
// @if CK_DEBUG_ENGINE //}
// @if CK_DEBUG_ENGINE //this._appliedOperations.push( operation );
operation._execute();
}
// @if CK_DEBUG_ENGINE // getAppliedOperation() {
// @if CK_DEBUG_ENGINE // if ( !this._appliedOperations ) {
// @if CK_DEBUG_ENGINE // return '';
// @if CK_DEBUG_ENGINE // }
// @if CK_DEBUG_ENGINE // return this._appliedOperations.map( JSON.stringify ).join( '-------' );
// @if CK_DEBUG_ENGINE // }
// @if CK_DEBUG_ENGINE // createReplayer( stringifiedOperations ) {
// @if CK_DEBUG_ENGINE // return new OperationReplayer( this, '-------', stringifiedOperations );
// @if CK_DEBUG_ENGINE // }
/**
* Inserts content at the position in the editor specified by the selection, as one would expect the paste
* functionality to work.
*
* This is a high-level method. It takes the {@link #schema schema} into consideration when inserting
* the content, clears the given selection's content before inserting nodes and moves the selection
* to its target position at the end of the process.
* It can split elements, merge them, wrap bare text nodes with paragraphs, etc. — just like the
* pasting feature should do.
*
* For lower-level methods see {@link module:engine/model/writer~Writer `Writer`}.
*
* This method, unlike {@link module:engine/model/writer~Writer `Writer`}'s methods, does not have to be used
* inside a {@link #change `change()` block}.
*
* # Conversion and schema
*
* Inserting elements and text nodes into the model is not enough to make CKEditor 5 render that content
* to the user. CKEditor 5 implements a model-view-controller architecture and what `model.insertContent()` does
* is only adding nodes to the model. Additionally, you need to define
* {@glink framework/guides/architecture/editing-engine#conversion converters} between the model and view
* and define those nodes in the {@glink framework/guides/architecture/editing-engine#schema schema}.
*
* So, while this method may seem similar to CKEditor 4 `editor.insertHtml()` (in fact, both methods
* are used for paste-like content insertion), the CKEditor 5 method cannot be use to insert arbitrary HTML
* unless converters are defined for all elements and attributes in that HTML.
*
* # Examples
*
* Using `insertContent()` with a manually created model structure:
*
* // Let's create a document fragment containing such content as:
* //
* // <paragraph>foo</paragraph>
* // <blockQuote>
* // <paragraph>bar</paragraph>
* // </blockQuote>
* const docFrag = editor.model.change( writer => {
* const p1 = writer.createElement( 'paragraph' );
* const p2 = writer.createElement( 'paragraph' );
* const blockQuote = writer.createElement( 'blockQuote' );
* const docFrag = writer.createDocumentFragment();
*
* writer.append( p1, docFrag );
* writer.append( blockQuote, docFrag );
* writer.append( p2, blockQuote );
* writer.insertText( 'foo', p1 );
* writer.insertText( 'bar', p2 );
*
* return docFrag;
* } );
*
* // insertContent() does not have to be used in a change() block. It can, though,
* // so this code could be moved to the callback defined above.
* editor.model.insertContent( docFrag );
*
* Using `insertContent()` with an HTML string converted to a model document fragment (similar to the pasting mechanism):
*
* // You can create your own HtmlDataProcessor instance or use editor.data.processor
* // if you have not overridden the default one (which is the HtmlDataProcessor instance).
* const htmlDP = new HtmlDataProcessor( viewDocument );
*
* // Convert an HTML string to a view document fragment:
* const viewFragment = htmlDP.toView( htmlString );
*
* // Convert the view document fragment to a model document fragment
* // in the context of $root. This conversion takes the schema into
* // account so if, for example, the view document fragment contained a bare text node,
* // this text node cannot be a child of $root, so it will be automatically
* // wrapped with a <paragraph>. You can define the context yourself (in the second parameter),
* // and e.g. convert the content like it would happen in a <paragraph>.
* // Note: The clipboard feature uses a custom context called $clipboardHolder
* // which has a loosened schema.
* const modelFragment = editor.data.toModel( viewFragment );
*
* editor.model.insertContent( modelFragment );
*
* By default this method will use the document selection but it can also be used with a position, range or selection instance.
*
* // Insert text at the current document selection position.
* editor.model.change( writer => {
* editor.model.insertContent( writer.createText( 'x' ) );
* } );
*
* // Insert text at a given position - the document selection will not be modified.
* editor.model.change( writer => {
* editor.model.insertContent( writer.createText( 'x' ), doc.getRoot(), 2 );
*
* // Which is a shorthand for:
* editor.model.insertContent( writer.createText( 'x' ), writer.createPositionAt( doc.getRoot(), 2 ) );
* } );
*
* If you want the document selection to be moved to the inserted content, use the
* {@link module:engine/model/writer~Writer#setSelection `setSelection()`} method of the writer after inserting
* the content:
*
* editor.model.change( writer => {
* const paragraph = writer.createElement( 'paragraph' );
*
* // Insert an empty paragraph at the beginning of the root.
* editor.model.insertContent( paragraph, writer.createPositionAt( editor.model.document.getRoot(), 0 ) );
*
* // Move the document selection to the inserted paragraph.
* writer.setSelection( paragraph, 'in' );
* } );
*
* If an instance of the {@link module:engine/model/selection~Selection model selection} is passed as `selectable`,
* the new content will be inserted at the passed selection (instead of document selection):
*
* editor.model.change( writer => {
* // Create a selection in a paragraph that will be used as a place of insertion.
* const selection = writer.createSelection( paragraph, 'in' );
*
* // Insert the new text at the created selection.
* editor.model.insertContent( writer.createText( 'x' ), selection );
*
* // insertContent() modifies the passed selection instance so it can be used to set the document selection.
* // Note: This is not necessary when you passed the document selection to insertContent().
* writer.setSelection( selection );
* } );
*
* @fires insertContent
* @param {module:engine/model/documentfragment~DocumentFragment|module:engine/model/item~Item} content The content to insert.
* @param {module:engine/model/selection~Selectable} [selectable=model.document.selection]
* The selection into which the content should be inserted. If not provided the current model document selection will be used.
* @param {Number|'before'|'end'|'after'|'on'|'in'} [placeOrOffset] To be used when a model item was passed as `selectable`.
* This param defines a position in relation to that item.
* @returns {module:engine/model/range~Range} Range which contains all the performed changes. This is a range that, if removed,
* would return the model to the state before the insertion. If no changes were preformed by `insertContent`, returns a range collapsed
* at the insertion position.
*/
insertContent( content, selectable, placeOrOffset ) {
return (0,_utils_insertcontent__WEBPACK_IMPORTED_MODULE_12__["default"])( this, content, selectable, placeOrOffset );
}
/**
* Deletes content of the selection and merge siblings. The resulting selection is always collapsed.
*
* **Note:** For the sake of predictability, the resulting selection should always be collapsed.
* In cases where a feature wants to modify deleting behavior so selection isn't collapsed
* (e.g. a table feature may want to keep row selection after pressing <kbd>Backspace</kbd>),
* then that behavior should be implemented in the view's listener. At the same time, the table feature
* will need to modify this method's behavior too, e.g. to "delete contents and then collapse
* the selection inside the last selected cell" or "delete the row and collapse selection somewhere near".
* That needs to be done in order to ensure that other features which use `deleteContent()` will work well with tables.
*
* @fires deleteContent
* @param {module:engine/model/selection~Selection|module:engine/model/documentselection~DocumentSelection} selection
* Selection of which the content should be deleted.
* @param {Object} [options]
* @param {Boolean} [options.leaveUnmerged=false] Whether to merge elements after removing the content of the selection.
*
* For example `<heading1>x[x</heading1><paragraph>y]y</paragraph>` will become:
*
* * `<heading1>x^y</heading1>` with the option disabled (`leaveUnmerged == false`)
* * `<heading1>x^</heading1><paragraph>y</paragraph>` with enabled (`leaveUnmerged == true`).
*
* Note: {@link module:engine/model/schema~Schema#isObject object} and {@link module:engine/model/schema~Schema#isLimit limit}
* elements will not be merged.
*
* @param {Boolean} [options.doNotResetEntireContent=false] Whether to skip replacing the entire content with a
* paragraph when the entire content was selected.
*
* For example `<heading1>[x</heading1><paragraph>y]</paragraph>` will become:
*
* * `<paragraph>^</paragraph>` with the option disabled (`doNotResetEntireContent == false`)
* * `<heading1>^</heading1>` with enabled (`doNotResetEntireContent == true`)
*
* @param {Boolean} [options.doNotAutoparagraph=false] Whether to create a paragraph if after content deletion selection is moved
* to a place where text cannot be inserted.
*
* For example `<paragraph>x</paragraph>[<imageBlock src="foo.jpg"></imageBlock>]` will become:
*
* * `<paragraph>x</paragraph><paragraph>[]</paragraph>` with the option disabled (`doNotAutoparagraph == false`)
* * `<paragraph>x[]</paragraph>` with the option enabled (`doNotAutoparagraph == true`).
*
* **Note:** if there is no valid position for the selection, the paragraph will always be created:
*
* `[<imageBlock src="foo.jpg"></imageBlock>]` -> `<paragraph>[]</paragraph>`.
*
* @param {'forward'|'backward'} [options.direction='backward'] The direction in which the content is being consumed.
* Deleting backward corresponds to using the <kbd>Backspace</kbd> key, while deleting content forward corresponds to
* the <kbd>Shift</kbd>+<kbd>Backspace</kbd> keystroke.
*/
deleteContent( selection, options ) {
(0,_utils_deletecontent__WEBPACK_IMPORTED_MODULE_13__["default"])( this, selection, options );
}
/**
* Modifies the selection. Currently, the supported modifications are:
*
* * Extending. The selection focus is moved in the specified `options.direction` with a step specified in `options.unit`.
* Possible values for `unit` are:
* * `'character'` (default) - moves selection by one user-perceived character. In most cases this means moving by one
* character in `String` sense. However, unicode also defines "combing marks". These are special symbols, that combines
* with a symbol before it ("base character") to create one user-perceived character. For example, `q̣̇` is a normal
* letter `q` with two "combining marks": upper dot (`Ux0307`) and lower dot (`Ux0323`). For most actions, i.e. extending
* selection by one position, it is correct to include both "base character" and all of it's "combining marks". That is
* why `'character'` value is most natural and common method of modifying selection.
* * `'codePoint'` - moves selection by one unicode code point. In contrary to, `'character'` unit, this will insert
* selection between "base character" and "combining mark", because "combining marks" have their own unicode code points.
* However, for technical reasons, unicode code points with values above `UxFFFF` are represented in native `String` by
* two characters, called "surrogate pairs". Halves of "surrogate pairs" have a meaning only when placed next to each other.
* For example `𨭎` is represented in `String` by `\uD862\uDF4E`. Both `\uD862` and `\uDF4E` do not have any meaning
* outside the pair (are rendered as ? when alone). Position between them would be incorrect. In this case, selection
* extension will include whole "surrogate pair".
* * `'word'` - moves selection by a whole word.
*
* **Note:** if you extend a forward selection in a backward direction you will in fact shrink it.
*
* @fires modifySelection
* @param {module:engine/model/selection~Selection|module:engine/model/documentselection~DocumentSelection} selection
* The selection to modify.
* @param {Object} [options]
* @param {'forward'|'backward'} [options.direction='forward'] The direction in which the selection should be modified.
* @param {'character'|'codePoint'|'word'} [options.unit='character'] The unit by which selection should be modified.
* @param {Boolean} [options.treatEmojiAsSingleUnit=false] Whether multi-characer emoji sequences should be handled as single unit.
*/
modifySelection( selection, options ) {
(0,_utils_modifyselection__WEBPACK_IMPORTED_MODULE_14__["default"])( this, selection, options );
}
/**
* Gets a clone of the selected content.
*
* For example, for the following selection:
*
* ```html
* <paragraph>x</paragraph>
* <blockQuote>
* <paragraph>y</paragraph>
* <heading1>fir[st</heading1>
* </blockQuote>
* <paragraph>se]cond</paragraph>
* <paragraph>z</paragraph>
* ```
*
* It will return a document fragment with such a content:
*
* ```html
* <blockQuote>
* <heading1>st</heading1>
* </blockQuote>
* <paragraph>se</paragraph>
* ```
*
* @fires getSelectedContent
* @param {module:engine/model/selection~Selection|module:engine/model/documentselection~DocumentSelection} selection
* The selection of which content will be returned.
* @returns {module:engine/model/documentfragment~DocumentFragment}
*/
getSelectedContent( selection ) {
return (0,_utils_getselectedcontent__WEBPACK_IMPORTED_MODULE_15__["default"])( this, selection );
}
/**
* Checks whether the given {@link module:engine/model/range~Range range} or
* {@link module:engine/model/element~Element element} has any meaningful content.
*
* Meaningful content is:
*
* * any text node (`options.ignoreWhitespaces` allows controlling whether this text node must also contain
* any non-whitespace characters),
* * or any {@link module:engine/model/schema~Schema#isContent content element},
* * or any {@link module:engine/model/markercollection~Marker marker} which
* {@link module:engine/model/markercollection~Marker#_affectsData affects data}.
*
* This means that a range containing an empty `<paragraph></paragraph>` is not considered to have a meaningful content.
* However, a range containing an `<imageBlock></imageBlock>` (which would normally be marked in the schema as an object element)
* is considered non-empty.
*
* @param {module:engine/model/range~Range|module:engine/model/element~Element} rangeOrElement Range or element to check.
* @param {Object} [options]
* @param {Boolean} [options.ignoreWhitespaces] Whether text node with whitespaces only should be considered empty.
* @param {Boolean} [options.ignoreMarkers] Whether markers should be ignored.
* @returns {Boolean}
*/
hasContent( rangeOrElement, options = {} ) {
const range = rangeOrElement instanceof _element__WEBPACK_IMPORTED_MODULE_7__["default"] ? _range__WEBPACK_IMPORTED_MODULE_8__["default"]._createIn( rangeOrElement ) : rangeOrElement;
if ( range.isCollapsed ) {
return false;
}
const { ignoreWhitespaces = false, ignoreMarkers = false } = options;
// Check if there are any markers which affects data in this given range.
if ( !ignoreMarkers ) {
for ( const intersectingMarker of this.markers.getMarkersIntersectingRange( range ) ) {
if ( intersectingMarker.affectsData ) {
return true;
}
}
}
for ( const item of range.getItems() ) {
if ( this.schema.isContent( item ) ) {
if ( item.is( '$textProxy' ) ) {
if ( !ignoreWhitespaces ) {
return true;
} else if ( item.data.search( /\S/ ) !== -1 ) {
return true;
}
} else {
return true;
}
}
}
return false;
}
/**
* Creates a position from the given root and path in that root.
*
* Note: This method is also available as
* {@link module:engine/model/writer~Writer#createPositionFromPath `Writer#createPositionFromPath()`}.
*
* @param {module:engine/model/element~Element|module:engine/model/documentfragment~DocumentFragment} root Root of the position.
* @param {Array.<Number>} path Position path. See {@link module:engine/model/position~Position#path}.
* @param {module:engine/model/position~PositionStickiness} [stickiness='toNone'] Position stickiness.
* See {@link module:engine/model/position~PositionStickiness}.
* @returns {module:engine/model/position~Position}
*/
createPositionFromPath( root, path, stickiness ) {
return new _position__WEBPACK_IMPORTED_MODULE_9__["default"]( root, path, stickiness );
}
/**
* Creates position at the given location. The location can be specified as:
*
* * a {@link module:engine/model/position~Position position},
* * a parent element and offset in that element,
* * a parent element and `'end'` (the position will be set at the end of that element),
* * a {@link module:engine/model/item~Item model item} and `'before'` or `'after'`
* (the position will be set before or after the given model item).
*
* This method is a shortcut to other factory methods such as:
*
* * {@link module:engine/model/model~Model#createPositionBefore `createPositionBefore()`},
* * {@link module:engine/model/model~Model#createPositionAfter `createPositionAfter()`}.
*
* Note: This method is also available as
* {@link module:engine/model/writer~Writer#createPositionAt `Writer#createPositionAt()`},
*
* @param {module:engine/model/item~Item|module:engine/model/position~Position} itemOrPosition
* @param {Number|'end'|'before'|'after'} [offset] Offset or one of the flags. Used only when
* first parameter is a {@link module:engine/model/item~Item model item}.
*/
createPositionAt( itemOrPosition, offset ) {
return _position__WEBPACK_IMPORTED_MODULE_9__["default"]._createAt( itemOrPosition, offset );
}
/**
* Creates a new position after the given {@link module:engine/model/item~Item model item}.
*
* Note: This method is also available as
* {@link module:engine/model/writer~Writer#createPositionAfter `Writer#createPositionAfter()`}.
*
* @param {module:engine/model/item~Item} item Item after which the position should be placed.
* @returns {module:engine/model/position~Position}
*/
createPositionAfter( item ) {
return _position__WEBPACK_IMPORTED_MODULE_9__["default"]._createAfter( item );
}
/**
* Creates a new position before the given {@link module:engine/model/item~Item model item}.
*
* Note: This method is also available as
* {@link module:engine/model/writer~Writer#createPositionBefore `Writer#createPositionBefore()`}.
*
* @param {module:engine/model/item~Item} item Item before which the position should be placed.
* @returns {module:engine/model/position~Position}
*/
createPositionBefore( item ) {
return _position__WEBPACK_IMPORTED_MODULE_9__["default"]._createBefore( item );
}
/**
* Creates a range spanning from the `start` position to the `end` position.
*
* Note: This method is also available as
* {@link module:engine/model/writer~Writer#createRange `Writer#createRange()`}:
*
* model.change( writer => {
* const range = writer.createRange( start, end );
* } );
*
* @param {module:engine/model/position~Position} start Start position.
* @param {module:engine/model/position~Position} [end] End position. If not set, the range will be collapsed
* to the `start` position.
* @returns {module:engine/model/range~Range}
*/
createRange( start, end ) {
return new _range__WEBPACK_IMPORTED_MODULE_8__["default"]( start, end );
}
/**
* Creates a range inside the given element which starts before the first child of
* that element and ends after the last child of that element.
*
* Note: This method is also available as
* {@link module:engine/model/writer~Writer#createRangeIn `Writer#createRangeIn()`}:
*
* model.change( writer => {
* const range = writer.createRangeIn( paragraph );
* } );
*
* @param {module:engine/model/element~Element} element Element which is a parent for the range.
* @returns {module:engine/model/range~Range}
*/
createRangeIn( element ) {
return _range__WEBPACK_IMPORTED_MODULE_8__["default"]._createIn( element );
}
/**
* Creates a range that starts before the given {@link module:engine/model/item~Item model item} and ends after it.
*
* Note: This method is also available on `writer` instance as
* {@link module:engine/model/writer~Writer#createRangeOn `Writer.createRangeOn()`}:
*
* model.change( writer => {
* const range = writer.createRangeOn( paragraph );
* } );
*
* @param {module:engine/model/item~Item} item
* @returns {module:engine/model/range~Range}
*/
createRangeOn( item ) {
return _range__WEBPACK_IMPORTED_MODULE_8__["default"]._createOn( item );
}
/**
* Creates a new selection instance based on the given {@link module:engine/model/selection~Selectable selectable}
* or creates an empty selection if no arguments were passed.
*
* Note: This method is also available as
* {@link module:engine/model/writer~Writer#createSelection `Writer#createSelection()`}.
*
* // Creates empty selection without ranges.
* const selection = writer.createSelection();
*
* // Creates selection at the given range.
* const range = writer.createRange( start, end );
* const selection = writer.createSelection( range );
*
* // Creates selection at the given ranges
* const ranges = [ writer.createRange( start1, end2 ), writer.createRange( star2, end2 ) ];
* const selection = writer.createSelection( ranges );
*
* // Creates selection from the other selection.
* // Note: It doesn't copies selection attributes.
* const otherSelection = writer.createSelection();
* const selection = writer.createSelection( otherSelection );
*
* // Creates selection from the given document selection.
* // Note: It doesn't copies selection attributes.
* const documentSelection = model.document.selection;
* const selection = writer.createSelection( documentSelection );
*
* // Creates selection at the given position.
* const position = writer.createPositionFromPath( root, path );
* const selection = writer.createSelection( position );
*
* // Creates selection at the given offset in the given element.
* const paragraph = writer.createElement( 'paragraph' );
* const selection = writer.createSelection( paragraph, offset );
*
* // Creates a range inside an {@link module:engine/model/element~Element element} which starts before the
* // first child of that element and ends after the last child of that element.
* const selection = writer.createSelection( paragraph, 'in' );
*
* // Creates a range on an {@link module:engine/model/item~Item item} which starts before the item and ends
* // just after the item.
* const selection = writer.createSelection( paragraph, 'on' );
*
* // Additional options (`'backward'`) can be specified as the last argument.
*
* // Creates backward selection.
* const selection = writer.createSelection( range, { backward: true } );
*
* @param {module:engine/model/selection~Selectable} selectable
* @param {Number|'before'|'end'|'after'|'on'|'in'} [placeOrOffset] Sets place or offset of the selection.
* @param {Object} [options]
* @param {Boolean} [options.backward] Sets this selection instance to be backward.
* @returns {module:engine/model/selection~Selection}
*/
createSelection( selectable, placeOrOffset, options ) {
return new _selection__WEBPACK_IMPORTED_MODULE_10__["default"]( selectable, placeOrOffset, options );
}
/**
* Creates a {@link module:engine/model/batch~Batch} instance.
*
* **Note:** In most cases creating a batch instance is not necessary as they are created when using:
*
* * {@link #change `change()`},
* * {@link #enqueueChange `enqueueChange()`}.
*
* @param {Object} [type] {@link module:engine/model/batch~Batch#constructor The type} of the batch.
* @returns {module:engine/model/batch~Batch}
*/
createBatch( type ) {
return new _batch__WEBPACK_IMPORTED_MODULE_0__["default"]( type );
}
/**
* Creates an operation instance from a JSON object (parsed JSON string).
*
* This is an alias for {@link module:engine/model/operation/operationfactory~OperationFactory.fromJSON `OperationFactory.fromJSON()`}.
*
* @param {Object} json Deserialized JSON object.
* @returns {module:engine/model/operation/operation~Operation}
*/
createOperationFromJSON( json ) {
return _operation_operationfactory__WEBPACK_IMPORTED_MODULE_11__["default"].fromJSON( json, this.document );
}
/**
* Removes all events listeners set by model instance and destroys {@link module:engine/model/document~Document}.
*/
destroy() {
this.document.destroy();
this.stopListening();
}
/**
* Common part of {@link module:engine/model/model~Model#change} and {@link module:engine/model/model~Model#enqueueChange}
* which calls callbacks and returns array of values returned by these callbacks.
*
* @private
* @returns {Array.<*>} Array of values returned by callbacks.
*/
_runPendingChanges() {
const ret = [];
this.fire( '_beforeChanges' );
while ( this._pendingChanges.length ) {
// Create a new writer using batch instance created for this chain of changes.
const currentBatch = this._pendingChanges[ 0 ].batch;
this._currentWriter = new _writer__WEBPACK_IMPORTED_MODULE_1__["default"]( this, currentBatch );
// Execute changes callback and gather the returned value.
const callbackReturnValue = this._pendingChanges[ 0 ].callback( this._currentWriter );
ret.push( callbackReturnValue );
this.document._handleChangeBlock( this._currentWriter );
this._pendingChanges.shift();
this._currentWriter = null;
}
this.fire( '_afterChanges' );
return ret;
}
/**
* Fired when entering the outermost {@link module:engine/model/model~Model#enqueueChange} or
* {@link module:engine/model/model~Model#change} block.
*
* @protected
* @event _beforeChanges
*/
/**
* Fired when leaving the outermost {@link module:engine/model/model~Model#enqueueChange} or
* {@link module:engine/model/model~Model#change} block.
*
* @protected
* @event _afterChanges
*/
/**
* Fired every time any {@link module:engine/model/operation/operation~Operation operation} is applied on the model
* using {@link #applyOperation}.
*
* Note that this event is suitable only for very specific use-cases. Use it if you need to listen to every single operation
* applied on the document. However, in most cases {@link module:engine/model/document~Document#event:change} should
* be used.
*
* A few callbacks are already added to this event by engine internal classes:
*
* * with `highest` priority operation is validated,
* * with `normal` priority operation is executed,
* * with `low` priority the {@link module:engine/model/document~Document} updates its version,
* * with `low` priority {@link module:engine/model/liveposition~LivePosition} and {@link module:engine/model/liverange~LiveRange}
* update themselves.
*
* @event applyOperation
* @param {Array} args Arguments of the `applyOperation` which is an array with a single element - applied
* {@link module:engine/model/operation/operation~Operation operation}.
*/
/**
* Event fired when {@link #insertContent} method is called.
*
* The {@link #insertContent default action of that method} is implemented as a
* listener to this event so it can be fully customized by the features.
*
* **Note** The `selectable` parameter for the {@link #insertContent} is optional. When `undefined` value is passed the method uses
* `model.document.selection`.
*
* @event insertContent
* @param {Array} args The arguments passed to the original method.
*/
/**
* Event fired when {@link #deleteContent} method is called.
*
* The {@link #deleteContent default action of that method} is implemented as a
* listener to this event so it can be fully customized by the features.
*
* @event deleteContent
* @param {Array} args The arguments passed to the original method.
*/
/**
* Event fired when {@link #modifySelection} method is called.
*
* The {@link #modifySelection default action of that method} is implemented as a
* listener to this event so it can be fully customized by the features.
*
* @event modifySelection
* @param {Array} args The arguments passed to the original method.
*/
/**
* Event fired when {@link #getSelectedContent} method is called.
*
* The {@link #getSelectedContent default action of that method} is implemented as a
* listener to this event so it can be fully customized by the features.
*
* @event getSelectedContent
* @param {Array} args The arguments passed to the original method.
*/
}
(0,_ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_6__["default"])( Model, _ckeditor_ckeditor5_utils_src_observablemixin__WEBPACK_IMPORTED_MODULE_5__["default"] );
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/model/node.js":
/*!*******************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/model/node.js ***!
\*******************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ Node)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_utils_src_tomap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/tomap */ "./node_modules/@ckeditor/ckeditor5-utils/src/tomap.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_comparearrays__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/comparearrays */ "./node_modules/@ckeditor/ckeditor5-utils/src/comparearrays.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_version__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/version */ "./node_modules/@ckeditor/ckeditor5-utils/src/version.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/model/node
*/
// To check if component is loaded more than once.
/**
* Model node. Most basic structure of model tree.
*
* This is an abstract class that is a base for other classes representing different nodes in model.
*
* **Note:** If a node is detached from the model tree, you can manipulate it using it's API.
* However, it is **very important** that nodes already attached to model tree should be only changed through
* {@link module:engine/model/writer~Writer Writer API}.
*
* Changes done by `Node` methods, like {@link module:engine/model/element~Element#_insertChild _insertChild} or
* {@link module:engine/model/node~Node#_setAttribute _setAttribute}
* do not generate {@link module:engine/model/operation/operation~Operation operations}
* which are essential for correct editor work if you modify nodes in {@link module:engine/model/document~Document document} root.
*
* The flow of working on `Node` (and classes that inherits from it) is as such:
* 1. You can create a `Node` instance, modify it using it's API.
* 2. Add `Node` to the model using `Batch` API.
* 3. Change `Node` that was already added to the model using `Batch` API.
*
* Similarly, you cannot use `Batch` API on a node that has not been added to the model tree, with the exception
* of {@link module:engine/model/writer~Writer#insert inserting} that node to the model tree.
*
* Be aware that using {@link module:engine/model/writer~Writer#remove remove from Batch API} does not allow to use `Node` API because
* the information about `Node` is still kept in model document.
*
* In case of {@link module:engine/model/element~Element element node}, adding and removing children also counts as changing a node and
* follows same rules.
*/
class Node {
/**
* Creates a model node.
*
* This is an abstract class, so this constructor should not be used directly.
*
* @abstract
* @param {Object} [attrs] Node's attributes. See {@link module:utils/tomap~toMap} for a list of accepted values.
*/
constructor( attrs ) {
/**
* Parent of this node. It could be {@link module:engine/model/element~Element}
* or {@link module:engine/model/documentfragment~DocumentFragment}.
* Equals to `null` if the node has no parent.
*
* @readonly
* @member {module:engine/model/element~Element|module:engine/model/documentfragment~DocumentFragment|null}
*/
this.parent = null;
/**
* Attributes set on this node.
*
* @private
* @member {Map} module:engine/model/node~Node#_attrs
*/
this._attrs = (0,_ckeditor_ckeditor5_utils_src_tomap__WEBPACK_IMPORTED_MODULE_0__["default"])( attrs );
}
/**
* Index of this node in it's parent or `null` if the node has no parent.
*
* Accessing this property throws an error if this node's parent element does not contain it.
* This means that model tree got broken.
*
* @readonly
* @type {Number|null}
*/
get index() {
let pos;
if ( !this.parent ) {
return null;
}
if ( ( pos = this.parent.getChildIndex( this ) ) === null ) {
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_1__["default"]( 'model-node-not-found-in-parent', this );
}
return pos;
}
/**
* Offset at which this node starts in it's parent. It is equal to the sum of {@link #offsetSize offsetSize}
* of all it's previous siblings. Equals to `null` if node has no parent.
*
* Accessing this property throws an error if this node's parent element does not contain it.
* This means that model tree got broken.
*
* @readonly
* @type {Number|null}
*/
get startOffset() {
let pos;
if ( !this.parent ) {
return null;
}
if ( ( pos = this.parent.getChildStartOffset( this ) ) === null ) {
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_1__["default"]( 'model-node-not-found-in-parent', this );
}
return pos;
}
/**
* Offset size of this node. Represents how much "offset space" is occupied by the node in it's parent.
* It is important for {@link module:engine/model/position~Position position}. When node has `offsetSize` greater than `1`, position
* can be placed between that node start and end. `offsetSize` greater than `1` is for nodes that represents more
* than one entity, i.e. {@link module:engine/model/text~Text text node}.
*
* @readonly
* @type {Number}
*/
get offsetSize() {
return 1;
}
/**
* Offset at which this node ends in it's parent. It is equal to the sum of this node's
* {@link module:engine/model/node~Node#startOffset start offset} and {@link #offsetSize offset size}.
* Equals to `null` if the node has no parent.
*
* @readonly
* @type {Number|null}
*/
get endOffset() {
if ( !this.parent ) {
return null;
}
return this.startOffset + this.offsetSize;
}
/**
* Node's next sibling or `null` if the node is a last child of it's parent or if the node has no parent.
*
* @readonly
* @type {module:engine/model/node~Node|null}
*/
get nextSibling() {
const index = this.index;
return ( index !== null && this.parent.getChild( index + 1 ) ) || null;
}
/**
* Node's previous sibling or `null` if the node is a first child of it's parent or if the node has no parent.
*
* @readonly
* @type {module:engine/model/node~Node|null}
*/
get previousSibling() {
const index = this.index;
return ( index !== null && this.parent.getChild( index - 1 ) ) || null;
}
/**
* The top-most ancestor of the node. If node has no parent it is the root itself. If the node is a part
* of {@link module:engine/model/documentfragment~DocumentFragment}, it's `root` is equal to that `DocumentFragment`.
*
* @readonly
* @type {module:engine/model/node~Node|module:engine/model/documentfragment~DocumentFragment}
*/
get root() {
let root = this; // eslint-disable-line consistent-this
while ( root.parent ) {
root = root.parent;
}
return root;
}
/**
* Returns true if the node is in a tree rooted in the document (is a descendant of one of its roots).
*
* @returns {Boolean}
*/
isAttached() {
return this.root.is( 'rootElement' );
}
/**
* Gets path to the node. The path is an array containing starting offsets of consecutive ancestors of this node,
* beginning from {@link module:engine/model/node~Node#root root}, down to this node's starting offset. The path can be used to
* create {@link module:engine/model/position~Position Position} instance.
*
* const abc = new Text( 'abc' );
* const foo = new Text( 'foo' );
* const h1 = new Element( 'h1', null, new Text( 'header' ) );
* const p = new Element( 'p', null, [ abc, foo ] );
* const div = new Element( 'div', null, [ h1, p ] );
* foo.getPath(); // Returns [ 1, 3 ]. `foo` is in `p` which is in `div`. `p` starts at offset 1, while `foo` at 3.
* h1.getPath(); // Returns [ 0 ].
* div.getPath(); // Returns [].
*
* @returns {Array.<Number>} The path.
*/
getPath() {
const path = [];
let node = this; // eslint-disable-line consistent-this
while ( node.parent ) {
path.unshift( node.startOffset );
node = node.parent;
}
return path;
}
/**
* Returns ancestors array of this node.
*
* @param {Object} options Options object.
* @param {Boolean} [options.includeSelf=false] When set to `true` this node will be also included in parent's array.
* @param {Boolean} [options.parentFirst=false] When set to `true`, array will be sorted from node's parent to root element,
* otherwise root element will be the first item in the array.
* @returns {Array} Array with ancestors.
*/
getAncestors( options = { includeSelf: false, parentFirst: false } ) {
const ancestors = [];
let parent = options.includeSelf ? this : this.parent;
while ( parent ) {
ancestors[ options.parentFirst ? 'push' : 'unshift' ]( parent );
parent = parent.parent;
}
return ancestors;
}
/**
* Returns a {@link module:engine/model/element~Element} or {@link module:engine/model/documentfragment~DocumentFragment}
* which is a common ancestor of both nodes.
*
* @param {module:engine/model/node~Node} node The second node.
* @param {Object} options Options object.
* @param {Boolean} [options.includeSelf=false] When set to `true` both nodes will be considered "ancestors" too.
* Which means that if e.g. node A is inside B, then their common ancestor will be B.
* @returns {module:engine/model/element~Element|module:engine/model/documentfragment~DocumentFragment|null}
*/
getCommonAncestor( node, options = {} ) {
const ancestorsA = this.getAncestors( options );
const ancestorsB = node.getAncestors( options );
let i = 0;
while ( ancestorsA[ i ] == ancestorsB[ i ] && ancestorsA[ i ] ) {
i++;
}
return i === 0 ? null : ancestorsA[ i - 1 ];
}
/**
* Returns whether this node is before given node. `false` is returned if nodes are in different trees (for example,
* in different {@link module:engine/model/documentfragment~DocumentFragment}s).
*
* @param {module:engine/model/node~Node} node Node to compare with.
* @returns {Boolean}
*/
isBefore( node ) {
// Given node is not before this node if they are same.
if ( this == node ) {
return false;
}
// Return `false` if it is impossible to compare nodes.
if ( this.root !== node.root ) {
return false;
}
const thisPath = this.getPath();
const nodePath = node.getPath();
const result = (0,_ckeditor_ckeditor5_utils_src_comparearrays__WEBPACK_IMPORTED_MODULE_2__["default"])( thisPath, nodePath );
switch ( result ) {
case 'prefix':
return true;
case 'extension':
return false;
default:
return thisPath[ result ] < nodePath[ result ];
}
}
/**
* Returns whether this node is after given node. `false` is returned if nodes are in different trees (for example,
* in different {@link module:engine/model/documentfragment~DocumentFragment}s).
*
* @param {module:engine/model/node~Node} node Node to compare with.
* @returns {Boolean}
*/
isAfter( node ) {
// Given node is not before this node if they are same.
if ( this == node ) {
return false;
}
// Return `false` if it is impossible to compare nodes.
if ( this.root !== node.root ) {
return false;
}
// In other cases, just check if the `node` is before, and return the opposite.
return !this.isBefore( node );
}
/**
* Checks if the node has an attribute with given key.
*
* @param {String} key Key of attribute to check.
* @returns {Boolean} `true` if attribute with given key is set on node, `false` otherwise.
*/
hasAttribute( key ) {
return this._attrs.has( key );
}
/**
* Gets an attribute value for given key or `undefined` if that attribute is not set on node.
*
* @param {String} key Key of attribute to look for.
* @returns {*} Attribute value or `undefined`.
*/
getAttribute( key ) {
return this._attrs.get( key );
}
/**
* Returns iterator that iterates over this node's attributes.
*
* Attributes are returned as arrays containing two items. First one is attribute key and second is attribute value.
* This format is accepted by native `Map` object and also can be passed in `Node` constructor.
*
* @returns {Iterable.<*>}
*/
getAttributes() {
return this._attrs.entries();
}
/**
* Returns iterator that iterates over this node's attribute keys.
*
* @returns {Iterable.<String>}
*/
getAttributeKeys() {
return this._attrs.keys();
}
/**
* Converts `Node` to plain object and returns it.
*
* @returns {Object} `Node` converted to plain object.
*/
toJSON() {
const json = {};
// Serializes attributes to the object.
// attributes = { a: 'foo', b: 1, c: true }.
if ( this._attrs.size ) {
json.attributes = Array.from( this._attrs ).reduce( ( result, attr ) => {
result[ attr[ 0 ] ] = attr[ 1 ];
return result;
}, {} );
}
return json;
}
/**
* Checks whether this object is of the given type.
*
* This method is useful when processing model objects that are of unknown type. For example, a function
* may return a {@link module:engine/model/documentfragment~DocumentFragment} or a {@link module:engine/model/node~Node}
* that can be either a text node or an element. This method can be used to check what kind of object is returned.
*
* someObject.is( 'element' ); // -> true if this is an element
* someObject.is( 'node' ); // -> true if this is a node (a text node or an element)
* someObject.is( 'documentFragment' ); // -> true if this is a document fragment
*
* Since this method is also available on a range of view objects, you can prefix the type of the object with
* `model:` or `view:` to check, for example, if this is the model's or view's element:
*
* modelElement.is( 'model:element' ); // -> true
* modelElement.is( 'view:element' ); // -> false
*
* By using this method it is also possible to check a name of an element:
*
* imageElement.is( 'element', 'imageBlock' ); // -> true
* imageElement.is( 'element', 'imageBlock' ); // -> same as above
* imageElement.is( 'model:element', 'imageBlock' ); // -> same as above, but more precise
*
* The list of model objects which implement the `is()` method:
*
* * {@link module:engine/model/node~Node#is `Node#is()`}
* * {@link module:engine/model/text~Text#is `Text#is()`}
* * {@link module:engine/model/element~Element#is `Element#is()`}
* * {@link module:engine/model/rootelement~RootElement#is `RootElement#is()`}
* * {@link module:engine/model/position~Position#is `Position#is()`}
* * {@link module:engine/model/liveposition~LivePosition#is `LivePosition#is()`}
* * {@link module:engine/model/range~Range#is `Range#is()`}
* * {@link module:engine/model/liverange~LiveRange#is `LiveRange#is()`}
* * {@link module:engine/model/documentfragment~DocumentFragment#is `DocumentFragment#is()`}
* * {@link module:engine/model/selection~Selection#is `Selection#is()`}
* * {@link module:engine/model/documentselection~DocumentSelection#is `DocumentSelection#is()`}
* * {@link module:engine/model/markercollection~Marker#is `Marker#is()`}
* * {@link module:engine/model/textproxy~TextProxy#is `TextProxy#is()`}
*
* @method #is
* @param {String} type Type to check.
* @returns {Boolean}
*/
is( type ) {
return type === 'node' || type === 'model:node';
}
/**
* Creates a copy of this node, that is a node with exactly same attributes, and returns it.
*
* @protected
* @returns {module:engine/model/node~Node} Node with same attributes as this node.
*/
_clone() {
return new Node( this._attrs );
}
/**
* Removes this node from it's parent.
*
* @see module:engine/model/writer~Writer#remove
* @protected
*/
_remove() {
this.parent._removeChildren( this.index );
}
/**
* Sets attribute on the node. If attribute with the same key already is set, it's value is overwritten.
*
* @see module:engine/model/writer~Writer#setAttribute
* @protected
* @param {String} key Key of attribute to set.
* @param {*} value Attribute value.
*/
_setAttribute( key, value ) {
this._attrs.set( key, value );
}
/**
* Removes all attributes from the node and sets given attributes.
*
* @see module:engine/model/writer~Writer#setAttributes
* @protected
* @param {Object} [attrs] Attributes to set. See {@link module:utils/tomap~toMap} for a list of accepted values.
*/
_setAttributesTo( attrs ) {
this._attrs = (0,_ckeditor_ckeditor5_utils_src_tomap__WEBPACK_IMPORTED_MODULE_0__["default"])( attrs );
}
/**
* Removes an attribute with given key from the node.
*
* @see module:engine/model/writer~Writer#removeAttribute
* @protected
* @param {String} key Key of attribute to remove.
* @returns {Boolean} `true` if the attribute was set on the element, `false` otherwise.
*/
_removeAttribute( key ) {
return this._attrs.delete( key );
}
/**
* Removes all attributes from the node.
*
* @see module:engine/model/writer~Writer#clearAttributes
* @protected
*/
_clearAttributes() {
this._attrs.clear();
}
}
/**
* The node's parent does not contain this node.
*
* @error model-node-not-found-in-parent
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/model/nodelist.js":
/*!***********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/model/nodelist.js ***!
\***********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ NodeList)
/* harmony export */ });
/* harmony import */ var _node__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./node */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/node.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/model/nodelist
*/
/**
* Provides an interface to operate on a list of {@link module:engine/model/node~Node nodes}. `NodeList` is used internally
* in classes like {@link module:engine/model/element~Element Element}
* or {@link module:engine/model/documentfragment~DocumentFragment DocumentFragment}.
*/
class NodeList {
/**
* Creates an empty node list.
*
* @protected
* @param {Iterable.<module:engine/model/node~Node>} nodes Nodes contained in this node list.
*/
constructor( nodes ) {
/**
* Nodes contained in this node list.
*
* @private
* @member {Array.<module:engine/model/node~Node>}
*/
this._nodes = [];
if ( nodes ) {
this._insertNodes( 0, nodes );
}
}
/**
* Iterable interface.
*
* Iterates over all nodes contained inside this node list.
*
* @returns {Iterable.<module:engine/model/node~Node>}
*/
[ Symbol.iterator ]() {
return this._nodes[ Symbol.iterator ]();
}
/**
* Number of nodes contained inside this node list.
*
* @readonly
* @type {Number}
*/
get length() {
return this._nodes.length;
}
/**
* Sum of {@link module:engine/model/node~Node#offsetSize offset sizes} of all nodes contained inside this node list.
*
* @readonly
* @type {Number}
*/
get maxOffset() {
return this._nodes.reduce( ( sum, node ) => sum + node.offsetSize, 0 );
}
/**
* Gets the node at the given index. Returns `null` if incorrect index was passed.
*
* @param {Number} index Index of node.
* @returns {module:engine/model/node~Node|null} Node at given index.
*/
getNode( index ) {
return this._nodes[ index ] || null;
}
/**
* Returns an index of the given node. Returns `null` if given node is not inside this node list.
*
* @param {module:engine/model/node~Node} node Child node to look for.
* @returns {Number|null} Child node's index.
*/
getNodeIndex( node ) {
const index = this._nodes.indexOf( node );
return index == -1 ? null : index;
}
/**
* Returns the starting offset of given node. Starting offset is equal to the sum of
* {@link module:engine/model/node~Node#offsetSize offset sizes} of all nodes that are before this node in this node list.
*
* @param {module:engine/model/node~Node} node Node to look for.
* @returns {Number|null} Node's starting offset.
*/
getNodeStartOffset( node ) {
const index = this.getNodeIndex( node );
return index === null ? null : this._nodes.slice( 0, index ).reduce( ( sum, node ) => sum + node.offsetSize, 0 );
}
/**
* Converts index to offset in node list.
*
* Returns starting offset of a node that is at given index. Throws {@link module:utils/ckeditorerror~CKEditorError CKEditorError}
* `model-nodelist-index-out-of-bounds` if given index is less than `0` or more than {@link #length}.
*
* @param {Number} index Node's index.
* @returns {Number} Node's starting offset.
*/
indexToOffset( index ) {
if ( index == this._nodes.length ) {
return this.maxOffset;
}
const node = this._nodes[ index ];
if ( !node ) {
/**
* Given index cannot be found in the node list.
*
* @error model-nodelist-index-out-of-bounds
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_1__["default"]( 'model-nodelist-index-out-of-bounds', this );
}
return this.getNodeStartOffset( node );
}
/**
* Converts offset in node list to index.
*
* Returns index of a node that occupies given offset. Throws {@link module:utils/ckeditorerror~CKEditorError CKEditorError}
* `model-nodelist-offset-out-of-bounds` if given offset is less than `0` or more than {@link #maxOffset}.
*
* @param {Number} offset Offset to look for.
* @returns {Number} Index of a node that occupies given offset.
*/
offsetToIndex( offset ) {
let totalOffset = 0;
for ( const node of this._nodes ) {
if ( offset >= totalOffset && offset < totalOffset + node.offsetSize ) {
return this.getNodeIndex( node );
}
totalOffset += node.offsetSize;
}
if ( totalOffset != offset ) {
/**
* Given offset cannot be found in the node list.
*
* @error model-nodelist-offset-out-of-bounds
* @param {Number} offset
* @param {module:engine/model/nodelist~NodeList} nodeList Stringified node list.
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_1__["default"]( 'model-nodelist-offset-out-of-bounds',
this,
{
offset,
nodeList: this
}
);
}
return this.length;
}
/**
* Inserts given nodes at given index.
*
* @protected
* @param {Number} index Index at which nodes should be inserted.
* @param {Iterable.<module:engine/model/node~Node>} nodes Nodes to be inserted.
*/
_insertNodes( index, nodes ) {
// Validation.
for ( const node of nodes ) {
if ( !( node instanceof _node__WEBPACK_IMPORTED_MODULE_0__["default"] ) ) {
/**
* Trying to insert an object which is not a Node instance.
*
* @error model-nodelist-insertnodes-not-node
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_1__["default"]( 'model-nodelist-insertnodes-not-node', this );
}
}
this._nodes.splice( index, 0, ...nodes );
}
/**
* Removes one or more nodes starting at the given index.
*
* @protected
* @param {Number} indexStart Index of the first node to remove.
* @param {Number} [howMany=1] Number of nodes to remove.
* @returns {Array.<module:engine/model/node~Node>} Array containing removed nodes.
*/
_removeNodes( indexStart, howMany = 1 ) {
return this._nodes.splice( indexStart, howMany );
}
/**
* Converts `NodeList` instance to an array containing nodes that were inserted in the node list. Nodes
* are also converted to their plain object representation.
*
* @returns {Array.<module:engine/model/node~Node>} `NodeList` instance converted to `Array`.
*/
toJSON() {
return this._nodes.map( node => node.toJSON() );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/attributeoperation.js":
/*!*******************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/attributeoperation.js ***!
\*******************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ AttributeOperation)
/* harmony export */ });
/* harmony import */ var _operation__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./operation */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/operation.js");
/* harmony import */ var _range__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../range */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/range.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./utils */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/utils.js");
/* harmony import */ var lodash_es__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! lodash-es */ "./node_modules/lodash-es/isEqual.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/model/operation/attributeoperation
*/
/**
* Operation to change nodes' attribute.
*
* Using this class you can add, remove or change value of the attribute.
*
* @extends module:engine/model/operation/operation~Operation
*/
class AttributeOperation extends _operation__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* Creates an operation that changes, removes or adds attributes.
*
* If only `newValue` is set, attribute will be added on a node. Note that all nodes in operation's range must not
* have an attribute with the same key as the added attribute.
*
* If only `oldValue` is set, then attribute with given key will be removed. Note that all nodes in operation's range
* must have an attribute with that key added.
*
* If both `newValue` and `oldValue` are set, then the operation will change the attribute value. Note that all nodes in
* operation's ranges must already have an attribute with given key and `oldValue` as value
*
* @param {module:engine/model/range~Range} range Range on which the operation should be applied. Must be a flat range.
* @param {String} key Key of an attribute to change or remove.
* @param {*} oldValue Old value of the attribute with given key or `null`, if attribute was not set before.
* @param {*} newValue New value of the attribute with given key or `null`, if operation should remove attribute.
* @param {Number|null} baseVersion Document {@link module:engine/model/document~Document#version} on which operation
* can be applied or `null` if the operation operates on detached (non-document) tree.
*/
constructor( range, key, oldValue, newValue, baseVersion ) {
super( baseVersion );
/**
* Range on which operation should be applied.
*
* @readonly
* @member {module:engine/model/range~Range}
*/
this.range = range.clone();
/**
* Key of an attribute to change or remove.
*
* @readonly
* @member {String}
*/
this.key = key;
/**
* Old value of the attribute with given key or `null`, if attribute was not set before.
*
* @readonly
* @member {*}
*/
this.oldValue = oldValue === undefined ? null : oldValue;
/**
* New value of the attribute with given key or `null`, if operation should remove attribute.
*
* @readonly
* @member {*}
*/
this.newValue = newValue === undefined ? null : newValue;
}
/**
* @inheritDoc
*/
get type() {
if ( this.oldValue === null ) {
return 'addAttribute';
} else if ( this.newValue === null ) {
return 'removeAttribute';
} else {
return 'changeAttribute';
}
}
/**
* Creates and returns an operation that has the same parameters as this operation.
*
* @returns {module:engine/model/operation/attributeoperation~AttributeOperation} Clone of this operation.
*/
clone() {
return new AttributeOperation( this.range, this.key, this.oldValue, this.newValue, this.baseVersion );
}
/**
* See {@link module:engine/model/operation/operation~Operation#getReversed `Operation#getReversed()`}.
*
* @returns {module:engine/model/operation/attributeoperation~AttributeOperation}
*/
getReversed() {
return new AttributeOperation( this.range, this.key, this.newValue, this.oldValue, this.baseVersion + 1 );
}
/**
* @inheritDoc
*/
toJSON() {
const json = super.toJSON();
json.range = this.range.toJSON();
return json;
}
/**
* @inheritDoc
*/
_validate() {
if ( !this.range.isFlat ) {
/**
* The range to change is not flat.
*
* @error attribute-operation-range-not-flat
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_2__["default"]( 'attribute-operation-range-not-flat', this );
}
for ( const item of this.range.getItems( { shallow: true } ) ) {
if ( this.oldValue !== null && !(0,lodash_es__WEBPACK_IMPORTED_MODULE_4__["default"])( item.getAttribute( this.key ), this.oldValue ) ) {
/**
* Changed node has different attribute value than operation's old attribute value.
*
* @error attribute-operation-wrong-old-value
* @param {module:engine/model/item~Item} item
* @param {String} key
* @param {*} value
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_2__["default"](
'attribute-operation-wrong-old-value',
this,
{ item, key: this.key, value: this.oldValue }
);
}
if ( this.oldValue === null && this.newValue !== null && item.hasAttribute( this.key ) ) {
/**
* The attribute with given key already exists for the given node.
*
* @error attribute-operation-attribute-exists
* @param {module:engine/model/node~Node} node
* @param {String} key
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_2__["default"](
'attribute-operation-attribute-exists',
this,
{ node: item, key: this.key }
);
}
}
}
/**
* @inheritDoc
*/
_execute() {
// If value to set is same as old value, don't do anything.
if ( !(0,lodash_es__WEBPACK_IMPORTED_MODULE_4__["default"])( this.oldValue, this.newValue ) ) {
// Execution.
(0,_utils__WEBPACK_IMPORTED_MODULE_3__._setAttribute)( this.range, this.key, this.newValue );
}
}
/**
* @inheritDoc
*/
static get className() {
return 'AttributeOperation';
}
/**
* Creates `AttributeOperation` object from deserilized object, i.e. from parsed JSON string.
*
* @param {Object} json Deserialized JSON object.
* @param {module:engine/model/document~Document} document Document on which this operation will be applied.
* @returns {module:engine/model/operation/attributeoperation~AttributeOperation}
*/
static fromJSON( json, document ) {
return new AttributeOperation( _range__WEBPACK_IMPORTED_MODULE_1__["default"].fromJSON( json.range, document ), json.key, json.oldValue, json.newValue, json.baseVersion );
}
// @if CK_DEBUG_ENGINE // toString() {
// @if CK_DEBUG_ENGINE // return `AttributeOperation( ${ this.baseVersion } ): ` +
// @if CK_DEBUG_ENGINE // `"${ this.key }": ${ JSON.stringify( this.oldValue ) }` +
// @if CK_DEBUG_ENGINE // ` -> ${ JSON.stringify( this.newValue ) }, ${ this.range }`;
// @if CK_DEBUG_ENGINE // }
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/detachoperation.js":
/*!****************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/detachoperation.js ***!
\****************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ DetachOperation)
/* harmony export */ });
/* harmony import */ var _operation__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./operation */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/operation.js");
/* harmony import */ var _range__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../range */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/range.js");
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./utils */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/utils.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/model/operation/detachoperation
*/
// @if CK_DEBUG_ENGINE // const ModelRange = require( '../range' ).default;
/**
* Operation to permanently remove node from detached root.
* Note this operation is only a local operation and won't be send to the other clients.
*
* @extends module:engine/model/operation/operation~Operation
*/
class DetachOperation extends _operation__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* Creates an insert operation.
*
* @param {module:engine/model/position~Position} sourcePosition
* Position before the first {@link module:engine/model/item~Item model item} to move.
* @param {Number} howMany Offset size of moved range. Moved range will start from `sourcePosition` and end at
* `sourcePosition` with offset shifted by `howMany`.
*/
constructor( sourcePosition, howMany ) {
super( null );
/**
* Position before the first {@link module:engine/model/item~Item model item} to detach.
*
* @member {module:engine/model/position~Position} #sourcePosition
*/
this.sourcePosition = sourcePosition.clone();
/**
* Offset size of moved range.
*
* @member {Number} #howMany
*/
this.howMany = howMany;
}
/**
* @inheritDoc
*/
get type() {
return 'detach';
}
/**
* @inheritDoc
*/
toJSON() {
const json = super.toJSON();
json.sourcePosition = this.sourcePosition.toJSON();
return json;
}
/**
* @inheritDoc
*/
_validate() {
if ( this.sourcePosition.root.document ) {
/**
* Cannot detach document node.
*
* @error detach-operation-on-document-node
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_3__["default"]( 'detach-operation-on-document-node', this );
}
}
/**
* @inheritDoc
*/
_execute() {
(0,_utils__WEBPACK_IMPORTED_MODULE_2__._remove)( _range__WEBPACK_IMPORTED_MODULE_1__["default"]._createFromPositionAndShift( this.sourcePosition, this.howMany ) );
}
/**
* @inheritDoc
*/
static get className() {
return 'DetachOperation';
}
// @if CK_DEBUG_ENGINE // toString() {
// @if CK_DEBUG_ENGINE // const range = ModelRange._createFromPositionAndShift( this.sourcePosition, this.howMany );
// @if CK_DEBUG_ENGINE // const nodes = Array.from( range.getItems() );
// @if CK_DEBUG_ENGINE // const nodeString = nodes.length > 1 ? `[ ${ nodes.length } ]` : nodes[ 0 ];
// @if CK_DEBUG_ENGINE // return `DetachOperation( ${ this.baseVersion } ): ${ nodeString } -> ${ range }`;
// @if CK_DEBUG_ENGINE // }
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/insertoperation.js":
/*!****************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/insertoperation.js ***!
\****************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ InsertOperation)
/* harmony export */ });
/* harmony import */ var _operation__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./operation */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/operation.js");
/* harmony import */ var _position__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../position */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/position.js");
/* harmony import */ var _nodelist__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../nodelist */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/nodelist.js");
/* harmony import */ var _moveoperation__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./moveoperation */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/moveoperation.js");
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./utils */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/utils.js");
/* harmony import */ var _text__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../text */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/text.js");
/* harmony import */ var _element__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ../element */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/element.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/model/operation/insertoperation
*/
/**
* Operation to insert one or more nodes at given position in the model.
*
* @extends module:engine/model/operation/operation~Operation
*/
class InsertOperation extends _operation__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* Creates an insert operation.
*
* @param {module:engine/model/position~Position} position Position of insertion.
* @param {module:engine/model/node~NodeSet} nodes The list of nodes to be inserted.
* @param {Number|null} baseVersion Document {@link module:engine/model/document~Document#version} on which operation
* can be applied or `null` if the operation operates on detached (non-document) tree.
*/
constructor( position, nodes, baseVersion ) {
super( baseVersion );
/**
* Position of insertion.
*
* @readonly
* @member {module:engine/model/position~Position} module:engine/model/operation/insertoperation~InsertOperation#position
*/
this.position = position.clone();
this.position.stickiness = 'toNone';
/**
* List of nodes to insert.
*
* @readonly
* @member {module:engine/model/nodelist~NodeList} module:engine/model/operation/insertoperation~InsertOperation#nodeList
*/
this.nodes = new _nodelist__WEBPACK_IMPORTED_MODULE_2__["default"]( (0,_utils__WEBPACK_IMPORTED_MODULE_4__._normalizeNodes)( nodes ) );
/**
* Flag deciding how the operation should be transformed. If set to `true`, nodes might get additional attributes
* during operational transformation. This happens when the operation insertion position is inside of a range
* where attributes have changed.
*
* @member {Boolean} module:engine/model/operation/insertoperation~InsertOperation#shouldReceiveAttributes
*/
this.shouldReceiveAttributes = false;
}
/**
* @inheritDoc
*/
get type() {
return 'insert';
}
/**
* Total offset size of inserted nodes.
*
* @returns {Number}
*/
get howMany() {
return this.nodes.maxOffset;
}
/**
* Creates and returns an operation that has the same parameters as this operation.
*
* @returns {module:engine/model/operation/insertoperation~InsertOperation} Clone of this operation.
*/
clone() {
const nodes = new _nodelist__WEBPACK_IMPORTED_MODULE_2__["default"]( [ ...this.nodes ].map( node => node._clone( true ) ) );
const insert = new InsertOperation( this.position, nodes, this.baseVersion );
insert.shouldReceiveAttributes = this.shouldReceiveAttributes;
return insert;
}
/**
* See {@link module:engine/model/operation/operation~Operation#getReversed `Operation#getReversed()`}.
*
* @returns {module:engine/model/operation/moveoperation~MoveOperation}
*/
getReversed() {
const graveyard = this.position.root.document.graveyard;
const gyPosition = new _position__WEBPACK_IMPORTED_MODULE_1__["default"]( graveyard, [ 0 ] );
return new _moveoperation__WEBPACK_IMPORTED_MODULE_3__["default"]( this.position, this.nodes.maxOffset, gyPosition, this.baseVersion + 1 );
}
/**
* @inheritDoc
*/
_validate() {
const targetElement = this.position.parent;
if ( !targetElement || targetElement.maxOffset < this.position.offset ) {
/**
* Insertion position is invalid.
*
* @error insert-operation-position-invalid
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_7__["default"](
'insert-operation-position-invalid',
this
);
}
}
/**
* @inheritDoc
*/
_execute() {
// What happens here is that we want original nodes be passed to writer because we want original nodes
// to be inserted to the model. But in InsertOperation, we want to keep those nodes as they were added
// to the operation, not modified. For example, text nodes can get merged or cropped while Elements can
// get children. It is important that InsertOperation has the copy of original nodes in intact state.
const originalNodes = this.nodes;
this.nodes = new _nodelist__WEBPACK_IMPORTED_MODULE_2__["default"]( [ ...originalNodes ].map( node => node._clone( true ) ) );
(0,_utils__WEBPACK_IMPORTED_MODULE_4__._insert)( this.position, originalNodes );
}
/**
* @inheritDoc
*/
toJSON() {
const json = super.toJSON();
json.position = this.position.toJSON();
json.nodes = this.nodes.toJSON();
return json;
}
/**
* @inheritDoc
*/
static get className() {
return 'InsertOperation';
}
/**
* Creates `InsertOperation` object from deserilized object, i.e. from parsed JSON string.
*
* @param {Object} json Deserialized JSON object.
* @param {module:engine/model/document~Document} document Document on which this operation will be applied.
* @returns {module:engine/model/operation/insertoperation~InsertOperation}
*/
static fromJSON( json, document ) {
const children = [];
for ( const child of json.nodes ) {
if ( child.name ) {
// If child has name property, it is an Element.
children.push( _element__WEBPACK_IMPORTED_MODULE_6__["default"].fromJSON( child ) );
} else {
// Otherwise, it is a Text node.
children.push( _text__WEBPACK_IMPORTED_MODULE_5__["default"].fromJSON( child ) );
}
}
const insert = new InsertOperation( _position__WEBPACK_IMPORTED_MODULE_1__["default"].fromJSON( json.position, document ), children, json.baseVersion );
insert.shouldReceiveAttributes = json.shouldReceiveAttributes;
return insert;
}
// @if CK_DEBUG_ENGINE // toString() {
// @if CK_DEBUG_ENGINE // const nodeString = this.nodes.length > 1 ? `[ ${ this.nodes.length } ]` : this.nodes.getNode( 0 );
// @if CK_DEBUG_ENGINE // return `InsertOperation( ${ this.baseVersion } ): ${ nodeString } -> ${ this.position }`;
// @if CK_DEBUG_ENGINE // }
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/markeroperation.js":
/*!****************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/markeroperation.js ***!
\****************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ MarkerOperation)
/* harmony export */ });
/* harmony import */ var _operation__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./operation */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/operation.js");
/* harmony import */ var _range__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../range */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/range.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/model/operation/markeroperation
*/
/**
* @extends module:engine/model/operation/operation~Operation
*/
class MarkerOperation extends _operation__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* @param {String} name Marker name.
* @param {module:engine/model/range~Range} oldRange Marker range before the change.
* @param {module:engine/model/range~Range} newRange Marker range after the change.
* @param {module:engine/model/markercollection~MarkerCollection} markers Marker collection on which change should be executed.
* @param {Boolean} affectsData Specifies whether the marker operation affects the data produced by the data pipeline
* (is persisted in the editor's data).
* @param {Number|null} baseVersion Document {@link module:engine/model/document~Document#version} on which operation
* can be applied or `null` if the operation operates on detached (non-document) tree.
*/
constructor( name, oldRange, newRange, markers, affectsData, baseVersion ) {
super( baseVersion );
/**
* Marker name.
*
* @readonly
* @member {String}
*/
this.name = name;
/**
* Marker range before the change.
*
* @readonly
* @member {module:engine/model/range~Range}
*/
this.oldRange = oldRange ? oldRange.clone() : null;
/**
* Marker range after the change.
*
* @readonly
* @member {module:engine/model/range~Range}
*/
this.newRange = newRange ? newRange.clone() : null;
/**
* Specifies whether the marker operation affects the data produced by the data pipeline
* (is persisted in the editor's data).
*
* @readonly
* @member {Boolean}
*/
this.affectsData = affectsData;
/**
* Marker collection on which change should be executed.
*
* @private
* @member {module:engine/model/markercollection~MarkerCollection}
*/
this._markers = markers;
}
/**
* @inheritDoc
*/
get type() {
return 'marker';
}
/**
* Creates and returns an operation that has the same parameters as this operation.
*
* @returns {module:engine/model/operation/markeroperation~MarkerOperation} Clone of this operation.
*/
clone() {
return new MarkerOperation( this.name, this.oldRange, this.newRange, this._markers, this.affectsData, this.baseVersion );
}
/**
* See {@link module:engine/model/operation/operation~Operation#getReversed `Operation#getReversed()`}.
*
* @returns {module:engine/model/operation/markeroperation~MarkerOperation}
*/
getReversed() {
return new MarkerOperation( this.name, this.newRange, this.oldRange, this._markers, this.affectsData, this.baseVersion + 1 );
}
/**
* @inheritDoc
*/
_execute() {
const type = this.newRange ? '_set' : '_remove';
this._markers[ type ]( this.name, this.newRange, true, this.affectsData );
}
/**
* @inheritDoc
*/
toJSON() {
const json = super.toJSON();
if ( this.oldRange ) {
json.oldRange = this.oldRange.toJSON();
}
if ( this.newRange ) {
json.newRange = this.newRange.toJSON();
}
delete json._markers;
return json;
}
/**
* @inheritDoc
*/
static get className() {
return 'MarkerOperation';
}
/**
* Creates `MarkerOperation` object from deserialized object, i.e. from parsed JSON string.
*
* @param {Object} json Deserialized JSON object.
* @param {module:engine/model/document~Document} document Document on which this operation will be applied.
* @returns {module:engine/model/operation/markeroperation~MarkerOperation}
*/
static fromJSON( json, document ) {
return new MarkerOperation(
json.name,
json.oldRange ? _range__WEBPACK_IMPORTED_MODULE_1__["default"].fromJSON( json.oldRange, document ) : null,
json.newRange ? _range__WEBPACK_IMPORTED_MODULE_1__["default"].fromJSON( json.newRange, document ) : null,
document.model.markers,
json.affectsData,
json.baseVersion
);
}
// @if CK_DEBUG_ENGINE // toString() {
// @if CK_DEBUG_ENGINE // return `MarkerOperation( ${ this.baseVersion } ): ` +
// @if CK_DEBUG_ENGINE // `"${ this.name }": ${ this.oldRange } -> ${ this.newRange }`;
// @if CK_DEBUG_ENGINE // }
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/mergeoperation.js":
/*!***************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/mergeoperation.js ***!
\***************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ MergeOperation)
/* harmony export */ });
/* harmony import */ var _operation__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./operation */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/operation.js");
/* harmony import */ var _splitoperation__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./splitoperation */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/splitoperation.js");
/* harmony import */ var _position__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../position */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/position.js");
/* harmony import */ var _range__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../range */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/range.js");
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./utils */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/utils.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/model/operation/mergeoperation
*/
/**
* Operation to merge two {@link module:engine/model/element~Element elements}.
*
* The merged element is the parent of {@link ~MergeOperation#sourcePosition} and it is merged into the parent of
* {@link ~MergeOperation#targetPosition}. All nodes from the merged element are moved to {@link ~MergeOperation#targetPosition}.
*
* The merged element is moved to the graveyard at {@link ~MergeOperation#graveyardPosition}.
*
* @extends module:engine/model/operation/operation~Operation
*/
class MergeOperation extends _operation__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* Creates a merge operation.
*
* @param {module:engine/model/position~Position} sourcePosition Position inside the merged element. All nodes from that
* element after that position will be moved to {@link ~#targetPosition}.
* @param {Number} howMany Summary offset size of nodes which will be moved from the merged element to the new parent.
* @param {module:engine/model/position~Position} targetPosition Position which the nodes from the merged elements will be moved to.
* @param {module:engine/model/position~Position} graveyardPosition Position in graveyard to which the merged element will be moved.
* @param {Number|null} baseVersion Document {@link module:engine/model/document~Document#version} on which operation
* can be applied or `null` if the operation operates on detached (non-document) tree.
*/
constructor( sourcePosition, howMany, targetPosition, graveyardPosition, baseVersion ) {
super( baseVersion );
/**
* Position inside the merged element. All nodes from that element after that position will be moved to {@link ~#targetPosition}.
*
* @member {module:engine/model/position~Position} module:engine/model/operation/mergeoperation~MergeOperation#sourcePosition
*/
this.sourcePosition = sourcePosition.clone();
// This is, and should always remain, the first position in its parent.
this.sourcePosition.stickiness = 'toPrevious';
/**
* Summary offset size of nodes which will be moved from the merged element to the new parent.
*
* @member {Number} module:engine/model/operation/mergeoperation~MergeOperation#howMany
*/
this.howMany = howMany;
/**
* Position which the nodes from the merged elements will be moved to.
*
* @member {module:engine/model/position~Position} module:engine/model/operation/mergeoperation~MergeOperation#targetPosition
*/
this.targetPosition = targetPosition.clone();
// Except of a rare scenario in `MergeOperation` x `MergeOperation` transformation,
// this is, and should always remain, the last position in its parent.
this.targetPosition.stickiness = 'toNext';
/**
* Position in graveyard to which the merged element will be moved.
*
* @member {module:engine/model/position~Position} module:engine/model/operation/mergeoperation~MergeOperation#graveyardPosition
*/
this.graveyardPosition = graveyardPosition.clone();
}
/**
* @inheritDoc
*/
get type() {
return 'merge';
}
/**
* Position before the merged element (which will be deleted).
*
* @readonly
* @type {module:engine/model/position~Position}
*/
get deletionPosition() {
return new _position__WEBPACK_IMPORTED_MODULE_2__["default"]( this.sourcePosition.root, this.sourcePosition.path.slice( 0, -1 ) );
}
/**
* Artificial range that contains all the nodes from the merged element that will be moved to {@link ~MergeOperation#sourcePosition}.
* The range starts at {@link ~MergeOperation#sourcePosition} and ends in the same parent, at `POSITIVE_INFINITY` offset.
*
* @readonly
* @type {module:engine/model/range~Range}
*/
get movedRange() {
const end = this.sourcePosition.getShiftedBy( Number.POSITIVE_INFINITY );
return new _range__WEBPACK_IMPORTED_MODULE_3__["default"]( this.sourcePosition, end );
}
/**
* Creates and returns an operation that has the same parameters as this operation.
*
* @returns {module:engine/model/operation/mergeoperation~MergeOperation} Clone of this operation.
*/
clone() {
return new this.constructor( this.sourcePosition, this.howMany, this.targetPosition, this.graveyardPosition, this.baseVersion );
}
/**
* See {@link module:engine/model/operation/operation~Operation#getReversed `Operation#getReversed()`}.
*
* @returns {module:engine/model/operation/splitoperation~SplitOperation}
*/
getReversed() {
// Positions in this method are transformed by this merge operation because the split operation bases on
// the context after this merge operation happened (because split operation reverses it).
// So we need to acknowledge that the merge operation happened and those positions changed a little.
const targetPosition = this.targetPosition._getTransformedByMergeOperation( this );
const path = this.sourcePosition.path.slice( 0, -1 );
const insertionPosition = new _position__WEBPACK_IMPORTED_MODULE_2__["default"]( this.sourcePosition.root, path )._getTransformedByMergeOperation( this );
return new _splitoperation__WEBPACK_IMPORTED_MODULE_1__["default"]( targetPosition, this.howMany, insertionPosition, this.graveyardPosition, this.baseVersion + 1 );
}
/**
* @inheritDoc
*/
_validate() {
const sourceElement = this.sourcePosition.parent;
const targetElement = this.targetPosition.parent;
// Validate whether merge operation has correct parameters.
if ( !sourceElement.parent ) {
/**
* Merge source position is invalid. The element to be merged must have a parent node.
*
* @error merge-operation-source-position-invalid
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_5__["default"]( 'merge-operation-source-position-invalid', this );
} else if ( !targetElement.parent ) {
/**
* Merge target position is invalid. The element to be merged must have a parent node.
*
* @error merge-operation-target-position-invalid
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_5__["default"]( 'merge-operation-target-position-invalid', this );
} else if ( this.howMany != sourceElement.maxOffset ) {
/**
* Merge operation specifies wrong number of nodes to move.
*
* @error merge-operation-how-many-invalid
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_5__["default"]( 'merge-operation-how-many-invalid', this );
}
}
/**
* @inheritDoc
*/
_execute() {
const mergedElement = this.sourcePosition.parent;
const sourceRange = _range__WEBPACK_IMPORTED_MODULE_3__["default"]._createIn( mergedElement );
(0,_utils__WEBPACK_IMPORTED_MODULE_4__._move)( sourceRange, this.targetPosition );
(0,_utils__WEBPACK_IMPORTED_MODULE_4__._move)( _range__WEBPACK_IMPORTED_MODULE_3__["default"]._createOn( mergedElement ), this.graveyardPosition );
}
/**
* @inheritDoc
*/
toJSON() {
const json = super.toJSON();
json.sourcePosition = json.sourcePosition.toJSON();
json.targetPosition = json.targetPosition.toJSON();
json.graveyardPosition = json.graveyardPosition.toJSON();
return json;
}
/**
* @inheritDoc
*/
static get className() {
return 'MergeOperation';
}
/**
* Creates `MergeOperation` object from deserilized object, i.e. from parsed JSON string.
*
* @param {Object} json Deserialized JSON object.
* @param {module:engine/model/document~Document} document Document on which this operation will be applied.
* @returns {module:engine/model/operation/mergeoperation~MergeOperation}
*/
static fromJSON( json, document ) {
const sourcePosition = _position__WEBPACK_IMPORTED_MODULE_2__["default"].fromJSON( json.sourcePosition, document );
const targetPosition = _position__WEBPACK_IMPORTED_MODULE_2__["default"].fromJSON( json.targetPosition, document );
const graveyardPosition = _position__WEBPACK_IMPORTED_MODULE_2__["default"].fromJSON( json.graveyardPosition, document );
return new this( sourcePosition, json.howMany, targetPosition, graveyardPosition, json.baseVersion );
}
// @if CK_DEBUG_ENGINE // toString() {
// @if CK_DEBUG_ENGINE // return `MergeOperation( ${ this.baseVersion } ): ` +
// @if CK_DEBUG_ENGINE // `${ this.sourcePosition } -> ${ this.targetPosition }` +
// @if CK_DEBUG_ENGINE // ` ( ${ this.howMany } ), ${ this.graveyardPosition }`;
// @if CK_DEBUG_ENGINE // }
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/moveoperation.js":
/*!**************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/moveoperation.js ***!
\**************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ MoveOperation)
/* harmony export */ });
/* harmony import */ var _operation__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./operation */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/operation.js");
/* harmony import */ var _position__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../position */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/position.js");
/* harmony import */ var _range__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../range */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/range.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_comparearrays__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/comparearrays */ "./node_modules/@ckeditor/ckeditor5-utils/src/comparearrays.js");
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./utils */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/model/operation/moveoperation
*/
// @if CK_DEBUG_ENGINE // const ModelRange = require( '../range' ).default;
/**
* Operation to move a range of {@link module:engine/model/item~Item model items}
* to given {@link module:engine/model/position~Position target position}.
*
* @extends module:engine/model/operation/operation~Operation
*/
class MoveOperation extends _operation__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* Creates a move operation.
*
* @param {module:engine/model/position~Position} sourcePosition
* Position before the first {@link module:engine/model/item~Item model item} to move.
* @param {Number} howMany Offset size of moved range. Moved range will start from `sourcePosition` and end at
* `sourcePosition` with offset shifted by `howMany`.
* @param {module:engine/model/position~Position} targetPosition Position at which moved nodes will be inserted.
* @param {Number|null} baseVersion Document {@link module:engine/model/document~Document#version} on which operation
* can be applied or `null` if the operation operates on detached (non-document) tree.
*/
constructor( sourcePosition, howMany, targetPosition, baseVersion ) {
super( baseVersion );
/**
* Position before the first {@link module:engine/model/item~Item model item} to move.
*
* @member {module:engine/model/position~Position} module:engine/model/operation/moveoperation~MoveOperation#sourcePosition
*/
this.sourcePosition = sourcePosition.clone();
// `'toNext'` because `sourcePosition` is a bit like a start of the moved range.
this.sourcePosition.stickiness = 'toNext';
/**
* Offset size of moved range.
*
* @member {Number} module:engine/model/operation/moveoperation~MoveOperation#howMany
*/
this.howMany = howMany;
/**
* Position at which moved nodes will be inserted.
*
* @member {module:engine/model/position~Position} module:engine/model/operation/moveoperation~MoveOperation#targetPosition
*/
this.targetPosition = targetPosition.clone();
this.targetPosition.stickiness = 'toNone';
}
/**
* @inheritDoc
*/
get type() {
if ( this.targetPosition.root.rootName == '$graveyard' ) {
return 'remove';
} else if ( this.sourcePosition.root.rootName == '$graveyard' ) {
return 'reinsert';
}
return 'move';
}
/**
* Creates and returns an operation that has the same parameters as this operation.
*
* @returns {module:engine/model/operation/moveoperation~MoveOperation} Clone of this operation.
*/
clone() {
return new this.constructor( this.sourcePosition, this.howMany, this.targetPosition, this.baseVersion );
}
/**
* Returns the start position of the moved range after it got moved. This may be different than
* {@link module:engine/model/operation/moveoperation~MoveOperation#targetPosition} in some cases, i.e. when a range is moved
* inside the same parent but {@link module:engine/model/operation/moveoperation~MoveOperation#targetPosition targetPosition}
* is after {@link module:engine/model/operation/moveoperation~MoveOperation#sourcePosition sourcePosition}.
*
* vv vv
* abcdefg ===> adefbcg
* ^ ^
* targetPos movedRangeStart
* offset 6 offset 4
*
* @returns {module:engine/model/position~Position}
*/
getMovedRangeStart() {
return this.targetPosition._getTransformedByDeletion( this.sourcePosition, this.howMany );
}
/**
* See {@link module:engine/model/operation/operation~Operation#getReversed `Operation#getReversed()`}.
*
* @returns {module:engine/model/operation/moveoperation~MoveOperation}
*/
getReversed() {
const newTargetPosition = this.sourcePosition._getTransformedByInsertion( this.targetPosition, this.howMany );
return new this.constructor( this.getMovedRangeStart(), this.howMany, newTargetPosition, this.baseVersion + 1 );
}
/**
* @inheritDoc
*/
_validate() {
const sourceElement = this.sourcePosition.parent;
const targetElement = this.targetPosition.parent;
const sourceOffset = this.sourcePosition.offset;
const targetOffset = this.targetPosition.offset;
// Validate whether move operation has correct parameters.
// Validation is pretty complex but move operation is one of the core ways to manipulate the document state.
// We expect that many errors might be connected with one of scenarios described below.
if ( sourceOffset + this.howMany > sourceElement.maxOffset ) {
/**
* The nodes which should be moved do not exist.
*
* @error move-operation-nodes-do-not-exist
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_3__["default"](
'move-operation-nodes-do-not-exist', this
);
} else if ( sourceElement === targetElement && sourceOffset < targetOffset && targetOffset < sourceOffset + this.howMany ) {
/**
* Trying to move a range of nodes into the middle of that range.
*
* @error move-operation-range-into-itself
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_3__["default"](
'move-operation-range-into-itself', this
);
} else if ( this.sourcePosition.root == this.targetPosition.root ) {
if ( (0,_ckeditor_ckeditor5_utils_src_comparearrays__WEBPACK_IMPORTED_MODULE_4__["default"])( this.sourcePosition.getParentPath(), this.targetPosition.getParentPath() ) == 'prefix' ) {
const i = this.sourcePosition.path.length - 1;
if ( this.targetPosition.path[ i ] >= sourceOffset && this.targetPosition.path[ i ] < sourceOffset + this.howMany ) {
/**
* Trying to move a range of nodes into one of nodes from that range.
*
* @error move-operation-node-into-itself
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_3__["default"](
'move-operation-node-into-itself', this
);
}
}
}
}
/**
* @inheritDoc
*/
_execute() {
(0,_utils__WEBPACK_IMPORTED_MODULE_5__._move)( _range__WEBPACK_IMPORTED_MODULE_2__["default"]._createFromPositionAndShift( this.sourcePosition, this.howMany ), this.targetPosition );
}
/**
* @inheritDoc
*/
toJSON() {
const json = super.toJSON();
json.sourcePosition = this.sourcePosition.toJSON();
json.targetPosition = this.targetPosition.toJSON();
return json;
}
/**
* @inheritDoc
*/
static get className() {
return 'MoveOperation';
}
/**
* Creates `MoveOperation` object from deserilized object, i.e. from parsed JSON string.
*
* @param {Object} json Deserialized JSON object.
* @param {module:engine/model/document~Document} document Document on which this operation will be applied.
* @returns {module:engine/model/operation/moveoperation~MoveOperation}
*/
static fromJSON( json, document ) {
const sourcePosition = _position__WEBPACK_IMPORTED_MODULE_1__["default"].fromJSON( json.sourcePosition, document );
const targetPosition = _position__WEBPACK_IMPORTED_MODULE_1__["default"].fromJSON( json.targetPosition, document );
return new this( sourcePosition, json.howMany, targetPosition, json.baseVersion );
}
// @if CK_DEBUG_ENGINE // toString() {
// @if CK_DEBUG_ENGINE // const range = ModelRange._createFromPositionAndShift( this.sourcePosition, this.howMany );
// @if CK_DEBUG_ENGINE // return `MoveOperation( ${ this.baseVersion } ): ${ range } -> ${ this.targetPosition }`;
// @if CK_DEBUG_ENGINE // }
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/nooperation.js":
/*!************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/nooperation.js ***!
\************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ NoOperation)
/* harmony export */ });
/* harmony import */ var _operation__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./operation */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/operation.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/model/operation/nooperation
*/
/**
* Operation which is doing nothing ("empty operation", "do-nothing operation", "noop"). This is an operation,
* which when executed does not change the tree model. It still has some parameters defined for transformation purposes.
*
* In most cases this operation is a result of transforming operations. When transformation returns
* {@link module:engine/model/operation/nooperation~NoOperation} it means that changes done by the transformed operation
* have already been applied.
*
* @extends module:engine/model/operation/operation~Operation
*/
class NoOperation extends _operation__WEBPACK_IMPORTED_MODULE_0__["default"] {
get type() {
return 'noop';
}
/**
* Creates and returns an operation that has the same parameters as this operation.
*
* @returns {module:engine/model/operation/nooperation~NoOperation} Clone of this operation.
*/
clone() {
return new NoOperation( this.baseVersion );
}
/**
* See {@link module:engine/model/operation/operation~Operation#getReversed `Operation#getReversed()`}.
*
* @returns {module:engine/model/operation/nooperation~NoOperation}
*/
getReversed() {
return new NoOperation( this.baseVersion + 1 );
}
_execute() {
}
/**
* @inheritDoc
*/
static get className() {
return 'NoOperation';
}
// @if CK_DEBUG_ENGINE // toString() {
// @if CK_DEBUG_ENGINE // return `NoOperation( ${ this.baseVersion } )`;
// @if CK_DEBUG_ENGINE // }
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/operation.js":
/*!**********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/operation.js ***!
\**********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ Operation)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/model/operation/operation
*/
/**
* Abstract base operation class.
*
* @abstract
*/
class Operation {
/**
* Base operation constructor.
*
* @param {Number|null} baseVersion Document {@link module:engine/model/document~Document#version} on which operation
* can be applied or `null` if the operation operates on detached (non-document) tree.
*/
constructor( baseVersion ) {
/**
* {@link module:engine/model/document~Document#version} on which operation can be applied. If you try to
* {@link module:engine/model/model~Model#applyOperation apply} operation with different base version than the
* {@link module:engine/model/document~Document#version document version} the
* {@link module:utils/ckeditorerror~CKEditorError model-document-applyOperation-wrong-version} error is thrown.
*
* @member {Number}
*/
this.baseVersion = baseVersion;
/**
* Defines whether operation is executed on attached or detached {@link module:engine/model/item~Item items}.
*
* @readonly
* @member {Boolean} #isDocumentOperation
*/
this.isDocumentOperation = this.baseVersion !== null;
/**
* {@link module:engine/model/batch~Batch Batch} to which the operation is added or `null` if the operation is not
* added to any batch yet.
*
* @member {module:engine/model/batch~Batch|null} #batch
*/
this.batch = null;
/**
* Operation type.
*
* @readonly
* @member {String} #type
*/
/**
* Creates and returns an operation that has the same parameters as this operation.
*
* @method #clone
* @returns {module:engine/model/operation/operation~Operation} Clone of this operation.
*/
/**
* Creates and returns a reverse operation. Reverse operation when executed right after
* the original operation will bring back tree model state to the point before the original
* operation execution. In other words, it reverses changes done by the original operation.
*
* Keep in mind that tree model state may change since executing the original operation,
* so reverse operation will be "outdated". In that case you will need to transform it by
* all operations that were executed after the original operation.
*
* @method #getReversed
* @returns {module:engine/model/operation/operation~Operation} Reversed operation.
*/
/**
* Executes the operation - modifications described by the operation properties will be applied to the model tree.
*
* @protected
* @method #_execute
*/
}
/**
* Checks whether the operation's parameters are correct and the operation can be correctly executed. Throws
* an error if operation is not valid.
*
* @protected
* @method #_validate
*/
_validate() {
}
/**
* Custom toJSON method to solve child-parent circular dependencies.
*
* @method #toJSON
* @returns {Object} Clone of this object with the operation property replaced with string.
*/
toJSON() {
// This method creates only a shallow copy, all nested objects should be defined separately.
// See https://github.com/ckeditor/ckeditor5-engine/issues/1477.
const json = Object.assign( {}, this );
json.__className = this.constructor.className;
// Remove reference to the parent `Batch` to avoid circular dependencies.
delete json.batch;
// Only document operations are shared with other clients so it is not necessary to keep this information.
delete json.isDocumentOperation;
return json;
}
/**
* Name of the operation class used for serialization.
*
* @type {String}
*/
static get className() {
return 'Operation';
}
/**
* Creates Operation object from deserilized object, i.e. from parsed JSON string.
*
* @param {Object} json Deserialized JSON object.
* @param {module:engine/model/document~Document} doc Document on which this operation will be applied.
* @returns {module:engine/model/operation/operation~Operation}
*/
static fromJSON( json ) {
return new this( json.baseVersion );
}
// @if CK_DEBUG_ENGINE // log() {
// @if CK_DEBUG_ENGINE // console.log( this.toString() );
// @if CK_DEBUG_ENGINE // }
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/operationfactory.js":
/*!*****************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/operationfactory.js ***!
\*****************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ OperationFactory)
/* harmony export */ });
/* harmony import */ var _operation_attributeoperation__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../operation/attributeoperation */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/attributeoperation.js");
/* harmony import */ var _operation_insertoperation__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../operation/insertoperation */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/insertoperation.js");
/* harmony import */ var _operation_markeroperation__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../operation/markeroperation */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/markeroperation.js");
/* harmony import */ var _operation_moveoperation__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../operation/moveoperation */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/moveoperation.js");
/* harmony import */ var _operation_nooperation__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../operation/nooperation */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/nooperation.js");
/* harmony import */ var _operation_operation__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../operation/operation */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/operation.js");
/* harmony import */ var _operation_renameoperation__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ../operation/renameoperation */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/renameoperation.js");
/* harmony import */ var _operation_rootattributeoperation__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ../operation/rootattributeoperation */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/rootattributeoperation.js");
/* harmony import */ var _operation_splitoperation__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ../operation/splitoperation */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/splitoperation.js");
/* harmony import */ var _operation_mergeoperation__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ../operation/mergeoperation */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/mergeoperation.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/model/operation/operationfactory
*/
const operations = {};
operations[ _operation_attributeoperation__WEBPACK_IMPORTED_MODULE_0__["default"].className ] = _operation_attributeoperation__WEBPACK_IMPORTED_MODULE_0__["default"];
operations[ _operation_insertoperation__WEBPACK_IMPORTED_MODULE_1__["default"].className ] = _operation_insertoperation__WEBPACK_IMPORTED_MODULE_1__["default"];
operations[ _operation_markeroperation__WEBPACK_IMPORTED_MODULE_2__["default"].className ] = _operation_markeroperation__WEBPACK_IMPORTED_MODULE_2__["default"];
operations[ _operation_moveoperation__WEBPACK_IMPORTED_MODULE_3__["default"].className ] = _operation_moveoperation__WEBPACK_IMPORTED_MODULE_3__["default"];
operations[ _operation_nooperation__WEBPACK_IMPORTED_MODULE_4__["default"].className ] = _operation_nooperation__WEBPACK_IMPORTED_MODULE_4__["default"];
operations[ _operation_operation__WEBPACK_IMPORTED_MODULE_5__["default"].className ] = _operation_operation__WEBPACK_IMPORTED_MODULE_5__["default"];
operations[ _operation_renameoperation__WEBPACK_IMPORTED_MODULE_6__["default"].className ] = _operation_renameoperation__WEBPACK_IMPORTED_MODULE_6__["default"];
operations[ _operation_rootattributeoperation__WEBPACK_IMPORTED_MODULE_7__["default"].className ] = _operation_rootattributeoperation__WEBPACK_IMPORTED_MODULE_7__["default"];
operations[ _operation_splitoperation__WEBPACK_IMPORTED_MODULE_8__["default"].className ] = _operation_splitoperation__WEBPACK_IMPORTED_MODULE_8__["default"];
operations[ _operation_mergeoperation__WEBPACK_IMPORTED_MODULE_9__["default"].className ] = _operation_mergeoperation__WEBPACK_IMPORTED_MODULE_9__["default"];
/**
* A factory class for creating operations.
*
* @abstract
*/
class OperationFactory {
/**
* Creates an operation instance from a JSON object (parsed JSON string).
*
* @param {Object} json Deserialized JSON object.
* @param {module:engine/model/document~Document} document Document on which this operation will be applied.
* @returns {module:engine/model/operation/operation~Operation}
*/
static fromJSON( json, document ) {
return operations[ json.__className ].fromJSON( json, document );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/renameoperation.js":
/*!****************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/renameoperation.js ***!
\****************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ RenameOperation)
/* harmony export */ });
/* harmony import */ var _operation__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./operation */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/operation.js");
/* harmony import */ var _element__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../element */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/element.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/* harmony import */ var _position__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../position */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/position.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/model/operation/renameoperation
*/
/**
* Operation to change element's name.
*
* Using this class you can change element's name.
*
* @extends module:engine/model/operation/operation~Operation
*/
class RenameOperation extends _operation__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* Creates an operation that changes element's name.
*
* @param {module:engine/model/position~Position} position Position before an element to change.
* @param {String} oldName Current name of the element.
* @param {String} newName New name for the element.
* @param {Number|null} baseVersion Document {@link module:engine/model/document~Document#version} on which operation
* can be applied or `null` if the operation operates on detached (non-document) tree.
*/
constructor( position, oldName, newName, baseVersion ) {
super( baseVersion );
/**
* Position before an element to change.
*
* @member {module:engine/model/position~Position} module:engine/model/operation/renameoperation~RenameOperation#position
*/
this.position = position;
// This position sticks to the next node because it is a position before the node that we want to change.
this.position.stickiness = 'toNext';
/**
* Current name of the element.
*
* @member {String} module:engine/model/operation/renameoperation~RenameOperation#oldName
*/
this.oldName = oldName;
/**
* New name for the element.
*
* @member {String} module:engine/model/operation/renameoperation~RenameOperation#newName
*/
this.newName = newName;
}
/**
* @inheritDoc
*/
get type() {
return 'rename';
}
/**
* Creates and returns an operation that has the same parameters as this operation.
*
* @returns {module:engine/model/operation/renameoperation~RenameOperation} Clone of this operation.
*/
clone() {
return new RenameOperation( this.position.clone(), this.oldName, this.newName, this.baseVersion );
}
/**
* See {@link module:engine/model/operation/operation~Operation#getReversed `Operation#getReversed()`}.
*
* @returns {module:engine/model/operation/renameoperation~RenameOperation}
*/
getReversed() {
return new RenameOperation( this.position.clone(), this.newName, this.oldName, this.baseVersion + 1 );
}
/**
* @inheritDoc
*/
_validate() {
const element = this.position.nodeAfter;
if ( !( element instanceof _element__WEBPACK_IMPORTED_MODULE_1__["default"] ) ) {
/**
* Given position is invalid or node after it is not instance of Element.
*
* @error rename-operation-wrong-position
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_2__["default"](
'rename-operation-wrong-position',
this
);
} else if ( element.name !== this.oldName ) {
/**
* Element to change has different name than operation's old name.
*
* @error rename-operation-wrong-name
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_2__["default"](
'rename-operation-wrong-name',
this
);
}
}
/**
* @inheritDoc
*/
_execute() {
const element = this.position.nodeAfter;
element.name = this.newName;
}
/**
* @inheritDoc
*/
toJSON() {
const json = super.toJSON();
json.position = this.position.toJSON();
return json;
}
/**
* @inheritDoc
*/
static get className() {
return 'RenameOperation';
}
/**
* Creates `RenameOperation` object from deserialized object, i.e. from parsed JSON string.
*
* @param {Object} json Deserialized JSON object.
* @param {module:engine/model/document~Document} document Document on which this operation will be applied.
* @returns {module:engine/model/operation/attributeoperation~AttributeOperation}
*/
static fromJSON( json, document ) {
return new RenameOperation( _position__WEBPACK_IMPORTED_MODULE_3__["default"].fromJSON( json.position, document ), json.oldName, json.newName, json.baseVersion );
}
// @if CK_DEBUG_ENGINE // toString() {
// @if CK_DEBUG_ENGINE // return `RenameOperation( ${ this.baseVersion } ): ` +
// @if CK_DEBUG_ENGINE // `${ this.position }: "${ this.oldName }" -> "${ this.newName }"`;
// @if CK_DEBUG_ENGINE // }
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/rootattributeoperation.js":
/*!***********************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/rootattributeoperation.js ***!
\***********************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ RootAttributeOperation)
/* harmony export */ });
/* harmony import */ var _operation__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./operation */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/operation.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/model/operation/rootattributeoperation
*/
/**
* Operation to change root element's attribute. Using this class you can add, remove or change value of the attribute.
*
* This operation is needed, because root elements can't be changed through
* @link module:engine/model/operation/attributeoperation~AttributeOperation}.
* It is because {@link module:engine/model/operation/attributeoperation~AttributeOperation}
* requires a range to change and root element can't
* be a part of range because every {@link module:engine/model/position~Position} has to be inside a root.
* {@link module:engine/model/position~Position} can't be created before a root element.
*
* @extends module:engine/model/operation/operation~Operation
*/
class RootAttributeOperation extends _operation__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* Creates an operation that changes, removes or adds attributes on root element.
*
* @see module:engine/model/operation/attributeoperation~AttributeOperation
* @param {module:engine/model/rootelement~RootElement} root Root element to change.
* @param {String} key Key of an attribute to change or remove.
* @param {*} oldValue Old value of the attribute with given key or `null` if adding a new attribute.
* @param {*} newValue New value to set for the attribute. If `null`, then the operation just removes the attribute.
* @param {Number|null} baseVersion Document {@link module:engine/model/document~Document#version} on which operation
* can be applied or `null` if the operation operates on detached (non-document) tree.
*/
constructor( root, key, oldValue, newValue, baseVersion ) {
super( baseVersion );
/**
* Root element to change.
*
* @readonly
* @member {module:engine/model/rootelement~RootElement}
*/
this.root = root;
/**
* Key of an attribute to change or remove.
*
* @readonly
* @member {String}
*/
this.key = key;
/**
* Old value of the attribute with given key or `null` if adding a new attribute.
*
* @readonly
* @member {*}
*/
this.oldValue = oldValue;
/**
* New value to set for the attribute. If `null`, then the operation just removes the attribute.
*
* @readonly
* @member {*}
*/
this.newValue = newValue;
}
/**
* @inheritDoc
*/
get type() {
if ( this.oldValue === null ) {
return 'addRootAttribute';
} else if ( this.newValue === null ) {
return 'removeRootAttribute';
} else {
return 'changeRootAttribute';
}
}
/**
* Creates and returns an operation that has the same parameters as this operation.
*
* @returns {module:engine/model/operation/rootattributeoperation~RootAttributeOperation} Clone of this operation.
*/
clone() {
return new RootAttributeOperation( this.root, this.key, this.oldValue, this.newValue, this.baseVersion );
}
/**
* See {@link module:engine/model/operation/operation~Operation#getReversed `Operation#getReversed()`}.
*
* @returns {module:engine/model/operation/rootattributeoperation~RootAttributeOperation}
*/
getReversed() {
return new RootAttributeOperation( this.root, this.key, this.newValue, this.oldValue, this.baseVersion + 1 );
}
/**
* @inheritDoc
*/
_validate() {
if ( this.root != this.root.root || this.root.is( 'documentFragment' ) ) {
/**
* The element to change is not a root element.
*
* @error rootattribute-operation-not-a-root
* @param {module:engine/model/rootelement~RootElement} root
* @param {String} key
* @param {*} value
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_1__["default"](
'rootattribute-operation-not-a-root',
this,
{ root: this.root, key: this.key }
);
}
if ( this.oldValue !== null && this.root.getAttribute( this.key ) !== this.oldValue ) {
/**
* The attribute which should be removed does not exists for the given node.
*
* @error rootattribute-operation-wrong-old-value
* @param {module:engine/model/rootelement~RootElement} root
* @param {String} key
* @param {*} value
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_1__["default"](
'rootattribute-operation-wrong-old-value',
this,
{ root: this.root, key: this.key }
);
}
if ( this.oldValue === null && this.newValue !== null && this.root.hasAttribute( this.key ) ) {
/**
* The attribute with given key already exists for the given node.
*
* @error rootattribute-operation-attribute-exists
* @param {module:engine/model/rootelement~RootElement} root
* @param {String} key
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_1__["default"](
'rootattribute-operation-attribute-exists',
this,
{ root: this.root, key: this.key }
);
}
}
/**
* @inheritDoc
*/
_execute() {
if ( this.newValue !== null ) {
this.root._setAttribute( this.key, this.newValue );
} else {
this.root._removeAttribute( this.key );
}
}
/**
* @inheritDoc
*/
toJSON() {
const json = super.toJSON();
json.root = this.root.toJSON();
return json;
}
/**
* @inheritDoc
*/
static get className() {
return 'RootAttributeOperation';
}
/**
* Creates RootAttributeOperation object from deserilized object, i.e. from parsed JSON string.
*
* @param {Object} json Deserialized JSON object.
* @param {module:engine/model/document~Document} document Document on which this operation will be applied.
* @returns {module:engine/model/operation/rootattributeoperation~RootAttributeOperation}
*/
static fromJSON( json, document ) {
if ( !document.getRoot( json.root ) ) {
/**
* Cannot create RootAttributeOperation for document. Root with specified name does not exist.
*
* @error rootattribute-operation-fromjson-no-root
* @param {String} rootName
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_1__["default"]( 'rootattribute-operation-fromjson-no-root', this, { rootName: json.root } );
}
return new RootAttributeOperation( document.getRoot( json.root ), json.key, json.oldValue, json.newValue, json.baseVersion );
}
// @if CK_DEBUG_ENGINE // toString() {
// @if CK_DEBUG_ENGINE // return `RootAttributeOperation( ${ this.baseVersion } ): ` +
// @if CK_DEBUG_ENGINE // `"${ this.key }": ${ JSON.stringify( this.oldValue ) }` +
// @if CK_DEBUG_ENGINE // ` -> ${ JSON.stringify( this.newValue ) }, ${ this.root.rootName }`;
// @if CK_DEBUG_ENGINE // }
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/splitoperation.js":
/*!***************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/splitoperation.js ***!
\***************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ SplitOperation)
/* harmony export */ });
/* harmony import */ var _operation__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./operation */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/operation.js");
/* harmony import */ var _mergeoperation__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./mergeoperation */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/mergeoperation.js");
/* harmony import */ var _position__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../position */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/position.js");
/* harmony import */ var _range__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../range */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/range.js");
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./utils */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/utils.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/model/operation/splitoperation
*/
/**
* Operation to split {@link module:engine/model/element~Element an element} at given
* {@link module:engine/model/operation/splitoperation~SplitOperation#splitPosition split position} into two elements,
* both containing a part of the element's original content.
*
* @extends module:engine/model/operation/operation~Operation
*/
class SplitOperation extends _operation__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* Creates a split operation.
*
* @param {module:engine/model/position~Position} splitPosition Position at which an element should be split.
* @param {Number} howMany Total offset size of elements that are in the split element after `position`.
* @param {module:engine/model/position~Position} insertionPosition Position at which the clone of split element
* (or element from graveyard) will be inserted.
* @param {module:engine/model/position~Position|null} graveyardPosition Position in the graveyard root before the element which
* should be used as a parent of the nodes after `position`. If it is not set, a copy of the the `position` parent will be used.
* @param {Number|null} baseVersion Document {@link module:engine/model/document~Document#version} on which operation
* can be applied or `null` if the operation operates on detached (non-document) tree.
*/
constructor( splitPosition, howMany, insertionPosition, graveyardPosition, baseVersion ) {
super( baseVersion );
/**
* Position at which an element should be split.
*
* @member {module:engine/model/position~Position} module:engine/model/operation/splitoperation~SplitOperation#splitPosition
*/
this.splitPosition = splitPosition.clone();
// Keep position sticking to the next node. This way any new content added at the place where the element is split
// will be left in the original element.
this.splitPosition.stickiness = 'toNext';
/**
* Total offset size of elements that are in the split element after `position`.
*
* @member {Number} module:engine/model/operation/splitoperation~SplitOperation#howMany
*/
this.howMany = howMany;
/**
* Position at which the clone of split element (or element from graveyard) will be inserted.
*
* @member {module:engine/model/position~Position} module:engine/model/operation/splitoperation~SplitOperation#insertionPosition
*/
this.insertionPosition = insertionPosition;
/**
* Position in the graveyard root before the element which should be used as a parent of the nodes after `position`.
* If it is not set, a copy of the the `position` parent will be used.
*
* The default behavior is to clone the split element. Element from graveyard is used during undo.
*
* @member {module:engine/model/position~Position|null} #graveyardPosition
*/
this.graveyardPosition = graveyardPosition ? graveyardPosition.clone() : null;
if ( this.graveyardPosition ) {
this.graveyardPosition.stickiness = 'toNext';
}
}
/**
* @inheritDoc
*/
get type() {
return 'split';
}
/**
* Position inside the new clone of a split element.
*
* This is a position where nodes that are after the split position will be moved to.
*
* @readonly
* @type {module:engine/model/position~Position}
*/
get moveTargetPosition() {
const path = this.insertionPosition.path.slice();
path.push( 0 );
return new _position__WEBPACK_IMPORTED_MODULE_2__["default"]( this.insertionPosition.root, path );
}
/**
* Artificial range that contains all the nodes from the split element that will be moved to the new element.
* The range starts at {@link ~#splitPosition} and ends in the same parent, at `POSITIVE_INFINITY` offset.
*
* @readonly
* @type {module:engine/model/range~Range}
*/
get movedRange() {
const end = this.splitPosition.getShiftedBy( Number.POSITIVE_INFINITY );
return new _range__WEBPACK_IMPORTED_MODULE_3__["default"]( this.splitPosition, end );
}
/**
* Creates and returns an operation that has the same parameters as this operation.
*
* @returns {module:engine/model/operation/splitoperation~SplitOperation} Clone of this operation.
*/
clone() {
return new this.constructor( this.splitPosition, this.howMany, this.insertionPosition, this.graveyardPosition, this.baseVersion );
}
/**
* See {@link module:engine/model/operation/operation~Operation#getReversed `Operation#getReversed()`}.
*
* @returns {module:engine/model/operation/mergeoperation~MergeOperation}
*/
getReversed() {
const graveyard = this.splitPosition.root.document.graveyard;
const graveyardPosition = new _position__WEBPACK_IMPORTED_MODULE_2__["default"]( graveyard, [ 0 ] );
return new _mergeoperation__WEBPACK_IMPORTED_MODULE_1__["default"]( this.moveTargetPosition, this.howMany, this.splitPosition, graveyardPosition, this.baseVersion + 1 );
}
/**
* @inheritDoc
*/
_validate() {
const element = this.splitPosition.parent;
const offset = this.splitPosition.offset;
// Validate whether split operation has correct parameters.
if ( !element || element.maxOffset < offset ) {
/**
* Split position is invalid.
*
* @error split-operation-position-invalid
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_5__["default"]( 'split-operation-position-invalid', this );
} else if ( !element.parent ) {
/**
* Cannot split root element.
*
* @error split-operation-split-in-root
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_5__["default"]( 'split-operation-split-in-root', this );
} else if ( this.howMany != element.maxOffset - this.splitPosition.offset ) {
/**
* Split operation specifies wrong number of nodes to move.
*
* @error split-operation-how-many-invalid
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_5__["default"]( 'split-operation-how-many-invalid', this );
} else if ( this.graveyardPosition && !this.graveyardPosition.nodeAfter ) {
/**
* Graveyard position invalid.
*
* @error split-operation-graveyard-position-invalid
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_5__["default"]( 'split-operation-graveyard-position-invalid', this );
}
}
/**
* @inheritDoc
*/
_execute() {
const splitElement = this.splitPosition.parent;
if ( this.graveyardPosition ) {
(0,_utils__WEBPACK_IMPORTED_MODULE_4__._move)( _range__WEBPACK_IMPORTED_MODULE_3__["default"]._createFromPositionAndShift( this.graveyardPosition, 1 ), this.insertionPosition );
} else {
const newElement = splitElement._clone();
(0,_utils__WEBPACK_IMPORTED_MODULE_4__._insert)( this.insertionPosition, newElement );
}
const sourceRange = new _range__WEBPACK_IMPORTED_MODULE_3__["default"](
_position__WEBPACK_IMPORTED_MODULE_2__["default"]._createAt( splitElement, this.splitPosition.offset ),
_position__WEBPACK_IMPORTED_MODULE_2__["default"]._createAt( splitElement, splitElement.maxOffset )
);
(0,_utils__WEBPACK_IMPORTED_MODULE_4__._move)( sourceRange, this.moveTargetPosition );
}
/**
* @inheritDoc
*/
toJSON() {
const json = super.toJSON();
json.splitPosition = this.splitPosition.toJSON();
json.insertionPosition = this.insertionPosition.toJSON();
if ( this.graveyardPosition ) {
json.graveyardPosition = this.graveyardPosition.toJSON();
}
return json;
}
/**
* @inheritDoc
*/
static get className() {
return 'SplitOperation';
}
/**
* Helper function that returns a default insertion position basing on given `splitPosition`. The default insertion
* position is after the split element.
*
* @param {module:engine/model/position~Position} splitPosition
* @returns {module:engine/model/position~Position}
*/
static getInsertionPosition( splitPosition ) {
const path = splitPosition.path.slice( 0, -1 );
path[ path.length - 1 ]++;
return new _position__WEBPACK_IMPORTED_MODULE_2__["default"]( splitPosition.root, path, 'toPrevious' );
}
/**
* Creates `SplitOperation` object from deserilized object, i.e. from parsed JSON string.
*
* @param {Object} json Deserialized JSON object.
* @param {module:engine/model/document~Document} document Document on which this operation will be applied.
* @returns {module:engine/model/operation/splitoperation~SplitOperation}
*/
static fromJSON( json, document ) {
const splitPosition = _position__WEBPACK_IMPORTED_MODULE_2__["default"].fromJSON( json.splitPosition, document );
const insertionPosition = _position__WEBPACK_IMPORTED_MODULE_2__["default"].fromJSON( json.insertionPosition, document );
const graveyardPosition = json.graveyardPosition ? _position__WEBPACK_IMPORTED_MODULE_2__["default"].fromJSON( json.graveyardPosition, document ) : null;
return new this( splitPosition, json.howMany, insertionPosition, graveyardPosition, json.baseVersion );
}
// @if CK_DEBUG_ENGINE // toString() {
// @if CK_DEBUG_ENGINE // return `SplitOperation( ${ this.baseVersion } ): ${ this.splitPosition } ` +
// @if CK_DEBUG_ENGINE // `( ${ this.howMany } ) -> ${ this.insertionPosition }` +
// @if CK_DEBUG_ENGINE // `${ this.graveyardPosition ? ' with ' + this.graveyardPosition : '' }`;
// @if CK_DEBUG_ENGINE // }
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/transform.js":
/*!**********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/transform.js ***!
\**********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "transform": () => (/* binding */ transform),
/* harmony export */ "transformSets": () => (/* binding */ transformSets)
/* harmony export */ });
/* harmony import */ var _insertoperation__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./insertoperation */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/insertoperation.js");
/* harmony import */ var _attributeoperation__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./attributeoperation */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/attributeoperation.js");
/* harmony import */ var _renameoperation__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./renameoperation */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/renameoperation.js");
/* harmony import */ var _markeroperation__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./markeroperation */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/markeroperation.js");
/* harmony import */ var _moveoperation__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./moveoperation */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/moveoperation.js");
/* harmony import */ var _rootattributeoperation__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./rootattributeoperation */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/rootattributeoperation.js");
/* harmony import */ var _mergeoperation__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./mergeoperation */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/mergeoperation.js");
/* harmony import */ var _splitoperation__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./splitoperation */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/splitoperation.js");
/* harmony import */ var _nooperation__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./nooperation */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/nooperation.js");
/* harmony import */ var _range__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ../range */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/range.js");
/* harmony import */ var _position__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ../position */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/position.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_comparearrays__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/comparearrays */ "./node_modules/@ckeditor/ckeditor5-utils/src/comparearrays.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
const transformations = new Map();
/**
* @module engine/model/operation/transform
*/
/**
* Sets a transformation function to be be used to transform instances of class `OperationA` by instances of class `OperationB`.
*
* The `transformationFunction` is passed three parameters:
*
* * `a` - operation to be transformed, an instance of `OperationA`,
* * `b` - operation to be transformed by, an instance of `OperationB`,
* * {@link module:engine/model/operation/transform~TransformationContext `context`} - object with additional information about
* transformation context.
*
* The `transformationFunction` should return transformation result, which is an array with one or multiple
* {@link module:engine/model/operation/operation~Operation operation} instances.
*
* @protected
* @param {Function} OperationA
* @param {Function} OperationB
* @param {Function} transformationFunction Function to use for transforming.
*/
function setTransformation( OperationA, OperationB, transformationFunction ) {
let aGroup = transformations.get( OperationA );
if ( !aGroup ) {
aGroup = new Map();
transformations.set( OperationA, aGroup );
}
aGroup.set( OperationB, transformationFunction );
}
/**
* Returns a previously set transformation function for transforming an instance of `OperationA` by an instance of `OperationB`.
*
* If no transformation was set for given pair of operations, {@link module:engine/model/operation/transform~noUpdateTransformation}
* is returned. This means that if no transformation was set, the `OperationA` instance will not change when transformed
* by the `OperationB` instance.
*
* @private
* @param {Function} OperationA
* @param {Function} OperationB
* @returns {Function} Function set to transform an instance of `OperationA` by an instance of `OperationB`.
*/
function getTransformation( OperationA, OperationB ) {
const aGroup = transformations.get( OperationA );
if ( aGroup && aGroup.has( OperationB ) ) {
return aGroup.get( OperationB );
}
return noUpdateTransformation;
}
/**
* A transformation function that only clones operation to transform, without changing it.
*
* @private
* @param {module:engine/model/operation/operation~Operation} a Operation to transform.
* @returns {Array.<module:engine/model/operation/operation~Operation>}
*/
function noUpdateTransformation( a ) {
return [ a ];
}
/**
* Transforms operation `a` by operation `b`.
*
* @param {module:engine/model/operation/operation~Operation} a Operation to be transformed.
* @param {module:engine/model/operation/operation~Operation} b Operation to transform by.
* @param {module:engine/model/operation/transform~TransformationContext} context Transformation context for this transformation.
* @returns {Array.<module:engine/model/operation/operation~Operation>} Transformation result.
*/
function transform( a, b, context = {} ) {
const transformationFunction = getTransformation( a.constructor, b.constructor );
/* eslint-disable no-useless-catch */
try {
a = a.clone();
return transformationFunction( a, b, context );
} catch ( e ) {
// @if CK_DEBUG // console.warn( 'Error during operation transformation!', e.message );
// @if CK_DEBUG // console.warn( 'Transformed operation', a );
// @if CK_DEBUG // console.warn( 'Operation transformed by', b );
// @if CK_DEBUG // console.warn( 'context.aIsStrong', context.aIsStrong );
// @if CK_DEBUG // console.warn( 'context.aWasUndone', context.aWasUndone );
// @if CK_DEBUG // console.warn( 'context.bWasUndone', context.bWasUndone );
// @if CK_DEBUG // console.warn( 'context.abRelation', context.abRelation );
// @if CK_DEBUG // console.warn( 'context.baRelation', context.baRelation );
throw e;
}
/* eslint-enable no-useless-catch */
}
/**
* Performs a transformation of two sets of operations - `operationsA` and `operationsB`. The transformation is two-way -
* both transformed `operationsA` and transformed `operationsB` are returned.
*
* Note, that the first operation in each set should base on the same document state (
* {@link module:engine/model/document~Document#version document version}).
*
* It is assumed that `operationsA` are "more important" during conflict resolution between two operations.
*
* New copies of both passed arrays and operations inside them are returned. Passed arguments are not altered.
*
* Base versions of the transformed operations sets are updated accordingly. For example, assume that base versions are `4`
* and there are `3` operations in `operationsA` and `5` operations in `operationsB`. Then:
*
* * transformed `operationsA` will start from base version `9` (`4` base version + `5` operations B),
* * transformed `operationsB` will start from base version `7` (`4` base version + `3` operations A).
*
* If no operation was broken into two during transformation, then both sets will end up with an operation that bases on version `11`:
*
* * transformed `operationsA` start from `9` and there are `3` of them, so the last will have `baseVersion` equal to `11`,
* * transformed `operationsB` start from `7` and there are `5` of them, so the last will have `baseVersion` equal to `11`.
*
* @param {Array.<module:engine/model/operation/operation~Operation>} operationsA
* @param {Array.<module:engine/model/operation/operation~Operation>} operationsB
* @param {Object} options Additional transformation options.
* @param {module:engine/model/document~Document|null} options.document Document which the operations change.
* @param {Boolean} [options.useRelations=false] Whether during transformation relations should be used (used during undo for
* better conflict resolution).
* @param {Boolean} [options.padWithNoOps=false] Whether additional {@link module:engine/model/operation/nooperation~NoOperation}s
* should be added to the transformation results to force the same last base version for both transformed sets (in case
* if some operations got broken into multiple operations during transformation).
* @returns {Object} Transformation result.
* @returns {Array.<module:engine/model/operation/operation~Operation>} return.operationsA Transformed `operationsA`.
* @returns {Array.<module:engine/model/operation/operation~Operation>} return.operationsB Transformed `operationsB`.
* @returns {Map} return.originalOperations A map that links transformed operations to original operations. The keys are the transformed
* operations and the values are the original operations from the input (`operationsA` and `operationsB`).
*/
function transformSets( operationsA, operationsB, options ) {
// Create new arrays so the originally passed arguments are not changed.
// No need to clone operations, they are cloned as they are transformed.
operationsA = operationsA.slice();
operationsB = operationsB.slice();
const contextFactory = new ContextFactory( options.document, options.useRelations, options.forceWeakRemove );
contextFactory.setOriginalOperations( operationsA );
contextFactory.setOriginalOperations( operationsB );
const originalOperations = contextFactory.originalOperations;
// If one of sets is empty there is simply nothing to transform, so return sets as they are.
if ( operationsA.length == 0 || operationsB.length == 0 ) {
return { operationsA, operationsB, originalOperations };
}
//
// Following is a description of transformation process:
//
// There are `operationsA` and `operationsB` to be transformed, both by both.
//
// So, suppose we have sets of two operations each: `operationsA` = `[ a1, a2 ]`, `operationsB` = `[ b1, b2 ]`.
//
// Remember, that we can only transform operations that base on the same context. We assert that `a1` and `b1` base on
// the same context and we transform them. Then, we get `a1'` and `b1'`. `a2` bases on a context with `a1` -- `a2`
// is an operation that followed `a1`. Similarly, `b2` bases on a context with `b1`.
//
// However, since `a1'` is a result of transformation by `b1`, `a1'` now also has a context with `b1`. This means that
// we can safely transform `a1'` by `b2`. As we finish transforming `a1`, we also transformed all `operationsB`.
// All `operationsB` also have context including `a1`. Now, we can properly transform `a2` by those operations.
//
// The transformation process can be visualized on a transformation diagram ("diamond diagram"):
//
// [the initial state]
// [common for a1 and b1]
//
// *
// / \
// / \
// b1 a1
// / \
// / \
// * *
// / \ / \
// / \ / \
// b2 a1' b1' a2
// / \ / \
// / \ / \
// * * *
// \ / \ /
// \ / \ /
// a1'' b2' a2' b1''
// \ / \ /
// \ / \ /
// * *
// \ /
// \ /
// a2'' b2''
// \ /
// \ /
// *
//
// [the final state]
//
// The final state can be reached from the initial state by applying `a1`, `a2`, `b1''` and `b2''`, as well as by
// applying `b1`, `b2`, `a1''`, `a2''`. Note how the operations get to a proper common state before each pair is
// transformed.
//
// Another thing to consider is that an operation during transformation can be broken into multiple operations.
// Suppose that `a1` * `b1` = `[ a11', a12' ]` (instead of `a1'` that we considered previously).
//
// In that case, we leave `a12'` for later and we continue transforming `a11'` until it is transformed by all `operationsB`
// (in our case it is just `b2`). At this point, `b1` is transformed by "whole" `a1`, while `b2` is only transformed
// by `a11'`. Similarly, `a12'` is only transformed by `b1`. This leads to a conclusion that we need to start transforming `a12'`
// from the moment just after it was broken. So, `a12'` is transformed by `b2`. Now, "the whole" `a1` is transformed
// by `operationsB`, while all `operationsB` are transformed by "the whole" `a1`. This means that we can continue with
// following `operationsA` (in our case it is just `a2`).
//
// Of course, also `operationsB` can be broken. However, since we focus on transforming operation `a` to the end,
// the only thing to do is to store both pieces of operation `b`, so that the next transformed operation `a` will
// be transformed by both of them.
//
// *
// / \
// / \
// / \
// b1 a1
// / \
// / \
// / \
// * *
// / \ / \
// / a11' / \
// / \ / \
// b2 * b1' a2
// / / \ / \
// / / a12' / \
// / / \ / \
// * b2' * *
// \ / / \ /
// a11'' / b21'' \ /
// \ / / \ /
// * * a2' b1''
// \ / \ \ /
// a12'' b22''\ \ /
// \ / \ \ /
// * a2'' *
// \ \ /
// \ \ b21'''
// \ \ /
// a2''' *
// \ /
// \ b22'''
// \ /
// *
//
// Note, how `a1` is broken and transformed into `a11'` and `a12'`, while `b2'` got broken and transformed into `b21''` and `b22''`.
//
// Having all that on mind, here is an outline for the transformation process algorithm:
//
// 1. We have `operationsA` and `operationsB` array, which we dynamically update as the transformation process goes.
//
// 2. We take next (or first) operation from `operationsA` and check from which operation `b` we need to start transforming it.
// All original `operationsA` are set to be transformed starting from the first operation `b`.
//
// 3. We take operations from `operationsB`, one by one, starting from the correct one, and transform operation `a`
// by operation `b` (and vice versa). We update `operationsA` and `operationsB` by replacing the original operations
// with the transformation results.
//
// 4. If operation is broken into multiple operations, we save all the new operations in the place of the
// original operation.
//
// 5. Additionally, if operation `a` was broken, for the "new" operation, we remember from which operation `b` it should
// be transformed by.
//
// 6. We continue transforming "current" operation `a` until it is transformed by all `operationsB`. Then, go to 2.
// unless the last operation `a` was transformed.
//
// The actual implementation of the above algorithm is slightly different, as only one loop (while) is used.
// The difference is that we have "current" `a` operation to transform and we store the index of the next `b` operation
// to transform by. Each loop operates on two indexes then: index pointing to currently processed `a` operation and
// index pointing to next `b` operation. Each loop is just one `a * b` + `b * a` transformation. After each loop
// operation `b` index is updated. If all `b` operations were visited for the current `a` operation, we change
// current `a` operation index to the next one.
//
// For each operation `a`, keeps information what is the index in `operationsB` from which the transformation should start.
const nextTransformIndex = new WeakMap();
// For all the original `operationsA`, set that they should be transformed starting from the first of `operationsB`.
for ( const op of operationsA ) {
nextTransformIndex.set( op, 0 );
}
// Additional data that is used for some postprocessing after the main transformation process is done.
const data = {
nextBaseVersionA: operationsA[ operationsA.length - 1 ].baseVersion + 1,
nextBaseVersionB: operationsB[ operationsB.length - 1 ].baseVersion + 1,
originalOperationsACount: operationsA.length,
originalOperationsBCount: operationsB.length
};
// Index of currently transformed operation `a`.
let i = 0;
// While not all `operationsA` are transformed...
while ( i < operationsA.length ) {
// Get "current" operation `a`.
const opA = operationsA[ i ];
// For the "current" operation `a`, get the index of the next operation `b` to transform by.
const indexB = nextTransformIndex.get( opA );
// If operation `a` was already transformed by every operation `b`, change "current" operation `a` to the next one.
if ( indexB == operationsB.length ) {
i++;
continue;
}
const opB = operationsB[ indexB ];
// Transform `a` by `b` and `b` by `a`.
const newOpsA = transform( opA, opB, contextFactory.getContext( opA, opB, true ) );
const newOpsB = transform( opB, opA, contextFactory.getContext( opB, opA, false ) );
// As a result we get one or more `newOpsA` and one or more `newOpsB` operations.
// Update contextual information about operations.
contextFactory.updateRelation( opA, opB );
contextFactory.setOriginalOperations( newOpsA, opA );
contextFactory.setOriginalOperations( newOpsB, opB );
// For new `a` operations, update their index of the next operation `b` to transform them by.
//
// This is needed even if there was only one result (`a` was not broken) because that information is used
// at the beginning of this loop every time.
for ( const newOpA of newOpsA ) {
// Acknowledge, that operation `b` also might be broken into multiple operations.
//
// This is why we raise `indexB` not just by 1. If `newOpsB` are multiple operations, they will be
// spliced in the place of `opB`. So we need to change `transformBy` accordingly, so that an operation won't
// be transformed by the same operation (part of it) again.
nextTransformIndex.set( newOpA, indexB + newOpsB.length );
}
// Update `operationsA` and `operationsB` with the transformed versions.
operationsA.splice( i, 1, ...newOpsA );
operationsB.splice( indexB, 1, ...newOpsB );
}
if ( options.padWithNoOps ) {
// If no-operations padding is enabled, count how many extra `a` and `b` operations were generated.
const brokenOperationsACount = operationsA.length - data.originalOperationsACount;
const brokenOperationsBCount = operationsB.length - data.originalOperationsBCount;
// Then, if that number is not the same, pad `operationsA` or `operationsB` with correct number of no-ops so
// that the base versions are equalled.
//
// Note that only one array will be updated, as only one of those subtractions can be greater than zero.
padWithNoOps( operationsA, brokenOperationsBCount - brokenOperationsACount );
padWithNoOps( operationsB, brokenOperationsACount - brokenOperationsBCount );
}
// Finally, update base versions of transformed operations.
updateBaseVersions( operationsA, data.nextBaseVersionB );
updateBaseVersions( operationsB, data.nextBaseVersionA );
return { operationsA, operationsB, originalOperations };
}
// Gathers additional data about operations processed during transformation. Can be used to obtain contextual information
// about two operations that are about to be transformed. This contextual information can be used for better conflict resolution.
class ContextFactory {
// Creates `ContextFactory` instance.
//
// @param {module:engine/model/document~Document} document Document which the operations change.
// @param {Boolean} useRelations Whether during transformation relations should be used (used during undo for
// better conflict resolution).
// @param {Boolean} [forceWeakRemove=false] If set to `false`, remove operation will be always stronger than move operation,
// so the removed nodes won't end up back in the document root. When set to `true`, context data will be used.
constructor( document, useRelations, forceWeakRemove = false ) {
// For each operation that is created during transformation process, we keep a reference to the original operation
// which it comes from. The original operation works as a kind of "identifier". Every contextual information
// gathered during transformation that we want to save for given operation, is actually saved for the original operation.
// This way no matter if operation `a` is cloned, then transformed, even breaks, we still have access to the previously
// gathered data through original operation reference.
this.originalOperations = new Map();
// `model.History` instance which information about undone operations will be taken from.
this._history = document.history;
// Whether additional context should be used.
this._useRelations = useRelations;
this._forceWeakRemove = !!forceWeakRemove;
// Relations is a double-map structure (maps in map) where for two operations we store how those operations were related
// to each other. Those relations are evaluated during transformation process. For every transformated pair of operations
// we keep relations between them.
this._relations = new Map();
}
// Sets "original operation" for given operations.
//
// During transformation process, operations are cloned, then changed, then processed again, sometimes broken into two
// or multiple operations. When gathering additional data it is important that all operations can be somehow linked
// so a cloned and transformed "version" still kept track of the data assigned earlier to it.
//
// The original operation object will be used as such an universal linking id. Throughout the transformation process
// all cloned operations will refer to "the original operation" when storing and reading additional data.
//
// If `takeFrom` is not set, each operation from `operations` array will be assigned itself as "the original operation".
// This should be used as an initialization step.
//
// If `takeFrom` is set, each operation from `operations` will be assigned the same original operation as assigned
// for `takeFrom` operation. This should be used to update original operations. It should be used in a way that
// `operations` are the result of `takeFrom` transformation to ensure proper "original operation propagation".
//
// @param {Array.<module:engine/model/operation/operation~Operation>} operations
// @param {module:engine/model/operation/operation~Operation|null} [takeFrom=null]
setOriginalOperations( operations, takeFrom = null ) {
const originalOperation = takeFrom ? this.originalOperations.get( takeFrom ) : null;
for ( const operation of operations ) {
this.originalOperations.set( operation, originalOperation || operation );
}
}
// Saves a relation between operations `opA` and `opB`.
//
// Relations are then later used to help solve conflicts when operations are transformed.
//
// @param {module:engine/model/operation/operation~Operation} opA
// @param {module:engine/model/operation/operation~Operation} opB
updateRelation( opA, opB ) {
// The use of relations is described in a bigger detail in transformation functions.
//
// In brief, this function, for specified pairs of operation types, checks how positions defined in those operations relate.
// Then those relations are saved. For example, for two move operations, it is saved if one of those operations target
// position is before the other operation source position. This kind of information gives contextual information when
// transformation is used during undo. Similar checks are done for other pairs of operations.
//
switch ( opA.constructor ) {
case _moveoperation__WEBPACK_IMPORTED_MODULE_4__["default"]: {
switch ( opB.constructor ) {
case _mergeoperation__WEBPACK_IMPORTED_MODULE_6__["default"]: {
if ( opA.targetPosition.isEqual( opB.sourcePosition ) || opB.movedRange.containsPosition( opA.targetPosition ) ) {
this._setRelation( opA, opB, 'insertAtSource' );
} else if ( opA.targetPosition.isEqual( opB.deletionPosition ) ) {
this._setRelation( opA, opB, 'insertBetween' );
} else if ( opA.targetPosition.isAfter( opB.sourcePosition ) ) {
this._setRelation( opA, opB, 'moveTargetAfter' );
}
break;
}
case _moveoperation__WEBPACK_IMPORTED_MODULE_4__["default"]: {
if ( opA.targetPosition.isEqual( opB.sourcePosition ) || opA.targetPosition.isBefore( opB.sourcePosition ) ) {
this._setRelation( opA, opB, 'insertBefore' );
} else {
this._setRelation( opA, opB, 'insertAfter' );
}
break;
}
}
break;
}
case _splitoperation__WEBPACK_IMPORTED_MODULE_7__["default"]: {
switch ( opB.constructor ) {
case _mergeoperation__WEBPACK_IMPORTED_MODULE_6__["default"]: {
if ( opA.splitPosition.isBefore( opB.sourcePosition ) ) {
this._setRelation( opA, opB, 'splitBefore' );
}
break;
}
case _moveoperation__WEBPACK_IMPORTED_MODULE_4__["default"]: {
if ( opA.splitPosition.isEqual( opB.sourcePosition ) || opA.splitPosition.isBefore( opB.sourcePosition ) ) {
this._setRelation( opA, opB, 'splitBefore' );
} else {
const range = _range__WEBPACK_IMPORTED_MODULE_9__["default"]._createFromPositionAndShift( opB.sourcePosition, opB.howMany );
if ( opA.splitPosition.hasSameParentAs( opB.sourcePosition ) && range.containsPosition( opA.splitPosition ) ) {
const howMany = range.end.offset - opA.splitPosition.offset;
const offset = opA.splitPosition.offset - range.start.offset;
this._setRelation( opA, opB, { howMany, offset } );
}
}
}
}
break;
}
case _mergeoperation__WEBPACK_IMPORTED_MODULE_6__["default"]: {
switch ( opB.constructor ) {
case _mergeoperation__WEBPACK_IMPORTED_MODULE_6__["default"]: {
if ( !opA.targetPosition.isEqual( opB.sourcePosition ) ) {
this._setRelation( opA, opB, 'mergeTargetNotMoved' );
}
if ( opA.sourcePosition.isEqual( opB.targetPosition ) ) {
this._setRelation( opA, opB, 'mergeSourceNotMoved' );
}
if ( opA.sourcePosition.isEqual( opB.sourcePosition ) ) {
this._setRelation( opA, opB, 'mergeSameElement' );
}
break;
}
case _splitoperation__WEBPACK_IMPORTED_MODULE_7__["default"]: {
if ( opA.sourcePosition.isEqual( opB.splitPosition ) ) {
this._setRelation( opA, opB, 'splitAtSource' );
}
}
}
break;
}
case _markeroperation__WEBPACK_IMPORTED_MODULE_3__["default"]: {
const markerRange = opA.newRange;
if ( !markerRange ) {
return;
}
switch ( opB.constructor ) {
case _moveoperation__WEBPACK_IMPORTED_MODULE_4__["default"]: {
const movedRange = _range__WEBPACK_IMPORTED_MODULE_9__["default"]._createFromPositionAndShift( opB.sourcePosition, opB.howMany );
const affectedLeft = movedRange.containsPosition( markerRange.start ) ||
movedRange.start.isEqual( markerRange.start );
const affectedRight = movedRange.containsPosition( markerRange.end ) ||
movedRange.end.isEqual( markerRange.end );
if ( ( affectedLeft || affectedRight ) && !movedRange.containsRange( markerRange ) ) {
this._setRelation( opA, opB, {
side: affectedLeft ? 'left' : 'right',
path: affectedLeft ? markerRange.start.path.slice() : markerRange.end.path.slice()
} );
}
break;
}
case _mergeoperation__WEBPACK_IMPORTED_MODULE_6__["default"]: {
const wasInLeftElement = markerRange.start.isEqual( opB.targetPosition );
const wasStartBeforeMergedElement = markerRange.start.isEqual( opB.deletionPosition );
const wasEndBeforeMergedElement = markerRange.end.isEqual( opB.deletionPosition );
const wasInRightElement = markerRange.end.isEqual( opB.sourcePosition );
if ( wasInLeftElement || wasStartBeforeMergedElement || wasEndBeforeMergedElement || wasInRightElement ) {
this._setRelation( opA, opB, {
wasInLeftElement,
wasStartBeforeMergedElement,
wasEndBeforeMergedElement,
wasInRightElement
} );
}
break;
}
}
break;
}
}
}
// Evaluates and returns contextual information about two given operations `opA` and `opB` which are about to be transformed.
//
// @param {module:engine/model/operation/operation~Operation} opA
// @param {module:engine/model/operation/operation~Operation} opB
// @returns {module:engine/model/operation/transform~TransformationContext}
getContext( opA, opB, aIsStrong ) {
return {
aIsStrong,
aWasUndone: this._wasUndone( opA ),
bWasUndone: this._wasUndone( opB ),
abRelation: this._useRelations ? this._getRelation( opA, opB ) : null,
baRelation: this._useRelations ? this._getRelation( opB, opA ) : null,
forceWeakRemove: this._forceWeakRemove
};
}
// Returns whether given operation `op` has already been undone.
//
// Information whether an operation was undone gives more context when making a decision when two operations are in conflict.
//
// @param {module:engine/model/operation/operation~Operation} op
// @returns {Boolean}
_wasUndone( op ) {
// For `op`, get its original operation. After all, if `op` is a clone (or even transformed clone) of another
// operation, literally `op` couldn't be undone. It was just generated. If anything, it was the operation it origins
// from which was undone. So get that original operation.
const originalOp = this.originalOperations.get( op );
// And check with the document if the original operation was undone.
return originalOp.wasUndone || this._history.isUndoneOperation( originalOp );
}
// Returns a relation between `opA` and an operation which is undone by `opB`. This can be `String` value if a relation
// was set earlier or `null` if there was no relation between those operations.
//
// This is a little tricky to understand, so let's compare it to `ContextFactory#_wasUndone`.
//
// When `wasUndone( opB )` is used, we check if the `opB` has already been undone. It is obvious, that the
// undoing operation must happen after the undone operation. So, essentially, we have `opB`, we take document history,
// we look forward in the future and ask if in that future `opB` was undone.
//
// Relations is a backward process to `wasUndone()`.
//
// Long story short - using relations is asking what happened in the past. Looking back. This time we have an undoing
// operation `opB` which has undone some other operation. When there is a transformation `opA` x `opB` and there is
// a conflict to solve and `opB` is an undoing operation, we can look back in the history and see what was a relation
// between `opA` and the operation which `opB` undone. Basing on that relation from the past, we can now make
// a better decision when resolving a conflict between two operations, because we know more about the context of
// those two operations.
//
// This is why this function does not return a relation directly between `opA` and `opB` because we need to look
// back to search for a meaningful contextual information.
//
// @param {module:engine/model/operation/operation~Operation} opA
// @param {module:engine/model/operation/operation~Operation} opB
// @returns {String|null}
_getRelation( opA, opB ) {
// Get the original operation. Similarly as in `wasUndone()` it is used as an universal identifier for stored data.
const origB = this.originalOperations.get( opB );
const undoneB = this._history.getUndoneOperation( origB );
// If `opB` is not undoing any operation, there is no relation.
if ( !undoneB ) {
return null;
}
const origA = this.originalOperations.get( opA );
const relationsA = this._relations.get( origA );
// Get all relations for `opA`, and check if there is a relation with `opB`-undone-counterpart. If so, return it.
if ( relationsA ) {
return relationsA.get( undoneB ) || null;
}
return null;
}
// Helper function for `ContextFactory#updateRelations`.
//
// @private
// @param {module:engine/model/operation/operation~Operation} opA
// @param {module:engine/model/operation/operation~Operation} opB
// @param {String} relation
_setRelation( opA, opB, relation ) {
// As always, setting is for original operations, not the clones/transformed operations.
const origA = this.originalOperations.get( opA );
const origB = this.originalOperations.get( opB );
let relationsA = this._relations.get( origA );
if ( !relationsA ) {
relationsA = new Map();
this._relations.set( origA, relationsA );
}
relationsA.set( origB, relation );
}
}
/**
* Holds additional contextual information about a transformed pair of operations (`a` and `b`). Those information
* can be used for better conflict resolving.
*
* @typedef {Object} module:engine/model/operation/transform~TransformationContext
*
* @property {Boolean} aIsStrong Whether `a` is strong operation in this transformation, or weak.
* @property {Boolean} aWasUndone Whether `a` operation was undone.
* @property {Boolean} bWasUndone Whether `b` operation was undone.
* @property {String|null} abRelation The relation between `a` operation and an operation undone by `b` operation.
* @property {String|null} baRelation The relation between `b` operation and an operation undone by `a` operation.
*/
/**
* An utility function that updates {@link module:engine/model/operation/operation~Operation#baseVersion base versions}
* of passed operations.
*
* The function simply sets `baseVersion` as a base version of the first passed operation and then increments it for
* each following operation in `operations`.
*
* @private
* @param {Array.<module:engine/model/operation/operation~Operation>} operations Operations to update.
* @param {Number} baseVersion Base version to set for the first operation in `operations`.
*/
function updateBaseVersions( operations, baseVersion ) {
for ( const operation of operations ) {
operation.baseVersion = baseVersion++;
}
}
/**
* Adds `howMany` instances of {@link module:engine/model/operation/nooperation~NoOperation} to `operations` set.
*
* @private
* @param {Array.<module:engine/model/operation/operation~Operation>} operations
* @param {Number} howMany
*/
function padWithNoOps( operations, howMany ) {
for ( let i = 0; i < howMany; i++ ) {
operations.push( new _nooperation__WEBPACK_IMPORTED_MODULE_8__["default"]( 0 ) );
}
}
// -----------------------
setTransformation( _attributeoperation__WEBPACK_IMPORTED_MODULE_1__["default"], _attributeoperation__WEBPACK_IMPORTED_MODULE_1__["default"], ( a, b, context ) => {
// If operations in conflict, check if their ranges intersect and manage them properly.
//
// Operations can be in conflict only if:
//
// * their key is the same (they change the same attribute), and
// * they are in the same parent (operations for ranges [ 1 ] - [ 3 ] and [ 2, 0 ] - [ 2, 5 ] change different
// elements and can't be in conflict).
if ( a.key === b.key && a.range.start.hasSameParentAs( b.range.start ) ) {
// First, we want to apply change to the part of a range that has not been changed by the other operation.
const operations = a.range.getDifference( b.range ).map( range => {
return new _attributeoperation__WEBPACK_IMPORTED_MODULE_1__["default"]( range, a.key, a.oldValue, a.newValue, 0 );
} );
// Then we take care of the common part of ranges.
const common = a.range.getIntersection( b.range );
if ( common ) {
// If this operation is more important, we also want to apply change to the part of the
// original range that has already been changed by the other operation. Since that range
// got changed we also have to update `oldValue`.
if ( context.aIsStrong ) {
operations.push( new _attributeoperation__WEBPACK_IMPORTED_MODULE_1__["default"]( common, b.key, b.newValue, a.newValue, 0 ) );
}
}
if ( operations.length == 0 ) {
return [ new _nooperation__WEBPACK_IMPORTED_MODULE_8__["default"]( 0 ) ];
}
return operations;
} else {
// If operations don't conflict, simply return an array containing just a clone of this operation.
return [ a ];
}
} );
setTransformation( _attributeoperation__WEBPACK_IMPORTED_MODULE_1__["default"], _insertoperation__WEBPACK_IMPORTED_MODULE_0__["default"], ( a, b ) => {
// Case 1:
//
// The attribute operation range includes the position where nodes were inserted.
// There are two possible scenarios: the inserted nodes were text and they should receive attributes or
// the inserted nodes were elements and they should not receive attributes.
//
if ( a.range.start.hasSameParentAs( b.position ) && a.range.containsPosition( b.position ) ) {
// If new nodes should not receive attributes, two separated ranges will be returned.
// Otherwise, one expanded range will be returned.
const range = a.range._getTransformedByInsertion( b.position, b.howMany, !b.shouldReceiveAttributes );
const result = range.map( r => {
return new _attributeoperation__WEBPACK_IMPORTED_MODULE_1__["default"]( r, a.key, a.oldValue, a.newValue, a.baseVersion );
} );
if ( b.shouldReceiveAttributes ) {
// `AttributeOperation#range` includes some newly inserted text.
// The operation should also change the attribute of that text. An example:
//
// Bold should be applied on the following range:
// <p>Fo[zb]ar</p>
//
// In meantime, new text is typed:
// <p>Fozxxbar</p>
//
// Bold should be applied also on the new text:
// <p>Fo[zxxb]ar</p>
// <p>Fo<$text bold="true">zxxb</$text>ar</p>
//
// There is a special case to consider here to consider.
//
// Consider setting an attribute with multiple possible values, for example `highlight`. The inserted text might
// have already an attribute value applied and the `oldValue` property of the attribute operation might be wrong:
//
// Attribute `highlight="yellow"` should be applied on the following range:
// <p>Fo[zb]ar<p>
//
// In meantime, character `x` with `highlight="red"` is typed:
// <p>Fo[z<$text highlight="red">x</$text>b]ar</p>
//
// In this case we cannot simply apply operation changing the attribute value from `null` to `"yellow"` for the whole range
// because that would lead to an exception (`oldValue` is incorrect for `x`).
//
// We also cannot break the original range as this would mess up a scenario when there are multiple following
// insert operations, because then only the first inserted character is included in those ranges:
// <p>Fo[z][x][b]ar</p> --> <p>Fo[z][x]x[b]ar</p> --> <p>Fo[z][x]xx[b]ar</p>
//
// So, the attribute range needs be expanded, no matter what attributes are set on the inserted nodes:
//
// <p>Fo[z<$text highlight="red">x</$text>b]ar</p> <--- Change from `null` to `yellow`, throwing an exception.
//
// But before that operation would be applied, we will add an additional attribute operation that will change
// attributes on the inserted nodes in a way which would make the original operation correct:
//
// <p>Fo[z{<$text highlight="red">}x</$text>b]ar</p> <--- Change range `{}` from `red` to `null`.
// <p>Fo[zxb]ar</p> <--- Now change from `null` to `yellow` is completely fine.
//
// Generate complementary attribute operation. Be sure to add it before the original operation.
const op = _getComplementaryAttributeOperations( b, a.key, a.oldValue );
if ( op ) {
result.unshift( op );
}
}
// If nodes should not receive new attribute, we are done here.
return result;
}
// If insert operation is not expanding the attribute operation range, simply transform the range.
a.range = a.range._getTransformedByInsertion( b.position, b.howMany, false )[ 0 ];
return [ a ];
} );
/**
* Helper function for `AttributeOperation` x `InsertOperation` (and reverse) transformation.
*
* For given `insertOperation` it checks the inserted node if it has an attribute `key` set to a value different
* than `newValue`. If so, it generates an `AttributeOperation` which changes the value of `key` attribute to `newValue`.
*
* @private
* @param {module:engine/model/operation/insertoperation~InsertOperation} insertOperation
* @param {String} key
* @param {*} newValue
* @returns {module:engine/model/operation/attributeoperation~AttributeOperation|null}
*/
function _getComplementaryAttributeOperations( insertOperation, key, newValue ) {
const nodes = insertOperation.nodes;
// At the beginning we store the attribute value from the first node.
const insertValue = nodes.getNode( 0 ).getAttribute( key );
if ( insertValue == newValue ) {
return null;
}
const range = new _range__WEBPACK_IMPORTED_MODULE_9__["default"]( insertOperation.position, insertOperation.position.getShiftedBy( insertOperation.howMany ) );
return new _attributeoperation__WEBPACK_IMPORTED_MODULE_1__["default"]( range, key, insertValue, newValue, 0 );
}
setTransformation( _attributeoperation__WEBPACK_IMPORTED_MODULE_1__["default"], _mergeoperation__WEBPACK_IMPORTED_MODULE_6__["default"], ( a, b ) => {
const ranges = [];
// Case 1:
//
// Attribute change on the merged element. In this case, the merged element was moved to the graveyard.
// An additional attribute operation that will change the (re)moved element needs to be generated.
//
if ( a.range.start.hasSameParentAs( b.deletionPosition ) ) {
if ( a.range.containsPosition( b.deletionPosition ) || a.range.start.isEqual( b.deletionPosition ) ) {
ranges.push( _range__WEBPACK_IMPORTED_MODULE_9__["default"]._createFromPositionAndShift( b.graveyardPosition, 1 ) );
}
}
const range = a.range._getTransformedByMergeOperation( b );
// Do not add empty (collapsed) ranges to the result. `range` may be collapsed if it contained only the merged element.
if ( !range.isCollapsed ) {
ranges.push( range );
}
// Create `AttributeOperation`s out of the ranges.
return ranges.map( range => {
return new _attributeoperation__WEBPACK_IMPORTED_MODULE_1__["default"]( range, a.key, a.oldValue, a.newValue, a.baseVersion );
} );
} );
setTransformation( _attributeoperation__WEBPACK_IMPORTED_MODULE_1__["default"], _moveoperation__WEBPACK_IMPORTED_MODULE_4__["default"], ( a, b ) => {
const ranges = _breakRangeByMoveOperation( a.range, b );
// Create `AttributeOperation`s out of the ranges.
return ranges.map( range => new _attributeoperation__WEBPACK_IMPORTED_MODULE_1__["default"]( range, a.key, a.oldValue, a.newValue, a.baseVersion ) );
} );
// Helper function for `AttributeOperation` x `MoveOperation` transformation.
//
// Takes the passed `range` and transforms it by move operation `moveOp` in a specific way. Only top-level nodes of `range`
// are considered to be in the range. If move operation moves nodes deep from inside of the range, those nodes won't
// be included in the result. In other words, top-level nodes of the ranges from the result are exactly the same as
// top-level nodes of the original `range`.
//
// This is important for `AttributeOperation` because, for its range, it changes only the top-level nodes. So we need to
// track only how those nodes have been affected by `MoveOperation`.
//
// @private
// @param {module:engine/model/range~Range} range
// @param {module:engine/model/operation/moveoperation~MoveOperation} moveOp
// @returns {Array.<module:engine/model/range~Range>}
function _breakRangeByMoveOperation( range, moveOp ) {
const moveRange = _range__WEBPACK_IMPORTED_MODULE_9__["default"]._createFromPositionAndShift( moveOp.sourcePosition, moveOp.howMany );
// We are transforming `range` (original range) by `moveRange` (range moved by move operation). As usual when it comes to
// transforming a ranges, we may have a common part of the ranges and we may have a difference part (zero to two ranges).
let common = null;
let difference = [];
// Let's compare the ranges.
if ( moveRange.containsRange( range, true ) ) {
// If the whole original range is moved, treat it whole as a common part. There's also no difference part.
common = range;
} else if ( range.start.hasSameParentAs( moveRange.start ) ) {
// If the ranges are "on the same level" (in the same parent) then move operation may move exactly those nodes
// that are changed by the attribute operation. In this case we get common part and difference part in the usual way.
difference = range.getDifference( moveRange );
common = range.getIntersection( moveRange );
} else {
// In any other situation we assume that original range is different than move range, that is that move operation
// moves other nodes that attribute operation change. Even if the moved range is deep inside in the original range.
//
// Note that this is different than in `.getIntersection` (we would get a common part in that case) and different
// than `.getDifference` (we would get two ranges).
difference = [ range ];
}
const result = [];
// The default behaviour of `_getTransformedByMove` might get wrong results for difference part, though, so
// we do it by hand.
for ( let diff of difference ) {
// First, transform the range by removing moved nodes. Since this is a difference, this is safe, `null` won't be returned
// as the range is different than the moved range.
diff = diff._getTransformedByDeletion( moveOp.sourcePosition, moveOp.howMany );
// Transform also `targetPosition`.
const targetPosition = moveOp.getMovedRangeStart();
// Spread the range only if moved nodes are inserted only between the top-level nodes of the `diff` range.
const spread = diff.start.hasSameParentAs( targetPosition );
// Transform by insertion of moved nodes.
diff = diff._getTransformedByInsertion( targetPosition, moveOp.howMany, spread );
result.push( ...diff );
}
// Common part can be simply transformed by the move operation. This is because move operation will not target to
// that common part (the operation would have to target inside its own moved range).
if ( common ) {
result.push(
common._getTransformedByMove( moveOp.sourcePosition, moveOp.targetPosition, moveOp.howMany, false )[ 0 ]
);
}
return result;
}
setTransformation( _attributeoperation__WEBPACK_IMPORTED_MODULE_1__["default"], _splitoperation__WEBPACK_IMPORTED_MODULE_7__["default"], ( a, b ) => {
// Case 1:
//
// Split node is the last node in `AttributeOperation#range`.
// `AttributeOperation#range` needs to be expanded to include the new (split) node.
//
// Attribute `type` to be changed to `numbered` but the `listItem` is split.
// <listItem type="bulleted">foobar</listItem>
//
// After split:
// <listItem type="bulleted">foo</listItem><listItem type="bulleted">bar</listItem>
//
// After attribute change:
// <listItem type="numbered">foo</listItem><listItem type="numbered">foo</listItem>
//
if ( a.range.end.isEqual( b.insertionPosition ) ) {
if ( !b.graveyardPosition ) {
a.range.end.offset++;
}
return [ a ];
}
// Case 2:
//
// Split position is inside `AttributeOperation#range`, at the same level, so the nodes to change are
// not going to make a flat range.
//
// Content with range-to-change and split position:
// <p>Fo[zb^a]r</p>
//
// After split:
// <p>Fozb</p><p>ar</p>
//
// Make two separate ranges containing all nodes to change:
// <p>Fo[zb]</p><p>[a]r</p>
//
if ( a.range.start.hasSameParentAs( b.splitPosition ) && a.range.containsPosition( b.splitPosition ) ) {
const secondPart = a.clone();
secondPart.range = new _range__WEBPACK_IMPORTED_MODULE_9__["default"](
b.moveTargetPosition.clone(),
a.range.end._getCombined( b.splitPosition, b.moveTargetPosition )
);
a.range.end = b.splitPosition.clone();
a.range.end.stickiness = 'toPrevious';
return [ a, secondPart ];
}
// The default case.
//
a.range = a.range._getTransformedBySplitOperation( b );
return [ a ];
} );
setTransformation( _insertoperation__WEBPACK_IMPORTED_MODULE_0__["default"], _attributeoperation__WEBPACK_IMPORTED_MODULE_1__["default"], ( a, b ) => {
const result = [ a ];
// Case 1:
//
// The attribute operation range includes the position where nodes were inserted.
// There are two possible scenarios: the inserted nodes were text and they should receive attributes or
// the inserted nodes were elements and they should not receive attributes.
//
// This is a mirror scenario to the one described in `AttributeOperation` x `InsertOperation` transformation,
// although this case is a little less complicated. In this case we simply need to change attributes of the
// inserted nodes and that's it.
//
if ( a.shouldReceiveAttributes && a.position.hasSameParentAs( b.range.start ) && b.range.containsPosition( a.position ) ) {
const op = _getComplementaryAttributeOperations( a, b.key, b.newValue );
if ( op ) {
result.push( op );
}
}
// The default case is: do nothing.
// `AttributeOperation` does not change the model tree structure so `InsertOperation` does not need to be changed.
//
return result;
} );
setTransformation( _insertoperation__WEBPACK_IMPORTED_MODULE_0__["default"], _insertoperation__WEBPACK_IMPORTED_MODULE_0__["default"], ( a, b, context ) => {
// Case 1:
//
// Two insert operations insert nodes at the same position. Since they are the same, it needs to be decided
// what will be the order of inserted nodes. However, there is no additional information to help in that
// decision. Also, when `b` will be transformed by `a`, the same order must be maintained.
//
// To achieve that, we will check if the operation is strong.
// If it is, it won't get transformed. If it is not, it will be moved.
//
if ( a.position.isEqual( b.position ) && context.aIsStrong ) {
return [ a ];
}
// The default case.
//
a.position = a.position._getTransformedByInsertOperation( b );
return [ a ];
} );
setTransformation( _insertoperation__WEBPACK_IMPORTED_MODULE_0__["default"], _moveoperation__WEBPACK_IMPORTED_MODULE_4__["default"], ( a, b ) => {
// The default case.
//
a.position = a.position._getTransformedByMoveOperation( b );
return [ a ];
} );
setTransformation( _insertoperation__WEBPACK_IMPORTED_MODULE_0__["default"], _splitoperation__WEBPACK_IMPORTED_MODULE_7__["default"], ( a, b ) => {
// The default case.
//
a.position = a.position._getTransformedBySplitOperation( b );
return [ a ];
} );
setTransformation( _insertoperation__WEBPACK_IMPORTED_MODULE_0__["default"], _mergeoperation__WEBPACK_IMPORTED_MODULE_6__["default"], ( a, b ) => {
a.position = a.position._getTransformedByMergeOperation( b );
return [ a ];
} );
// -----------------------
setTransformation( _markeroperation__WEBPACK_IMPORTED_MODULE_3__["default"], _insertoperation__WEBPACK_IMPORTED_MODULE_0__["default"], ( a, b ) => {
if ( a.oldRange ) {
a.oldRange = a.oldRange._getTransformedByInsertOperation( b )[ 0 ];
}
if ( a.newRange ) {
a.newRange = a.newRange._getTransformedByInsertOperation( b )[ 0 ];
}
return [ a ];
} );
setTransformation( _markeroperation__WEBPACK_IMPORTED_MODULE_3__["default"], _markeroperation__WEBPACK_IMPORTED_MODULE_3__["default"], ( a, b, context ) => {
if ( a.name == b.name ) {
if ( context.aIsStrong ) {
a.oldRange = b.newRange ? b.newRange.clone() : null;
} else {
return [ new _nooperation__WEBPACK_IMPORTED_MODULE_8__["default"]( 0 ) ];
}
}
return [ a ];
} );
setTransformation( _markeroperation__WEBPACK_IMPORTED_MODULE_3__["default"], _mergeoperation__WEBPACK_IMPORTED_MODULE_6__["default"], ( a, b ) => {
if ( a.oldRange ) {
a.oldRange = a.oldRange._getTransformedByMergeOperation( b );
}
if ( a.newRange ) {
a.newRange = a.newRange._getTransformedByMergeOperation( b );
}
return [ a ];
} );
setTransformation( _markeroperation__WEBPACK_IMPORTED_MODULE_3__["default"], _moveoperation__WEBPACK_IMPORTED_MODULE_4__["default"], ( a, b, context ) => {
if ( a.oldRange ) {
a.oldRange = _range__WEBPACK_IMPORTED_MODULE_9__["default"]._createFromRanges( a.oldRange._getTransformedByMoveOperation( b ) );
}
if ( a.newRange ) {
if ( context.abRelation ) {
const aNewRange = _range__WEBPACK_IMPORTED_MODULE_9__["default"]._createFromRanges( a.newRange._getTransformedByMoveOperation( b ) );
if ( context.abRelation.side == 'left' && b.targetPosition.isEqual( a.newRange.start ) ) {
a.newRange.start.path = context.abRelation.path;
a.newRange.end = aNewRange.end;
return [ a ];
} else if ( context.abRelation.side == 'right' && b.targetPosition.isEqual( a.newRange.end ) ) {
a.newRange.start = aNewRange.start;
a.newRange.end.path = context.abRelation.path;
return [ a ];
}
}
a.newRange = _range__WEBPACK_IMPORTED_MODULE_9__["default"]._createFromRanges( a.newRange._getTransformedByMoveOperation( b ) );
}
return [ a ];
} );
setTransformation( _markeroperation__WEBPACK_IMPORTED_MODULE_3__["default"], _splitoperation__WEBPACK_IMPORTED_MODULE_7__["default"], ( a, b, context ) => {
if ( a.oldRange ) {
a.oldRange = a.oldRange._getTransformedBySplitOperation( b );
}
if ( a.newRange ) {
if ( context.abRelation ) {
const aNewRange = a.newRange._getTransformedBySplitOperation( b );
if ( a.newRange.start.isEqual( b.splitPosition ) && context.abRelation.wasStartBeforeMergedElement ) {
a.newRange.start = _position__WEBPACK_IMPORTED_MODULE_10__["default"]._createAt( b.insertionPosition );
} else if ( a.newRange.start.isEqual( b.splitPosition ) && !context.abRelation.wasInLeftElement ) {
a.newRange.start = _position__WEBPACK_IMPORTED_MODULE_10__["default"]._createAt( b.moveTargetPosition );
}
if ( a.newRange.end.isEqual( b.splitPosition ) && context.abRelation.wasInRightElement ) {
a.newRange.end = _position__WEBPACK_IMPORTED_MODULE_10__["default"]._createAt( b.moveTargetPosition );
} else if ( a.newRange.end.isEqual( b.splitPosition ) && context.abRelation.wasEndBeforeMergedElement ) {
a.newRange.end = _position__WEBPACK_IMPORTED_MODULE_10__["default"]._createAt( b.insertionPosition );
} else {
a.newRange.end = aNewRange.end;
}
return [ a ];
}
a.newRange = a.newRange._getTransformedBySplitOperation( b );
}
return [ a ];
} );
// -----------------------
setTransformation( _mergeoperation__WEBPACK_IMPORTED_MODULE_6__["default"], _insertoperation__WEBPACK_IMPORTED_MODULE_0__["default"], ( a, b ) => {
if ( a.sourcePosition.hasSameParentAs( b.position ) ) {
a.howMany += b.howMany;
}
a.sourcePosition = a.sourcePosition._getTransformedByInsertOperation( b );
a.targetPosition = a.targetPosition._getTransformedByInsertOperation( b );
return [ a ];
} );
setTransformation( _mergeoperation__WEBPACK_IMPORTED_MODULE_6__["default"], _mergeoperation__WEBPACK_IMPORTED_MODULE_6__["default"], ( a, b, context ) => {
// Case 1:
//
// Same merge operations.
//
// Both operations have same source and target positions. So the element already got merged and there is
// theoretically nothing to do.
//
if ( a.sourcePosition.isEqual( b.sourcePosition ) && a.targetPosition.isEqual( b.targetPosition ) ) {
// There are two ways that we can provide a do-nothing operation.
//
// First is simply a NoOperation instance. We will use it if `b` operation was not undone.
//
// Second is a merge operation that has the source operation in the merged element - in the graveyard -
// same target position and `howMany` equal to `0`. So it is basically merging an empty element from graveyard
// which is almost the same as NoOperation.
//
// This way the merge operation can be later transformed by split operation
// to provide correct undo. This will be used if `b` operation was undone (only then it is correct).
//
if ( !context.bWasUndone ) {
return [ new _nooperation__WEBPACK_IMPORTED_MODULE_8__["default"]( 0 ) ];
} else {
const path = b.graveyardPosition.path.slice();
path.push( 0 );
a.sourcePosition = new _position__WEBPACK_IMPORTED_MODULE_10__["default"]( b.graveyardPosition.root, path );
a.howMany = 0;
return [ a ];
}
}
// Case 2:
//
// Same merge source position but different target position.
//
// This can happen during collaboration. For example, if one client merged a paragraph to the previous paragraph
// and the other person removed that paragraph and merged the same paragraph to something before:
//
// Client A:
// <p>Foo</p><p>Bar</p><p>[]Xyz</p>
// <p>Foo</p><p>BarXyz</p>
//
// Client B:
// <p>Foo</p>[<p>Bar</p>]<p>Xyz</p>
// <p>Foo</p><p>[]Xyz</p>
// <p>FooXyz</p>
//
// In this case we need to decide where finally "Xyz" will land:
//
// <p>FooXyz</p> graveyard: <p>Bar</p>
// <p>Foo</p> graveyard: <p>BarXyz</p>
//
// Let's move it in a way so that a merge operation that does not target to graveyard is more important so that
// nodes does not end up in the graveyard. It makes sense. Both for Client A and for Client B "Xyz" finally did not
// end up in the graveyard (see above).
//
// If neither or both operations point to graveyard, then let `aIsStrong` decide.
//
if (
a.sourcePosition.isEqual( b.sourcePosition ) && !a.targetPosition.isEqual( b.targetPosition ) &&
!context.bWasUndone && context.abRelation != 'splitAtSource'
) {
const aToGraveyard = a.targetPosition.root.rootName == '$graveyard';
const bToGraveyard = b.targetPosition.root.rootName == '$graveyard';
// If `aIsWeak` it means that `a` points to graveyard while `b` doesn't. Don't move nodes then.
const aIsWeak = aToGraveyard && !bToGraveyard;
// If `bIsWeak` it means that `b` points to graveyard while `a` doesn't. Force moving nodes then.
const bIsWeak = bToGraveyard && !aToGraveyard;
// Force move if `b` is weak or neither operation is weak but `a` is stronger through `context.aIsStrong`.
const forceMove = bIsWeak || ( !aIsWeak && context.aIsStrong );
if ( forceMove ) {
const sourcePosition = b.targetPosition._getTransformedByMergeOperation( b );
const targetPosition = a.targetPosition._getTransformedByMergeOperation( b );
return [ new _moveoperation__WEBPACK_IMPORTED_MODULE_4__["default"]( sourcePosition, a.howMany, targetPosition, 0 ) ];
} else {
return [ new _nooperation__WEBPACK_IMPORTED_MODULE_8__["default"]( 0 ) ];
}
}
// The default case.
//
if ( a.sourcePosition.hasSameParentAs( b.targetPosition ) ) {
a.howMany += b.howMany;
}
a.sourcePosition = a.sourcePosition._getTransformedByMergeOperation( b );
a.targetPosition = a.targetPosition._getTransformedByMergeOperation( b );
// Handle positions in graveyard.
// If graveyard positions are same and `a` operation is strong - do not transform.
if ( !a.graveyardPosition.isEqual( b.graveyardPosition ) || !context.aIsStrong ) {
a.graveyardPosition = a.graveyardPosition._getTransformedByMergeOperation( b );
}
return [ a ];
} );
setTransformation( _mergeoperation__WEBPACK_IMPORTED_MODULE_6__["default"], _moveoperation__WEBPACK_IMPORTED_MODULE_4__["default"], ( a, b, context ) => {
// Case 1:
//
// The element to merge got removed.
//
// Merge operation does support merging elements which are not siblings. So it would not be a problem
// from technical point of view. However, if the element was removed, the intention of the user deleting it
// was to have it all deleted, together with its children. From user experience point of view, moving back the
// removed nodes might be unexpected. This means that in this scenario we will block the merging.
//
// The exception of this rule would be if the remove operation was later undone.
//
const removedRange = _range__WEBPACK_IMPORTED_MODULE_9__["default"]._createFromPositionAndShift( b.sourcePosition, b.howMany );
if ( b.type == 'remove' && !context.bWasUndone && !context.forceWeakRemove ) {
if ( a.deletionPosition.hasSameParentAs( b.sourcePosition ) && removedRange.containsPosition( a.sourcePosition ) ) {
return [ new _nooperation__WEBPACK_IMPORTED_MODULE_8__["default"]( 0 ) ];
}
}
// The default case.
//
if ( a.sourcePosition.hasSameParentAs( b.targetPosition ) ) {
a.howMany += b.howMany;
}
if ( a.sourcePosition.hasSameParentAs( b.sourcePosition ) ) {
a.howMany -= b.howMany;
}
a.sourcePosition = a.sourcePosition._getTransformedByMoveOperation( b );
a.targetPosition = a.targetPosition._getTransformedByMoveOperation( b );
// `MergeOperation` graveyard position is like `MoveOperation` target position. It is a position where element(s) will
// be moved. Like in other similar cases, we need to consider the scenario when those positions are same.
// Here, we will treat `MergeOperation` like it is always strong (see `InsertOperation` x `InsertOperation` for comparison).
// This means that we won't transform graveyard position if it is equal to move operation target position.
if ( !a.graveyardPosition.isEqual( b.targetPosition ) ) {
a.graveyardPosition = a.graveyardPosition._getTransformedByMoveOperation( b );
}
return [ a ];
} );
setTransformation( _mergeoperation__WEBPACK_IMPORTED_MODULE_6__["default"], _splitoperation__WEBPACK_IMPORTED_MODULE_7__["default"], ( a, b, context ) => {
if ( b.graveyardPosition ) {
// If `b` operation defines graveyard position, a node from graveyard will be moved. This means that we need to
// transform `a.graveyardPosition` accordingly.
a.graveyardPosition = a.graveyardPosition._getTransformedByDeletion( b.graveyardPosition, 1 );
// This is a scenario foreseen in `MergeOperation` x `MergeOperation`, with two identical merge operations.
//
// So, there was `MergeOperation` x `MergeOperation` transformation earlier. Now, `a` is a merge operation which
// source position is in graveyard. Interestingly, split operation wants to use the node to be merged by `a`. This
// means that `b` is undoing that merge operation from earlier, which caused `a` to be in graveyard.
//
// If that's the case, at this point, we will only "fix" `a.howMany`. It was earlier set to `0` in
// `MergeOperation` x `MergeOperation` transformation. Later transformations in this function will change other
// properties.
//
if ( a.deletionPosition.isEqual( b.graveyardPosition ) ) {
a.howMany = b.howMany;
}
}
// Case 1:
//
// Merge operation moves nodes to the place where split happens.
// This is a classic situation when there are two paragraphs, and there is a split (enter) after the first
// paragraph and there is a merge (delete) at the beginning of the second paragraph:
//
// <p>Foo{}</p><p>[]Bar</p>.
//
// Split is after `Foo`, while merge is from `Bar` to the end of `Foo`.
//
// State after split:
// <p>Foo</p><p></p><p>Bar</p>
//
// Now, `Bar` should be merged to the new paragraph:
// <p>Foo</p><p>Bar</p>
//
// Instead of merging it to the original paragraph:
// <p>FooBar</p><p></p>
//
// This means that `targetPosition` needs to be transformed. This is the default case though.
// For example, if the split would be after `F`, `targetPosition` should also be transformed.
//
// There are three exceptions, though, when we want to keep `targetPosition` as it was.
//
// First exception is when the merge target position is inside an element (not at the end, as usual). This
// happens when the merge operation earlier was transformed by "the same" merge operation. If merge operation
// targets inside the element we want to keep the original target position (and not transform it) because
// we have additional context telling us that we want to merge to the original element. We can check if the
// merge operation points inside element by checking what is `SplitOperation#howMany`. Since merge target position
// is same as split position, if `howMany` is non-zero, it means that the merge target position is inside an element.
//
// Second exception is when the element to merge is in the graveyard and split operation uses it. In that case
// if target position would be transformed, the merge operation would target at the source position:
//
// root: <p>Foo</p> graveyard: <p></p>
//
// SplitOperation: root [ 0, 3 ] using graveyard [ 0 ] (howMany = 0)
// MergeOperation: graveyard [ 0, 0 ] -> root [ 0, 3 ] (howMany = 0)
//
// Since split operation moves the graveyard node back to the root, the merge operation source position changes.
// We would like to merge from the empty <p> to the "Foo" <p>:
//
// root: <p>Foo</p><p></p> graveyard:
//
// MergeOperation#sourcePosition = root [ 1, 0 ]
//
// If `targetPosition` is transformed, it would become root [ 1, 0 ] as well. It has to be kept as it was.
//
// Third exception is connected with relations. If this happens during undo and we have explicit information
// that target position has not been affected by the operation which is undone by this split then this split should
// not move the target position either.
//
if ( a.targetPosition.isEqual( b.splitPosition ) ) {
const mergeInside = b.howMany != 0;
const mergeSplittingElement = b.graveyardPosition && a.deletionPosition.isEqual( b.graveyardPosition );
if ( mergeInside || mergeSplittingElement || context.abRelation == 'mergeTargetNotMoved' ) {
a.sourcePosition = a.sourcePosition._getTransformedBySplitOperation( b );
return [ a ];
}
}
// Case 2:
//
// Merge source is at the same position as split position. This sometimes happen, mostly during undo.
// The decision here is mostly to choose whether merge source position should stay where it is (so it will be at the end of the
// split element) or should be move to the beginning of the new element.
//
if ( a.sourcePosition.isEqual( b.splitPosition ) ) {
// Use context to check if `SplitOperation` is not undoing a merge operation, that didn't change the `a` operation.
// This scenario happens the undone merge operation moved nodes at the source position of `a` operation.
// In that case `a` operation source position should stay where it is.
if ( context.abRelation == 'mergeSourceNotMoved' ) {
a.howMany = 0;
a.targetPosition = a.targetPosition._getTransformedBySplitOperation( b );
return [ a ];
}
// This merge operation might have been earlier transformed by a merge operation which both merged the same element.
// See that case in `MergeOperation` x `MergeOperation` transformation. In that scenario, if the merge operation has been undone,
// the special case is not applied.
//
// Now, the merge operation is transformed by the split which has undone that previous merge operation.
// So now we are fixing situation which was skipped in `MergeOperation` x `MergeOperation` case.
//
if ( context.abRelation == 'mergeSameElement' || a.sourcePosition.offset > 0 ) {
a.sourcePosition = b.moveTargetPosition.clone();
a.targetPosition = a.targetPosition._getTransformedBySplitOperation( b );
return [ a ];
}
}
// The default case.
//
if ( a.sourcePosition.hasSameParentAs( b.splitPosition ) ) {
a.howMany = b.splitPosition.offset;
}
a.sourcePosition = a.sourcePosition._getTransformedBySplitOperation( b );
a.targetPosition = a.targetPosition._getTransformedBySplitOperation( b );
return [ a ];
} );
// -----------------------
setTransformation( _moveoperation__WEBPACK_IMPORTED_MODULE_4__["default"], _insertoperation__WEBPACK_IMPORTED_MODULE_0__["default"], ( a, b ) => {
const moveRange = _range__WEBPACK_IMPORTED_MODULE_9__["default"]._createFromPositionAndShift( a.sourcePosition, a.howMany );
const transformed = moveRange._getTransformedByInsertOperation( b, false )[ 0 ];
a.sourcePosition = transformed.start;
a.howMany = transformed.end.offset - transformed.start.offset;
// See `InsertOperation` x `MoveOperation` transformation for details on this case.
//
// In summary, both operations point to the same place, so the order of nodes needs to be decided.
// `MoveOperation` is considered weaker, so it is always transformed, unless there was a certain relation
// between operations.
//
if ( !a.targetPosition.isEqual( b.position ) ) {
a.targetPosition = a.targetPosition._getTransformedByInsertOperation( b );
}
return [ a ];
} );
setTransformation( _moveoperation__WEBPACK_IMPORTED_MODULE_4__["default"], _moveoperation__WEBPACK_IMPORTED_MODULE_4__["default"], ( a, b, context ) => {
//
// Setting and evaluating some variables that will be used in special cases and default algorithm.
//
// Create ranges from `MoveOperations` properties.
const rangeA = _range__WEBPACK_IMPORTED_MODULE_9__["default"]._createFromPositionAndShift( a.sourcePosition, a.howMany );
const rangeB = _range__WEBPACK_IMPORTED_MODULE_9__["default"]._createFromPositionAndShift( b.sourcePosition, b.howMany );
// Assign `context.aIsStrong` to a different variable, because the value may change during execution of
// this algorithm and we do not want to override original `context.aIsStrong` that will be used in later transformations.
let aIsStrong = context.aIsStrong;
// This will be used to decide the order of nodes if both operations target at the same position.
// By default, use strong/weak operation mechanism.
let insertBefore = !context.aIsStrong;
// If the relation is set, then use it to decide nodes order.
if ( context.abRelation == 'insertBefore' || context.baRelation == 'insertAfter' ) {
insertBefore = true;
} else if ( context.abRelation == 'insertAfter' || context.baRelation == 'insertBefore' ) {
insertBefore = false;
}
// `a.targetPosition` could be affected by the `b` operation. We will transform it.
let newTargetPosition;
if ( a.targetPosition.isEqual( b.targetPosition ) && insertBefore ) {
newTargetPosition = a.targetPosition._getTransformedByDeletion(
b.sourcePosition,
b.howMany
);
} else {
newTargetPosition = a.targetPosition._getTransformedByMove(
b.sourcePosition,
b.targetPosition,
b.howMany
);
}
//
// Special case #1 + mirror.
//
// Special case when both move operations' target positions are inside nodes that are
// being moved by the other move operation. So in other words, we move ranges into inside of each other.
// This case can't be solved reasonably (on the other hand, it should not happen often).
if ( _moveTargetIntoMovedRange( a, b ) && _moveTargetIntoMovedRange( b, a ) ) {
// Instead of transforming operation, we return a reverse of the operation that we transform by.
// So when the results of this "transformation" will be applied, `b` MoveOperation will get reversed.
return [ b.getReversed() ];
}
//
// End of special case #1.
//
//
// Special case #2.
//
// Check if `b` operation targets inside `rangeA`.
const bTargetsToA = rangeA.containsPosition( b.targetPosition );
// If `b` targets to `rangeA` and `rangeA` contains `rangeB`, `b` operation has no influence on `a` operation.
// You might say that operation `b` is captured inside operation `a`.
if ( bTargetsToA && rangeA.containsRange( rangeB, true ) ) {
// There is a mini-special case here, where `rangeB` is on other level than `rangeA`. That's why
// we need to transform `a` operation anyway.
rangeA.start = rangeA.start._getTransformedByMove( b.sourcePosition, b.targetPosition, b.howMany );
rangeA.end = rangeA.end._getTransformedByMove( b.sourcePosition, b.targetPosition, b.howMany );
return _makeMoveOperationsFromRanges( [ rangeA ], newTargetPosition );
}
//
// Special case #2 mirror.
//
const aTargetsToB = rangeB.containsPosition( a.targetPosition );
if ( aTargetsToB && rangeB.containsRange( rangeA, true ) ) {
// `a` operation is "moved together" with `b` operation.
// Here, just move `rangeA` "inside" `rangeB`.
rangeA.start = rangeA.start._getCombined( b.sourcePosition, b.getMovedRangeStart() );
rangeA.end = rangeA.end._getCombined( b.sourcePosition, b.getMovedRangeStart() );
return _makeMoveOperationsFromRanges( [ rangeA ], newTargetPosition );
}
//
// End of special case #2.
//
//
// Special case #3 + mirror.
//
// `rangeA` has a node which is an ancestor of `rangeB`. In other words, `rangeB` is inside `rangeA`
// but not on the same tree level. In such case ranges have common part but we have to treat it
// differently, because in such case those ranges are not really conflicting and should be treated like
// two separate ranges. Also we have to discard two difference parts.
const aCompB = (0,_ckeditor_ckeditor5_utils_src_comparearrays__WEBPACK_IMPORTED_MODULE_11__["default"])( a.sourcePosition.getParentPath(), b.sourcePosition.getParentPath() );
if ( aCompB == 'prefix' || aCompB == 'extension' ) {
// Transform `rangeA` by `b` operation and make operation out of it, and that's all.
// Note that this is a simplified version of default case, but here we treat the common part (whole `rangeA`)
// like a one difference part.
rangeA.start = rangeA.start._getTransformedByMove( b.sourcePosition, b.targetPosition, b.howMany );
rangeA.end = rangeA.end._getTransformedByMove( b.sourcePosition, b.targetPosition, b.howMany );
return _makeMoveOperationsFromRanges( [ rangeA ], newTargetPosition );
}
//
// End of special case #3.
//
//
// Default case - ranges are on the same level or are not connected with each other.
//
// Modifier for default case.
// Modifies `aIsStrong` flag in certain conditions.
//
// If only one of operations is a remove operation, we force remove operation to be the "stronger" one
// to provide more expected results.
if ( a.type == 'remove' && b.type != 'remove' && !context.aWasUndone && !context.forceWeakRemove ) {
aIsStrong = true;
} else if ( a.type != 'remove' && b.type == 'remove' && !context.bWasUndone && !context.forceWeakRemove ) {
aIsStrong = false;
}
// Handle operation's source ranges - check how `rangeA` is affected by `b` operation.
// This will aggregate transformed ranges.
const ranges = [];
// Get the "difference part" of `a` operation source range.
// This is an array with one or two ranges. Two ranges if `rangeB` is inside `rangeA`.
const difference = rangeA.getDifference( rangeB );
for ( const range of difference ) {
// Transform those ranges by `b` operation. For example if `b` moved range from before those ranges, fix those ranges.
range.start = range.start._getTransformedByDeletion( b.sourcePosition, b.howMany );
range.end = range.end._getTransformedByDeletion( b.sourcePosition, b.howMany );
// If `b` operation targets into `rangeA` on the same level, spread `rangeA` into two ranges.
const shouldSpread = (0,_ckeditor_ckeditor5_utils_src_comparearrays__WEBPACK_IMPORTED_MODULE_11__["default"])( range.start.getParentPath(), b.getMovedRangeStart().getParentPath() ) == 'same';
const newRanges = range._getTransformedByInsertion( b.getMovedRangeStart(), b.howMany, shouldSpread );
ranges.push( ...newRanges );
}
// Then, we have to manage the "common part" of both move ranges.
const common = rangeA.getIntersection( rangeB );
if ( common !== null && aIsStrong ) {
// Calculate the new position of that part of original range.
common.start = common.start._getCombined( b.sourcePosition, b.getMovedRangeStart() );
common.end = common.end._getCombined( b.sourcePosition, b.getMovedRangeStart() );
// Take care of proper range order.
//
// Put `common` at appropriate place. Keep in mind that we are interested in original order.
// Basically there are only three cases: there is zero, one or two difference ranges.
//
// If there is zero difference ranges, just push `common` in the array.
if ( ranges.length === 0 ) {
ranges.push( common );
}
// If there is one difference range, we need to check whether common part was before it or after it.
else if ( ranges.length == 1 ) {
if ( rangeB.start.isBefore( rangeA.start ) || rangeB.start.isEqual( rangeA.start ) ) {
ranges.unshift( common );
} else {
ranges.push( common );
}
}
// If there are more ranges (which means two), put common part between them. This is the only scenario
// where there could be two difference ranges so we don't have to make any comparisons.
else {
ranges.splice( 1, 0, common );
}
}
if ( ranges.length === 0 ) {
// If there are no "source ranges", nothing should be changed.
// Note that this can happen only if `aIsStrong == false` and `rangeA.isEqual( rangeB )`.
return [ new _nooperation__WEBPACK_IMPORTED_MODULE_8__["default"]( a.baseVersion ) ];
}
return _makeMoveOperationsFromRanges( ranges, newTargetPosition );
} );
setTransformation( _moveoperation__WEBPACK_IMPORTED_MODULE_4__["default"], _splitoperation__WEBPACK_IMPORTED_MODULE_7__["default"], ( a, b, context ) => {
let newTargetPosition = a.targetPosition.clone();
// Do not transform if target position is same as split insertion position and this split comes from undo.
// This should be done on relations but it is too much work for now as it would require relations working in collaboration.
// We need to make a decision how we will resolve such conflict and this is less harmful way.
if ( !a.targetPosition.isEqual( b.insertionPosition ) || !b.graveyardPosition || context.abRelation == 'moveTargetAfter' ) {
newTargetPosition = a.targetPosition._getTransformedBySplitOperation( b );
}
// Case 1:
//
// Last element in the moved range got split.
//
// In this case the default range transformation will not work correctly as the element created by
// split operation would be outside the range. The range to move needs to be fixed manually.
//
const moveRange = _range__WEBPACK_IMPORTED_MODULE_9__["default"]._createFromPositionAndShift( a.sourcePosition, a.howMany );
if ( moveRange.end.isEqual( b.insertionPosition ) ) {
// Do it only if this is a "natural" split, not a one that comes from undo.
// If this is undo split, only `targetPosition` needs to be changed (if the move is a remove).
if ( !b.graveyardPosition ) {
a.howMany++;
}
a.targetPosition = newTargetPosition;
return [ a ];
}
// Case 2:
//
// Split happened between the moved nodes. In this case two ranges to move need to be generated.
//
// Characters `ozba` are moved to the end of paragraph `Xyz` but split happened.
// <p>F[oz|ba]r</p><p>Xyz</p>
//
// After split:
// <p>F[oz</p><p>ba]r</p><p>Xyz</p>
//
// Correct ranges:
// <p>F[oz]</p><p>[ba]r</p><p>Xyz</p>
//
// After move:
// <p>F</p><p>r</p><p>Xyzozba</p>
//
if ( moveRange.start.hasSameParentAs( b.splitPosition ) && moveRange.containsPosition( b.splitPosition ) ) {
let rightRange = new _range__WEBPACK_IMPORTED_MODULE_9__["default"]( b.splitPosition, moveRange.end );
rightRange = rightRange._getTransformedBySplitOperation( b );
const ranges = [
new _range__WEBPACK_IMPORTED_MODULE_9__["default"]( moveRange.start, b.splitPosition ),
rightRange
];
return _makeMoveOperationsFromRanges( ranges, newTargetPosition );
}
// Case 3:
//
// Move operation targets at the split position. We need to decide if the nodes should be inserted
// at the end of the split element or at the beginning of the new element.
//
if ( a.targetPosition.isEqual( b.splitPosition ) && context.abRelation == 'insertAtSource' ) {
newTargetPosition = b.moveTargetPosition;
}
// Case 4:
//
// Move operation targets just after the split element. We need to decide if the nodes should be inserted
// between two parts of split element, or after the new element.
//
// Split at `|`, while move operation moves `<p>Xyz</p>` and targets at `^`:
// <p>Foo|bar</p>^<p>baz</p>
// <p>Foo</p>^<p>bar</p><p>baz</p> or <p>Foo</p><p>bar</p>^<p>baz</p>?
//
// If there is no contextual information between operations (for example, they come from collaborative
// editing), we don't want to put some unrelated content (move) between parts of related content (split parts).
// However, if the split is from undo, in the past, the moved content might be targeting between the
// split parts, meaning that was exactly user's intention:
//
// <p>Foo</p>^<p>bar</p> <--- original situation, in "past".
// <p>Foobar</p>^ <--- after merge target position is transformed.
// <p>Foo|bar</p>^ <--- then the merge is undone, and split happens, which leads us to current situation.
//
// In this case it is pretty clear that the intention was to put new paragraph between those nodes,
// so we need to transform accordingly. We can detect this scenario thanks to relations.
//
if ( a.targetPosition.isEqual( b.insertionPosition ) && context.abRelation == 'insertBetween' ) {
newTargetPosition = a.targetPosition;
}
// The default case.
//
const transformed = moveRange._getTransformedBySplitOperation( b );
const ranges = [ transformed ];
// Case 5:
//
// Moved range contains graveyard element used by split operation. Add extra move operation to the result.
//
if ( b.graveyardPosition ) {
const movesGraveyardElement = moveRange.start.isEqual( b.graveyardPosition ) || moveRange.containsPosition( b.graveyardPosition );
if ( a.howMany > 1 && movesGraveyardElement && !context.aWasUndone ) {
ranges.push( _range__WEBPACK_IMPORTED_MODULE_9__["default"]._createFromPositionAndShift( b.insertionPosition, 1 ) );
}
}
return _makeMoveOperationsFromRanges( ranges, newTargetPosition );
} );
setTransformation( _moveoperation__WEBPACK_IMPORTED_MODULE_4__["default"], _mergeoperation__WEBPACK_IMPORTED_MODULE_6__["default"], ( a, b, context ) => {
const movedRange = _range__WEBPACK_IMPORTED_MODULE_9__["default"]._createFromPositionAndShift( a.sourcePosition, a.howMany );
if ( b.deletionPosition.hasSameParentAs( a.sourcePosition ) && movedRange.containsPosition( b.sourcePosition ) ) {
if ( a.type == 'remove' && !context.forceWeakRemove ) {
// Case 1:
//
// The element to remove got merged.
//
// Merge operation does support merging elements which are not siblings. So it would not be a problem
// from technical point of view. However, if the element was removed, the intention of the user
// deleting it was to have it all deleted. From user experience point of view, moving back the
// removed nodes might be unexpected. This means that in this scenario we will reverse merging and remove the element.
//
if ( !context.aWasUndone ) {
const results = [];
let gyMoveSource = b.graveyardPosition.clone();
let splitNodesMoveSource = b.targetPosition._getTransformedByMergeOperation( b );
if ( a.howMany > 1 ) {
results.push( new _moveoperation__WEBPACK_IMPORTED_MODULE_4__["default"]( a.sourcePosition, a.howMany - 1, a.targetPosition, 0 ) );
gyMoveSource = gyMoveSource._getTransformedByMove( a.sourcePosition, a.targetPosition, a.howMany - 1 );
splitNodesMoveSource = splitNodesMoveSource._getTransformedByMove( a.sourcePosition, a.targetPosition, a.howMany - 1 );
}
const gyMoveTarget = b.deletionPosition._getCombined( a.sourcePosition, a.targetPosition );
const gyMove = new _moveoperation__WEBPACK_IMPORTED_MODULE_4__["default"]( gyMoveSource, 1, gyMoveTarget, 0 );
const splitNodesMoveTargetPath = gyMove.getMovedRangeStart().path.slice();
splitNodesMoveTargetPath.push( 0 );
const splitNodesMoveTarget = new _position__WEBPACK_IMPORTED_MODULE_10__["default"]( gyMove.targetPosition.root, splitNodesMoveTargetPath );
splitNodesMoveSource = splitNodesMoveSource._getTransformedByMove( gyMoveSource, gyMoveTarget, 1 );
const splitNodesMove = new _moveoperation__WEBPACK_IMPORTED_MODULE_4__["default"]( splitNodesMoveSource, b.howMany, splitNodesMoveTarget, 0 );
results.push( gyMove );
results.push( splitNodesMove );
return results;
}
} else {
// Case 2:
//
// The element to move got merged and it was the only element to move.
// In this case just don't do anything, leave the node in the graveyard. Without special case
// it would be a move operation that moves 0 nodes, so maybe it is better just to return no-op.
//
if ( a.howMany == 1 ) {
if ( !context.bWasUndone ) {
return [ new _nooperation__WEBPACK_IMPORTED_MODULE_8__["default"]( 0 ) ];
} else {
a.sourcePosition = b.graveyardPosition.clone();
a.targetPosition = a.targetPosition._getTransformedByMergeOperation( b );
return [ a ];
}
}
}
}
// The default case.
//
const moveRange = _range__WEBPACK_IMPORTED_MODULE_9__["default"]._createFromPositionAndShift( a.sourcePosition, a.howMany );
const transformed = moveRange._getTransformedByMergeOperation( b );
a.sourcePosition = transformed.start;
a.howMany = transformed.end.offset - transformed.start.offset;
a.targetPosition = a.targetPosition._getTransformedByMergeOperation( b );
return [ a ];
} );
// -----------------------
setTransformation( _renameoperation__WEBPACK_IMPORTED_MODULE_2__["default"], _insertoperation__WEBPACK_IMPORTED_MODULE_0__["default"], ( a, b ) => {
a.position = a.position._getTransformedByInsertOperation( b );
return [ a ];
} );
setTransformation( _renameoperation__WEBPACK_IMPORTED_MODULE_2__["default"], _mergeoperation__WEBPACK_IMPORTED_MODULE_6__["default"], ( a, b ) => {
// Case 1:
//
// Element to rename got merged, so it was moved to `b.graveyardPosition`.
//
if ( a.position.isEqual( b.deletionPosition ) ) {
a.position = b.graveyardPosition.clone();
a.position.stickiness = 'toNext';
return [ a ];
}
a.position = a.position._getTransformedByMergeOperation( b );
return [ a ];
} );
setTransformation( _renameoperation__WEBPACK_IMPORTED_MODULE_2__["default"], _moveoperation__WEBPACK_IMPORTED_MODULE_4__["default"], ( a, b ) => {
a.position = a.position._getTransformedByMoveOperation( b );
return [ a ];
} );
setTransformation( _renameoperation__WEBPACK_IMPORTED_MODULE_2__["default"], _renameoperation__WEBPACK_IMPORTED_MODULE_2__["default"], ( a, b, context ) => {
if ( a.position.isEqual( b.position ) ) {
if ( context.aIsStrong ) {
a.oldName = b.newName;
} else {
return [ new _nooperation__WEBPACK_IMPORTED_MODULE_8__["default"]( 0 ) ];
}
}
return [ a ];
} );
setTransformation( _renameoperation__WEBPACK_IMPORTED_MODULE_2__["default"], _splitoperation__WEBPACK_IMPORTED_MODULE_7__["default"], ( a, b ) => {
// Case 1:
//
// The element to rename has been split. In this case, the new element should be also renamed.
//
// User decides to change the paragraph to a list item:
// <paragraph>Foobar</paragraph>
//
// However, in meantime, split happens:
// <paragraph>Foo</paragraph><paragraph>bar</paragraph>
//
// As a result, rename both elements:
// <listItem>Foo</listItem><listItem>bar</listItem>
//
const renamePath = a.position.path;
const splitPath = b.splitPosition.getParentPath();
if ( (0,_ckeditor_ckeditor5_utils_src_comparearrays__WEBPACK_IMPORTED_MODULE_11__["default"])( renamePath, splitPath ) == 'same' && !b.graveyardPosition ) {
const extraRename = new _renameoperation__WEBPACK_IMPORTED_MODULE_2__["default"]( a.position.getShiftedBy( 1 ), a.oldName, a.newName, 0 );
return [ a, extraRename ];
}
// The default case.
//
a.position = a.position._getTransformedBySplitOperation( b );
return [ a ];
} );
// -----------------------
setTransformation( _rootattributeoperation__WEBPACK_IMPORTED_MODULE_5__["default"], _rootattributeoperation__WEBPACK_IMPORTED_MODULE_5__["default"], ( a, b, context ) => {
if ( a.root === b.root && a.key === b.key ) {
if ( !context.aIsStrong || a.newValue === b.newValue ) {
return [ new _nooperation__WEBPACK_IMPORTED_MODULE_8__["default"]( 0 ) ];
} else {
a.oldValue = b.newValue;
}
}
return [ a ];
} );
// -----------------------
setTransformation( _splitoperation__WEBPACK_IMPORTED_MODULE_7__["default"], _insertoperation__WEBPACK_IMPORTED_MODULE_0__["default"], ( a, b ) => {
// The default case.
//
if ( a.splitPosition.hasSameParentAs( b.position ) && a.splitPosition.offset < b.position.offset ) {
a.howMany += b.howMany;
}
a.splitPosition = a.splitPosition._getTransformedByInsertOperation( b );
a.insertionPosition = a.insertionPosition._getTransformedByInsertOperation( b );
return [ a ];
} );
setTransformation( _splitoperation__WEBPACK_IMPORTED_MODULE_7__["default"], _mergeoperation__WEBPACK_IMPORTED_MODULE_6__["default"], ( a, b, context ) => {
// Case 1:
//
// Split element got merged. If two different elements were merged, clients will have different content.
//
// Example. Merge at `{}`, split at `[]`:
// <heading>Foo</heading>{}<paragraph>B[]ar</paragraph>
//
// On merge side it will look like this:
// <heading>FooB[]ar</heading>
// <heading>FooB</heading><heading>ar</heading>
//
// On split side it will look like this:
// <heading>Foo</heading>{}<paragraph>B</paragraph><paragraph>ar</paragraph>
// <heading>FooB</heading><paragraph>ar</paragraph>
//
// Clearly, the second element is different for both clients.
//
// We could use the removed merge element from graveyard as a split element but then clients would have a different
// model state (in graveyard), because the split side client would still have an element in graveyard (removed by merge).
//
// To overcome this, in `SplitOperation` x `MergeOperation` transformation we will add additional `SplitOperation`
// in the graveyard, which will actually clone the merged-and-deleted element. Then, that cloned element will be
// used for splitting. Example below.
//
// Original state:
// <heading>Foo</heading>{}<paragraph>B[]ar</paragraph>
//
// Merge side client:
//
// After merge:
// <heading>FooB[]ar</heading> graveyard: <paragraph></paragraph>
//
// Extra split:
// <heading>FooB[]ar</heading> graveyard: <paragraph></paragraph><paragraph></paragraph>
//
// Use the "cloned" element from graveyard:
// <heading>FooB</heading><paragraph>ar</paragraph> graveyard: <paragraph></paragraph>
//
// Split side client:
//
// After split:
// <heading>Foo</heading>{}<paragraph>B</paragraph><paragraph>ar</paragraph>
//
// After merge:
// <heading>FooB</heading><paragraph>ar</paragraph> graveyard: <paragraph></paragraph>
//
// This special case scenario only applies if the original split operation clones the split element.
// If the original split operation has `graveyardPosition` set, it all doesn't have sense because split operation
// knows exactly which element it should use. So there would be no original problem with different contents.
//
// Additionally, the special case applies only if the merge wasn't already undone.
//
if ( !a.graveyardPosition && !context.bWasUndone && a.splitPosition.hasSameParentAs( b.sourcePosition ) ) {
const splitPath = b.graveyardPosition.path.slice();
splitPath.push( 0 );
const splitPosition = new _position__WEBPACK_IMPORTED_MODULE_10__["default"]( b.graveyardPosition.root, splitPath );
const insertionPosition = _splitoperation__WEBPACK_IMPORTED_MODULE_7__["default"].getInsertionPosition( new _position__WEBPACK_IMPORTED_MODULE_10__["default"]( b.graveyardPosition.root, splitPath ) );
const additionalSplit = new _splitoperation__WEBPACK_IMPORTED_MODULE_7__["default"]( splitPosition, 0, insertionPosition, null, 0 );
a.splitPosition = a.splitPosition._getTransformedByMergeOperation( b );
a.insertionPosition = _splitoperation__WEBPACK_IMPORTED_MODULE_7__["default"].getInsertionPosition( a.splitPosition );
a.graveyardPosition = additionalSplit.insertionPosition.clone();
a.graveyardPosition.stickiness = 'toNext';
return [ additionalSplit, a ];
}
// The default case.
//
if ( a.splitPosition.hasSameParentAs( b.deletionPosition ) && !a.splitPosition.isAfter( b.deletionPosition ) ) {
a.howMany--;
}
if ( a.splitPosition.hasSameParentAs( b.targetPosition ) ) {
a.howMany += b.howMany;
}
a.splitPosition = a.splitPosition._getTransformedByMergeOperation( b );
a.insertionPosition = _splitoperation__WEBPACK_IMPORTED_MODULE_7__["default"].getInsertionPosition( a.splitPosition );
if ( a.graveyardPosition ) {
a.graveyardPosition = a.graveyardPosition._getTransformedByMergeOperation( b );
}
return [ a ];
} );
setTransformation( _splitoperation__WEBPACK_IMPORTED_MODULE_7__["default"], _moveoperation__WEBPACK_IMPORTED_MODULE_4__["default"], ( a, b, context ) => {
const rangeToMove = _range__WEBPACK_IMPORTED_MODULE_9__["default"]._createFromPositionAndShift( b.sourcePosition, b.howMany );
if ( a.graveyardPosition ) {
// Case 1:
//
// Split operation graveyard node was moved. In this case move operation is stronger. Since graveyard element
// is already moved to the correct position, we need to only move the nodes after the split position.
// This will be done by `MoveOperation` instead of `SplitOperation`.
//
const gyElementMoved = rangeToMove.start.isEqual( a.graveyardPosition ) || rangeToMove.containsPosition( a.graveyardPosition );
if ( !context.bWasUndone && gyElementMoved ) {
const sourcePosition = a.splitPosition._getTransformedByMoveOperation( b );
const newParentPosition = a.graveyardPosition._getTransformedByMoveOperation( b );
const newTargetPath = newParentPosition.path.slice();
newTargetPath.push( 0 );
const newTargetPosition = new _position__WEBPACK_IMPORTED_MODULE_10__["default"]( newParentPosition.root, newTargetPath );
const moveOp = new _moveoperation__WEBPACK_IMPORTED_MODULE_4__["default"]( sourcePosition, a.howMany, newTargetPosition, 0 );
return [ moveOp ];
}
a.graveyardPosition = a.graveyardPosition._getTransformedByMoveOperation( b );
}
// Case 2:
//
// Split is at a position where nodes were moved.
//
// This is a scenario described in `MoveOperation` x `SplitOperation` transformation but from the
// "split operation point of view".
//
const splitAtTarget = a.splitPosition.isEqual( b.targetPosition );
if ( splitAtTarget && ( context.baRelation == 'insertAtSource' || context.abRelation == 'splitBefore' ) ) {
a.howMany += b.howMany;
a.splitPosition = a.splitPosition._getTransformedByDeletion( b.sourcePosition, b.howMany );
a.insertionPosition = _splitoperation__WEBPACK_IMPORTED_MODULE_7__["default"].getInsertionPosition( a.splitPosition );
return [ a ];
}
if ( splitAtTarget && context.abRelation && context.abRelation.howMany ) {
const { howMany, offset } = context.abRelation;
a.howMany += howMany;
a.splitPosition = a.splitPosition.getShiftedBy( offset );
return [ a ];
}
// Case 3:
//
// If the split position is inside the moved range, we need to shift the split position to a proper place.
// The position cannot be moved together with moved range because that would result in splitting of an incorrect element.
//
// Characters `bc` should be moved to the second paragraph while split position is between them:
// <paragraph>A[b|c]d</paragraph><paragraph>Xyz</paragraph>
//
// After move, new split position is incorrect:
// <paragraph>Ad</paragraph><paragraph>Xb|cyz</paragraph>
//
// Correct split position:
// <paragraph>A|d</paragraph><paragraph>Xbcyz</paragraph>
//
// After split:
// <paragraph>A</paragraph><paragraph>d</paragraph><paragraph>Xbcyz</paragraph>
//
if ( a.splitPosition.hasSameParentAs( b.sourcePosition ) && rangeToMove.containsPosition( a.splitPosition ) ) {
const howManyRemoved = b.howMany - ( a.splitPosition.offset - b.sourcePosition.offset );
a.howMany -= howManyRemoved;
if ( a.splitPosition.hasSameParentAs( b.targetPosition ) && a.splitPosition.offset < b.targetPosition.offset ) {
a.howMany += b.howMany;
}
a.splitPosition = b.sourcePosition.clone();
a.insertionPosition = _splitoperation__WEBPACK_IMPORTED_MODULE_7__["default"].getInsertionPosition( a.splitPosition );
return [ a ];
}
// The default case.
// Don't change `howMany` if move operation does not really move anything.
//
if ( !b.sourcePosition.isEqual( b.targetPosition ) ) {
if ( a.splitPosition.hasSameParentAs( b.sourcePosition ) && a.splitPosition.offset <= b.sourcePosition.offset ) {
a.howMany -= b.howMany;
}
if ( a.splitPosition.hasSameParentAs( b.targetPosition ) && a.splitPosition.offset < b.targetPosition.offset ) {
a.howMany += b.howMany;
}
}
// Change position stickiness to force a correct transformation.
a.splitPosition.stickiness = 'toNone';
a.splitPosition = a.splitPosition._getTransformedByMoveOperation( b );
a.splitPosition.stickiness = 'toNext';
if ( a.graveyardPosition ) {
a.insertionPosition = a.insertionPosition._getTransformedByMoveOperation( b );
} else {
a.insertionPosition = _splitoperation__WEBPACK_IMPORTED_MODULE_7__["default"].getInsertionPosition( a.splitPosition );
}
return [ a ];
} );
setTransformation( _splitoperation__WEBPACK_IMPORTED_MODULE_7__["default"], _splitoperation__WEBPACK_IMPORTED_MODULE_7__["default"], ( a, b, context ) => {
// Case 1:
//
// Split at the same position.
//
// If there already was a split at the same position as in `a` operation, it means that the intention
// conveyed by `a` operation has already been fulfilled and `a` should not do anything (to avoid double split).
//
// However, there is a difference if these are new splits or splits created by undo. These have different
// intentions. Also splits moving back different elements from graveyard have different intentions. They
// are just different operations.
//
// So we cancel split operation only if it was really identical.
//
// Also, there is additional case, where split operations aren't identical and should not be cancelled, however the
// default transformation is incorrect too.
//
if ( a.splitPosition.isEqual( b.splitPosition ) ) {
if ( !a.graveyardPosition && !b.graveyardPosition ) {
return [ new _nooperation__WEBPACK_IMPORTED_MODULE_8__["default"]( 0 ) ];
}
if ( a.graveyardPosition && b.graveyardPosition && a.graveyardPosition.isEqual( b.graveyardPosition ) ) {
return [ new _nooperation__WEBPACK_IMPORTED_MODULE_8__["default"]( 0 ) ];
}
// Use context to know that the `a.splitPosition` should stay where it is.
// This happens during undo when first a merge operation moved nodes to `a.splitPosition` and now `b` operation undoes that merge.
if ( context.abRelation == 'splitBefore' ) {
// Since split is at the same position, there are no nodes left to split.
a.howMany = 0;
// Note: there was `if ( a.graveyardPosition )` here but it was uncovered in tests and I couldn't find any scenarios for now.
// That would have to be a `SplitOperation` that didn't come from undo but is transformed by operations that were undone.
// It could happen if `context` is enabled in collaboration.
a.graveyardPosition = a.graveyardPosition._getTransformedBySplitOperation( b );
return [ a ];
}
}
// Case 2:
//
// Same node is using to split different elements. This happens in undo when previously same element was merged to
// two different elements. This is described in `MergeOperation` x `MergeOperation` transformation.
//
// In this case we will follow the same logic. We will assume that `insertionPosition` is same for both
// split operations. This might not always be true but in the real cases that were experienced it was. After all,
// if these splits are reverses of merge operations that were merging the same element, then the `insertionPosition`
// should be same for both of those splits.
//
// Again, we will decide which operation is stronger by checking if split happens in graveyard or in non-graveyard root.
//
if ( a.graveyardPosition && b.graveyardPosition && a.graveyardPosition.isEqual( b.graveyardPosition ) ) {
const aInGraveyard = a.splitPosition.root.rootName == '$graveyard';
const bInGraveyard = b.splitPosition.root.rootName == '$graveyard';
// If `aIsWeak` it means that `a` points to graveyard while `b` doesn't. Don't move nodes then.
const aIsWeak = aInGraveyard && !bInGraveyard;
// If `bIsWeak` it means that `b` points to graveyard while `a` doesn't. Force moving nodes then.
const bIsWeak = bInGraveyard && !aInGraveyard;
// Force move if `b` is weak or neither operation is weak but `a` is stronger through `context.aIsStrong`.
const forceMove = bIsWeak || ( !aIsWeak && context.aIsStrong );
if ( forceMove ) {
const result = [];
// First we need to move any nodes split by `b` back to where they were.
// Do it only if `b` actually moved something.
if ( b.howMany ) {
result.push( new _moveoperation__WEBPACK_IMPORTED_MODULE_4__["default"]( b.moveTargetPosition, b.howMany, b.splitPosition, 0 ) );
}
// Then we need to move nodes from `a` split position to their new element.
// Do it only if `a` actually should move something.
if ( a.howMany ) {
result.push( new _moveoperation__WEBPACK_IMPORTED_MODULE_4__["default"]( a.splitPosition, a.howMany, a.moveTargetPosition, 0 ) );
}
return result;
} else {
return [ new _nooperation__WEBPACK_IMPORTED_MODULE_8__["default"]( 0 ) ];
}
}
if ( a.graveyardPosition ) {
a.graveyardPosition = a.graveyardPosition._getTransformedBySplitOperation( b );
}
// Case 3:
//
// Position where operation `b` inserted a new node after split is the same as the operation `a` split position.
// As in similar cases, there is ambiguity if the split should be before the new node (created by `b`) or after.
//
if ( a.splitPosition.isEqual( b.insertionPosition ) && context.abRelation == 'splitBefore' ) {
a.howMany++;
return [ a ];
}
// Case 4:
//
// This is a mirror to the case 2. above.
//
if ( b.splitPosition.isEqual( a.insertionPosition ) && context.baRelation == 'splitBefore' ) {
const newPositionPath = b.insertionPosition.path.slice();
newPositionPath.push( 0 );
const newPosition = new _position__WEBPACK_IMPORTED_MODULE_10__["default"]( b.insertionPosition.root, newPositionPath );
const moveOp = new _moveoperation__WEBPACK_IMPORTED_MODULE_4__["default"]( a.insertionPosition, 1, newPosition, 0 );
return [ a, moveOp ];
}
// The default case.
//
if ( a.splitPosition.hasSameParentAs( b.splitPosition ) && a.splitPosition.offset < b.splitPosition.offset ) {
a.howMany -= b.howMany;
}
a.splitPosition = a.splitPosition._getTransformedBySplitOperation( b );
a.insertionPosition = _splitoperation__WEBPACK_IMPORTED_MODULE_7__["default"].getInsertionPosition( a.splitPosition );
return [ a ];
} );
// Checks whether `MoveOperation` `targetPosition` is inside a node from the moved range of the other `MoveOperation`.
//
// @private
// @param {module:engine/model/operation/moveoperation~MoveOperation} a
// @param {module:engine/model/operation/moveoperation~MoveOperation} b
// @returns {Boolean}
function _moveTargetIntoMovedRange( a, b ) {
return a.targetPosition._getTransformedByDeletion( b.sourcePosition, b.howMany ) === null;
}
// Helper function for `MoveOperation` x `MoveOperation` transformation. Converts given ranges and target position to
// move operations and returns them.
//
// Ranges and target position will be transformed on-the-fly when generating operations.
//
// Given `ranges` should be in the order of how they were in the original transformed operation.
//
// Given `targetPosition` is the target position of the first range from `ranges`.
//
// @private
// @param {Array.<module:engine/model/range~Range>} ranges
// @param {module:engine/model/position~Position} targetPosition
// @returns {Array.<module:engine/model/operation/moveoperation~MoveOperation>}
function _makeMoveOperationsFromRanges( ranges, targetPosition ) {
// At this moment we have some ranges and a target position, to which those ranges should be moved.
// Order in `ranges` array is the go-to order of after transformation.
//
// We are almost done. We have `ranges` and `targetPosition` to make operations from.
// Unfortunately, those operations may affect each other. Precisely, first operation after move
// may affect source range and target position of second and third operation. Same with second
// operation affecting third.
//
// We need to fix those source ranges and target positions once again, before converting `ranges` to operations.
const operations = [];
// Keep in mind that nothing will be transformed if there is just one range in `ranges`.
for ( let i = 0; i < ranges.length; i++ ) {
// Create new operation out of a range and target position.
const range = ranges[ i ];
const op = new _moveoperation__WEBPACK_IMPORTED_MODULE_4__["default"](
range.start,
range.end.offset - range.start.offset,
targetPosition,
0
);
operations.push( op );
// Transform other ranges by the generated operation.
for ( let j = i + 1; j < ranges.length; j++ ) {
// All ranges in `ranges` array should be:
//
// * non-intersecting (these are part of original operation source range), and
// * `targetPosition` does not target into them (opposite would mean that transformed operation targets "inside itself").
//
// This means that the transformation will be "clean" and always return one result.
ranges[ j ] = ranges[ j ]._getTransformedByMove( op.sourcePosition, op.targetPosition, op.howMany )[ 0 ];
}
targetPosition = targetPosition._getTransformedByMove( op.sourcePosition, op.targetPosition, op.howMany );
}
return operations;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/utils.js":
/*!******************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/utils.js ***!
\******************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "_insert": () => (/* binding */ _insert),
/* harmony export */ "_move": () => (/* binding */ _move),
/* harmony export */ "_normalizeNodes": () => (/* binding */ _normalizeNodes),
/* harmony export */ "_remove": () => (/* binding */ _remove),
/* harmony export */ "_setAttribute": () => (/* binding */ _setAttribute)
/* harmony export */ });
/* harmony import */ var _node__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../node */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/node.js");
/* harmony import */ var _text__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../text */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/text.js");
/* harmony import */ var _textproxy__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../textproxy */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/textproxy.js");
/* harmony import */ var _range__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../range */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/range.js");
/* harmony import */ var _documentfragment__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../documentfragment */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/documentfragment.js");
/* harmony import */ var _nodelist__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../nodelist */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/nodelist.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/model/operation/utils
*/
/**
* Contains functions used for composing model tree by {@link module:engine/model/operation/operation~Operation operations}.
* Those functions are built on top of {@link module:engine/model/node~Node node}, and it's child classes', APIs.
*
* @protected
* @namespace utils
*/
/**
* Inserts given nodes at given position.
*
* @protected
* @function module:engine/model/operation/utils~utils.insert
* @param {module:engine/model/position~Position} position Position at which nodes should be inserted.
* @param {module:engine/model/node~NodeSet} nodes Nodes to insert.
* @returns {module:engine/model/range~Range} Range spanning over inserted elements.
*/
function _insert( position, nodes ) {
nodes = _normalizeNodes( nodes );
// We have to count offset before inserting nodes because they can get merged and we would get wrong offsets.
const offset = nodes.reduce( ( sum, node ) => sum + node.offsetSize, 0 );
const parent = position.parent;
// Insertion might be in a text node, we should split it if that's the case.
_splitNodeAtPosition( position );
const index = position.index;
// Insert nodes at given index. After splitting we have a proper index and insertion is between nodes,
// using basic `Element` API.
parent._insertChild( index, nodes );
// Merge text nodes, if possible. Merging is needed only at points where inserted nodes "touch" "old" nodes.
_mergeNodesAtIndex( parent, index + nodes.length );
_mergeNodesAtIndex( parent, index );
return new _range__WEBPACK_IMPORTED_MODULE_3__["default"]( position, position.getShiftedBy( offset ) );
}
/**
* Removed nodes in given range. Only {@link module:engine/model/range~Range#isFlat flat} ranges are accepted.
*
* @protected
* @function module:engine/model/operation/utils~utils._remove
* @param {module:engine/model/range~Range} range Range containing nodes to remove.
* @returns {Array.<module:engine/model/node~Node>}
*/
function _remove( range ) {
if ( !range.isFlat ) {
/**
* Trying to remove a range which starts and ends in different element.
*
* @error operation-utils-remove-range-not-flat
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_6__["default"](
'operation-utils-remove-range-not-flat',
this
);
}
const parent = range.start.parent;
// Range may be inside text nodes, we have to split them if that's the case.
_splitNodeAtPosition( range.start );
_splitNodeAtPosition( range.end );
// Remove the text nodes using basic `Element` API.
const removed = parent._removeChildren( range.start.index, range.end.index - range.start.index );
// Merge text nodes, if possible. After some nodes were removed, node before and after removed range will be
// touching at the position equal to the removed range beginning. We check merging possibility there.
_mergeNodesAtIndex( parent, range.start.index );
return removed;
}
/**
* Moves nodes in given range to given target position. Only {@link module:engine/model/range~Range#isFlat flat} ranges are accepted.
*
* @protected
* @function module:engine/model/operation/utils~utils.move
* @param {module:engine/model/range~Range} sourceRange Range containing nodes to move.
* @param {module:engine/model/position~Position} targetPosition Position to which nodes should be moved.
* @returns {module:engine/model/range~Range} Range containing moved nodes.
*/
function _move( sourceRange, targetPosition ) {
if ( !sourceRange.isFlat ) {
/**
* Trying to move a range which starts and ends in different element.
*
* @error operation-utils-move-range-not-flat
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_6__["default"](
'operation-utils-move-range-not-flat',
this
);
}
const nodes = _remove( sourceRange );
// We have to fix `targetPosition` because model changed after nodes from `sourceRange` got removed and
// that change might have an impact on `targetPosition`.
targetPosition = targetPosition._getTransformedByDeletion( sourceRange.start, sourceRange.end.offset - sourceRange.start.offset );
return _insert( targetPosition, nodes );
}
/**
* Sets given attribute on nodes in given range. The attributes are only set on top-level nodes of the range, not on its children.
*
* @protected
* @function module:engine/model/operation/utils~utils._setAttribute
* @param {module:engine/model/range~Range} range Range containing nodes that should have the attribute set. Must be a flat range.
* @param {String} key Key of attribute to set.
* @param {*} value Attribute value.
*/
function _setAttribute( range, key, value ) {
// Range might start or end in text nodes, so we have to split them.
_splitNodeAtPosition( range.start );
_splitNodeAtPosition( range.end );
// Iterate over all items in the range.
for ( const item of range.getItems( { shallow: true } ) ) {
// Iterator will return `TextProxy` instances but we know that those text proxies will
// always represent full text nodes (this is guaranteed thanks to splitting we did before).
// So, we can operate on those text proxies' text nodes.
const node = item.is( '$textProxy' ) ? item.textNode : item;
if ( value !== null ) {
node._setAttribute( key, value );
} else {
node._removeAttribute( key );
}
// After attributes changing it may happen that some text nodes can be merged. Try to merge with previous node.
_mergeNodesAtIndex( node.parent, node.index );
}
// Try to merge last changed node with it's previous sibling (not covered by the loop above).
_mergeNodesAtIndex( range.end.parent, range.end.index );
}
/**
* Normalizes given object or an array of objects to an array of {@link module:engine/model/node~Node nodes}. See
* {@link module:engine/model/node~NodeSet NodeSet} for details on how normalization is performed.
*
* @protected
* @function module:engine/model/operation/utils~utils.normalizeNodes
* @param {module:engine/model/node~NodeSet} nodes Objects to normalize.
* @returns {Array.<module:engine/model/node~Node>} Normalized nodes.
*/
function _normalizeNodes( nodes ) {
const normalized = [];
if ( !( nodes instanceof Array ) ) {
nodes = [ nodes ];
}
// Convert instances of classes other than Node.
for ( let i = 0; i < nodes.length; i++ ) {
if ( typeof nodes[ i ] == 'string' ) {
normalized.push( new _text__WEBPACK_IMPORTED_MODULE_1__["default"]( nodes[ i ] ) );
} else if ( nodes[ i ] instanceof _textproxy__WEBPACK_IMPORTED_MODULE_2__["default"] ) {
normalized.push( new _text__WEBPACK_IMPORTED_MODULE_1__["default"]( nodes[ i ].data, nodes[ i ].getAttributes() ) );
} else if ( nodes[ i ] instanceof _documentfragment__WEBPACK_IMPORTED_MODULE_4__["default"] || nodes[ i ] instanceof _nodelist__WEBPACK_IMPORTED_MODULE_5__["default"] ) {
for ( const child of nodes[ i ] ) {
normalized.push( child );
}
} else if ( nodes[ i ] instanceof _node__WEBPACK_IMPORTED_MODULE_0__["default"] ) {
normalized.push( nodes[ i ] );
}
// Skip unrecognized type.
}
// Merge text nodes.
for ( let i = 1; i < normalized.length; i++ ) {
const node = normalized[ i ];
const prev = normalized[ i - 1 ];
if ( node instanceof _text__WEBPACK_IMPORTED_MODULE_1__["default"] && prev instanceof _text__WEBPACK_IMPORTED_MODULE_1__["default"] && _haveSameAttributes( node, prev ) ) {
// Doing this instead changing `prev.data` because `data` is readonly.
normalized.splice( i - 1, 2, new _text__WEBPACK_IMPORTED_MODULE_1__["default"]( prev.data + node.data, prev.getAttributes() ) );
i--;
}
}
return normalized;
}
// Checks if nodes before and after given index in given element are {@link module:engine/model/text~Text text nodes} and
// merges them into one node if they have same attributes.
//
// Merging is done by removing two text nodes and inserting a new text node containing data from both merged text nodes.
//
// @private
// @param {module:engine/model/element~Element} element Parent element of nodes to merge.
// @param {Number} index Index between nodes to merge.
function _mergeNodesAtIndex( element, index ) {
const nodeBefore = element.getChild( index - 1 );
const nodeAfter = element.getChild( index );
// Check if both of those nodes are text objects with same attributes.
if ( nodeBefore && nodeAfter && nodeBefore.is( '$text' ) && nodeAfter.is( '$text' ) && _haveSameAttributes( nodeBefore, nodeAfter ) ) {
// Append text of text node after index to the before one.
const mergedNode = new _text__WEBPACK_IMPORTED_MODULE_1__["default"]( nodeBefore.data + nodeAfter.data, nodeBefore.getAttributes() );
// Remove separate text nodes.
element._removeChildren( index - 1, 2 );
// Insert merged text node.
element._insertChild( index - 1, mergedNode );
}
}
// Checks if given position is in a text node, and if so, splits the text node in two text nodes, each of them
// containing a part of original text node.
//
// @private
// @param {module:engine/model/position~Position} position Position at which node should be split.
function _splitNodeAtPosition( position ) {
const textNode = position.textNode;
const element = position.parent;
if ( textNode ) {
const offsetDiff = position.offset - textNode.startOffset;
const index = textNode.index;
element._removeChildren( index, 1 );
const firstPart = new _text__WEBPACK_IMPORTED_MODULE_1__["default"]( textNode.data.substr( 0, offsetDiff ), textNode.getAttributes() );
const secondPart = new _text__WEBPACK_IMPORTED_MODULE_1__["default"]( textNode.data.substr( offsetDiff ), textNode.getAttributes() );
element._insertChild( index, [ firstPart, secondPart ] );
}
}
// Checks whether two given nodes have same attributes.
//
// @private
// @param {module:engine/model/node~Node} nodeA Node to check.
// @param {module:engine/model/node~Node} nodeB Node to check.
// @returns {Boolean} `true` if nodes have same attributes, `false` otherwise.
function _haveSameAttributes( nodeA, nodeB ) {
const iteratorA = nodeA.getAttributes();
const iteratorB = nodeB.getAttributes();
for ( const attr of iteratorA ) {
if ( attr[ 1 ] !== nodeB.getAttribute( attr[ 0 ] ) ) {
return false;
}
iteratorB.next();
}
return iteratorB.next().done;
}
/**
* Value that can be normalized to an array of {@link module:engine/model/node~Node nodes}.
*
* Non-arrays are normalized as follows:
* * {@link module:engine/model/node~Node Node} is left as is,
* * {@link module:engine/model/textproxy~TextProxy TextProxy} and `String` are normalized to {@link module:engine/model/text~Text Text},
* * {@link module:engine/model/nodelist~NodeList NodeList} is normalized to an array containing all nodes that are in that node list,
* * {@link module:engine/model/documentfragment~DocumentFragment DocumentFragment} is normalized to an array containing all of it's
* * children.
*
* Arrays are processed item by item like non-array values and flattened to one array. Normalization always results in
* a flat array of {@link module:engine/model/node~Node nodes}. Consecutive text nodes (or items normalized to text nodes) will be
* merged if they have same attributes.
*
* @typedef {module:engine/model/node~Node|module:engine/model/textproxy~TextProxy|String|
* module:engine/model/nodelist~NodeList|module:engine/model/documentfragment~DocumentFragment|Iterable}
* module:engine/model/node~NodeSet
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/model/position.js":
/*!***********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/model/position.js ***!
\***********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ Position),
/* harmony export */ "getNodeAfterPosition": () => (/* binding */ getNodeAfterPosition),
/* harmony export */ "getNodeBeforePosition": () => (/* binding */ getNodeBeforePosition),
/* harmony export */ "getTextNodeAtPosition": () => (/* binding */ getTextNodeAtPosition)
/* harmony export */ });
/* harmony import */ var _treewalker__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./treewalker */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/treewalker.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_comparearrays__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/comparearrays */ "./node_modules/@ckeditor/ckeditor5-utils/src/comparearrays.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_version__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/version */ "./node_modules/@ckeditor/ckeditor5-utils/src/version.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/model/position
*/
// To check if component is loaded more than once.
/**
* Represents a position in the model tree.
*
* A position is represented by its {@link module:engine/model/position~Position#root} and
* a {@link module:engine/model/position~Position#path} in that root.
*
* You can create position instances via its constructor or the `createPosition*()` factory methods of
* {@link module:engine/model/model~Model} and {@link module:engine/model/writer~Writer}.
*
* **Note:** Position is based on offsets, not indexes. This means that a position between two text nodes
* `foo` and `bar` has offset `3`, not `1`. See {@link module:engine/model/position~Position#path} for more information.
*
* Since a position in the model is represented by a {@link module:engine/model/position~Position#root position root} and
* {@link module:engine/model/position~Position#path position path} it is possible to create positions placed in non-existing places.
* This requirement is important for operational transformation algorithms.
*
* Also, {@link module:engine/model/operation/operation~Operation operations}
* kept in the {@link module:engine/model/document~Document#history document history}
* are storing positions (and ranges) which were correct when those operations were applied, but may not be correct
* after the document has changed.
*
* When changes are applied to the model, it may also happen that {@link module:engine/model/position~Position#parent position parent}
* will change even if position path has not changed. Keep in mind, that if a position leads to non-existing element,
* {@link module:engine/model/position~Position#parent} and some other properties and methods will throw errors.
*
* In most cases, position with wrong path is caused by an error in code, but it is sometimes needed, as described above.
*/
class Position {
/**
* Creates a position.
*
* @param {module:engine/model/element~Element|module:engine/model/documentfragment~DocumentFragment} root Root of the position.
* @param {Array.<Number>} path Position path. See {@link module:engine/model/position~Position#path}.
* @param {module:engine/model/position~PositionStickiness} [stickiness='toNone'] Position stickiness.
* See {@link module:engine/model/position~PositionStickiness}.
*/
constructor( root, path, stickiness = 'toNone' ) {
if ( !root.is( 'element' ) && !root.is( 'documentFragment' ) ) {
/**
* Position root is invalid.
*
* Positions can only be anchored in elements or document fragments.
*
* @error model-position-root-invalid
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_2__["default"](
'model-position-root-invalid',
root
);
}
if ( !( path instanceof Array ) || path.length === 0 ) {
/**
* Position path must be an array with at least one item.
*
* @error model-position-path-incorrect-format
* @param path
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_2__["default"](
'model-position-path-incorrect-format',
root,
{ path }
);
}
// Normalize the root and path when element (not root) is passed.
if ( root.is( 'rootElement' ) ) {
path = path.slice();
} else {
path = [ ...root.getPath(), ...path ];
root = root.root;
}
/**
* Root of the position path.
*
* @readonly
* @member {module:engine/model/element~Element|module:engine/model/documentfragment~DocumentFragment}
* module:engine/model/position~Position#root
*/
this.root = root;
/**
* Position of the node in the tree. **Path contains offsets, not indexes.**
*
* Position can be placed before, after or in a {@link module:engine/model/node~Node node} if that node has
* {@link module:engine/model/node~Node#offsetSize} greater than `1`. Items in position path are
* {@link module:engine/model/node~Node#startOffset starting offsets} of position ancestors, starting from direct root children,
* down to the position offset in it's parent.
*
* ROOT
* |- P before: [ 0 ] after: [ 1 ]
* |- UL before: [ 1 ] after: [ 2 ]
* |- LI before: [ 1, 0 ] after: [ 1, 1 ]
* | |- foo before: [ 1, 0, 0 ] after: [ 1, 0, 3 ]
* |- LI before: [ 1, 1 ] after: [ 1, 2 ]
* |- bar before: [ 1, 1, 0 ] after: [ 1, 1, 3 ]
*
* `foo` and `bar` are representing {@link module:engine/model/text~Text text nodes}. Since text nodes has offset size
* greater than `1` you can place position offset between their start and end:
*
* ROOT
* |- P
* |- UL
* |- LI
* | |- f^o|o ^ has path: [ 1, 0, 1 ] | has path: [ 1, 0, 2 ]
* |- LI
* |- b^a|r ^ has path: [ 1, 1, 1 ] | has path: [ 1, 1, 2 ]
*
* @readonly
* @member {Array.<Number>} module:engine/model/position~Position#path
*/
this.path = path;
/**
* Position stickiness. See {@link module:engine/model/position~PositionStickiness}.
*
* @member {module:engine/model/position~PositionStickiness} module:engine/model/position~Position#stickiness
*/
this.stickiness = stickiness;
}
/**
* Offset at which this position is located in its {@link module:engine/model/position~Position#parent parent}. It is equal
* to the last item in position {@link module:engine/model/position~Position#path path}.
*
* @type {Number}
*/
get offset() {
return this.path[ this.path.length - 1 ];
}
set offset( newOffset ) {
this.path[ this.path.length - 1 ] = newOffset;
}
/**
* Parent element of this position.
*
* Keep in mind that `parent` value is calculated when the property is accessed.
* If {@link module:engine/model/position~Position#path position path}
* leads to a non-existing element, `parent` property will throw error.
*
* Also it is a good idea to cache `parent` property if it is used frequently in an algorithm (i.e. in a long loop).
*
* @readonly
* @type {module:engine/model/element~Element|module:engine/model/documentfragment~DocumentFragment}
*/
get parent() {
let parent = this.root;
for ( let i = 0; i < this.path.length - 1; i++ ) {
parent = parent.getChild( parent.offsetToIndex( this.path[ i ] ) );
if ( !parent ) {
/**
* The position's path is incorrect. This means that a position does not point to
* a correct place in the tree and hence, some of its methods and getters cannot work correctly.
*
* **Note**: Unlike DOM and view positions, in the model, the
* {@link module:engine/model/position~Position#parent position's parent} is always an element or a document fragment.
* The last offset in the {@link module:engine/model/position~Position#path position's path} is the point in this element
* where this position points.
*
* Read more about model positions and offsets in
* the {@glink framework/guides/architecture/editing-engine#indexes-and-offsets Editing engine architecture guide}.
*
* @error model-position-path-incorrect
* @param {module:engine/model/position~Position} position The incorrect position.
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_2__["default"]( 'model-position-path-incorrect', this, { position: this } );
}
}
if ( parent.is( '$text' ) ) {
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_2__["default"]( 'model-position-path-incorrect', this, { position: this } );
}
return parent;
}
/**
* Position {@link module:engine/model/position~Position#offset offset} converted to an index in position's parent node. It is
* equal to the {@link module:engine/model/node~Node#index index} of a node after this position. If position is placed
* in text node, position index is equal to the index of that text node.
*
* @readonly
* @type {Number}
*/
get index() {
return this.parent.offsetToIndex( this.offset );
}
/**
* Returns {@link module:engine/model/text~Text text node} instance in which this position is placed or `null` if this
* position is not in a text node.
*
* @readonly
* @type {module:engine/model/text~Text|null}
*/
get textNode() {
return getTextNodeAtPosition( this, this.parent );
}
/**
* Node directly after this position or `null` if this position is in text node.
*
* @readonly
* @type {module:engine/model/node~Node|null}
*/
get nodeAfter() {
// Cache the parent and reuse for performance reasons. See #6579 and #6582.
const parent = this.parent;
return getNodeAfterPosition( this, parent, getTextNodeAtPosition( this, parent ) );
}
/**
* Node directly before this position or `null` if this position is in text node.
*
* @readonly
* @type {module:engine/model/node~Node|null}
*/
get nodeBefore() {
// Cache the parent and reuse for performance reasons. See #6579 and #6582.
const parent = this.parent;
return getNodeBeforePosition( this, parent, getTextNodeAtPosition( this, parent ) );
}
/**
* Is `true` if position is at the beginning of its {@link module:engine/model/position~Position#parent parent}, `false` otherwise.
*
* @readonly
* @type {Boolean}
*/
get isAtStart() {
return this.offset === 0;
}
/**
* Is `true` if position is at the end of its {@link module:engine/model/position~Position#parent parent}, `false` otherwise.
*
* @readonly
* @type {Boolean}
*/
get isAtEnd() {
return this.offset == this.parent.maxOffset;
}
/**
* Checks whether this position is before or after given position.
*
* This method is safe to use it on non-existing positions (for example during operational transformation).
*
* @param {module:engine/model/position~Position} otherPosition Position to compare with.
* @returns {module:engine/model/position~PositionRelation}
*/
compareWith( otherPosition ) {
if ( this.root != otherPosition.root ) {
return 'different';
}
const result = (0,_ckeditor_ckeditor5_utils_src_comparearrays__WEBPACK_IMPORTED_MODULE_1__["default"])( this.path, otherPosition.path );
switch ( result ) {
case 'same':
return 'same';
case 'prefix':
return 'before';
case 'extension':
return 'after';
default:
return this.path[ result ] < otherPosition.path[ result ] ? 'before' : 'after';
}
}
/**
* Gets the farthest position which matches the callback using
* {@link module:engine/model/treewalker~TreeWalker TreeWalker}.
*
* For example:
*
* getLastMatchingPosition( value => value.type == 'text' );
* // <paragraph>[]foo</paragraph> -> <paragraph>foo[]</paragraph>
*
* getLastMatchingPosition( value => value.type == 'text', { direction: 'backward' } );
* // <paragraph>foo[]</paragraph> -> <paragraph>[]foo</paragraph>
*
* getLastMatchingPosition( value => false );
* // Do not move the position.
*
* @param {Function} skip Callback function. Gets {@link module:engine/model/treewalker~TreeWalkerValue} and should
* return `true` if the value should be skipped or `false` if not.
* @param {Object} options Object with configuration options. See {@link module:engine/model/treewalker~TreeWalker}.
*
* @returns {module:engine/model/position~Position} The position after the last item which matches the `skip` callback test.
*/
getLastMatchingPosition( skip, options = {} ) {
options.startPosition = this;
const treeWalker = new _treewalker__WEBPACK_IMPORTED_MODULE_0__["default"]( options );
treeWalker.skip( skip );
return treeWalker.position;
}
/**
* Returns a path to this position's parent. Parent path is equal to position {@link module:engine/model/position~Position#path path}
* but without the last item.
*
* This method is safe to use it on non-existing positions (for example during operational transformation).
*
* @returns {Array.<Number>} Path to the parent.
*/
getParentPath() {
return this.path.slice( 0, -1 );
}
/**
* Returns ancestors array of this position, that is this position's parent and its ancestors.
*
* @returns {Array.<module:engine/model/item~Item>} Array with ancestors.
*/
getAncestors() {
const parent = this.parent;
if ( parent.is( 'documentFragment' ) ) {
return [ parent ];
} else {
return parent.getAncestors( { includeSelf: true } );
}
}
/**
* Returns the parent element of the given name. Returns null if the position is not inside the desired parent.
*
* @param {String} parentName The name of the parent element to find.
* @returns {module:engine/model/element~Element|null}
*/
findAncestor( parentName ) {
const parent = this.parent;
if ( parent.is( 'element' ) ) {
return parent.findAncestor( parentName, { includeSelf: true } );
}
return null;
}
/**
* Returns the slice of two position {@link #path paths} which is identical. The {@link #root roots}
* of these two paths must be identical.
*
* This method is safe to use it on non-existing positions (for example during operational transformation).
*
* @param {module:engine/model/position~Position} position The second position.
* @returns {Array.<Number>} The common path.
*/
getCommonPath( position ) {
if ( this.root != position.root ) {
return [];
}
// We find on which tree-level start and end have the lowest common ancestor
const cmp = (0,_ckeditor_ckeditor5_utils_src_comparearrays__WEBPACK_IMPORTED_MODULE_1__["default"])( this.path, position.path );
// If comparison returned string it means that arrays are same.
const diffAt = ( typeof cmp == 'string' ) ? Math.min( this.path.length, position.path.length ) : cmp;
return this.path.slice( 0, diffAt );
}
/**
* Returns an {@link module:engine/model/element~Element} or {@link module:engine/model/documentfragment~DocumentFragment}
* which is a common ancestor of both positions. The {@link #root roots} of these two positions must be identical.
*
* @param {module:engine/model/position~Position} position The second position.
* @returns {module:engine/model/element~Element|module:engine/model/documentfragment~DocumentFragment|null}
*/
getCommonAncestor( position ) {
const ancestorsA = this.getAncestors();
const ancestorsB = position.getAncestors();
let i = 0;
while ( ancestorsA[ i ] == ancestorsB[ i ] && ancestorsA[ i ] ) {
i++;
}
return i === 0 ? null : ancestorsA[ i - 1 ];
}
/**
* Returns a new instance of `Position`, that has same {@link #parent parent} but it's offset
* is shifted by `shift` value (can be a negative value).
*
* This method is safe to use it on non-existing positions (for example during operational transformation).
*
* @param {Number} shift Offset shift. Can be a negative value.
* @returns {module:engine/model/position~Position} Shifted position.
*/
getShiftedBy( shift ) {
const shifted = this.clone();
const offset = shifted.offset + shift;
shifted.offset = offset < 0 ? 0 : offset;
return shifted;
}
/**
* Checks whether this position is after given position.
*
* This method is safe to use it on non-existing positions (for example during operational transformation).
*
* @see module:engine/model/position~Position#isBefore
* @param {module:engine/model/position~Position} otherPosition Position to compare with.
* @returns {Boolean} True if this position is after given position.
*/
isAfter( otherPosition ) {
return this.compareWith( otherPosition ) == 'after';
}
/**
* Checks whether this position is before given position.
*
* **Note:** watch out when using negation of the value returned by this method, because the negation will also
* be `true` if positions are in different roots and you might not expect this. You should probably use
* `a.isAfter( b ) || a.isEqual( b )` or `!a.isBefore( p ) && a.root == b.root` in most scenarios. If your
* condition uses multiple `isAfter` and `isBefore` checks, build them so they do not use negated values, i.e.:
*
* if ( a.isBefore( b ) && c.isAfter( d ) ) {
* // do A.
* } else {
* // do B.
* }
*
* or, if you have only one if-branch:
*
* if ( !( a.isBefore( b ) && c.isAfter( d ) ) {
* // do B.
* }
*
* rather than:
*
* if ( !a.isBefore( b ) || && !c.isAfter( d ) ) {
* // do B.
* } else {
* // do A.
* }
*
* This method is safe to use it on non-existing positions (for example during operational transformation).
*
* @param {module:engine/model/position~Position} otherPosition Position to compare with.
* @returns {Boolean} True if this position is before given position.
*/
isBefore( otherPosition ) {
return this.compareWith( otherPosition ) == 'before';
}
/**
* Checks whether this position is equal to given position.
*
* This method is safe to use it on non-existing positions (for example during operational transformation).
*
* @param {module:engine/model/position~Position} otherPosition Position to compare with.
* @returns {Boolean} True if positions are same.
*/
isEqual( otherPosition ) {
return this.compareWith( otherPosition ) == 'same';
}
/**
* Checks whether this position is touching given position. Positions touch when there are no text nodes
* or empty nodes in a range between them. Technically, those positions are not equal but in many cases
* they are very similar or even indistinguishable.
*
* @param {module:engine/model/position~Position} otherPosition Position to compare with.
* @returns {Boolean} True if positions touch.
*/
isTouching( otherPosition ) {
let left = null;
let right = null;
const compare = this.compareWith( otherPosition );
switch ( compare ) {
case 'same':
return true;
case 'before':
left = Position._createAt( this );
right = Position._createAt( otherPosition );
break;
case 'after':
left = Position._createAt( otherPosition );
right = Position._createAt( this );
break;
default:
return false;
}
// Cached for optimization purposes.
let leftParent = left.parent;
while ( left.path.length + right.path.length ) {
if ( left.isEqual( right ) ) {
return true;
}
if ( left.path.length > right.path.length ) {
if ( left.offset !== leftParent.maxOffset ) {
return false;
}
left.path = left.path.slice( 0, -1 );
leftParent = leftParent.parent;
left.offset++;
} else {
if ( right.offset !== 0 ) {
return false;
}
right.path = right.path.slice( 0, -1 );
}
}
}
/**
* Checks whether this object is of the given.
*
* position.is( 'position' ); // -> true
* position.is( 'model:position' ); // -> true
*
* position.is( 'view:position' ); // -> false
* position.is( 'documentSelection' ); // -> false
*
* {@link module:engine/model/node~Node#is Check the entire list of model objects} which implement the `is()` method.
*
* @param {String} type
* @returns {Boolean}
*/
is( type ) {
return type === 'position' || type === 'model:position';
}
/**
* Checks if two positions are in the same parent.
*
* This method is safe to use it on non-existing positions (for example during operational transformation).
*
* @param {module:engine/model/position~Position} position Position to compare with.
* @returns {Boolean} `true` if positions have the same parent, `false` otherwise.
*/
hasSameParentAs( position ) {
if ( this.root !== position.root ) {
return false;
}
const thisParentPath = this.getParentPath();
const posParentPath = position.getParentPath();
return (0,_ckeditor_ckeditor5_utils_src_comparearrays__WEBPACK_IMPORTED_MODULE_1__["default"])( thisParentPath, posParentPath ) == 'same';
}
/**
* Returns a copy of this position that is transformed by given `operation`.
*
* The new position's parameters are updated accordingly to the effect of the `operation`.
*
* For example, if `n` nodes are inserted before the position, the returned position {@link ~Position#offset} will be
* increased by `n`. If the position was in a merged element, it will be accordingly moved to the new element, etc.
*
* This method is safe to use it on non-existing positions (for example during operational transformation).
*
* @param {module:engine/model/operation/operation~Operation} operation Operation to transform by.
* @returns {module:engine/model/position~Position} Transformed position.
*/
getTransformedByOperation( operation ) {
let result;
switch ( operation.type ) {
case 'insert':
result = this._getTransformedByInsertOperation( operation );
break;
case 'move':
case 'remove':
case 'reinsert':
result = this._getTransformedByMoveOperation( operation );
break;
case 'split':
result = this._getTransformedBySplitOperation( operation );
break;
case 'merge':
result = this._getTransformedByMergeOperation( operation );
break;
default:
result = Position._createAt( this );
break;
}
return result;
}
/**
* Returns a copy of this position transformed by an insert operation.
*
* @protected
* @param {module:engine/model/operation/insertoperation~InsertOperation} operation
* @returns {module:engine/model/position~Position}
*/
_getTransformedByInsertOperation( operation ) {
return this._getTransformedByInsertion( operation.position, operation.howMany );
}
/**
* Returns a copy of this position transformed by a move operation.
*
* @protected
* @param {module:engine/model/operation/moveoperation~MoveOperation} operation
* @returns {module:engine/model/position~Position}
*/
_getTransformedByMoveOperation( operation ) {
return this._getTransformedByMove( operation.sourcePosition, operation.targetPosition, operation.howMany );
}
/**
* Returns a copy of this position transformed by a split operation.
*
* @protected
* @param {module:engine/model/operation/splitoperation~SplitOperation} operation
* @returns {module:engine/model/position~Position}
*/
_getTransformedBySplitOperation( operation ) {
const movedRange = operation.movedRange;
const isContained = movedRange.containsPosition( this ) ||
( movedRange.start.isEqual( this ) && this.stickiness == 'toNext' );
if ( isContained ) {
return this._getCombined( operation.splitPosition, operation.moveTargetPosition );
} else {
if ( operation.graveyardPosition ) {
return this._getTransformedByMove( operation.graveyardPosition, operation.insertionPosition, 1 );
} else {
return this._getTransformedByInsertion( operation.insertionPosition, 1 );
}
}
}
/**
* Returns a copy of this position transformed by merge operation.
*
* @protected
* @param {module:engine/model/operation/mergeoperation~MergeOperation} operation
* @returns {module:engine/model/position~Position}
*/
_getTransformedByMergeOperation( operation ) {
const movedRange = operation.movedRange;
const isContained = movedRange.containsPosition( this ) || movedRange.start.isEqual( this );
let pos;
if ( isContained ) {
pos = this._getCombined( operation.sourcePosition, operation.targetPosition );
if ( operation.sourcePosition.isBefore( operation.targetPosition ) ) {
// Above happens during OT when the merged element is moved before the merged-to element.
pos = pos._getTransformedByDeletion( operation.deletionPosition, 1 );
}
} else if ( this.isEqual( operation.deletionPosition ) ) {
pos = Position._createAt( operation.deletionPosition );
} else {
pos = this._getTransformedByMove( operation.deletionPosition, operation.graveyardPosition, 1 );
}
return pos;
}
/**
* Returns a copy of this position that is updated by removing `howMany` nodes starting from `deletePosition`.
* It may happen that this position is in a removed node. If that is the case, `null` is returned instead.
*
* @protected
* @param {module:engine/model/position~Position} deletePosition Position before the first removed node.
* @param {Number} howMany How many nodes are removed.
* @returns {module:engine/model/position~Position|null} Transformed position or `null`.
*/
_getTransformedByDeletion( deletePosition, howMany ) {
const transformed = Position._createAt( this );
// This position can't be affected if deletion was in a different root.
if ( this.root != deletePosition.root ) {
return transformed;
}
if ( (0,_ckeditor_ckeditor5_utils_src_comparearrays__WEBPACK_IMPORTED_MODULE_1__["default"])( deletePosition.getParentPath(), this.getParentPath() ) == 'same' ) {
// If nodes are removed from the node that is pointed by this position...
if ( deletePosition.offset < this.offset ) {
// And are removed from before an offset of that position...
if ( deletePosition.offset + howMany > this.offset ) {
// Position is in removed range, it's no longer in the tree.
return null;
} else {
// Decrement the offset accordingly.
transformed.offset -= howMany;
}
}
} else if ( (0,_ckeditor_ckeditor5_utils_src_comparearrays__WEBPACK_IMPORTED_MODULE_1__["default"])( deletePosition.getParentPath(), this.getParentPath() ) == 'prefix' ) {
// If nodes are removed from a node that is on a path to this position...
const i = deletePosition.path.length - 1;
if ( deletePosition.offset <= this.path[ i ] ) {
// And are removed from before next node of that path...
if ( deletePosition.offset + howMany > this.path[ i ] ) {
// If the next node of that path is removed return null
// because the node containing this position got removed.
return null;
} else {
// Otherwise, decrement index on that path.
transformed.path[ i ] -= howMany;
}
}
}
return transformed;
}
/**
* Returns a copy of this position that is updated by inserting `howMany` nodes at `insertPosition`.
*
* @protected
* @param {module:engine/model/position~Position} insertPosition Position where nodes are inserted.
* @param {Number} howMany How many nodes are inserted.
* @returns {module:engine/model/position~Position} Transformed position.
*/
_getTransformedByInsertion( insertPosition, howMany ) {
const transformed = Position._createAt( this );
// This position can't be affected if insertion was in a different root.
if ( this.root != insertPosition.root ) {
return transformed;
}
if ( (0,_ckeditor_ckeditor5_utils_src_comparearrays__WEBPACK_IMPORTED_MODULE_1__["default"])( insertPosition.getParentPath(), this.getParentPath() ) == 'same' ) {
// If nodes are inserted in the node that is pointed by this position...
if ( insertPosition.offset < this.offset || ( insertPosition.offset == this.offset && this.stickiness != 'toPrevious' ) ) {
// And are inserted before an offset of that position...
// "Push" this positions offset.
transformed.offset += howMany;
}
} else if ( (0,_ckeditor_ckeditor5_utils_src_comparearrays__WEBPACK_IMPORTED_MODULE_1__["default"])( insertPosition.getParentPath(), this.getParentPath() ) == 'prefix' ) {
// If nodes are inserted in a node that is on a path to this position...
const i = insertPosition.path.length - 1;
if ( insertPosition.offset <= this.path[ i ] ) {
// And are inserted before next node of that path...
// "Push" the index on that path.
transformed.path[ i ] += howMany;
}
}
return transformed;
}
/**
* Returns a copy of this position that is updated by moving `howMany` nodes from `sourcePosition` to `targetPosition`.
*
* @protected
* @param {module:engine/model/position~Position} sourcePosition Position before the first element to move.
* @param {module:engine/model/position~Position} targetPosition Position where moved elements will be inserted.
* @param {Number} howMany How many consecutive nodes to move, starting from `sourcePosition`.
* @returns {module:engine/model/position~Position} Transformed position.
*/
_getTransformedByMove( sourcePosition, targetPosition, howMany ) {
// Update target position, as it could be affected by nodes removal.
targetPosition = targetPosition._getTransformedByDeletion( sourcePosition, howMany );
if ( sourcePosition.isEqual( targetPosition ) ) {
// If `targetPosition` is equal to `sourcePosition` this isn't really any move. Just return position as it is.
return Position._createAt( this );
}
// Moving a range removes nodes from their original position. We acknowledge this by proper transformation.
const transformed = this._getTransformedByDeletion( sourcePosition, howMany );
const isMoved = transformed === null ||
( sourcePosition.isEqual( this ) && this.stickiness == 'toNext' ) ||
( sourcePosition.getShiftedBy( howMany ).isEqual( this ) && this.stickiness == 'toPrevious' );
if ( isMoved ) {
// This position is inside moved range (or sticks to it).
// In this case, we calculate a combination of this position, move source position and target position.
return this._getCombined( sourcePosition, targetPosition );
} else {
// This position is not inside a removed range.
//
// In next step, we simply reflect inserting `howMany` nodes, which might further affect the position.
return transformed._getTransformedByInsertion( targetPosition, howMany );
}
}
/**
* Returns a new position that is a combination of this position and given positions.
*
* The combined position is a copy of this position transformed by moving a range starting at `source` position
* to the `target` position. It is expected that this position is inside the moved range.
*
* Example:
*
* let original = model.createPositionFromPath( root, [ 2, 3, 1 ] );
* let source = model.createPositionFromPath( root, [ 2, 2 ] );
* let target = model.createPositionFromPath( otherRoot, [ 1, 1, 3 ] );
* original._getCombined( source, target ); // path is [ 1, 1, 4, 1 ], root is `otherRoot`
*
* Explanation:
*
* We have a position `[ 2, 3, 1 ]` and move some nodes from `[ 2, 2 ]` to `[ 1, 1, 3 ]`. The original position
* was inside moved nodes and now should point to the new place. The moved nodes will be after
* positions `[ 1, 1, 3 ]`, `[ 1, 1, 4 ]`, `[ 1, 1, 5 ]`. Since our position was in the second moved node,
* the transformed position will be in a sub-tree of a node at `[ 1, 1, 4 ]`. Looking at original path, we
* took care of `[ 2, 3 ]` part of it. Now we have to add the rest of the original path to the transformed path.
* Finally, the transformed position will point to `[ 1, 1, 4, 1 ]`.
*
* @protected
* @param {module:engine/model/position~Position} source Beginning of the moved range.
* @param {module:engine/model/position~Position} target Position where the range is moved.
* @returns {module:engine/model/position~Position} Combined position.
*/
_getCombined( source, target ) {
const i = source.path.length - 1;
// The first part of a path to combined position is a path to the place where nodes were moved.
const combined = Position._createAt( target );
combined.stickiness = this.stickiness;
// Then we have to update the rest of the path.
// Fix the offset because this position might be after `from` position and we have to reflect that.
combined.offset = combined.offset + this.path[ i ] - source.offset;
// Then, add the rest of the path.
// If this position is at the same level as `from` position nothing will get added.
combined.path = [ ...combined.path, ...this.path.slice( i + 1 ) ];
return combined;
}
/**
* @inheritDoc
*/
toJSON() {
return {
root: this.root.toJSON(),
path: Array.from( this.path ),
stickiness: this.stickiness
};
}
/**
* Returns a new position that is equal to current position.
*
* @returns {module:engine/model/position~Position}
*/
clone() {
return new this.constructor( this.root, this.path, this.stickiness );
}
/**
* Creates position at the given location. The location can be specified as:
*
* * a {@link module:engine/model/position~Position position},
* * parent element and offset (offset defaults to `0`),
* * parent element and `'end'` (sets position at the end of that element),
* * {@link module:engine/model/item~Item model item} and `'before'` or `'after'` (sets position before or after given model item).
*
* This method is a shortcut to other factory methods such as:
*
* * {@link module:engine/model/position~Position._createBefore},
* * {@link module:engine/model/position~Position._createAfter}.
*
* @param {module:engine/model/item~Item|module:engine/model/position~Position} itemOrPosition
* @param {Number|'end'|'before'|'after'} [offset] Offset or one of the flags. Used only when the
* first parameter is a {@link module:engine/model/item~Item model item}.
* @param {module:engine/model/position~PositionStickiness} [stickiness='toNone'] Position stickiness. Used only when the
* first parameter is a {@link module:engine/model/item~Item model item}.
* @protected
*/
static _createAt( itemOrPosition, offset, stickiness = 'toNone' ) {
if ( itemOrPosition instanceof Position ) {
return new Position( itemOrPosition.root, itemOrPosition.path, itemOrPosition.stickiness );
} else {
const node = itemOrPosition;
if ( offset == 'end' ) {
offset = node.maxOffset;
} else if ( offset == 'before' ) {
return this._createBefore( node, stickiness );
} else if ( offset == 'after' ) {
return this._createAfter( node, stickiness );
} else if ( offset !== 0 && !offset ) {
/**
* {@link module:engine/model/model~Model#createPositionAt `Model#createPositionAt()`}
* requires the offset to be specified when the first parameter is a model item.
*
* @error model-createpositionat-offset-required
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_2__["default"]( 'model-createpositionat-offset-required', [ this, itemOrPosition ] );
}
if ( !node.is( 'element' ) && !node.is( 'documentFragment' ) ) {
/**
* Position parent have to be a model element or model document fragment.
*
* @error model-position-parent-incorrect
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_2__["default"](
'model-position-parent-incorrect',
[ this, itemOrPosition ]
);
}
const path = node.getPath();
path.push( offset );
return new this( node.root, path, stickiness );
}
}
/**
* Creates a new position, after given {@link module:engine/model/item~Item model item}.
*
* @param {module:engine/model/item~Item} item Item after which the position should be placed.
* @param {module:engine/model/position~PositionStickiness} [stickiness='toNone'] Position stickiness.
* @returns {module:engine/model/position~Position}
* @protected
*/
static _createAfter( item, stickiness ) {
if ( !item.parent ) {
/**
* You can not make a position after a root element.
*
* @error model-position-after-root
* @param {module:engine/model/item~Item} root
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_2__["default"](
'model-position-after-root',
[ this, item ],
{ root: item }
);
}
return this._createAt( item.parent, item.endOffset, stickiness );
}
/**
* Creates a new position, before the given {@link module:engine/model/item~Item model item}.
*
* @param {module:engine/model/item~Item} item Item before which the position should be placed.
* @param {module:engine/model/position~PositionStickiness} [stickiness='toNone'] Position stickiness.
* @returns {module:engine/model/position~Position}
* @protected
*/
static _createBefore( item, stickiness ) {
if ( !item.parent ) {
/**
* You can not make a position before a root element.
*
* @error model-position-before-root
* @param {module:engine/model/item~Item} root
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_2__["default"](
'model-position-before-root',
item,
{ root: item }
);
}
return this._createAt( item.parent, item.startOffset, stickiness );
}
/**
* Creates a `Position` instance from given plain object (i.e. parsed JSON string).
*
* @param {Object} json Plain object to be converted to `Position`.
* @param {module:engine/model/document~Document} doc Document object that will be position owner.
* @returns {module:engine/model/position~Position} `Position` instance created using given plain object.
*/
static fromJSON( json, doc ) {
if ( json.root === '$graveyard' ) {
const pos = new Position( doc.graveyard, json.path );
pos.stickiness = json.stickiness;
return pos;
}
if ( !doc.getRoot( json.root ) ) {
/**
* Cannot create position for document. Root with specified name does not exist.
*
* @error model-position-fromjson-no-root
* @param {String} rootName
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_2__["default"](
'model-position-fromjson-no-root',
doc,
{ rootName: json.root }
);
}
return new Position( doc.getRoot( json.root ), json.path, json.stickiness );
}
// @if CK_DEBUG_ENGINE // toString() {
// @if CK_DEBUG_ENGINE // return `${ this.root } [ ${ this.path.join( ', ' ) } ]`;
// @if CK_DEBUG_ENGINE // }
// @if CK_DEBUG_ENGINE // log() {
// @if CK_DEBUG_ENGINE // console.log( 'ModelPosition: ' + this );
// @if CK_DEBUG_ENGINE // }
}
/**
* A flag indicating whether this position is `'before'` or `'after'` or `'same'` as given position.
* If positions are in different roots `'different'` flag is returned.
*
* @typedef {String} module:engine/model/position~PositionRelation
*/
/**
* Represents how position is "sticking" with neighbour nodes. Used to define how position should be transformed (moved)
* in edge cases. Possible values: `'toNone'`, `'toNext'`, `'toPrevious'`.
*
* Examples:
*
* Insert. Position is at | and nodes are inserted at the same position, marked as ^:
*
* - sticks to none: <p>f^|oo</p> -> <p>fbar|oo</p>
* - sticks to next node: <p>f^|oo</p> -> <p>fbar|oo</p>
* - sticks to previous node: <p>f|^oo</p> -> <p>f|baroo</p>
*
*
* Move. Position is at | and range [oo] is moved to position ^:
*
* - sticks to none: <p>f|[oo]</p><p>b^ar</p> -> <p>f|</p><p>booar</p>
* - sticks to none: <p>f[oo]|</p><p>b^ar</p> -> <p>f|</p><p>booar</p>
*
* - sticks to next node: <p>f|[oo]</p><p>b^ar</p> -> <p>f</p><p>b|ooar</p>
* - sticks to next node: <p>f[oo]|</p><p>b^ar</p> -> <p>f|</p><p>booar</p>
*
* - sticks to previous node: <p>f|[oo]</p><p>b^ar</p> -> <p>f|</p><p>booar</p>
* - sticks to previous node: <p>f[oo]|</p><p>b^ar</p> -> <p>f</p><p>boo|ar</p>
*
* @typedef {String} module:engine/model/position~PositionStickiness
*/
/**
* Returns a text node at the given position.
*
* This is a helper function optimized to reuse the position parent instance for performance reasons.
*
* Normally, you should use {@link module:engine/model/position~Position#textNode `Position#textNode`}.
* If you start hitting performance issues with {@link module:engine/model/position~Position#parent `Position#parent`}
* check if your algorithm does not access it multiple times (which can happen directly or indirectly via other position properties).
*
* See https://github.com/ckeditor/ckeditor5/issues/6579.
*
* See also:
*
* * {@link module:engine/model/position~getNodeAfterPosition}
* * {@link module:engine/model/position~getNodeBeforePosition}
*
* @param {module:engine/model/position~Position} position
* @param {module:engine/model/element~Element|module:engine/model/documentfragment~DocumentFragment} positionParent The parent of the
* given position.
* @returns {module:engine/model/text~Text|null}
*/
function getTextNodeAtPosition( position, positionParent ) {
const node = positionParent.getChild( positionParent.offsetToIndex( position.offset ) );
if ( node && node.is( '$text' ) && node.startOffset < position.offset ) {
return node;
}
return null;
}
/**
* Returns the node after the given position.
*
* This is a helper function optimized to reuse the position parent instance and the calculation of the text node at the
* specific position for performance reasons.
*
* Normally, you should use {@link module:engine/model/position~Position#nodeAfter `Position#nodeAfter`}.
* If you start hitting performance issues with {@link module:engine/model/position~Position#parent `Position#parent`} and/or
* {@link module:engine/model/position~Position#textNode `Position#textNode`}
* check if your algorithm does not access those properties multiple times
* (which can happen directly or indirectly via other position properties).
*
* See https://github.com/ckeditor/ckeditor5/issues/6579 and https://github.com/ckeditor/ckeditor5/issues/6582.
*
* See also:
*
* * {@link module:engine/model/position~getTextNodeAtPosition}
* * {@link module:engine/model/position~getNodeBeforePosition}
*
* @param {module:engine/model/position~Position} position
* @param {module:engine/model/element~Element|module:engine/model/documentfragment~DocumentFragment} positionParent The parent of the
* given position.
* @param {module:engine/model/text~Text|null} textNode Text node at the given position.
* @returns {module:engine/model/node~Node|null}
*/
function getNodeAfterPosition( position, positionParent, textNode ) {
if ( textNode !== null ) {
return null;
}
return positionParent.getChild( positionParent.offsetToIndex( position.offset ) );
}
/**
* Returns the node before the given position.
*
* Refer to {@link module:engine/model/position~getNodeBeforePosition} for documentation on when to use this util method.
*
* See also:
*
* * {@link module:engine/model/position~getTextNodeAtPosition}
* * {@link module:engine/model/position~getNodeAfterPosition}
*
* @param {module:engine/model/position~Position} position
* @param {module:engine/model/element~Element|module:engine/model/documentfragment~DocumentFragment} positionParent The parent of the
* given position.
* @param {module:engine/model/text~Text|null} textNode Text node at the given position.
* @returns {module:engine/model/node~Node|null}
*/
function getNodeBeforePosition( position, positionParent, textNode ) {
if ( textNode !== null ) {
return null;
}
return positionParent.getChild( positionParent.offsetToIndex( position.offset ) - 1 );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/model/range.js":
/*!********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/model/range.js ***!
\********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ Range)
/* harmony export */ });
/* harmony import */ var _position__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./position */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/position.js");
/* harmony import */ var _treewalker__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./treewalker */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/treewalker.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_comparearrays__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/comparearrays */ "./node_modules/@ckeditor/ckeditor5-utils/src/comparearrays.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/model/range
*/
/**
* Represents a range in the model tree.
*
* A range is defined by its {@link module:engine/model/range~Range#start} and {@link module:engine/model/range~Range#end}
* positions.
*
* You can create range instances via its constructor or the `createRange*()` factory methods of
* {@link module:engine/model/model~Model} and {@link module:engine/model/writer~Writer}.
*/
class Range {
/**
* Creates a range spanning from `start` position to `end` position.
*
* @param {module:engine/model/position~Position} start The start position.
* @param {module:engine/model/position~Position} [end] The end position. If not set,
* the range will be collapsed at the `start` position.
*/
constructor( start, end = null ) {
/**
* Start position.
*
* @readonly
* @member {module:engine/model/position~Position}
*/
this.start = _position__WEBPACK_IMPORTED_MODULE_0__["default"]._createAt( start );
/**
* End position.
*
* @readonly
* @member {module:engine/model/position~Position}
*/
this.end = end ? _position__WEBPACK_IMPORTED_MODULE_0__["default"]._createAt( end ) : _position__WEBPACK_IMPORTED_MODULE_0__["default"]._createAt( start );
// If the range is collapsed, treat in a similar way as a position and set its boundaries stickiness to 'toNone'.
// In other case, make the boundaries stick to the "inside" of the range.
this.start.stickiness = this.isCollapsed ? 'toNone' : 'toNext';
this.end.stickiness = this.isCollapsed ? 'toNone' : 'toPrevious';
}
/**
* Iterable interface.
*
* Iterates over all {@link module:engine/model/item~Item items} that are in this range and returns
* them together with additional information like length or {@link module:engine/model/position~Position positions},
* grouped as {@link module:engine/model/treewalker~TreeWalkerValue}.
* It iterates over all {@link module:engine/model/textproxy~TextProxy text contents} that are inside the range
* and all the {@link module:engine/model/element~Element}s that are entered into when iterating over this range.
*
* This iterator uses {@link module:engine/model/treewalker~TreeWalker} with `boundaries` set to this range
* and `ignoreElementEnd` option set to `true`.
*
* @returns {Iterable.<module:engine/model/treewalker~TreeWalkerValue>}
*/
* [ Symbol.iterator ]() {
yield* new _treewalker__WEBPACK_IMPORTED_MODULE_1__["default"]( { boundaries: this, ignoreElementEnd: true } );
}
/**
* Returns whether the range is collapsed, that is if {@link #start} and
* {@link #end} positions are equal.
*
* @type {Boolean}
*/
get isCollapsed() {
return this.start.isEqual( this.end );
}
/**
* Returns whether this range is flat, that is if {@link #start} position and
* {@link #end} position are in the same {@link module:engine/model/position~Position#parent}.
*
* @type {Boolean}
*/
get isFlat() {
const startParentPath = this.start.getParentPath();
const endParentPath = this.end.getParentPath();
return (0,_ckeditor_ckeditor5_utils_src_comparearrays__WEBPACK_IMPORTED_MODULE_3__["default"])( startParentPath, endParentPath ) == 'same';
}
/**
* Range root element.
*
* @type {module:engine/model/element~Element|module:engine/model/documentfragment~DocumentFragment}
*/
get root() {
return this.start.root;
}
/**
* Checks whether this range contains given {@link module:engine/model/position~Position position}.
*
* @param {module:engine/model/position~Position} position Position to check.
* @returns {Boolean} `true` if given {@link module:engine/model/position~Position position} is contained
* in this range,`false` otherwise.
*/
containsPosition( position ) {
return position.isAfter( this.start ) && position.isBefore( this.end );
}
/**
* Checks whether this range contains given {@link ~Range range}.
*
* @param {module:engine/model/range~Range} otherRange Range to check.
* @param {Boolean} [loose=false] Whether the check is loose or strict. If the check is strict (`false`), compared range cannot
* start or end at the same position as this range boundaries. If the check is loose (`true`), compared range can start, end or
* even be equal to this range. Note that collapsed ranges are always compared in strict mode.
* @returns {Boolean} `true` if given {@link ~Range range} boundaries are contained by this range, `false` otherwise.
*/
containsRange( otherRange, loose = false ) {
if ( otherRange.isCollapsed ) {
loose = false;
}
const containsStart = this.containsPosition( otherRange.start ) || ( loose && this.start.isEqual( otherRange.start ) );
const containsEnd = this.containsPosition( otherRange.end ) || ( loose && this.end.isEqual( otherRange.end ) );
return containsStart && containsEnd;
}
/**
* Checks whether given {@link module:engine/model/item~Item} is inside this range.
*
* @param {module:engine/model/item~Item} item Model item to check.
*/
containsItem( item ) {
const pos = _position__WEBPACK_IMPORTED_MODULE_0__["default"]._createBefore( item );
return this.containsPosition( pos ) || this.start.isEqual( pos );
}
/**
* Checks whether this object is of the given.
*
* range.is( 'range' ); // -> true
* range.is( 'model:range' ); // -> true
*
* range.is( 'view:range' ); // -> false
* range.is( 'documentSelection' ); // -> false
*
* {@link module:engine/model/node~Node#is Check the entire list of model objects} which implement the `is()` method.
*
* @param {String} type
* @returns {Boolean}
*/
is( type ) {
return type === 'range' || type === 'model:range';
}
/**
* Two ranges are equal if their {@link #start} and {@link #end} positions are equal.
*
* @param {module:engine/model/range~Range} otherRange Range to compare with.
* @returns {Boolean} `true` if ranges are equal, `false` otherwise.
*/
isEqual( otherRange ) {
return this.start.isEqual( otherRange.start ) && this.end.isEqual( otherRange.end );
}
/**
* Checks and returns whether this range intersects with given range.
*
* @param {module:engine/model/range~Range} otherRange Range to compare with.
* @returns {Boolean} `true` if ranges intersect, `false` otherwise.
*/
isIntersecting( otherRange ) {
return this.start.isBefore( otherRange.end ) && this.end.isAfter( otherRange.start );
}
/**
* Computes which part(s) of this {@link ~Range range} is not a part of given {@link ~Range range}.
* Returned array contains zero, one or two {@link ~Range ranges}.
*
* Examples:
*
* let range = model.createRange(
* model.createPositionFromPath( root, [ 2, 7 ] ),
* model.createPositionFromPath( root, [ 4, 0, 1 ] )
* );
* let otherRange = model.createRange( model.createPositionFromPath( root, [ 1 ] ), model.createPositionFromPath( root, [ 5 ] ) );
* let transformed = range.getDifference( otherRange );
* // transformed array has no ranges because `otherRange` contains `range`
*
* otherRange = model.createRange( model.createPositionFromPath( root, [ 1 ] ), model.createPositionFromPath( root, [ 3 ] ) );
* transformed = range.getDifference( otherRange );
* // transformed array has one range: from [ 3 ] to [ 4, 0, 1 ]
*
* otherRange = model.createRange( model.createPositionFromPath( root, [ 3 ] ), model.createPositionFromPath( root, [ 4 ] ) );
* transformed = range.getDifference( otherRange );
* // transformed array has two ranges: from [ 2, 7 ] to [ 3 ] and from [ 4 ] to [ 4, 0, 1 ]
*
* @param {module:engine/model/range~Range} otherRange Range to differentiate against.
* @returns {Array.<module:engine/model/range~Range>} The difference between ranges.
*/
getDifference( otherRange ) {
const ranges = [];
if ( this.isIntersecting( otherRange ) ) {
// Ranges intersect.
if ( this.containsPosition( otherRange.start ) ) {
// Given range start is inside this range. This means that we have to
// add shrunken range - from the start to the middle of this range.
ranges.push( new Range( this.start, otherRange.start ) );
}
if ( this.containsPosition( otherRange.end ) ) {
// Given range end is inside this range. This means that we have to
// add shrunken range - from the middle of this range to the end.
ranges.push( new Range( otherRange.end, this.end ) );
}
} else {
// Ranges do not intersect, return the original range.
ranges.push( new Range( this.start, this.end ) );
}
return ranges;
}
/**
* Returns an intersection of this {@link ~Range range} and given {@link ~Range range}.
* Intersection is a common part of both of those ranges. If ranges has no common part, returns `null`.
*
* Examples:
*
* let range = model.createRange(
* model.createPositionFromPath( root, [ 2, 7 ] ),
* model.createPositionFromPath( root, [ 4, 0, 1 ] )
* );
* let otherRange = model.createRange( model.createPositionFromPath( root, [ 1 ] ), model.createPositionFromPath( root, [ 2 ] ) );
* let transformed = range.getIntersection( otherRange ); // null - ranges have no common part
*
* otherRange = model.createRange( model.createPositionFromPath( root, [ 3 ] ), model.createPositionFromPath( root, [ 5 ] ) );
* transformed = range.getIntersection( otherRange ); // range from [ 3 ] to [ 4, 0, 1 ]
*
* @param {module:engine/model/range~Range} otherRange Range to check for intersection.
* @returns {module:engine/model/range~Range|null} A common part of given ranges or `null` if ranges have no common part.
*/
getIntersection( otherRange ) {
if ( this.isIntersecting( otherRange ) ) {
// Ranges intersect, so a common range will be returned.
// At most, it will be same as this range.
let commonRangeStart = this.start;
let commonRangeEnd = this.end;
if ( this.containsPosition( otherRange.start ) ) {
// Given range start is inside this range. This means thaNt we have to
// shrink common range to the given range start.
commonRangeStart = otherRange.start;
}
if ( this.containsPosition( otherRange.end ) ) {
// Given range end is inside this range. This means that we have to
// shrink common range to the given range end.
commonRangeEnd = otherRange.end;
}
return new Range( commonRangeStart, commonRangeEnd );
}
// Ranges do not intersect, so they do not have common part.
return null;
}
/**
* Returns a range created by joining this {@link ~Range range} with the given {@link ~Range range}.
* If ranges have no common part, returns `null`.
*
* Examples:
*
* let range = model.createRange(
* model.createPositionFromPath( root, [ 2, 7 ] ),
* model.createPositionFromPath( root, [ 4, 0, 1 ] )
* );
* let otherRange = model.createRange(
* model.createPositionFromPath( root, [ 1 ] ),
* model.createPositionFromPath( root, [ 2 ] )
* );
* let transformed = range.getJoined( otherRange ); // null - ranges have no common part
*
* otherRange = model.createRange(
* model.createPositionFromPath( root, [ 3 ] ),
* model.createPositionFromPath( root, [ 5 ] )
* );
* transformed = range.getJoined( otherRange ); // range from [ 2, 7 ] to [ 5 ]
*
* @param {module:engine/model/range~Range} otherRange Range to be joined.
* @param {Boolean} [loose=false] Whether the intersection check is loose or strict. If the check is strict (`false`),
* ranges are tested for intersection or whether start/end positions are equal. If the check is loose (`true`),
* compared range is also checked if it's {@link module:engine/model/position~Position#isTouching touching} current range.
* @returns {module:engine/model/range~Range|null} A sum of given ranges or `null` if ranges have no common part.
*/
getJoined( otherRange, loose = false ) {
let shouldJoin = this.isIntersecting( otherRange );
if ( !shouldJoin ) {
if ( this.start.isBefore( otherRange.start ) ) {
shouldJoin = loose ? this.end.isTouching( otherRange.start ) : this.end.isEqual( otherRange.start );
} else {
shouldJoin = loose ? otherRange.end.isTouching( this.start ) : otherRange.end.isEqual( this.start );
}
}
if ( !shouldJoin ) {
return null;
}
let startPosition = this.start;
let endPosition = this.end;
if ( otherRange.start.isBefore( startPosition ) ) {
startPosition = otherRange.start;
}
if ( otherRange.end.isAfter( endPosition ) ) {
endPosition = otherRange.end;
}
return new Range( startPosition, endPosition );
}
/**
* Computes and returns the smallest set of {@link #isFlat flat} ranges, that covers this range in whole.
*
* See an example of a model structure (`[` and `]` are range boundaries):
*
* root root
* |- element DIV DIV P2 P3 DIV
* | |- element H H P1 f o o b a r H P4
* | | |- "fir[st" fir[st lorem se]cond ipsum
* | |- element P1
* | | |- "lorem" ||
* |- element P2 ||
* | |- "foo" VV
* |- element P3
* | |- "bar" root
* |- element DIV DIV [P2 P3] DIV
* | |- element H H [P1] f o o b a r H P4
* | | |- "se]cond" fir[st] lorem [se]cond ipsum
* | |- element P4
* | | |- "ipsum"
*
* As it can be seen, letters contained in the range are: `stloremfoobarse`, spread across different parents.
* We are looking for minimal set of flat ranges that contains the same nodes.
*
* Minimal flat ranges for above range `( [ 0, 0, 3 ], [ 3, 0, 2 ] )` will be:
*
* ( [ 0, 0, 3 ], [ 0, 0, 5 ] ) = "st"
* ( [ 0, 1 ], [ 0, 2 ] ) = element P1 ("lorem")
* ( [ 1 ], [ 3 ] ) = element P2, element P3 ("foobar")
* ( [ 3, 0, 0 ], [ 3, 0, 2 ] ) = "se"
*
* **Note:** if an {@link module:engine/model/element~Element element} is not wholly contained in this range, it won't be returned
* in any of the returned flat ranges. See in the example how `H` elements at the beginning and at the end of the range
* were omitted. Only their parts that were wholly in the range were returned.
*
* **Note:** this method is not returning flat ranges that contain no nodes.
*
* @returns {Array.<module:engine/model/range~Range>} Array of flat ranges covering this range.
*/
getMinimalFlatRanges() {
const ranges = [];
const diffAt = this.start.getCommonPath( this.end ).length;
const pos = _position__WEBPACK_IMPORTED_MODULE_0__["default"]._createAt( this.start );
let posParent = pos.parent;
// Go up.
while ( pos.path.length > diffAt + 1 ) {
const howMany = posParent.maxOffset - pos.offset;
if ( howMany !== 0 ) {
ranges.push( new Range( pos, pos.getShiftedBy( howMany ) ) );
}
pos.path = pos.path.slice( 0, -1 );
pos.offset++;
posParent = posParent.parent;
}
// Go down.
while ( pos.path.length <= this.end.path.length ) {
const offset = this.end.path[ pos.path.length - 1 ];
const howMany = offset - pos.offset;
if ( howMany !== 0 ) {
ranges.push( new Range( pos, pos.getShiftedBy( howMany ) ) );
}
pos.offset = offset;
pos.path.push( 0 );
}
return ranges;
}
/**
* Creates a {@link module:engine/model/treewalker~TreeWalker TreeWalker} instance with this range as a boundary.
*
* For example, to iterate over all items in the entire document root:
*
* // Create a range spanning over the entire root content:
* const range = editor.model.createRangeIn( editor.model.document.getRoot() );
*
* // Iterate over all items in this range:
* for ( const value of range.getWalker() ) {
* console.log( value.item );
* }
*
* @param {Object} options Object with configuration options. See {@link module:engine/model/treewalker~TreeWalker}.
* @param {module:engine/model/position~Position} [options.startPosition]
* @param {Boolean} [options.singleCharacters=false]
* @param {Boolean} [options.shallow=false]
* @param {Boolean} [options.ignoreElementEnd=false]
* @returns {module:engine/model/treewalker~TreeWalker}
*/
getWalker( options = {} ) {
options.boundaries = this;
return new _treewalker__WEBPACK_IMPORTED_MODULE_1__["default"]( options );
}
/**
* Returns an iterator that iterates over all {@link module:engine/model/item~Item items} that are in this range and returns
* them.
*
* This method uses {@link module:engine/model/treewalker~TreeWalker} with `boundaries` set to this range and `ignoreElementEnd` option
* set to `true`. However it returns only {@link module:engine/model/item~Item model items},
* not {@link module:engine/model/treewalker~TreeWalkerValue}.
*
* You may specify additional options for the tree walker. See {@link module:engine/model/treewalker~TreeWalker} for
* a full list of available options.
*
* @param {Object} [options] Object with configuration options. See {@link module:engine/model/treewalker~TreeWalker}.
* @returns {Iterable.<module:engine/model/item~Item>}
*/
* getItems( options = {} ) {
options.boundaries = this;
options.ignoreElementEnd = true;
const treeWalker = new _treewalker__WEBPACK_IMPORTED_MODULE_1__["default"]( options );
for ( const value of treeWalker ) {
yield value.item;
}
}
/**
* Returns an iterator that iterates over all {@link module:engine/model/position~Position positions} that are boundaries or
* contained in this range.
*
* This method uses {@link module:engine/model/treewalker~TreeWalker} with `boundaries` set to this range. However it returns only
* {@link module:engine/model/position~Position positions}, not {@link module:engine/model/treewalker~TreeWalkerValue}.
*
* You may specify additional options for the tree walker. See {@link module:engine/model/treewalker~TreeWalker} for
* a full list of available options.
*
* @param {Object} options Object with configuration options. See {@link module:engine/model/treewalker~TreeWalker}.
* @returns {Iterable.<module:engine/model/position~Position>}
*/
* getPositions( options = {} ) {
options.boundaries = this;
const treeWalker = new _treewalker__WEBPACK_IMPORTED_MODULE_1__["default"]( options );
yield treeWalker.position;
for ( const value of treeWalker ) {
yield value.nextPosition;
}
}
/**
* Returns a range that is a result of transforming this range by given `operation`.
*
* **Note:** transformation may break one range into multiple ranges (for example, when a part of the range is
* moved to a different part of document tree). For this reason, an array is returned by this method and it
* may contain one or more `Range` instances.
*
* @param {module:engine/model/operation/operation~Operation} operation Operation to transform range by.
* @returns {Array.<module:engine/model/range~Range>} Range which is the result of transformation.
*/
getTransformedByOperation( operation ) {
switch ( operation.type ) {
case 'insert':
return this._getTransformedByInsertOperation( operation );
case 'move':
case 'remove':
case 'reinsert':
return this._getTransformedByMoveOperation( operation );
case 'split':
return [ this._getTransformedBySplitOperation( operation ) ];
case 'merge':
return [ this._getTransformedByMergeOperation( operation ) ];
}
return [ new Range( this.start, this.end ) ];
}
/**
* Returns a range that is a result of transforming this range by multiple `operations`.
*
* @see ~Range#getTransformedByOperation
* @param {Iterable.<module:engine/model/operation/operation~Operation>} operations Operations to transform the range by.
* @returns {Array.<module:engine/model/range~Range>} Range which is the result of transformation.
*/
getTransformedByOperations( operations ) {
const ranges = [ new Range( this.start, this.end ) ];
for ( const operation of operations ) {
for ( let i = 0; i < ranges.length; i++ ) {
const result = ranges[ i ].getTransformedByOperation( operation );
ranges.splice( i, 1, ...result );
i += result.length - 1;
}
}
// It may happen that a range is split into two, and then the part of second "piece" is moved into first
// "piece". In this case we will have incorrect third range, which should not be included in the result --
// because it is already included in the first "piece". In this loop we are looking for all such ranges that
// are inside other ranges and we simply remove them.
for ( let i = 0; i < ranges.length; i++ ) {
const range = ranges[ i ];
for ( let j = i + 1; j < ranges.length; j++ ) {
const next = ranges[ j ];
if ( range.containsRange( next ) || next.containsRange( range ) || range.isEqual( next ) ) {
ranges.splice( j, 1 );
}
}
}
return ranges;
}
/**
* Returns an {@link module:engine/model/element~Element} or {@link module:engine/model/documentfragment~DocumentFragment}
* which is a common ancestor of the range's both ends (in which the entire range is contained).
*
* @returns {module:engine/model/element~Element|module:engine/model/documentfragment~DocumentFragment|null}
*/
getCommonAncestor() {
return this.start.getCommonAncestor( this.end );
}
/**
* Returns an {@link module:engine/model/element~Element Element} contained by the range.
* The element will be returned when it is the **only** node within the range and **fully–contained**
* at the same time.
*
* @returns {module:engine/model/element~Element|null}
*/
getContainedElement() {
if ( this.isCollapsed ) {
return null;
}
const nodeAfterStart = this.start.nodeAfter;
const nodeBeforeEnd = this.end.nodeBefore;
if ( nodeAfterStart && nodeAfterStart.is( 'element' ) && nodeAfterStart === nodeBeforeEnd ) {
return nodeAfterStart;
}
return null;
}
/**
* Converts `Range` to plain object and returns it.
*
* @returns {Object} `Node` converted to plain object.
*/
toJSON() {
return {
start: this.start.toJSON(),
end: this.end.toJSON()
};
}
/**
* Returns a new range that is equal to current range.
*
* @returns {module:engine/model/range~Range}
*/
clone() {
return new this.constructor( this.start, this.end );
}
/**
* Returns a result of transforming a copy of this range by insert operation.
*
* One or more ranges may be returned as a result of this transformation.
*
* @protected
* @param {module:engine/model/operation/insertoperation~InsertOperation} operation
* @returns {Array.<module:engine/model/range~Range>}
*/
_getTransformedByInsertOperation( operation, spread = false ) {
return this._getTransformedByInsertion( operation.position, operation.howMany, spread );
}
/**
* Returns a result of transforming a copy of this range by move operation.
*
* One or more ranges may be returned as a result of this transformation.
*
* @protected
* @param {module:engine/model/operation/moveoperation~MoveOperation} operation
* @returns {Array.<module:engine/model/range~Range>}
*/
_getTransformedByMoveOperation( operation, spread = false ) {
const sourcePosition = operation.sourcePosition;
const howMany = operation.howMany;
const targetPosition = operation.targetPosition;
return this._getTransformedByMove( sourcePosition, targetPosition, howMany, spread );
}
/**
* Returns a result of transforming a copy of this range by split operation.
*
* Always one range is returned. The transformation is done in a way to not break the range.
*
* @protected
* @param {module:engine/model/operation/splitoperation~SplitOperation} operation
* @returns {module:engine/model/range~Range}
*/
_getTransformedBySplitOperation( operation ) {
const start = this.start._getTransformedBySplitOperation( operation );
let end = this.end._getTransformedBySplitOperation( operation );
if ( this.end.isEqual( operation.insertionPosition ) ) {
end = this.end.getShiftedBy( 1 );
}
// Below may happen when range contains graveyard element used by split operation.
if ( start.root != end.root ) {
// End position was next to the moved graveyard element and was moved with it.
// Fix it by using old `end` which has proper `root`.
end = this.end.getShiftedBy( -1 );
}
return new Range( start, end );
}
/**
* Returns a result of transforming a copy of this range by merge operation.
*
* Always one range is returned. The transformation is done in a way to not break the range.
*
* @protected
* @param {module:engine/model/operation/mergeoperation~MergeOperation} operation
* @returns {module:engine/model/range~Range}
*/
_getTransformedByMergeOperation( operation ) {
// Special case when the marker is set on "the closing tag" of an element. Marker can be set like that during
// transformations, especially when a content of a few block elements were removed. For example:
//
// {} is the transformed range, [] is the removed range.
// <p>F[o{o</p><p>B}ar</p><p>Xy]z</p>
//
// <p>Fo{o</p><p>B}ar</p><p>z</p>
// <p>F{</p><p>B}ar</p><p>z</p>
// <p>F{</p>}<p>z</p>
// <p>F{}z</p>
//
if ( this.start.isEqual( operation.targetPosition ) && this.end.isEqual( operation.deletionPosition ) ) {
return new Range( this.start );
}
let start = this.start._getTransformedByMergeOperation( operation );
let end = this.end._getTransformedByMergeOperation( operation );
if ( start.root != end.root ) {
// This happens when the end position was next to the merged (deleted) element.
// Then, the end position was moved to the graveyard root. In this case we need to fix
// the range cause its boundaries would be in different roots.
end = this.end.getShiftedBy( -1 );
}
if ( start.isAfter( end ) ) {
// This happens in three following cases:
//
// Case 1: Merge operation source position is before the target position (due to some transformations, OT, etc.)
// This means that start can be moved before the end of the range.
//
// Before: <p>a{a</p><p>b}b</p><p>cc</p>
// Merge: <p>b}b</p><p>cca{a</p>
// Fix: <p>{b}b</p><p>ccaa</p>
//
// Case 2: Range start is before merged node but not directly.
// Result should include all nodes that were in the original range.
//
// Before: <p>aa</p>{<p>cc</p><p>b}b</p>
// Merge: <p>aab}b</p>{<p>cc</p>
// Fix: <p>aa{bb</p><p>cc</p>}
//
// The range is expanded by an additional `b` letter but it is better than dropping the whole `cc` paragraph.
//
// Case 3: Range start is directly before merged node.
// Resulting range should include only nodes from the merged element:
//
// Before: <p>aa</p>{<p>b}b</p><p>cc</p>
// Merge: <p>aab}b</p>{<p>cc</p>
// Fix: <p>aa{b}b</p><p>cc</p>
//
if ( operation.sourcePosition.isBefore( operation.targetPosition ) ) {
// Case 1.
start = _position__WEBPACK_IMPORTED_MODULE_0__["default"]._createAt( end );
start.offset = 0;
} else {
if ( !operation.deletionPosition.isEqual( start ) ) {
// Case 2.
end = operation.deletionPosition;
}
// In both case 2 and 3 start is at the end of the merge-to element.
start = operation.targetPosition;
}
return new Range( start, end );
}
return new Range( start, end );
}
/**
* Returns an array containing one or two {@link ~Range ranges} that are a result of transforming this
* {@link ~Range range} by inserting `howMany` nodes at `insertPosition`. Two {@link ~Range ranges} are
* returned if the insertion was inside this {@link ~Range range} and `spread` is set to `true`.
*
* Examples:
*
* let range = model.createRange(
* model.createPositionFromPath( root, [ 2, 7 ] ),
* model.createPositionFromPath( root, [ 4, 0, 1 ] )
* );
* let transformed = range._getTransformedByInsertion( model.createPositionFromPath( root, [ 1 ] ), 2 );
* // transformed array has one range from [ 4, 7 ] to [ 6, 0, 1 ]
*
* transformed = range._getTransformedByInsertion( model.createPositionFromPath( root, [ 4, 0, 0 ] ), 4 );
* // transformed array has one range from [ 2, 7 ] to [ 4, 0, 5 ]
*
* transformed = range._getTransformedByInsertion( model.createPositionFromPath( root, [ 3, 2 ] ), 4 );
* // transformed array has one range, which is equal to original range
*
* transformed = range._getTransformedByInsertion( model.createPositionFromPath( root, [ 3, 2 ] ), 4, true );
* // transformed array has two ranges: from [ 2, 7 ] to [ 3, 2 ] and from [ 3, 6 ] to [ 4, 0, 1 ]
*
* @protected
* @param {module:engine/model/position~Position} insertPosition Position where nodes are inserted.
* @param {Number} howMany How many nodes are inserted.
* @param {Boolean} [spread] Flag indicating whether this {~Range range} should be spread if insertion
* was inside the range. Defaults to `false`.
* @returns {Array.<module:engine/model/range~Range>} Result of the transformation.
*/
_getTransformedByInsertion( insertPosition, howMany, spread = false ) {
if ( spread && this.containsPosition( insertPosition ) ) {
// Range has to be spread. The first part is from original start to the spread point.
// The other part is from spread point to the original end, but transformed by
// insertion to reflect insertion changes.
return [
new Range( this.start, insertPosition ),
new Range(
insertPosition.getShiftedBy( howMany ),
this.end._getTransformedByInsertion( insertPosition, howMany )
)
];
} else {
const range = new Range( this.start, this.end );
range.start = range.start._getTransformedByInsertion( insertPosition, howMany );
range.end = range.end._getTransformedByInsertion( insertPosition, howMany );
return [ range ];
}
}
/**
* Returns an array containing {@link ~Range ranges} that are a result of transforming this
* {@link ~Range range} by moving `howMany` nodes from `sourcePosition` to `targetPosition`.
*
* @protected
* @param {module:engine/model/position~Position} sourcePosition Position from which nodes are moved.
* @param {module:engine/model/position~Position} targetPosition Position to where nodes are moved.
* @param {Number} howMany How many nodes are moved.
* @param {Boolean} [spread=false] Whether the range should be spread if the move points inside the range.
* @returns {Array.<module:engine/model/range~Range>} Result of the transformation.
*/
_getTransformedByMove( sourcePosition, targetPosition, howMany, spread = false ) {
// Special case for transforming a collapsed range. Just transform it like a position.
if ( this.isCollapsed ) {
const newPos = this.start._getTransformedByMove( sourcePosition, targetPosition, howMany );
return [ new Range( newPos ) ];
}
// Special case for transformation when a part of the range is moved towards the range.
//
// Examples:
//
// <div><p>ab</p><p>c[d</p></div><p>e]f</p> --> <div><p>ab</p></div><p>c[d</p><p>e]f</p>
// <p>e[f</p><div><p>a]b</p><p>cd</p></div> --> <p>e[f</p><p>a]b</p><div><p>cd</p></div>
//
// Without this special condition, the default algorithm leaves an "artifact" range from one of `differenceSet` parts:
//
// <div><p>ab</p><p>c[d</p></div><p>e]f</p> --> <div><p>ab</p>{</div>}<p>c[d</p><p>e]f</p>
//
// This special case is applied only if the range is to be kept together (not spread).
const moveRange = Range._createFromPositionAndShift( sourcePosition, howMany );
const insertPosition = targetPosition._getTransformedByDeletion( sourcePosition, howMany );
if ( this.containsPosition( targetPosition ) && !spread ) {
if ( moveRange.containsPosition( this.start ) || moveRange.containsPosition( this.end ) ) {
const start = this.start._getTransformedByMove( sourcePosition, targetPosition, howMany );
const end = this.end._getTransformedByMove( sourcePosition, targetPosition, howMany );
return [ new Range( start, end ) ];
}
}
// Default algorithm.
let result;
const differenceSet = this.getDifference( moveRange );
let difference = null;
const common = this.getIntersection( moveRange );
if ( differenceSet.length == 1 ) {
// `moveRange` and this range may intersect but may be separate.
difference = new Range(
differenceSet[ 0 ].start._getTransformedByDeletion( sourcePosition, howMany ),
differenceSet[ 0 ].end._getTransformedByDeletion( sourcePosition, howMany )
);
} else if ( differenceSet.length == 2 ) {
// `moveRange` is inside this range.
difference = new Range(
this.start,
this.end._getTransformedByDeletion( sourcePosition, howMany )
);
} // else, `moveRange` contains this range.
if ( difference ) {
result = difference._getTransformedByInsertion( insertPosition, howMany, common !== null || spread );
} else {
result = [];
}
if ( common ) {
const transformedCommon = new Range(
common.start._getCombined( moveRange.start, insertPosition ),
common.end._getCombined( moveRange.start, insertPosition )
);
if ( result.length == 2 ) {
result.splice( 1, 0, transformedCommon );
} else {
result.push( transformedCommon );
}
}
return result;
}
/**
* Returns a copy of this range that is transformed by deletion of `howMany` nodes from `deletePosition`.
*
* If the deleted range is intersecting with the transformed range, the transformed range will be shrank.
*
* If the deleted range contains transformed range, `null` will be returned.
*
* @protected
* @param {module:engine/model/position~Position} deletionPosition Position from which nodes are removed.
* @param {Number} howMany How many nodes are removed.
* @returns {module:engine/model/range~Range|null} Result of the transformation.
*/
_getTransformedByDeletion( deletePosition, howMany ) {
let newStart = this.start._getTransformedByDeletion( deletePosition, howMany );
let newEnd = this.end._getTransformedByDeletion( deletePosition, howMany );
if ( newStart == null && newEnd == null ) {
return null;
}
if ( newStart == null ) {
newStart = deletePosition;
}
if ( newEnd == null ) {
newEnd = deletePosition;
}
return new Range( newStart, newEnd );
}
/**
* Creates a new range, spreading from specified {@link module:engine/model/position~Position position} to a position moved by
* given `shift`. If `shift` is a negative value, shifted position is treated as the beginning of the range.
*
* @protected
* @param {module:engine/model/position~Position} position Beginning of the range.
* @param {Number} shift How long the range should be.
* @returns {module:engine/model/range~Range}
*/
static _createFromPositionAndShift( position, shift ) {
const start = position;
const end = position.getShiftedBy( shift );
return shift > 0 ? new this( start, end ) : new this( end, start );
}
/**
* Creates a range inside an {@link module:engine/model/element~Element element} which starts before the first child of
* that element and ends after the last child of that element.
*
* @protected
* @param {module:engine/model/element~Element} element Element which is a parent for the range.
* @returns {module:engine/model/range~Range}
*/
static _createIn( element ) {
return new this( _position__WEBPACK_IMPORTED_MODULE_0__["default"]._createAt( element, 0 ), _position__WEBPACK_IMPORTED_MODULE_0__["default"]._createAt( element, element.maxOffset ) );
}
/**
* Creates a range that starts before given {@link module:engine/model/item~Item model item} and ends after it.
*
* @protected
* @param {module:engine/model/item~Item} item
* @returns {module:engine/model/range~Range}
*/
static _createOn( item ) {
return this._createFromPositionAndShift( _position__WEBPACK_IMPORTED_MODULE_0__["default"]._createBefore( item ), item.offsetSize );
}
/**
* Combines all ranges from the passed array into a one range. At least one range has to be passed.
* Passed ranges must not have common parts.
*
* The first range from the array is a reference range. If other ranges start or end on the exactly same position where
* the reference range, they get combined into one range.
*
* [ ][] [ ][ ][ ][ ][] [ ] // Passed ranges, shown sorted
* [ ] // The result of the function if the first range was a reference range.
* [ ] // The result of the function if the third-to-seventh range was a reference range.
* [ ] // The result of the function if the last range was a reference range.
*
* @param {Array.<module:engine/model/range~Range>} ranges Ranges to combine.
* @returns {module:engine/model/range~Range} Combined range.
*/
static _createFromRanges( ranges ) {
if ( ranges.length === 0 ) {
/**
* At least one range has to be passed to
* {@link module:engine/model/range~Range._createFromRanges `Range._createFromRanges()`}.
*
* @error range-create-from-ranges-empty-array
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_2__["default"](
'range-create-from-ranges-empty-array',
null
);
} else if ( ranges.length == 1 ) {
return ranges[ 0 ].clone();
}
// 1. Set the first range in `ranges` array as a reference range.
// If we are going to return just a one range, one of the ranges need to be the reference one.
// Other ranges will be stuck to that range, if possible.
const ref = ranges[ 0 ];
// 2. Sort all the ranges so it's easier to process them.
ranges.sort( ( a, b ) => {
return a.start.isAfter( b.start ) ? 1 : -1;
} );
// 3. Check at which index the reference range is now.
const refIndex = ranges.indexOf( ref );
// 4. At this moment we don't need the original range.
// We are going to modify the result and we need to return a new instance of Range.
// We have to create a copy of the reference range.
const result = new this( ref.start, ref.end );
// 5. Ranges should be checked and glued starting from the range that is closest to the reference range.
// Since ranges are sorted, start with the range with index that is closest to reference range index.
if ( refIndex > 0 ) {
for ( let i = refIndex - 1; true; i++ ) {
if ( ranges[ i ].end.isEqual( result.start ) ) {
result.start = _position__WEBPACK_IMPORTED_MODULE_0__["default"]._createAt( ranges[ i ].start );
} else {
// If ranges are not starting/ending at the same position there is no point in looking further.
break;
}
}
}
// 6. Ranges should be checked and glued starting from the range that is closest to the reference range.
// Since ranges are sorted, start with the range with index that is closest to reference range index.
for ( let i = refIndex + 1; i < ranges.length; i++ ) {
if ( ranges[ i ].start.isEqual( result.end ) ) {
result.end = _position__WEBPACK_IMPORTED_MODULE_0__["default"]._createAt( ranges[ i ].end );
} else {
// If ranges are not starting/ending at the same position there is no point in looking further.
break;
}
}
return result;
}
/**
* Creates a `Range` instance from given plain object (i.e. parsed JSON string).
*
* @param {Object} json Plain object to be converted to `Range`.
* @param {module:engine/model/document~Document} doc Document object that will be range owner.
* @returns {module:engine/model/range~Range} `Range` instance created using given plain object.
*/
static fromJSON( json, doc ) {
return new this( _position__WEBPACK_IMPORTED_MODULE_0__["default"].fromJSON( json.start, doc ), _position__WEBPACK_IMPORTED_MODULE_0__["default"].fromJSON( json.end, doc ) );
}
// @if CK_DEBUG_ENGINE // toString() {
// @if CK_DEBUG_ENGINE // return `${ this.root } [ ${ this.start.path.join( ', ' ) } ] - [ ${ this.end.path.join( ', ' ) } ]`;
// @if CK_DEBUG_ENGINE // }
// @if CK_DEBUG_ENGINE // log() {
// @if CK_DEBUG_ENGINE // console.log( 'ModelPosition: ' + this );
// @if CK_DEBUG_ENGINE // }
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/model/rootelement.js":
/*!**************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/model/rootelement.js ***!
\**************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ RootElement)
/* harmony export */ });
/* harmony import */ var _element__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./element */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/element.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/model/rootelement
*/
/**
* Type of {@link module:engine/model/element~Element} that is a root of a model tree.
* @extends module:engine/model/element~Element
*/
class RootElement extends _element__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* Creates root element.
*
* @param {module:engine/model/document~Document} document Document that is an owner of this root.
* @param {String} name Node name.
* @param {String} [rootName='main'] Unique root name used to identify this root
* element by {@link module:engine/model/document~Document}.
*/
constructor( document, name, rootName = 'main' ) {
super( name );
/**
* Document that is an owner of this root.
*
* @private
* @member {module:engine/model/document~Document}
*/
this._document = document;
/**
* Unique root name used to identify this root element by {@link module:engine/model/document~Document}.
*
* @readonly
* @member {String}
*/
this.rootName = rootName;
}
/**
* {@link module:engine/model/document~Document Document} that owns this root element.
*
* @readonly
* @type {module:engine/model/document~Document|null}
*/
get document() {
return this._document;
}
/**
* Checks whether this object is of the given.
*
* rootElement.is( 'rootElement' ); // -> true
* rootElement.is( 'element' ); // -> true
* rootElement.is( 'node' ); // -> true
* rootElement.is( 'model:rootElement' ); // -> true
* rootElement.is( 'model:element' ); // -> true
* rootElement.is( 'model:node' ); // -> true
*
* rootElement.is( 'view:element' ); // -> false
* rootElement.is( 'documentFragment' ); // -> false
*
* Assuming that the object being checked is an element, you can also check its
* {@link module:engine/model/element~Element#name name}:
*
* rootElement.is( 'rootElement', '$root' ); // -> same as above
*
* {@link module:engine/model/node~Node#is Check the entire list of model objects} which implement the `is()` method.
*
* @param {String} type Type to check.
* @param {String} [name] Element name.
* @returns {Boolean}
*/
is( type, name ) {
if ( !name ) {
return type === 'rootElement' || type === 'model:rootElement' ||
// From super.is(). This is highly utilised method and cannot call super. See ckeditor/ckeditor5#6529.
type === 'element' || type === 'model:element' ||
type === 'node' || type === 'model:node';
}
return name === this.name && (
type === 'rootElement' || type === 'model:rootElement' ||
// From super.is(). This is highly utilised method and cannot call super. See ckeditor/ckeditor5#6529.
type === 'element' || type === 'model:element'
);
}
/**
* Converts `RootElement` instance to `String` containing it's name.
*
* @returns {String} `RootElement` instance converted to `String`.
*/
toJSON() {
return this.rootName;
}
// @if CK_DEBUG_ENGINE // toString() {
// @if CK_DEBUG_ENGINE // return this.rootName;
// @if CK_DEBUG_ENGINE // }
// @if CK_DEBUG_ENGINE // log() {
// @if CK_DEBUG_ENGINE // console.log( 'ModelRootElement: ' + this );
// @if CK_DEBUG_ENGINE // }
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/model/schema.js":
/*!*********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/model/schema.js ***!
\*********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "SchemaContext": () => (/* binding */ SchemaContext),
/* harmony export */ "default": () => (/* binding */ Schema)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_observablemixin__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/observablemixin */ "./node_modules/@ckeditor/ckeditor5-utils/src/observablemixin.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/mix */ "./node_modules/@ckeditor/ckeditor5-utils/src/mix.js");
/* harmony import */ var _range__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./range */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/range.js");
/* harmony import */ var _position__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./position */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/position.js");
/* harmony import */ var _element__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./element */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/element.js");
/* harmony import */ var _text__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./text */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/text.js");
/* harmony import */ var _treewalker__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./treewalker */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/treewalker.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/model/schema
*/
/**
* The model's schema. It defines the allowed and disallowed structures of nodes as well as nodes' attributes.
* The schema is usually defined by the features and based on them, the editing framework and features
* make decisions on how to change and process the model.
*
* The instance of schema is available in {@link module:engine/model/model~Model#schema `editor.model.schema`}.
*
* Read more about the schema in:
*
* * The {@glink framework/guides/architecture/editing-engine#schema schema section} of the
* {@glink framework/guides/architecture/editing-engine Introduction to the Editing engine architecture} guide.
* * The {@glink framework/guides/deep-dive/schema Schema deep dive} guide.
*
* @mixes module:utils/observablemixin~ObservableMixin
*/
class Schema {
/**
* Creates a schema instance.
*/
constructor() {
this._sourceDefinitions = {};
/**
* A dictionary containing attribute properties.
*
* @private
* @member {Object.<String,String>}
*/
this._attributeProperties = {};
this.decorate( 'checkChild' );
this.decorate( 'checkAttribute' );
this.on( 'checkAttribute', ( evt, args ) => {
args[ 0 ] = new SchemaContext( args[ 0 ] );
}, { priority: 'highest' } );
this.on( 'checkChild', ( evt, args ) => {
args[ 0 ] = new SchemaContext( args[ 0 ] );
args[ 1 ] = this.getDefinition( args[ 1 ] );
}, { priority: 'highest' } );
}
/**
* Registers a schema item. Can only be called once for every item name.
*
* schema.register( 'paragraph', {
* inheritAllFrom: '$block'
* } );
*
* @param {String} itemName
* @param {module:engine/model/schema~SchemaItemDefinition} definition
*/
register( itemName, definition ) {
if ( this._sourceDefinitions[ itemName ] ) {
/**
* A single item cannot be registered twice in the schema.
*
* This situation may happen when:
*
* * Two or more plugins called {@link #register `register()`} with the same name. This will usually mean that
* there is a collision between plugins which try to use the same element in the model. Unfortunately,
* the only way to solve this is by modifying one of these plugins to use a unique model element name.
* * A single plugin was loaded twice. This happens when it is installed by npm/yarn in two versions
* and usually means one or more of the following issues:
* * a version mismatch (two of your dependencies require two different versions of this plugin),
* * incorrect imports (this plugin is somehow imported twice in a way which confuses webpack),
* * mess in `node_modules/` (`rm -rf node_modules/` may help).
*
* **Note:** Check the logged `itemName` to better understand which plugin was duplicated/conflicting.
*
* @param itemName The name of the model element that is being registered twice.
* @error schema-cannot-register-item-twice
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_0__["default"](
'schema-cannot-register-item-twice',
this,
{
itemName
}
);
}
this._sourceDefinitions[ itemName ] = [
Object.assign( {}, definition )
];
this._clearCache();
}
/**
* Extends a {@link #register registered} item's definition.
*
* Extending properties such as `allowIn` will add more items to the existing properties,
* while redefining properties such as `isBlock` will override the previously defined ones.
*
* schema.register( 'foo', {
* allowIn: '$root',
* isBlock: true;
* } );
* schema.extend( 'foo', {
* allowIn: 'blockQuote',
* isBlock: false
* } );
*
* schema.getDefinition( 'foo' );
* // {
* // allowIn: [ '$root', 'blockQuote' ],
* // isBlock: false
* // }
*
* @param {String} itemName
* @param {module:engine/model/schema~SchemaItemDefinition} definition
*/
extend( itemName, definition ) {
if ( !this._sourceDefinitions[ itemName ] ) {
/**
* Cannot extend an item which was not registered yet.
*
* This error happens when a plugin tries to extend the schema definition of an item which was not
* {@link #register registered} yet.
*
* @param itemName The name of the model element which is being extended.
* @error schema-cannot-extend-missing-item
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_0__["default"]( 'schema-cannot-extend-missing-item', this, {
itemName
} );
}
this._sourceDefinitions[ itemName ].push( Object.assign( {}, definition ) );
this._clearCache();
}
/**
* Returns data of all registered items.
*
* This method should normally be used for reflection purposes (e.g. defining a clone of a certain element,
* checking a list of all block elements, etc).
* Use specific methods (such as {@link #checkChild `checkChild()`} or {@link #isLimit `isLimit()`})
* in other cases.
*
* @returns {Object.<String,module:engine/model/schema~SchemaCompiledItemDefinition>}
*/
getDefinitions() {
if ( !this._compiledDefinitions ) {
this._compile();
}
return this._compiledDefinitions;
}
/**
* Returns a definition of the given item or `undefined` if an item is not registered.
*
* This method should normally be used for reflection purposes (e.g. defining a clone of a certain element,
* checking a list of all block elements, etc).
* Use specific methods (such as {@link #checkChild `checkChild()`} or {@link #isLimit `isLimit()`})
* in other cases.
*
* @param {module:engine/model/item~Item|module:engine/model/schema~SchemaContextItem|String} item
* @returns {module:engine/model/schema~SchemaCompiledItemDefinition}
*/
getDefinition( item ) {
let itemName;
if ( typeof item == 'string' ) {
itemName = item;
} else if ( item.is && ( item.is( '$text' ) || item.is( '$textProxy' ) ) ) {
itemName = '$text';
}
// Element or module:engine/model/schema~SchemaContextItem.
else {
itemName = item.name;
}
return this.getDefinitions()[ itemName ];
}
/**
* Returns `true` if the given item is registered in the schema.
*
* schema.isRegistered( 'paragraph' ); // -> true
* schema.isRegistered( editor.model.document.getRoot() ); // -> true
* schema.isRegistered( 'foo' ); // -> false
*
* @param {module:engine/model/item~Item|module:engine/model/schema~SchemaContextItem|String} item
* @returns {Boolean}
*/
isRegistered( item ) {
return !!this.getDefinition( item );
}
/**
* Returns `true` if the given item is defined to be
* a block by the {@link module:engine/model/schema~SchemaItemDefinition}'s `isBlock` property.
*
* schema.isBlock( 'paragraph' ); // -> true
* schema.isBlock( '$root' ); // -> false
*
* const paragraphElement = writer.createElement( 'paragraph' );
* schema.isBlock( paragraphElement ); // -> true
*
* See the {@glink framework/guides/deep-dive/schema#block-elements Block elements} section of
* the {@glink framework/guides/deep-dive/schema Schema deep dive} guide for more details.
*
* @param {module:engine/model/item~Item|module:engine/model/schema~SchemaContextItem|String} item
* @returns {Boolean}
*/
isBlock( item ) {
const def = this.getDefinition( item );
return !!( def && def.isBlock );
}
/**
* Returns `true` if the given item should be treated as a limit element.
*
* It considers an item to be a limit element if its
* {@link module:engine/model/schema~SchemaItemDefinition}'s
* {@link module:engine/model/schema~SchemaItemDefinition#isLimit `isLimit`} or
* {@link module:engine/model/schema~SchemaItemDefinition#isObject `isObject`} property
* was set to `true`.
*
* schema.isLimit( 'paragraph' ); // -> false
* schema.isLimit( '$root' ); // -> true
* schema.isLimit( editor.model.document.getRoot() ); // -> true
* schema.isLimit( 'imageBlock' ); // -> true
*
* See the {@glink framework/guides/deep-dive/schema#limit-elements Limit elements} section of
* the {@glink framework/guides/deep-dive/schema Schema deep dive} guide for more details.
*
* @param {module:engine/model/item~Item|module:engine/model/schema~SchemaContextItem|String} item
* @returns {Boolean}
*/
isLimit( item ) {
const def = this.getDefinition( item );
if ( !def ) {
return false;
}
return !!( def.isLimit || def.isObject );
}
/**
* Returns `true` if the given item should be treated as an object element.
*
* It considers an item to be an object element if its
* {@link module:engine/model/schema~SchemaItemDefinition}'s
* {@link module:engine/model/schema~SchemaItemDefinition#isObject `isObject`} property
* was set to `true`.
*
* schema.isObject( 'paragraph' ); // -> false
* schema.isObject( 'imageBlock' ); // -> true
*
* const imageElement = writer.createElement( 'imageBlock' );
* schema.isObject( imageElement ); // -> true
*
* See the {@glink framework/guides/deep-dive/schema#object-elements Object elements} section of
* the {@glink framework/guides/deep-dive/schema Schema deep dive} guide for more details.
*
* @param {module:engine/model/item~Item|module:engine/model/schema~SchemaContextItem|String} item
* @returns {Boolean}
*/
isObject( item ) {
const def = this.getDefinition( item );
if ( !def ) {
return false;
}
// Note: Check out the implementation of #isLimit(), #isSelectable(), and #isContent()
// to understand why these three constitute an object.
return !!( def.isObject || ( def.isLimit && def.isSelectable && def.isContent ) );
}
/**
* Returns `true` if the given item is defined to be
* an inline element by the {@link module:engine/model/schema~SchemaItemDefinition}'s `isInline` property.
*
* schema.isInline( 'paragraph' ); // -> false
* schema.isInline( 'softBreak' ); // -> true
*
* const text = writer.createText( 'foo' );
* schema.isInline( text ); // -> true
*
* See the {@glink framework/guides/deep-dive/schema#inline-elements Inline elements} section of
* the {@glink framework/guides/deep-dive/schema Schema deep dive} guide for more details.
*
* @param {module:engine/model/item~Item|module:engine/model/schema~SchemaContextItem|String} item
* @returns {Boolean}
*/
isInline( item ) {
const def = this.getDefinition( item );
return !!( def && def.isInline );
}
/**
* Returns `true` if the given item is defined to be
* a selectable element by the {@link module:engine/model/schema~SchemaItemDefinition}'s `isSelectable` property.
*
* schema.isSelectable( 'paragraph' ); // -> false
* schema.isSelectable( 'heading1' ); // -> false
* schema.isSelectable( 'imageBlock' ); // -> true
* schema.isSelectable( 'tableCell' ); // -> true
*
* const text = writer.createText( 'foo' );
* schema.isSelectable( text ); // -> false
*
* See the {@glink framework/guides/deep-dive/schema#selectable-elements Selectable elements section} of
* the {@glink framework/guides/deep-dive/schema Schema deep dive} guide for more details.
*
* @param {module:engine/model/item~Item|module:engine/model/schema~SchemaContextItem|String} item
* @returns {Boolean}
*/
isSelectable( item ) {
const def = this.getDefinition( item );
if ( !def ) {
return false;
}
return !!( def.isSelectable || def.isObject );
}
/**
* Returns `true` if the given item is defined to be
* a content by the {@link module:engine/model/schema~SchemaItemDefinition}'s `isContent` property.
*
* schema.isContent( 'paragraph' ); // -> false
* schema.isContent( 'heading1' ); // -> false
* schema.isContent( 'imageBlock' ); // -> true
* schema.isContent( 'horizontalLine' ); // -> true
*
* const text = writer.createText( 'foo' );
* schema.isContent( text ); // -> true
*
* See the {@glink framework/guides/deep-dive/schema#content-elements Content elements section} of
* the {@glink framework/guides/deep-dive/schema Schema deep dive} guide for more details.
*
* @param {module:engine/model/item~Item|module:engine/model/schema~SchemaContextItem|String} item
* @returns {Boolean}
*/
isContent( item ) {
const def = this.getDefinition( item );
if ( !def ) {
return false;
}
return !!( def.isContent || def.isObject );
}
/**
* Checks whether the given node (`child`) can be a child of the given context.
*
* schema.checkChild( model.document.getRoot(), paragraph ); // -> false
*
* schema.register( 'paragraph', {
* allowIn: '$root'
* } );
* schema.checkChild( model.document.getRoot(), paragraph ); // -> true
*
* Note: When verifying whether the given node can be a child of the given context, the
* schema also verifies the entire context — from its root to its last element. Therefore, it is possible
* for `checkChild()` to return `false` even though the context's last element can contain the checked child.
* It happens if one of the context's elements does not allow its child.
*
* @fires checkChild
* @param {module:engine/model/schema~SchemaContextDefinition} context The context in which the child will be checked.
* @param {module:engine/model/node~Node|String} def The child to check.
* @returns {Boolean}
*/
checkChild( context, def ) {
// Note: context and child are already normalized here to a SchemaContext and SchemaCompiledItemDefinition.
if ( !def ) {
return false;
}
return this._checkContextMatch( def, context );
}
/**
* Checks whether the given attribute can be applied in the given context (on the last
* item of the context).
*
* schema.checkAttribute( textNode, 'bold' ); // -> false
*
* schema.extend( '$text', {
* allowAttributes: 'bold'
* } );
* schema.checkAttribute( textNode, 'bold' ); // -> true
*
* @fires checkAttribute
* @param {module:engine/model/schema~SchemaContextDefinition} context The context in which the attribute will be checked.
* @param {String} attributeName
* @returns {Boolean}
*/
checkAttribute( context, attributeName ) {
const def = this.getDefinition( context.last );
if ( !def ) {
return false;
}
return def.allowAttributes.includes( attributeName );
}
/**
* Checks whether the given element (`elementToMerge`) can be merged with the specified base element (`positionOrBaseElement`).
*
* In other words — whether `elementToMerge`'s children {@link #checkChild are allowed} in the `positionOrBaseElement`.
*
* This check ensures that elements merged with {@link module:engine/model/writer~Writer#merge `Writer#merge()`}
* will be valid.
*
* Instead of elements, you can pass the instance of the {@link module:engine/model/position~Position} class as the
* `positionOrBaseElement`. It means that the elements before and after the position will be checked whether they can be merged.
*
* @param {module:engine/model/position~Position|module:engine/model/element~Element} positionOrBaseElement The position or base
* element to which the `elementToMerge` will be merged.
* @param {module:engine/model/element~Element} elementToMerge The element to merge. Required if `positionOrBaseElement` is an element.
* @returns {Boolean}
*/
checkMerge( positionOrBaseElement, elementToMerge = null ) {
if ( positionOrBaseElement instanceof _position__WEBPACK_IMPORTED_MODULE_4__["default"] ) {
const nodeBefore = positionOrBaseElement.nodeBefore;
const nodeAfter = positionOrBaseElement.nodeAfter;
if ( !( nodeBefore instanceof _element__WEBPACK_IMPORTED_MODULE_5__["default"] ) ) {
/**
* The node before the merge position must be an element.
*
* @error schema-check-merge-no-element-before
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_0__["default"](
'schema-check-merge-no-element-before',
this
);
}
if ( !( nodeAfter instanceof _element__WEBPACK_IMPORTED_MODULE_5__["default"] ) ) {
/**
* The node after the merge position must be an element.
*
* @error schema-check-merge-no-element-after
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_0__["default"](
'schema-check-merge-no-element-after',
this
);
}
return this.checkMerge( nodeBefore, nodeAfter );
}
for ( const child of elementToMerge.getChildren() ) {
if ( !this.checkChild( positionOrBaseElement, child ) ) {
return false;
}
}
return true;
}
/**
* Allows registering a callback to the {@link #checkChild} method calls.
*
* Callbacks allow you to implement rules which are not otherwise possible to achieve
* by using the declarative API of {@link module:engine/model/schema~SchemaItemDefinition}.
* For example, by using this method you can disallow elements in specific contexts.
*
* This method is a shorthand for using the {@link #event:checkChild} event. For even better control,
* you can use that event instead.
*
* Example:
*
* // Disallow heading1 directly inside a blockQuote.
* schema.addChildCheck( ( context, childDefinition ) => {
* if ( context.endsWith( 'blockQuote' ) && childDefinition.name == 'heading1' ) {
* return false;
* }
* } );
*
* Which translates to:
*
* schema.on( 'checkChild', ( evt, args ) => {
* const context = args[ 0 ];
* const childDefinition = args[ 1 ];
*
* if ( context.endsWith( 'blockQuote' ) && childDefinition && childDefinition.name == 'heading1' ) {
* // Prevent next listeners from being called.
* evt.stop();
* // Set the checkChild()'s return value.
* evt.return = false;
* }
* }, { priority: 'high' } );
*
* @param {Function} callback The callback to be called. It is called with two parameters:
* {@link module:engine/model/schema~SchemaContext} (context) instance and
* {@link module:engine/model/schema~SchemaCompiledItemDefinition} (child-to-check definition).
* The callback may return `true/false` to override `checkChild()`'s return value. If it does not return
* a boolean value, the default algorithm (or other callbacks) will define `checkChild()`'s return value.
*/
addChildCheck( callback ) {
this.on( 'checkChild', ( evt, [ ctx, childDef ] ) => {
// checkChild() was called with a non-registered child.
// In 99% cases such check should return false, so not to overcomplicate all callbacks
// don't even execute them.
if ( !childDef ) {
return;
}
const retValue = callback( ctx, childDef );
if ( typeof retValue == 'boolean' ) {
evt.stop();
evt.return = retValue;
}
}, { priority: 'high' } );
}
/**
* Allows registering a callback to the {@link #checkAttribute} method calls.
*
* Callbacks allow you to implement rules which are not otherwise possible to achieve
* by using the declarative API of {@link module:engine/model/schema~SchemaItemDefinition}.
* For example, by using this method you can disallow attribute if node to which it is applied
* is contained within some other element (e.g. you want to disallow `bold` on `$text` within `heading1`).
*
* This method is a shorthand for using the {@link #event:checkAttribute} event. For even better control,
* you can use that event instead.
*
* Example:
*
* // Disallow bold on $text inside heading1.
* schema.addAttributeCheck( ( context, attributeName ) => {
* if ( context.endsWith( 'heading1 $text' ) && attributeName == 'bold' ) {
* return false;
* }
* } );
*
* Which translates to:
*
* schema.on( 'checkAttribute', ( evt, args ) => {
* const context = args[ 0 ];
* const attributeName = args[ 1 ];
*
* if ( context.endsWith( 'heading1 $text' ) && attributeName == 'bold' ) {
* // Prevent next listeners from being called.
* evt.stop();
* // Set the checkAttribute()'s return value.
* evt.return = false;
* }
* }, { priority: 'high' } );
*
* @param {Function} callback The callback to be called. It is called with two parameters:
* {@link module:engine/model/schema~SchemaContext} (context) instance and attribute name.
* The callback may return `true/false` to override `checkAttribute()`'s return value. If it does not return
* a boolean value, the default algorithm (or other callbacks) will define `checkAttribute()`'s return value.
*/
addAttributeCheck( callback ) {
this.on( 'checkAttribute', ( evt, [ ctx, attributeName ] ) => {
const retValue = callback( ctx, attributeName );
if ( typeof retValue == 'boolean' ) {
evt.stop();
evt.return = retValue;
}
}, { priority: 'high' } );
}
/**
* This method allows assigning additional metadata to the model attributes. For example,
* {@link module:engine/model/schema~AttributeProperties `AttributeProperties#isFormatting` property} is
* used to mark formatting attributes (like `bold` or `italic`).
*
* // Mark bold as a formatting attribute.
* schema.setAttributeProperties( 'bold', {
* isFormatting: true
* } );
*
* // Override code not to be considered a formatting markup.
* schema.setAttributeProperties( 'code', {
* isFormatting: false
* } );
*
* Properties are not limited to members defined in the
* {@link module:engine/model/schema~AttributeProperties `AttributeProperties` type} and you can also use custom properties:
*
* schema.setAttributeProperties( 'blockQuote', {
* customProperty: 'value'
* } );
*
* Subsequent calls with the same attribute will extend its custom properties:
*
* schema.setAttributeProperties( 'blockQuote', {
* one: 1
* } );
*
* schema.setAttributeProperties( 'blockQuote', {
* two: 2
* } );
*
* console.log( schema.getAttributeProperties( 'blockQuote' ) );
* // Logs: { one: 1, two: 2 }
*
* @param {String} attributeName A name of the attribute to receive the properties.
* @param {module:engine/model/schema~AttributeProperties} properties A dictionary of properties.
*/
setAttributeProperties( attributeName, properties ) {
this._attributeProperties[ attributeName ] = Object.assign( this.getAttributeProperties( attributeName ), properties );
}
/**
* Returns properties associated with a given model attribute. See {@link #setAttributeProperties `setAttributeProperties()`}.
*
* @param {String} attributeName A name of the attribute.
* @returns {module:engine/model/schema~AttributeProperties}
*/
getAttributeProperties( attributeName ) {
return this._attributeProperties[ attributeName ] || {};
}
/**
* Returns the lowest {@link module:engine/model/schema~Schema#isLimit limit element} containing the entire
* selection/range/position or the root otherwise.
*
* @param {module:engine/model/selection~Selection|module:engine/model/documentselection~DocumentSelection|
* module:engine/model/range~Range|module:engine/model/position~Position} selectionOrRangeOrPosition
* The selection/range/position to check.
* @returns {module:engine/model/element~Element} The lowest limit element containing
* the entire `selectionOrRangeOrPosition`.
*/
getLimitElement( selectionOrRangeOrPosition ) {
let element;
if ( selectionOrRangeOrPosition instanceof _position__WEBPACK_IMPORTED_MODULE_4__["default"] ) {
element = selectionOrRangeOrPosition.parent;
} else {
const ranges = selectionOrRangeOrPosition instanceof _range__WEBPACK_IMPORTED_MODULE_3__["default"] ?
[ selectionOrRangeOrPosition ] :
Array.from( selectionOrRangeOrPosition.getRanges() );
// Find the common ancestor for all selection's ranges.
element = ranges
.reduce( ( element, range ) => {
const rangeCommonAncestor = range.getCommonAncestor();
if ( !element ) {
return rangeCommonAncestor;
}
return element.getCommonAncestor( rangeCommonAncestor, { includeSelf: true } );
}, null );
}
while ( !this.isLimit( element ) ) {
if ( element.parent ) {
element = element.parent;
} else {
break;
}
}
return element;
}
/**
* Checks whether the attribute is allowed in selection:
*
* * if the selection is not collapsed, then checks if the attribute is allowed on any of nodes in that range,
* * if the selection is collapsed, then checks if on the selection position there's a text with the
* specified attribute allowed.
*
* @param {module:engine/model/selection~Selection|module:engine/model/documentselection~DocumentSelection} selection
* Selection which will be checked.
* @param {String} attribute The name of the attribute to check.
* @returns {Boolean}
*/
checkAttributeInSelection( selection, attribute ) {
if ( selection.isCollapsed ) {
const firstPosition = selection.getFirstPosition();
const context = [
...firstPosition.getAncestors(),
new _text__WEBPACK_IMPORTED_MODULE_6__["default"]( '', selection.getAttributes() )
];
// Check whether schema allows for a text with the attribute in the selection.
return this.checkAttribute( context, attribute );
} else {
const ranges = selection.getRanges();
// For all ranges, check nodes in them until you find a node that is allowed to have the attribute.
for ( const range of ranges ) {
for ( const value of range ) {
if ( this.checkAttribute( value.item, attribute ) ) {
// If we found a node that is allowed to have the attribute, return true.
return true;
}
}
}
}
// If we haven't found such node, return false.
return false;
}
/**
* Transforms the given set of ranges into a set of ranges where the given attribute is allowed (and can be applied).
*
* @param {Array.<module:engine/model/range~Range>} ranges Ranges to be validated.
* @param {String} attribute The name of the attribute to check.
* @returns {Iterable.<module:engine/model/range~Range>} Ranges in which the attribute is allowed.
*/
* getValidRanges( ranges, attribute ) {
ranges = convertToMinimalFlatRanges( ranges );
for ( const range of ranges ) {
yield* this._getValidRangesForRange( range, attribute );
}
}
/**
* Basing on given `position`, finds and returns a {@link module:engine/model/range~Range range} which is
* nearest to that `position` and is a correct range for selection.
*
* The correct selection range might be collapsed when it is located in a position where the text node can be placed.
* Non-collapsed range is returned when selection can be placed around element marked as an "object" in
* the {@link module:engine/model/schema~Schema schema}.
*
* Direction of searching for the nearest correct selection range can be specified as:
*
* * `both` - searching will be performed in both ways,
* * `forward` - searching will be performed only forward,
* * `backward` - searching will be performed only backward.
*
* When valid selection range cannot be found, `null` is returned.
*
* @param {module:engine/model/position~Position} position Reference position where new selection range should be looked for.
* @param {'both'|'forward'|'backward'} [direction='both'] Search direction.
* @returns {module:engine/model/range~Range|null} Nearest selection range or `null` if one cannot be found.
*/
getNearestSelectionRange( position, direction = 'both' ) {
// Return collapsed range if provided position is valid.
if ( this.checkChild( position, '$text' ) ) {
return new _range__WEBPACK_IMPORTED_MODULE_3__["default"]( position );
}
let backwardWalker, forwardWalker;
// Never leave a limit element.
const limitElement = position.getAncestors().reverse().find( item => this.isLimit( item ) ) || position.root;
if ( direction == 'both' || direction == 'backward' ) {
backwardWalker = new _treewalker__WEBPACK_IMPORTED_MODULE_7__["default"]( {
boundaries: _range__WEBPACK_IMPORTED_MODULE_3__["default"]._createIn( limitElement ),
startPosition: position,
direction: 'backward'
} );
}
if ( direction == 'both' || direction == 'forward' ) {
forwardWalker = new _treewalker__WEBPACK_IMPORTED_MODULE_7__["default"]( {
boundaries: _range__WEBPACK_IMPORTED_MODULE_3__["default"]._createIn( limitElement ),
startPosition: position
} );
}
for ( const data of combineWalkers( backwardWalker, forwardWalker ) ) {
const type = ( data.walker == backwardWalker ? 'elementEnd' : 'elementStart' );
const value = data.value;
if ( value.type == type && this.isObject( value.item ) ) {
return _range__WEBPACK_IMPORTED_MODULE_3__["default"]._createOn( value.item );
}
if ( this.checkChild( value.nextPosition, '$text' ) ) {
return new _range__WEBPACK_IMPORTED_MODULE_3__["default"]( value.nextPosition );
}
}
return null;
}
/**
* Tries to find position ancestors that allow to insert a given node.
* It starts searching from the given position and goes node by node to the top of the model tree
* as long as a {@link module:engine/model/schema~Schema#isLimit limit element}, an
* {@link module:engine/model/schema~Schema#isObject object element} or a topmost ancestor is not reached.
*
* @param {module:engine/model/position~Position} position The position that the search will start from.
* @param {module:engine/model/node~Node|String} node The node for which an allowed parent should be found or its name.
* @returns {module:engine/model/element~Element|null} element Allowed parent or null if nothing was found.
*/
findAllowedParent( position, node ) {
let parent = position.parent;
while ( parent ) {
if ( this.checkChild( parent, node ) ) {
return parent;
}
// Do not split limit elements.
if ( this.isLimit( parent ) ) {
return null;
}
parent = parent.parent;
}
return null;
}
/**
* Removes attributes disallowed by the schema.
*
* @param {Iterable.<module:engine/model/node~Node>} nodes Nodes that will be filtered.
* @param {module:engine/model/writer~Writer} writer
*/
removeDisallowedAttributes( nodes, writer ) {
for ( const node of nodes ) {
// When node is a `Text` it has no children, so just filter it out.
if ( node.is( '$text' ) ) {
removeDisallowedAttributeFromNode( this, node, writer );
}
// In a case of `Element` iterates through positions between nodes inside this element
// and filter out node before the current position, or position parent when position
// is at start of an element. Using positions prevent from omitting merged nodes
// see https://github.com/ckeditor/ckeditor5-engine/issues/1789.
else {
const rangeInNode = _range__WEBPACK_IMPORTED_MODULE_3__["default"]._createIn( node );
const positionsInRange = rangeInNode.getPositions();
for ( const position of positionsInRange ) {
const item = position.nodeBefore || position.parent;
removeDisallowedAttributeFromNode( this, item, writer );
}
}
}
}
/**
* Creates an instance of the schema context.
*
* @param {module:engine/model/schema~SchemaContextDefinition} context
* @returns {module:engine/model/schema~SchemaContext}
*/
createContext( context ) {
return new SchemaContext( context );
}
/**
* @private
*/
_clearCache() {
this._compiledDefinitions = null;
}
/**
* @private
*/
_compile() {
const compiledDefinitions = {};
const sourceRules = this._sourceDefinitions;
const itemNames = Object.keys( sourceRules );
for ( const itemName of itemNames ) {
compiledDefinitions[ itemName ] = compileBaseItemRule( sourceRules[ itemName ], itemName );
}
for ( const itemName of itemNames ) {
compileAllowChildren( compiledDefinitions, itemName );
}
for ( const itemName of itemNames ) {
compileAllowContentOf( compiledDefinitions, itemName );
}
for ( const itemName of itemNames ) {
compileAllowWhere( compiledDefinitions, itemName );
}
for ( const itemName of itemNames ) {
compileAllowAttributesOf( compiledDefinitions, itemName );
compileInheritPropertiesFrom( compiledDefinitions, itemName );
}
for ( const itemName of itemNames ) {
cleanUpAllowIn( compiledDefinitions, itemName );
setupAllowChildren( compiledDefinitions, itemName );
cleanUpAllowAttributes( compiledDefinitions, itemName );
}
this._compiledDefinitions = compiledDefinitions;
}
/**
* @private
* @param {module:engine/model/schema~SchemaCompiledItemDefinition} def
* @param {module:engine/model/schema~SchemaContext} context
* @param {Number} contextItemIndex
*/
_checkContextMatch( def, context, contextItemIndex = context.length - 1 ) {
const contextItem = context.getItem( contextItemIndex );
if ( def.allowIn.includes( contextItem.name ) ) {
if ( contextItemIndex == 0 ) {
return true;
} else {
const parentRule = this.getDefinition( contextItem );
return this._checkContextMatch( parentRule, context, contextItemIndex - 1 );
}
} else {
return false;
}
}
/**
* Takes a flat range and an attribute name. Traverses the range recursively and deeply to find and return all ranges
* inside the given range on which the attribute can be applied.
*
* This is a helper function for {@link ~Schema#getValidRanges}.
*
* @private
* @param {module:engine/model/range~Range} range The range to process.
* @param {String} attribute The name of the attribute to check.
* @returns {Iterable.<module:engine/model/range~Range>} Ranges in which the attribute is allowed.
*/
* _getValidRangesForRange( range, attribute ) {
let start = range.start;
let end = range.start;
for ( const item of range.getItems( { shallow: true } ) ) {
if ( item.is( 'element' ) ) {
yield* this._getValidRangesForRange( _range__WEBPACK_IMPORTED_MODULE_3__["default"]._createIn( item ), attribute );
}
if ( !this.checkAttribute( item, attribute ) ) {
if ( !start.isEqual( end ) ) {
yield new _range__WEBPACK_IMPORTED_MODULE_3__["default"]( start, end );
}
start = _position__WEBPACK_IMPORTED_MODULE_4__["default"]._createAfter( item );
}
end = _position__WEBPACK_IMPORTED_MODULE_4__["default"]._createAfter( item );
}
if ( !start.isEqual( end ) ) {
yield new _range__WEBPACK_IMPORTED_MODULE_3__["default"]( start, end );
}
}
}
(0,_ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_2__["default"])( Schema, _ckeditor_ckeditor5_utils_src_observablemixin__WEBPACK_IMPORTED_MODULE_1__["default"] );
/**
* Event fired when the {@link #checkChild} method is called. It allows plugging in
* additional behavior, for example implementing rules which cannot be defined using the declarative
* {@link module:engine/model/schema~SchemaItemDefinition} interface.
*
* **Note:** The {@link #addChildCheck} method is a more handy way to register callbacks. Internally,
* it registers a listener to this event but comes with a simpler API and it is the recommended choice
* in most of the cases.
*
* The {@link #checkChild} method fires an event because it is
* {@link module:utils/observablemixin~ObservableMixin#decorate decorated} with it. Thanks to that you can
* use this event in various ways, but the most important use case is overriding standard behavior of the
* `checkChild()` method. Let's see a typical listener template:
*
* schema.on( 'checkChild', ( evt, args ) => {
* const context = args[ 0 ];
* const childDefinition = args[ 1 ];
* }, { priority: 'high' } );
*
* The listener is added with a `high` priority to be executed before the default method is really called. The `args` callback
* parameter contains arguments passed to `checkChild( context, child )`. However, the `context` parameter is already
* normalized to a {@link module:engine/model/schema~SchemaContext} instance and `child` to a
* {@link module:engine/model/schema~SchemaCompiledItemDefinition} instance, so you do not have to worry about
* the various ways how `context` and `child` may be passed to `checkChild()`.
*
* **Note:** `childDefinition` may be `undefined` if `checkChild()` was called with a non-registered element.
*
* So, in order to implement a rule "disallow `heading1` in `blockQuote`", you can add such a listener:
*
* schema.on( 'checkChild', ( evt, args ) => {
* const context = args[ 0 ];
* const childDefinition = args[ 1 ];
*
* if ( context.endsWith( 'blockQuote' ) && childDefinition && childDefinition.name == 'heading1' ) {
* // Prevent next listeners from being called.
* evt.stop();
* // Set the checkChild()'s return value.
* evt.return = false;
* }
* }, { priority: 'high' } );
*
* Allowing elements in specific contexts will be a far less common use case, because it is normally handled by the
* `allowIn` rule from {@link module:engine/model/schema~SchemaItemDefinition}. But if you have a complex scenario
* where `listItem` should be allowed only in element `foo` which must be in element `bar`, then this would be the way:
*
* schema.on( 'checkChild', ( evt, args ) => {
* const context = args[ 0 ];
* const childDefinition = args[ 1 ];
*
* if ( context.endsWith( 'bar foo' ) && childDefinition.name == 'listItem' ) {
* // Prevent next listeners from being called.
* evt.stop();
* // Set the checkChild()'s return value.
* evt.return = true;
* }
* }, { priority: 'high' } );
*
* @event checkChild
* @param {Array} args The `checkChild()`'s arguments.
*/
/**
* Event fired when the {@link #checkAttribute} method is called. It allows plugging in
* additional behavior, for example implementing rules which cannot be defined using the declarative
* {@link module:engine/model/schema~SchemaItemDefinition} interface.
*
* **Note:** The {@link #addAttributeCheck} method is a more handy way to register callbacks. Internally,
* it registers a listener to this event but comes with a simpler API and it is the recommended choice
* in most of the cases.
*
* The {@link #checkAttribute} method fires an event because it is
* {@link module:utils/observablemixin~ObservableMixin#decorate decorated} with it. Thanks to that you can
* use this event in various ways, but the most important use case is overriding the standard behavior of the
* `checkAttribute()` method. Let's see a typical listener template:
*
* schema.on( 'checkAttribute', ( evt, args ) => {
* const context = args[ 0 ];
* const attributeName = args[ 1 ];
* }, { priority: 'high' } );
*
* The listener is added with a `high` priority to be executed before the default method is really called. The `args` callback
* parameter contains arguments passed to `checkAttribute( context, attributeName )`. However, the `context` parameter is already
* normalized to a {@link module:engine/model/schema~SchemaContext} instance, so you do not have to worry about
* the various ways how `context` may be passed to `checkAttribute()`.
*
* So, in order to implement a rule "disallow `bold` in a text which is in a `heading1`, you can add such a listener:
*
* schema.on( 'checkAttribute', ( evt, args ) => {
* const context = args[ 0 ];
* const attributeName = args[ 1 ];
*
* if ( context.endsWith( 'heading1 $text' ) && attributeName == 'bold' ) {
* // Prevent next listeners from being called.
* evt.stop();
* // Set the checkAttribute()'s return value.
* evt.return = false;
* }
* }, { priority: 'high' } );
*
* Allowing attributes in specific contexts will be a far less common use case, because it is normally handled by the
* `allowAttributes` rule from {@link module:engine/model/schema~SchemaItemDefinition}. But if you have a complex scenario
* where `bold` should be allowed only in element `foo` which must be in element `bar`, then this would be the way:
*
* schema.on( 'checkAttribute', ( evt, args ) => {
* const context = args[ 0 ];
* const attributeName = args[ 1 ];
*
* if ( context.endsWith( 'bar foo $text' ) && attributeName == 'bold' ) {
* // Prevent next listeners from being called.
* evt.stop();
* // Set the checkAttribute()'s return value.
* evt.return = true;
* }
* }, { priority: 'high' } );
*
* @event checkAttribute
* @param {Array} args The `checkAttribute()`'s arguments.
*/
/**
* A definition of a {@link module:engine/model/schema~Schema schema} item.
*
* You can define the following rules:
*
* * {@link ~SchemaItemDefinition#allowIn `allowIn`} – Defines in which other items this item will be allowed.
* * {@link ~SchemaItemDefinition#allowChildren `allowChildren`} – Defines which other items are allowed inside this item.
* * {@link ~SchemaItemDefinition#allowAttributes `allowAttributes`} – Defines allowed attributes of the given item.
* * {@link ~SchemaItemDefinition#allowContentOf `allowContentOf`} – Inherits "allowed children" from other items.
* * {@link ~SchemaItemDefinition#allowWhere `allowWhere`} – Inherits "allowed in" from other items.
* * {@link ~SchemaItemDefinition#allowAttributesOf `allowAttributesOf`} – Inherits attributes from other items.
* * {@link ~SchemaItemDefinition#inheritTypesFrom `inheritTypesFrom`} – Inherits `is*` properties of other items.
* * {@link ~SchemaItemDefinition#inheritAllFrom `inheritAllFrom`} –
* A shorthand for `allowContentOf`, `allowWhere`, `allowAttributesOf`, `inheritTypesFrom`.
*
* # The `is*` properties
*
* There are a couple commonly used `is*` properties. Their role is to assign additional semantics to schema items.
* You can define more properties but you will also need to implement support for them in the existing editor features.
*
* * {@link ~SchemaItemDefinition#isBlock `isBlock`} – Whether this item is paragraph-like.
* Generally speaking, content is usually made out of blocks like paragraphs, list items, images, headings, etc.
* * {@link ~SchemaItemDefinition#isInline `isInline`} – Whether an item is "text-like" and should be treated as an inline node.
* Examples of inline elements: `$text`, `softBreak` (`<br>`), etc.
* * {@link ~SchemaItemDefinition#isLimit `isLimit`} – It can be understood as whether this element
* should not be split by <kbd>Enter</kbd>. Examples of limit elements: `$root`, table cell, image caption, etc.
* In other words, all actions that happen inside a limit element are limited to its content.
* All objects are treated as limit elements, too.
* * {@link ~SchemaItemDefinition#isObject `isObject`} – Whether an item is "self-contained" and should be treated as a whole.
* Examples of object elements: `imageBlock`, `table`, `video`, etc. An object is also a limit, so
* {@link module:engine/model/schema~Schema#isLimit `isLimit()`} returns `true` for object elements automatically.
*
* Read more about the meaning of these types in the
* {@glink framework/guides/deep-dive/schema#defining-additional-semantics dedicated section of the Schema deep dive} guide.
*
* # Generic items
*
* There are three basic generic items: `$root`, `$block` and `$text`.
* They are defined as follows:
*
* this.schema.register( '$root', {
* isLimit: true
* } );
* this.schema.register( '$block', {
* allowIn: '$root',
* isBlock: true
* } );
* this.schema.register( '$text', {
* allowIn: '$block',
* isInline: true
* } );
*
* They reflect typical editor content that is contained within one root, consists of several blocks
* (paragraphs, lists items, headings, images) which, in turn, may contain text inside.
*
* By inheriting from the generic items you can define new items which will get extended by other editor features.
* Read more about generic types in the {@glink framework/guides/deep-dive/schema Schema deep dive} guide.
*
* # Example definitions
*
* Allow `paragraph` in roots and block quotes:
*
* schema.register( 'paragraph', {
* allowIn: [ '$root', 'blockQuote' ],
* isBlock: true
* } );
*
* Allow `paragraph` everywhere where `$block` is allowed (i.e. in `$root`):
*
* schema.register( 'paragraph', {
* allowWhere: '$block',
* isBlock: true
* } );
*
* Allow `paragraph` inside a `$root` and allow `$text` as a `paragraph` child:
*
* schema.register( 'paragraph', {
* allowIn: '$root',
* allowChildren: '$text',
* isBlock: true
* } );
*
* Make `imageBlock` a block object, which is allowed everywhere where `$block` is.
* Also, allow `src` and `alt` attributes in it:
*
* schema.register( 'imageBlock', {
* allowWhere: '$block',
* allowAttributes: [ 'src', 'alt' ],
* isBlock: true,
* isObject: true
* } );
*
* Make `caption` allowed in `imageBlock` and make it allow all the content of `$block`s (usually, `$text`).
* Also, mark it as a limit element so it cannot be split:
*
* schema.register( 'caption', {
* allowIn: 'imageBlock',
* allowContentOf: '$block',
* isLimit: true
* } );
*
* Make `listItem` inherit all from `$block` but also allow additional attributes:
*
* schema.register( 'listItem', {
* inheritAllFrom: '$block',
* allowAttributes: [ 'listType', 'listIndent' ]
* } );
*
* Which translates to:
*
* schema.register( 'listItem', {
* allowWhere: '$block',
* allowContentOf: '$block',
* allowAttributesOf: '$block',
* inheritTypesFrom: '$block',
* allowAttributes: [ 'listType', 'listIndent' ]
* } );
*
* # Tips
*
* * Check schema definitions of existing features to see how they are defined.
* * If you want to publish your feature so other developers can use it, try to use
* generic items as much as possible.
* * Keep your model clean. Limit it to the actual data and store information in a normalized way.
* * Remember about defining the `is*` properties. They do not affect the allowed structures, but they can
* affect how the editor features treat your elements.
*
* @typedef {Object} module:engine/model/schema~SchemaItemDefinition
*
* @property {String|Array.<String>} allowIn Defines in which other items this item will be allowed.
* @property {String|Array.<String>} allowChildren Defines which other items are allowed inside this item.
* @property {String|Array.<String>} allowAttributes Defines allowed attributes of the given item.
* @property {String|Array.<String>} allowContentOf Inherits "allowed children" from other items.
* @property {String|Array.<String>} allowWhere Inherits "allowed in" from other items.
* @property {String|Array.<String>} allowAttributesOf Inherits attributes from other items.
* @property {String|Array.<String>} inheritTypesFrom Inherits `is*` properties of other items.
* @property {String} inheritAllFrom A shorthand for `allowContentOf`, `allowWhere`, `allowAttributesOf`, `inheritTypesFrom`.
*
* @property {Boolean} isBlock
* Whether this item is paragraph-like. Generally speaking, content is usually made out of blocks
* like paragraphs, list items, images, headings, etc. All these elements are marked as blocks. A block
* should not allow another block inside. Note: There is also the `$block` generic item which has `isBlock` set to `true`.
* Most block type items will inherit from `$block` (through `inheritAllFrom`).
*
* Read more about the block elements in the
* {@glink framework/guides/deep-dive/schema#block-elements Block elements section} of
* the {@glink framework/guides/deep-dive/schema Schema deep dive}.
*
* @property {Boolean} isInline
* Whether an item is "text-like" and should be treated as an inline node. Examples of inline elements:
* `$text`, `softBreak` (`<br>`), etc.
*
* Read more about the inline elements in the
* {@glink framework/guides/deep-dive/schema#inline-elements Inline elements section} of the Schema deep dive guide.
*
* @property {Boolean} isLimit
* It can be understood as whether this element should not be split by <kbd>Enter</kbd>.
* Examples of limit elements: `$root`, table cell, image caption, etc. In other words, all actions that happen inside
* a limit element are limited to its content.
*
* Read more about the limit elements in the
* {@glink framework/guides/deep-dive/schema#limit-elements Limit elements section} of
* the {@glink framework/guides/deep-dive/schema Schema deep dive} guide.
*
* @property {Boolean} isObject
* Whether an item is "self-contained" and should be treated as a whole. Examples of object elements:
* `imageBlock`, `table`, `video`, etc.
*
* **Note:** An object is also a limit, so
* {@link module:engine/model/schema~Schema#isLimit `isLimit()`} returns `true` for object elements automatically.
*
* Read more about the object elements in the
* {@glink framework/guides/deep-dive/schema#object-elements Object elements section} of the Schema deep dive guide.
*
* @property {Boolean} isSelectable
* `true` when an element should be selectable as a whole by the user. Examples of selectable elements: `imageBlock`, `table`, `tableCell`,
* etc.
*
* **Note:** An object is also a selectable element, so
* {@link module:engine/model/schema~Schema#isSelectable `isSelectable()`} returns `true` for object elements automatically.
*
* Read more about selectable elements in the
* {@glink framework/guides/deep-dive/schema#selectable-elements Selectable elements section} of
* the {@glink framework/guides/deep-dive/schema Schema deep dive} guide.
*
* @property {Boolean} isContent
* An item is a content when it always finds its way to the editor data output regardless of the number and type of its descendants.
* Examples of content elements: `$text`, `imageBlock`, `table`, etc. (but not `paragraph`, `heading1` or `tableCell`).
*
* **Note:** An object is also a content element, so
* {@link module:engine/model/schema~Schema#isContent `isContent()`} returns `true` for object elements automatically.
*
* Read more about content elements in the
* {@glink framework/guides/deep-dive/schema#content-elements Content elements section} of
* the {@glink framework/guides/deep-dive/schema Schema deep dive} guide.
*/
/**
* A simplified version of {@link module:engine/model/schema~SchemaItemDefinition} after
* compilation by the {@link module:engine/model/schema~Schema schema}.
* Rules fed to the schema by {@link module:engine/model/schema~Schema#register}
* and {@link module:engine/model/schema~Schema#extend} methods are defined in the
* {@link module:engine/model/schema~SchemaItemDefinition} format.
* Later on, they are compiled to `SchemaCompiledItemDefinition` so when you use e.g.
* the {@link module:engine/model/schema~Schema#getDefinition} method you get the compiled version.
*
* The compiled version contains only the following properties:
*
* * The `name` property,
* * The `is*` properties,
* * The `allowIn` array,
* * The `allowChildren` array,
* * The `allowAttributes` array.
*
* @typedef {Object} module:engine/model/schema~SchemaCompiledItemDefinition
*/
/**
* A schema context — a list of ancestors of a given position in the document.
*
* Considering such position:
*
* <$root>
* <blockQuote>
* <paragraph>
* ^
* </paragraph>
* </blockQuote>
* </$root>
*
* The context of this position is its {@link module:engine/model/position~Position#getAncestors lists of ancestors}:
*
* [ rootElement, blockQuoteElement, paragraphElement ]
*
* Contexts are used in the {@link module:engine/model/schema~Schema#event:checkChild `Schema#checkChild`} and
* {@link module:engine/model/schema~Schema#event:checkAttribute `Schema#checkAttribute`} events as a definition
* of a place in the document where the check occurs. The context instances are created based on the first arguments
* of the {@link module:engine/model/schema~Schema#checkChild `Schema#checkChild()`} and
* {@link module:engine/model/schema~Schema#checkAttribute `Schema#checkAttribute()`} methods so when
* using these methods you need to use {@link module:engine/model/schema~SchemaContextDefinition}s.
*/
class SchemaContext {
/**
* Creates an instance of the context.
*
* @param {module:engine/model/schema~SchemaContextDefinition} context
*/
constructor( context ) {
if ( context instanceof SchemaContext ) {
return context;
}
if ( typeof context == 'string' ) {
context = [ context ];
} else if ( !Array.isArray( context ) ) {
// `context` is item or position.
// Position#getAncestors() doesn't accept any parameters but it works just fine here.
context = context.getAncestors( { includeSelf: true } );
}
this._items = context.map( mapContextItem );
}
/**
* The number of items.
*
* @type {Number}
*/
get length() {
return this._items.length;
}
/**
* The last item (the lowest node).
*
* @type {module:engine/model/schema~SchemaContextItem}
*/
get last() {
return this._items[ this._items.length - 1 ];
}
/**
* Iterable interface.
*
* Iterates over all context items.
*
* @returns {Iterable.<module:engine/model/schema~SchemaContextItem>}
*/
[ Symbol.iterator ]() {
return this._items[ Symbol.iterator ]();
}
/**
* Returns a new schema context instance with an additional item.
*
* Item can be added as:
*
* const context = new SchemaContext( [ '$root' ] );
*
* // An element.
* const fooElement = writer.createElement( 'fooElement' );
* const newContext = context.push( fooElement ); // [ '$root', 'fooElement' ]
*
* // A text node.
* const text = writer.createText( 'foobar' );
* const newContext = context.push( text ); // [ '$root', '$text' ]
*
* // A string (element name).
* const newContext = context.push( 'barElement' ); // [ '$root', 'barElement' ]
*
* **Note** {@link module:engine/model/node~Node} that is already in the model tree will be added as the only item
* (without ancestors).
*
* @param {String|module:engine/model/node~Node|Array<String|module:engine/model/node~Node>} item An item that will be added
* to the current context.
* @returns {module:engine/model/schema~SchemaContext} A new schema context instance with an additional item.
*/
push( item ) {
const ctx = new SchemaContext( [ item ] );
ctx._items = [ ...this._items, ...ctx._items ];
return ctx;
}
/**
* Gets an item on the given index.
*
* @returns {module:engine/model/schema~SchemaContextItem}
*/
getItem( index ) {
return this._items[ index ];
}
/**
* Returns the names of items.
*
* @returns {Iterable.<String>}
*/
* getNames() {
yield* this._items.map( item => item.name );
}
/**
* Checks whether the context ends with the given nodes.
*
* const ctx = new SchemaContext( [ rootElement, paragraphElement, textNode ] );
*
* ctx.endsWith( '$text' ); // -> true
* ctx.endsWith( 'paragraph $text' ); // -> true
* ctx.endsWith( '$root' ); // -> false
* ctx.endsWith( 'paragraph' ); // -> false
*
* @param {String} query
* @returns {Boolean}
*/
endsWith( query ) {
return Array.from( this.getNames() ).join( ' ' ).endsWith( query );
}
/**
* Checks whether the context starts with the given nodes.
*
* const ctx = new SchemaContext( [ rootElement, paragraphElement, textNode ] );
*
* ctx.endsWith( '$root' ); // -> true
* ctx.endsWith( '$root paragraph' ); // -> true
* ctx.endsWith( '$text' ); // -> false
* ctx.endsWith( 'paragraph' ); // -> false
*
* @param {String} query
* @returns {Boolean}
*/
startsWith( query ) {
return Array.from( this.getNames() ).join( ' ' ).startsWith( query );
}
}
/**
* The definition of a {@link module:engine/model/schema~SchemaContext schema context}.
*
* Contexts can be created in multiple ways:
*
* * By defining a **node** – in this cases this node and all its ancestors will be used.
* * By defining a **position** in the document – in this case all its ancestors will be used.
* * By defining an **array of nodes** – in this case this array defines the entire context.
* * By defining a **name of node** - in this case node will be "mocked". It is not recommended because context
* will be unrealistic (e.g. attributes of these nodes are not specified). However, at times this may be the only
* way to define the context (e.g. when checking some hypothetical situation).
* * By defining an **array of node names** (potentially, mixed with real nodes) – The same as **name of node**
* but it is possible to create a path.
* * By defining a {@link module:engine/model/schema~SchemaContext} instance - in this case the same instance as provided
* will be return.
*
* Examples of context definitions passed to the {@link module:engine/model/schema~Schema#checkChild `Schema#checkChild()`}
* method:
*
* // Assuming that we have a $root > blockQuote > paragraph structure, the following code
* // will check node 'foo' in the following context:
* // [ rootElement, blockQuoteElement, paragraphElement ]
* const contextDefinition = paragraphElement;
* const childToCheck = 'foo';
* schema.checkChild( contextDefinition, childToCheck );
*
* // Also check in [ rootElement, blockQuoteElement, paragraphElement ].
* schema.checkChild( model.createPositionAt( paragraphElement, 0 ), 'foo' );
*
* // Check in [ rootElement, paragraphElement ].
* schema.checkChild( [ rootElement, paragraphElement ], 'foo' );
*
* // Check only fakeParagraphElement.
* schema.checkChild( 'paragraph', 'foo' );
*
* // Check in [ fakeRootElement, fakeBarElement, paragraphElement ].
* schema.checkChild( [ '$root', 'bar', paragraphElement ], 'foo' );
*
* All these `checkChild()` calls will fire {@link module:engine/model/schema~Schema#event:checkChild `Schema#checkChild`}
* events in which `args[ 0 ]` is an instance of the context. Therefore, you can write a listener like this:
*
* schema.on( 'checkChild', ( evt, args ) => {
* const ctx = args[ 0 ];
*
* console.log( Array.from( ctx.getNames() ) );
* } );
*
* Which will log the following:
*
* [ '$root', 'blockQuote', 'paragraph' ]
* [ '$root', 'paragraph' ]
* [ '$root', 'bar', 'paragraph' ]
*
* Note: When using the {@link module:engine/model/schema~Schema#checkAttribute `Schema#checkAttribute()`} method
* you may want to check whether a text node may have an attribute. A {@link module:engine/model/text~Text} is a
* correct way to define a context so you can do this:
*
* schema.checkAttribute( textNode, 'bold' );
*
* But sometimes you want to check whether a text at a given position might've had some attribute,
* in which case you can create a context by mixing in an array of elements with a `'$text'` string:
*
* // Check in [ rootElement, paragraphElement, textNode ].
* schema.checkChild( [ ...positionInParagraph.getAncestors(), '$text' ], 'bold' );
*
* @typedef {module:engine/model/node~Node|module:engine/model/position~Position|module:engine/model/schema~SchemaContext|
* String|Array.<String|module:engine/model/node~Node>} module:engine/model/schema~SchemaContextDefinition
*/
/**
* An item of the {@link module:engine/model/schema~SchemaContext schema context}.
*
* It contains 3 properties:
*
* * `name` – the name of this item,
* * `* getAttributeKeys()` – a generator of keys of item attributes,
* * `getAttribute( keyName )` – a method to get attribute values.
*
* The context item interface is a highly simplified version of {@link module:engine/model/node~Node} and its role
* is to expose only the information which schema checks are able to provide (which is the name of the node and
* node's attributes).
*
* schema.on( 'checkChild', ( evt, args ) => {
* const ctx = args[ 0 ];
* const firstItem = ctx.getItem( 0 );
*
* console.log( firstItem.name ); // -> '$root'
* console.log( firstItem.getAttribute( 'foo' ) ); // -> 'bar'
* console.log( Array.from( firstItem.getAttributeKeys() ) ); // -> [ 'foo', 'faa' ]
* } );
*
* @typedef {Object} module:engine/model/schema~SchemaContextItem
*/
/**
* A structure containing additional metadata describing the attribute.
*
* See {@link module:engine/model/schema~Schema#setAttributeProperties `Schema#setAttributeProperties()`} for usage examples.
*
* @typedef {Object} module:engine/model/schema~AttributeProperties
* @property {Boolean} [isFormatting] Indicates that the attribute should be considered as a visual formatting, like `bold`, `italic` or
* `fontSize` rather than semantic attribute (such as `src`, `listType`, etc.). For example, it is used by the "Remove format" feature.
* @property {Boolean} [copyOnEnter] Indicates that given text attribute should be copied to the next block when enter is pressed.
*/
function compileBaseItemRule( sourceItemRules, itemName ) {
const itemRule = {
name: itemName,
allowIn: [],
allowContentOf: [],
allowWhere: [],
allowAttributes: [],
allowAttributesOf: [],
allowChildren: [],
inheritTypesFrom: []
};
copyTypes( sourceItemRules, itemRule );
copyProperty( sourceItemRules, itemRule, 'allowIn' );
copyProperty( sourceItemRules, itemRule, 'allowContentOf' );
copyProperty( sourceItemRules, itemRule, 'allowWhere' );
copyProperty( sourceItemRules, itemRule, 'allowAttributes' );
copyProperty( sourceItemRules, itemRule, 'allowAttributesOf' );
copyProperty( sourceItemRules, itemRule, 'allowChildren' );
copyProperty( sourceItemRules, itemRule, 'inheritTypesFrom' );
makeInheritAllWork( sourceItemRules, itemRule );
return itemRule;
}
function compileAllowChildren( compiledDefinitions, itemName ) {
const item = compiledDefinitions[ itemName ];
for ( const allowChildrenItem of item.allowChildren ) {
const allowedChildren = compiledDefinitions[ allowChildrenItem ];
// The allowChildren property may point to an unregistered element.
if ( !allowedChildren ) {
continue;
}
allowedChildren.allowIn.push( itemName );
}
// The allowIn property already includes correct items, reset the allowChildren property
// to avoid duplicates later when setting up compilation results.
item.allowChildren.length = 0;
}
function compileAllowContentOf( compiledDefinitions, itemName ) {
for ( const allowContentOfItemName of compiledDefinitions[ itemName ].allowContentOf ) {
// The allowContentOf property may point to an unregistered element.
if ( compiledDefinitions[ allowContentOfItemName ] ) {
const allowedChildren = getAllowedChildren( compiledDefinitions, allowContentOfItemName );
allowedChildren.forEach( allowedItem => {
allowedItem.allowIn.push( itemName );
} );
}
}
delete compiledDefinitions[ itemName ].allowContentOf;
}
function compileAllowWhere( compiledDefinitions, itemName ) {
for ( const allowWhereItemName of compiledDefinitions[ itemName ].allowWhere ) {
const inheritFrom = compiledDefinitions[ allowWhereItemName ];
// The allowWhere property may point to an unregistered element.
if ( inheritFrom ) {
const allowedIn = inheritFrom.allowIn;
compiledDefinitions[ itemName ].allowIn.push( ...allowedIn );
}
}
delete compiledDefinitions[ itemName ].allowWhere;
}
function compileAllowAttributesOf( compiledDefinitions, itemName ) {
for ( const allowAttributeOfItem of compiledDefinitions[ itemName ].allowAttributesOf ) {
const inheritFrom = compiledDefinitions[ allowAttributeOfItem ];
if ( inheritFrom ) {
const inheritAttributes = inheritFrom.allowAttributes;
compiledDefinitions[ itemName ].allowAttributes.push( ...inheritAttributes );
}
}
delete compiledDefinitions[ itemName ].allowAttributesOf;
}
function compileInheritPropertiesFrom( compiledDefinitions, itemName ) {
const item = compiledDefinitions[ itemName ];
for ( const inheritPropertiesOfItem of item.inheritTypesFrom ) {
const inheritFrom = compiledDefinitions[ inheritPropertiesOfItem ];
if ( inheritFrom ) {
const typeNames = Object.keys( inheritFrom ).filter( name => name.startsWith( 'is' ) );
for ( const name of typeNames ) {
if ( !( name in item ) ) {
item[ name ] = inheritFrom[ name ];
}
}
}
}
delete item.inheritTypesFrom;
}
// Remove items which weren't registered (because it may break some checks or we'd need to complicate them).
// Make sure allowIn doesn't contain repeated values.
function cleanUpAllowIn( compiledDefinitions, itemName ) {
const itemRule = compiledDefinitions[ itemName ];
const existingItems = itemRule.allowIn.filter( itemToCheck => compiledDefinitions[ itemToCheck ] );
itemRule.allowIn = Array.from( new Set( existingItems ) );
}
// Setup allowChildren items based on allowIn.
function setupAllowChildren( compiledDefinitions, itemName ) {
const itemRule = compiledDefinitions[ itemName ];
for ( const allowedParentItemName of itemRule.allowIn ) {
const allowedParentItem = compiledDefinitions[ allowedParentItemName ];
allowedParentItem.allowChildren.push( itemName );
}
}
function cleanUpAllowAttributes( compiledDefinitions, itemName ) {
const itemRule = compiledDefinitions[ itemName ];
itemRule.allowAttributes = Array.from( new Set( itemRule.allowAttributes ) );
}
function copyTypes( sourceItemRules, itemRule ) {
for ( const sourceItemRule of sourceItemRules ) {
const typeNames = Object.keys( sourceItemRule ).filter( name => name.startsWith( 'is' ) );
for ( const name of typeNames ) {
itemRule[ name ] = sourceItemRule[ name ];
}
}
}
function copyProperty( sourceItemRules, itemRule, propertyName ) {
for ( const sourceItemRule of sourceItemRules ) {
if ( typeof sourceItemRule[ propertyName ] == 'string' ) {
itemRule[ propertyName ].push( sourceItemRule[ propertyName ] );
} else if ( Array.isArray( sourceItemRule[ propertyName ] ) ) {
itemRule[ propertyName ].push( ...sourceItemRule[ propertyName ] );
}
}
}
function makeInheritAllWork( sourceItemRules, itemRule ) {
for ( const sourceItemRule of sourceItemRules ) {
const inheritFrom = sourceItemRule.inheritAllFrom;
if ( inheritFrom ) {
itemRule.allowContentOf.push( inheritFrom );
itemRule.allowWhere.push( inheritFrom );
itemRule.allowAttributesOf.push( inheritFrom );
itemRule.inheritTypesFrom.push( inheritFrom );
}
}
}
function getAllowedChildren( compiledDefinitions, itemName ) {
const itemRule = compiledDefinitions[ itemName ];
return getValues( compiledDefinitions ).filter( def => def.allowIn.includes( itemRule.name ) );
}
function getValues( obj ) {
return Object.keys( obj ).map( key => obj[ key ] );
}
function mapContextItem( ctxItem ) {
if ( typeof ctxItem == 'string' || ctxItem.is( 'documentFragment' ) ) {
return {
name: typeof ctxItem == 'string' ? ctxItem : '$documentFragment',
* getAttributeKeys() {},
getAttribute() {}
};
} else {
return {
// '$text' means text nodes and text proxies.
name: ctxItem.is( 'element' ) ? ctxItem.name : '$text',
* getAttributeKeys() {
yield* ctxItem.getAttributeKeys();
},
getAttribute( key ) {
return ctxItem.getAttribute( key );
}
};
}
}
// Generator function returning values from provided walkers, switching between them at each iteration. If only one walker
// is provided it will return data only from that walker.
//
// @param {module:engine/module/treewalker~TreeWalker} [backward] Walker iterating in backward direction.
// @param {module:engine/module/treewalker~TreeWalker} [forward] Walker iterating in forward direction.
// @returns {Iterable.<Object>} Object returned at each iteration contains `value` and `walker` (informing which walker returned
// given value) fields.
function* combineWalkers( backward, forward ) {
let done = false;
while ( !done ) {
done = true;
if ( backward ) {
const step = backward.next();
if ( !step.done ) {
done = false;
yield {
walker: backward,
value: step.value
};
}
}
if ( forward ) {
const step = forward.next();
if ( !step.done ) {
done = false;
yield {
walker: forward,
value: step.value
};
}
}
}
}
// Takes an array of non-intersecting ranges. For each of them gets minimal flat ranges covering that range and returns
// all those minimal flat ranges.
//
// @param {Array.<module:engine/model/range~Range>} ranges Ranges to process.
// @returns {Iterable.<module:engine/model/range~Range>} Minimal flat ranges of given `ranges`.
function* convertToMinimalFlatRanges( ranges ) {
for ( const range of ranges ) {
yield* range.getMinimalFlatRanges();
}
}
function removeDisallowedAttributeFromNode( schema, node, writer ) {
for ( const attribute of node.getAttributeKeys() ) {
if ( !schema.checkAttribute( node, attribute ) ) {
writer.removeAttribute( attribute, node );
}
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/model/selection.js":
/*!************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/model/selection.js ***!
\************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ Selection)
/* harmony export */ });
/* harmony import */ var _position__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./position */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/position.js");
/* harmony import */ var _node__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./node */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/node.js");
/* harmony import */ var _range__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./range */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/range.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_emittermixin__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/emittermixin */ "./node_modules/@ckeditor/ckeditor5-utils/src/emittermixin.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/mix */ "./node_modules/@ckeditor/ckeditor5-utils/src/mix.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_isiterable__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/isiterable */ "./node_modules/@ckeditor/ckeditor5-utils/src/isiterable.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/model/selection
*/
/**
* Selection is a set of {@link module:engine/model/range~Range ranges}. It has a direction specified by its
* {@link module:engine/model/selection~Selection#anchor anchor} and {@link module:engine/model/selection~Selection#focus focus}
* (it can be {@link module:engine/model/selection~Selection#isBackward forward or backward}).
* Additionally, selection may have its own attributes (think – whether text typed in in this selection
* should have those attributes – e.g. whether you type a bolded text).
*
* @mixes module:utils/emittermixin~EmitterMixin
*/
class Selection {
/**
* Creates a new selection instance based on the given {@link module:engine/model/selection~Selectable selectable}
* or creates an empty selection if no arguments were passed.
*
* // Creates empty selection without ranges.
* const selection = writer.createSelection();
*
* // Creates selection at the given range.
* const range = writer.createRange( start, end );
* const selection = writer.createSelection( range );
*
* // Creates selection at the given ranges
* const ranges = [ writer.createRange( start1, end2 ), writer.createRange( star2, end2 ) ];
* const selection = writer.createSelection( ranges );
*
* // Creates selection from the other selection.
* // Note: It doesn't copies selection attributes.
* const otherSelection = writer.createSelection();
* const selection = writer.createSelection( otherSelection );
*
* // Creates selection from the given document selection.
* // Note: It doesn't copies selection attributes.
* const documentSelection = model.document.selection;
* const selection = writer.createSelection( documentSelection );
*
* // Creates selection at the given position.
* const position = writer.createPositionFromPath( root, path );
* const selection = writer.createSelection( position );
*
* // Creates selection at the given offset in the given element.
* const paragraph = writer.createElement( 'paragraph' );
* const selection = writer.createSelection( paragraph, offset );
*
* // Creates a range inside an {@link module:engine/model/element~Element element} which starts before the
* // first child of that element and ends after the last child of that element.
* const selection = writer.createSelection( paragraph, 'in' );
*
* // Creates a range on an {@link module:engine/model/item~Item item} which starts before the item and ends
* // just after the item.
* const selection = writer.createSelection( paragraph, 'on' );
*
* Selection's constructor allow passing additional options (`'backward'`) as the last argument.
*
* // Creates backward selection.
* const selection = writer.createSelection( range, { backward: true } );
*
* @param {module:engine/model/selection~Selectable} [selectable]
* @param {Number|'before'|'end'|'after'|'on'|'in'} [placeOrOffset] Sets place or offset of the selection.
* @param {Object} [options]
* @param {Boolean} [options.backward] Sets this selection instance to be backward.
*/
constructor( selectable, placeOrOffset, options ) {
/**
* Specifies whether the last added range was added as a backward or forward range.
*
* @private
* @type {Boolean}
*/
this._lastRangeBackward = false;
/**
* Stores selection ranges.
*
* @protected
* @type {Array.<module:engine/model/range~Range>}
*/
this._ranges = [];
/**
* List of attributes set on current selection.
*
* @protected
* @type {Map.<String,*>}
*/
this._attrs = new Map();
if ( selectable ) {
this.setTo( selectable, placeOrOffset, options );
}
}
/**
* Selection anchor. Anchor is the position from which the selection was started. If a user is making a selection
* by dragging the mouse, the anchor is where the user pressed the mouse button (the beginning of the selection).
*
* Anchor and {@link #focus} define the direction of the selection, which is important
* when expanding/shrinking selection. The focus moves, while the anchor should remain in the same place.
*
* Anchor is always set to the {@link module:engine/model/range~Range#start start} or
* {@link module:engine/model/range~Range#end end} position of the last of selection's ranges. Whether it is
* the `start` or `end` depends on the specified `options.backward`. See the {@link #setTo `setTo()`} method.
*
* May be set to `null` if there are no ranges in the selection.
*
* @see #focus
* @readonly
* @type {module:engine/model/position~Position|null}
*/
get anchor() {
if ( this._ranges.length > 0 ) {
const range = this._ranges[ this._ranges.length - 1 ];
return this._lastRangeBackward ? range.end : range.start;
}
return null;
}
/**
* Selection focus. Focus is the position where the selection ends. If a user is making a selection
* by dragging the mouse, the focus is where the mouse cursor is.
*
* May be set to `null` if there are no ranges in the selection.
*
* @see #anchor
* @readonly
* @type {module:engine/model/position~Position|null}
*/
get focus() {
if ( this._ranges.length > 0 ) {
const range = this._ranges[ this._ranges.length - 1 ];
return this._lastRangeBackward ? range.start : range.end;
}
return null;
}
/**
* Whether the selection is collapsed. Selection is collapsed when there is exactly one range in it
* and it is collapsed.
*
* @readonly
* @type {Boolean}
*/
get isCollapsed() {
const length = this._ranges.length;
if ( length === 1 ) {
return this._ranges[ 0 ].isCollapsed;
} else {
return false;
}
}
/**
* Returns the number of ranges in the selection.
*
* @readonly
* @type {Number}
*/
get rangeCount() {
return this._ranges.length;
}
/**
* Specifies whether the selection's {@link #focus} precedes the selection's {@link #anchor}.
*
* @readonly
* @type {Boolean}
*/
get isBackward() {
return !this.isCollapsed && this._lastRangeBackward;
}
/**
* Checks whether this selection is equal to the given selection. Selections are equal if they have the same directions,
* the same number of ranges and all ranges from one selection equal to ranges from the another selection.
*
* @param {module:engine/model/selection~Selection|module:engine/model/documentselection~DocumentSelection} otherSelection
* Selection to compare with.
* @returns {Boolean} `true` if selections are equal, `false` otherwise.
*/
isEqual( otherSelection ) {
if ( this.rangeCount != otherSelection.rangeCount ) {
return false;
} else if ( this.rangeCount === 0 ) {
return true;
}
if ( !this.anchor.isEqual( otherSelection.anchor ) || !this.focus.isEqual( otherSelection.focus ) ) {
return false;
}
for ( const thisRange of this._ranges ) {
let found = false;
for ( const otherRange of otherSelection._ranges ) {
if ( thisRange.isEqual( otherRange ) ) {
found = true;
break;
}
}
if ( !found ) {
return false;
}
}
return true;
}
/**
* Returns an iterable object that iterates over copies of selection ranges.
*
* @returns {Iterable.<module:engine/model/range~Range>}
*/
* getRanges() {
for ( const range of this._ranges ) {
yield new _range__WEBPACK_IMPORTED_MODULE_2__["default"]( range.start, range.end );
}
}
/**
* Returns a copy of the first range in the selection.
* First range is the one which {@link module:engine/model/range~Range#start start} position
* {@link module:engine/model/position~Position#isBefore is before} start position of all other ranges
* (not to confuse with the first range added to the selection).
*
* Returns `null` if there are no ranges in selection.
*
* @returns {module:engine/model/range~Range|null}
*/
getFirstRange() {
let first = null;
for ( const range of this._ranges ) {
if ( !first || range.start.isBefore( first.start ) ) {
first = range;
}
}
return first ? new _range__WEBPACK_IMPORTED_MODULE_2__["default"]( first.start, first.end ) : null;
}
/**
* Returns a copy of the last range in the selection.
* Last range is the one which {@link module:engine/model/range~Range#end end} position
* {@link module:engine/model/position~Position#isAfter is after} end position of all other ranges (not to confuse with the range most
* recently added to the selection).
*
* Returns `null` if there are no ranges in selection.
*
* @returns {module:engine/model/range~Range|null}
*/
getLastRange() {
let last = null;
for ( const range of this._ranges ) {
if ( !last || range.end.isAfter( last.end ) ) {
last = range;
}
}
return last ? new _range__WEBPACK_IMPORTED_MODULE_2__["default"]( last.start, last.end ) : null;
}
/**
* Returns the first position in the selection.
* First position is the position that {@link module:engine/model/position~Position#isBefore is before}
* any other position in the selection.
*
* Returns `null` if there are no ranges in selection.
*
* @returns {module:engine/model/position~Position|null}
*/
getFirstPosition() {
const first = this.getFirstRange();
return first ? first.start.clone() : null;
}
/**
* Returns the last position in the selection.
* Last position is the position that {@link module:engine/model/position~Position#isAfter is after}
* any other position in the selection.
*
* Returns `null` if there are no ranges in selection.
*
* @returns {module:engine/model/position~Position|null}
*/
getLastPosition() {
const lastRange = this.getLastRange();
return lastRange ? lastRange.end.clone() : null;
}
/**
* Sets this selection's ranges and direction to the specified location based on the given
* {@link module:engine/model/selection~Selectable selectable}.
*
* // Removes all selection's ranges.
* selection.setTo( null );
*
* // Sets selection to the given range.
* const range = writer.createRange( start, end );
* selection.setTo( range );
*
* // Sets selection to given ranges.
* const ranges = [ writer.createRange( start1, end2 ), writer.createRange( star2, end2 ) ];
* selection.setTo( ranges );
*
* // Sets selection to other selection.
* // Note: It doesn't copies selection attributes.
* const otherSelection = writer.createSelection();
* selection.setTo( otherSelection );
*
* // Sets selection to the given document selection.
* // Note: It doesn't copies selection attributes.
* const documentSelection = new DocumentSelection( doc );
* selection.setTo( documentSelection );
*
* // Sets collapsed selection at the given position.
* const position = writer.createPositionFromPath( root, path );
* selection.setTo( position );
*
* // Sets collapsed selection at the position of the given node and an offset.
* selection.setTo( paragraph, offset );
*
* Creates a range inside an {@link module:engine/model/element~Element element} which starts before the first child of
* that element and ends after the last child of that element.
*
* selection.setTo( paragraph, 'in' );
*
* Creates a range on an {@link module:engine/model/item~Item item} which starts before the item and ends just after the item.
*
* selection.setTo( paragraph, 'on' );
*
* `Selection#setTo()`' method allow passing additional options (`backward`) as the last argument.
*
* // Sets backward selection.
* const selection = writer.createSelection( range, { backward: true } );
*
* @param {module:engine/model/selection~Selectable} selectable
* @param {Number|'before'|'end'|'after'|'on'|'in'} [placeOrOffset] Sets place or offset of the selection.
* @param {Object} [options]
* @param {Boolean} [options.backward] Sets this selection instance to be backward.
*/
setTo( selectable, placeOrOffset, options ) {
if ( selectable === null ) {
this._setRanges( [] );
} else if ( selectable instanceof Selection ) {
this._setRanges( selectable.getRanges(), selectable.isBackward );
} else if ( selectable && typeof selectable.getRanges == 'function' ) {
// We assume that the selectable is a DocumentSelection.
// It can't be imported here, because it would lead to circular imports.
this._setRanges( selectable.getRanges(), selectable.isBackward );
} else if ( selectable instanceof _range__WEBPACK_IMPORTED_MODULE_2__["default"] ) {
this._setRanges( [ selectable ], !!placeOrOffset && !!placeOrOffset.backward );
} else if ( selectable instanceof _position__WEBPACK_IMPORTED_MODULE_0__["default"] ) {
this._setRanges( [ new _range__WEBPACK_IMPORTED_MODULE_2__["default"]( selectable ) ] );
} else if ( selectable instanceof _node__WEBPACK_IMPORTED_MODULE_1__["default"] ) {
const backward = !!options && !!options.backward;
let range;
if ( placeOrOffset == 'in' ) {
range = _range__WEBPACK_IMPORTED_MODULE_2__["default"]._createIn( selectable );
} else if ( placeOrOffset == 'on' ) {
range = _range__WEBPACK_IMPORTED_MODULE_2__["default"]._createOn( selectable );
} else if ( placeOrOffset !== undefined ) {
range = new _range__WEBPACK_IMPORTED_MODULE_2__["default"]( _position__WEBPACK_IMPORTED_MODULE_0__["default"]._createAt( selectable, placeOrOffset ) );
} else {
/**
* selection.setTo requires the second parameter when the first parameter is a node.
*
* @error model-selection-setto-required-second-parameter
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_4__["default"]( 'model-selection-setto-required-second-parameter', [ this, selectable ] );
}
this._setRanges( [ range ], backward );
} else if ( (0,_ckeditor_ckeditor5_utils_src_isiterable__WEBPACK_IMPORTED_MODULE_6__["default"])( selectable ) ) {
// We assume that the selectable is an iterable of ranges.
this._setRanges( selectable, placeOrOffset && !!placeOrOffset.backward );
} else {
/**
* Cannot set the selection to the given place.
*
* Invalid parameters were specified when setting the selection. Common issues:
*
* * A {@link module:engine/model/textproxy~TextProxy} instance was passed instead of
* a real {@link module:engine/model/text~Text}.
* * View nodes were passed instead of model nodes.
* * `null`/`undefined` was passed.
*
* @error model-selection-setto-not-selectable
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_4__["default"]( 'model-selection-setto-not-selectable', [ this, selectable ] );
}
}
/**
* Replaces all ranges that were added to the selection with given array of ranges. Last range of the array
* is treated like the last added range and is used to set {@link module:engine/model/selection~Selection#anchor} and
* {@link module:engine/model/selection~Selection#focus}. Accepts a flag describing in which direction the selection is made.
*
* @protected
* @fires change:range
* @param {Iterable.<module:engine/model/range~Range>} newRanges Ranges to set.
* @param {Boolean} [isLastBackward=false] Flag describing if last added range was selected forward - from start to end (`false`)
* or backward - from end to start (`true`).
*/
_setRanges( newRanges, isLastBackward = false ) {
newRanges = Array.from( newRanges );
// Check whether there is any range in new ranges set that is different than all already added ranges.
const anyNewRange = newRanges.some( newRange => {
if ( !( newRange instanceof _range__WEBPACK_IMPORTED_MODULE_2__["default"] ) ) {
/**
* Selection range set to an object that is not an instance of {@link module:engine/model/range~Range}.
*
* Only {@link module:engine/model/range~Range} instances can be used to set a selection.
* Common mistakes leading to this error are:
*
* * using DOM `Range` object,
* * incorrect CKEditor 5 installation with multiple `ckeditor5-engine` packages having different versions.
*
* @error model-selection-set-ranges-not-range
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_4__["default"](
'model-selection-set-ranges-not-range',
[ this, newRanges ]
);
}
return this._ranges.every( oldRange => {
return !oldRange.isEqual( newRange );
} );
} );
// Don't do anything if nothing changed.
if ( newRanges.length === this._ranges.length && !anyNewRange ) {
return;
}
this._removeAllRanges();
for ( const range of newRanges ) {
this._pushRange( range );
}
this._lastRangeBackward = !!isLastBackward;
this.fire( 'change:range', { directChange: true } );
}
/**
* Moves {@link module:engine/model/selection~Selection#focus} to the specified location.
*
* The location can be specified in the same form as
* {@link module:engine/model/writer~Writer#createPositionAt writer.createPositionAt()} parameters.
*
* @fires change:range
* @param {module:engine/model/item~Item|module:engine/model/position~Position} itemOrPosition
* @param {Number|'end'|'before'|'after'} [offset] Offset or one of the flags. Used only when
* first parameter is a {@link module:engine/model/item~Item model item}.
*/
setFocus( itemOrPosition, offset ) {
if ( this.anchor === null ) {
/**
* Cannot set selection focus if there are no ranges in selection.
*
* @error model-selection-setfocus-no-ranges
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_4__["default"]( 'model-selection-setfocus-no-ranges', [ this, itemOrPosition ] );
}
const newFocus = _position__WEBPACK_IMPORTED_MODULE_0__["default"]._createAt( itemOrPosition, offset );
if ( newFocus.compareWith( this.focus ) == 'same' ) {
return;
}
const anchor = this.anchor;
if ( this._ranges.length ) {
this._popRange();
}
if ( newFocus.compareWith( anchor ) == 'before' ) {
this._pushRange( new _range__WEBPACK_IMPORTED_MODULE_2__["default"]( newFocus, anchor ) );
this._lastRangeBackward = true;
} else {
this._pushRange( new _range__WEBPACK_IMPORTED_MODULE_2__["default"]( anchor, newFocus ) );
this._lastRangeBackward = false;
}
this.fire( 'change:range', { directChange: true } );
}
/**
* Gets an attribute value for given key or `undefined` if that attribute is not set on the selection.
*
* @param {String} key Key of attribute to look for.
* @returns {*} Attribute value or `undefined`.
*/
getAttribute( key ) {
return this._attrs.get( key );
}
/**
* Returns iterable that iterates over this selection's attributes.
*
* Attributes are returned as arrays containing two items. First one is attribute key and second is attribute value.
* This format is accepted by native `Map` object and also can be passed in `Node` constructor.
*
* @returns {Iterable.<*>}
*/
getAttributes() {
return this._attrs.entries();
}
/**
* Returns iterable that iterates over this selection's attribute keys.
*
* @returns {Iterable.<String>}
*/
getAttributeKeys() {
return this._attrs.keys();
}
/**
* Checks if the selection has an attribute for given key.
*
* @param {String} key Key of attribute to check.
* @returns {Boolean} `true` if attribute with given key is set on selection, `false` otherwise.
*/
hasAttribute( key ) {
return this._attrs.has( key );
}
/**
* Removes an attribute with given key from the selection.
*
* If given attribute was set on the selection, fires the {@link #event:change:range} event with
* removed attribute key.
*
* @fires change:attribute
* @param {String} key Key of attribute to remove.
*/
removeAttribute( key ) {
if ( this.hasAttribute( key ) ) {
this._attrs.delete( key );
this.fire( 'change:attribute', { attributeKeys: [ key ], directChange: true } );
}
}
/**
* Sets attribute on the selection. If attribute with the same key already is set, it's value is overwritten.
*
* If the attribute value has changed, fires the {@link #event:change:range} event with
* the attribute key.
*
* @fires change:attribute
* @param {String} key Key of attribute to set.
* @param {*} value Attribute value.
*/
setAttribute( key, value ) {
if ( this.getAttribute( key ) !== value ) {
this._attrs.set( key, value );
this.fire( 'change:attribute', { attributeKeys: [ key ], directChange: true } );
}
}
/**
* Returns the selected element. {@link module:engine/model/element~Element Element} is considered as selected if there is only
* one range in the selection, and that range contains exactly one element.
* Returns `null` if there is no selected element.
*
* @returns {module:engine/model/element~Element|null}
*/
getSelectedElement() {
if ( this.rangeCount !== 1 ) {
return null;
}
return this.getFirstRange().getContainedElement();
}
/**
* Checks whether this object is of the given.
*
* selection.is( 'selection' ); // -> true
* selection.is( 'model:selection' ); // -> true
*
* selection.is( 'view:selection' ); // -> false
* selection.is( 'range' ); // -> false
*
* {@link module:engine/model/node~Node#is Check the entire list of model objects} which implement the `is()` method.
*
* @param {String} type
* @returns {Boolean}
*/
is( type ) {
return type === 'selection' || type === 'model:selection';
}
/**
* Gets elements of type {@link module:engine/model/schema~Schema#isBlock "block"} touched by the selection.
*
* This method's result can be used for example to apply block styling to all blocks covered by this selection.
*
* **Note:** `getSelectedBlocks()` returns blocks that are nested in other non-block elements
* but will not return blocks nested in other blocks.
*
* In this case the function will return exactly all 3 paragraphs (note: `<blockQuote>` is not a block itself):
*
* <paragraph>[a</paragraph>
* <blockQuote>
* <paragraph>b</paragraph>
* </blockQuote>
* <paragraph>c]d</paragraph>
*
* In this case the paragraph will also be returned, despite the collapsed selection:
*
* <paragraph>[]a</paragraph>
*
* In such a scenario, however, only blocks A, B & E will be returned as blocks C & D are nested in block B:
*
* [<blockA></blockA>
* <blockB>
* <blockC></blockC>
* <blockD></blockD>
* </blockB>
* <blockE></blockE>]
*
* If the selection is inside a block all the inner blocks (A & B) are returned:
*
* <block>
* <blockA>[a</blockA>
* <blockB>b]</blockB>
* </block>
*
* **Special case**: If a selection ends at the beginning of a block, that block is not returned as from user perspective
* this block wasn't selected. See [#984](https://github.com/ckeditor/ckeditor5-engine/issues/984) for more details.
*
* <paragraph>[a</paragraph>
* <paragraph>b</paragraph>
* <paragraph>]c</paragraph> // this block will not be returned
*
* @returns {Iterable.<module:engine/model/element~Element>}
*/
* getSelectedBlocks() {
const visited = new WeakSet();
for ( const range of this.getRanges() ) {
// Get start block of range in case of a collapsed range.
const startBlock = getParentBlock( range.start, visited );
if ( startBlock && isTopBlockInRange( startBlock, range ) ) {
yield startBlock;
}
for ( const value of range.getWalker() ) {
const block = value.item;
if ( value.type == 'elementEnd' && isUnvisitedTopBlock( block, visited, range ) ) {
yield block;
}
}
const endBlock = getParentBlock( range.end, visited );
// #984. Don't return the end block if the range ends right at its beginning.
if ( endBlock && !range.end.isTouching( _position__WEBPACK_IMPORTED_MODULE_0__["default"]._createAt( endBlock, 0 ) ) && isTopBlockInRange( endBlock, range ) ) {
yield endBlock;
}
}
}
/**
* Checks whether the selection contains the entire content of the given element. This means that selection must start
* at a position {@link module:engine/model/position~Position#isTouching touching} the element's start and ends at position
* touching the element's end.
*
* By default, this method will check whether the entire content of the selection's current root is selected.
* Useful to check if e.g. the user has just pressed <kbd>Ctrl</kbd> + <kbd>A</kbd>.
*
* @param {module:engine/model/element~Element} [element=this.anchor.root]
* @returns {Boolean}
*/
containsEntireContent( element = this.anchor.root ) {
const limitStartPosition = _position__WEBPACK_IMPORTED_MODULE_0__["default"]._createAt( element, 0 );
const limitEndPosition = _position__WEBPACK_IMPORTED_MODULE_0__["default"]._createAt( element, 'end' );
return limitStartPosition.isTouching( this.getFirstPosition() ) &&
limitEndPosition.isTouching( this.getLastPosition() );
}
/**
* Adds given range to internal {@link #_ranges ranges array}. Throws an error
* if given range is intersecting with any range that is already stored in this selection.
*
* @protected
* @param {module:engine/model/range~Range} range Range to add.
*/
_pushRange( range ) {
this._checkRange( range );
this._ranges.push( new _range__WEBPACK_IMPORTED_MODULE_2__["default"]( range.start, range.end ) );
}
/**
* Checks if given range intersects with ranges that are already in the selection. Throws an error if it does.
*
* @protected
* @param {module:engine/model/range~Range} range Range to check.
*/
_checkRange( range ) {
for ( let i = 0; i < this._ranges.length; i++ ) {
if ( range.isIntersecting( this._ranges[ i ] ) ) {
/**
* Trying to add a range that intersects with another range in the selection.
*
* @error model-selection-range-intersects
* @param {module:engine/model/range~Range} addedRange Range that was added to the selection.
* @param {module:engine/model/range~Range} intersectingRange Range in the selection that intersects with `addedRange`.
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_4__["default"](
'model-selection-range-intersects',
[ this, range ],
{ addedRange: range, intersectingRange: this._ranges[ i ] }
);
}
}
}
/**
* Deletes ranges from internal range array. Uses {@link #_popRange _popRange} to
* ensure proper ranges removal.
*
* @protected
*/
_removeAllRanges() {
while ( this._ranges.length > 0 ) {
this._popRange();
}
}
/**
* Removes most recently added range from the selection.
*
* @protected
*/
_popRange() {
this._ranges.pop();
}
/**
* Fired when selection range(s) changed.
*
* @event change:range
* @param {Boolean} directChange In case of {@link module:engine/model/selection~Selection} class it is always set
* to `true` which indicates that the selection change was caused by a direct use of selection's API.
* The {@link module:engine/model/documentselection~DocumentSelection}, however, may change because its position
* was directly changed through the {@link module:engine/model/writer~Writer writer} or because its position was
* changed because the structure of the model has been changed (which means an indirect change).
* The indirect change does not occur in case of normal (detached) selections because they are "static" (as "not live")
* which mean that they are not updated once the document changes.
*/
/**
* Fired when selection attribute changed.
*
* @event change:attribute
* @param {Boolean} directChange In case of {@link module:engine/model/selection~Selection} class it is always set
* to `true` which indicates that the selection change was caused by a direct use of selection's API.
* The {@link module:engine/model/documentselection~DocumentSelection}, however, may change because its attributes
* were directly changed through the {@link module:engine/model/writer~Writer writer} or because its position was
* changed in the model and its attributes were refreshed (which means an indirect change).
* The indirect change does not occur in case of normal (detached) selections because they are "static" (as "not live")
* which mean that they are not updated once the document changes.
* @param {Array.<String>} attributeKeys Array containing keys of attributes that changed.
*/
}
(0,_ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_5__["default"])( Selection, _ckeditor_ckeditor5_utils_src_emittermixin__WEBPACK_IMPORTED_MODULE_3__["default"] );
// Checks whether the given element extends $block in the schema and has a parent (is not a root).
// Marks it as already visited.
function isUnvisitedBlock( element, visited ) {
if ( visited.has( element ) ) {
return false;
}
visited.add( element );
return element.root.document.model.schema.isBlock( element ) && element.parent;
}
// Checks if the given element is a $block was not previously visited and is a top block in a range.
function isUnvisitedTopBlock( element, visited, range ) {
return isUnvisitedBlock( element, visited ) && isTopBlockInRange( element, range );
}
// Finds the lowest element in position's ancestors which is a block.
// It will search until first ancestor that is a limit element.
// Marks all ancestors as already visited to not include any of them later on.
function getParentBlock( position, visited ) {
const element = position.parent;
const schema = element.root.document.model.schema;
const ancestors = position.parent.getAncestors( { parentFirst: true, includeSelf: true } );
let hasParentLimit = false;
const block = ancestors.find( element => {
// Stop searching after first parent node that is limit element.
if ( hasParentLimit ) {
return false;
}
hasParentLimit = schema.isLimit( element );
return !hasParentLimit && isUnvisitedBlock( element, visited );
} );
// Mark all ancestors of this position's parent, because find() might've stopped early and
// the found block may be a child of another block.
ancestors.forEach( element => visited.add( element ) );
return block;
}
// Checks if the blocks is not nested in other block inside a range.
//
// @param {module:engine/model/element~Element} block Block to check.
// @param {module:engine/model/range~Range} range Range to check.
function isTopBlockInRange( block, range ) {
const parentBlock = findAncestorBlock( block );
if ( !parentBlock ) {
return true;
}
// Add loose flag to check as parentRange can be equal to range.
const isParentInRange = range.containsRange( _range__WEBPACK_IMPORTED_MODULE_2__["default"]._createOn( parentBlock ), true );
return !isParentInRange;
}
// Returns first ancestor block of a node.
//
// @param {module:engine/model/node~Node} node
// @returns {module:engine/model/node~Node|undefined}
function findAncestorBlock( node ) {
const schema = node.root.document.model.schema;
let parent = node.parent;
while ( parent ) {
if ( schema.isBlock( parent ) ) {
return parent;
}
parent = parent.parent;
}
}
/**
* An entity that is used to set selection.
*
* See also {@link module:engine/model/selection~Selection#setTo}
*
* @typedef {
* module:engine/model/selection~Selection|
* module:engine/model/documentselection~DocumentSelection|
* module:engine/model/position~Position|
* module:engine/model/range~Range|
* module:engine/model/node~Node|
* Iterable.<module:engine/model/range~Range>|
* null
* } module:engine/model/selection~Selectable
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/model/text.js":
/*!*******************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/model/text.js ***!
\*******************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ Text)
/* harmony export */ });
/* harmony import */ var _node__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./node */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/node.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/model/text
*/
// @if CK_DEBUG_ENGINE // const { convertMapToStringifiedObject } = require( '../dev-utils/utils' );
/**
* Model text node. Type of {@link module:engine/model/node~Node node} that contains {@link module:engine/model/text~Text#data text data}.
*
* **Important:** see {@link module:engine/model/node~Node} to read about restrictions using `Text` and `Node` API.
*
* **Note:** keep in mind that `Text` instances might indirectly got removed from model tree when model is changed.
* This happens when {@link module:engine/model/writer~Writer model writer} is used to change model and the text node is merged with
* another text node. Then, both text nodes are removed and a new text node is inserted into the model. Because of
* this behavior, keeping references to `Text` is not recommended. Instead, consider creating
* {@link module:engine/model/liveposition~LivePosition live position} placed before the text node.
*
* @extends module:engine/model/node~Node
*/
class Text extends _node__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* Creates a text node.
*
* **Note:** Constructor of this class shouldn't be used directly in the code.
* Use the {@link module:engine/model/writer~Writer#createText} method instead.
*
* @protected
* @param {String} data Node's text.
* @param {Object} [attrs] Node's attributes. See {@link module:utils/tomap~toMap} for a list of accepted values.
*/
constructor( data, attrs ) {
super( attrs );
/**
* Text data contained in this text node.
*
* @protected
* @type {String}
*/
this._data = data || '';
}
/**
* @inheritDoc
*/
get offsetSize() {
return this.data.length;
}
/**
* Returns a text data contained in the node.
*
* @readonly
* @type {String}
*/
get data() {
return this._data;
}
/**
* Checks whether this object is of the given.
*
* text.is( '$text' ); // -> true
* text.is( 'node' ); // -> true
* text.is( 'model:$text' ); // -> true
* text.is( 'model:node' ); // -> true
*
* text.is( 'view:$text' ); // -> false
* text.is( 'documentSelection' ); // -> false
*
* {@link module:engine/model/node~Node#is Check the entire list of model objects} which implement the `is()` method.
*
* **Note:** Until version 20.0.0 this method wasn't accepting `'$text'` type. The legacy `'text'` type is still
* accepted for backward compatibility.
*
* @param {String} type Type to check.
* @returns {Boolean}
*/
is( type ) {
return type === '$text' || type === 'model:$text' ||
// This are legacy values kept for backward compatibility.
type === 'text' || type === 'model:text' ||
// From super.is(). This is highly utilised method and cannot call super. See ckeditor/ckeditor5#6529.
type === 'node' || type === 'model:node';
}
/**
* Converts `Text` instance to plain object and returns it.
*
* @returns {Object} `Text` instance converted to plain object.
*/
toJSON() {
const json = super.toJSON();
json.data = this.data;
return json;
}
/**
* Creates a copy of this text node and returns it. Created text node has same text data and attributes as original text node.
*
* @protected
* @returns {module:engine/model/text~Text} `Text` instance created using given plain object.
*/
_clone() {
return new Text( this.data, this.getAttributes() );
}
/**
* Creates a `Text` instance from given plain object (i.e. parsed JSON string).
*
* @param {Object} json Plain object to be converted to `Text`.
* @returns {module:engine/model/text~Text} `Text` instance created using given plain object.
*/
static fromJSON( json ) {
return new Text( json.data, json.attributes );
}
// @if CK_DEBUG_ENGINE // toString() {
// @if CK_DEBUG_ENGINE // return `#${ this.data }`;
// @if CK_DEBUG_ENGINE // }
// @if CK_DEBUG_ENGINE // logExtended() {
// @if CK_DEBUG_ENGINE // console.log( `ModelText: ${ this }, attrs: ${ convertMapToStringifiedObject( this.getAttributes() ) }` );
// @if CK_DEBUG_ENGINE // }
// @if CK_DEBUG_ENGINE // log() {
// @if CK_DEBUG_ENGINE // console.log( 'ModelText: ' + this );
// @if CK_DEBUG_ENGINE // }
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/model/textproxy.js":
/*!************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/model/textproxy.js ***!
\************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ TextProxy)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/model/textproxy
*/
// @if CK_DEBUG_ENGINE // const { convertMapToStringifiedObject } = require( '../dev-utils/utils' );
/**
* `TextProxy` represents a part of {@link module:engine/model/text~Text text node}.
*
* Since {@link module:engine/model/position~Position positions} can be placed between characters of a text node,
* {@link module:engine/model/range~Range ranges} may contain only parts of text nodes. When {@link module:engine/model/range~Range#getItems
* getting items}
* contained in such range, we need to represent a part of that text node, since returning the whole text node would be incorrect.
* `TextProxy` solves this issue.
*
* `TextProxy` has an API similar to {@link module:engine/model/text~Text Text} and allows to do most of the common tasks performed
* on model nodes.
*
* **Note:** Some `TextProxy` instances may represent whole text node, not just a part of it.
* See {@link module:engine/model/textproxy~TextProxy#isPartial}.
*
* **Note:** `TextProxy` is not an instance of {@link module:engine/model/node~Node node}. Keep this in mind when using it as a
* parameter of methods.
*
* **Note:** `TextProxy` is a readonly interface. If you want to perform changes on model data represented by a `TextProxy`
* use {@link module:engine/model/writer~Writer model writer API}.
*
* **Note:** `TextProxy` instances are created on the fly, basing on the current state of model. Because of this, it is
* highly unrecommended to store references to `TextProxy` instances. `TextProxy` instances are not refreshed when
* model changes, so they might get invalidated. Instead, consider creating {@link module:engine/model/liveposition~LivePosition live
* position}.
*
* `TextProxy` instances are created by {@link module:engine/model/treewalker~TreeWalker model tree walker}. You should not need to create
* an instance of this class by your own.
*/
class TextProxy {
/**
* Creates a text proxy.
*
* @protected
* @param {module:engine/model/text~Text} textNode Text node which part is represented by this text proxy.
* @param {Number} offsetInText Offset in {@link module:engine/model/textproxy~TextProxy#textNode text node} from which the text proxy
* starts.
* @param {Number} length Text proxy length, that is how many text node's characters, starting from `offsetInText` it represents.
* @constructor
*/
constructor( textNode, offsetInText, length ) {
/**
* Text node which part is represented by this text proxy.
*
* @readonly
* @member {module:engine/model/text~Text}
*/
this.textNode = textNode;
if ( offsetInText < 0 || offsetInText > textNode.offsetSize ) {
/**
* Given `offsetInText` value is incorrect.
*
* @error model-textproxy-wrong-offsetintext
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_0__["default"]( 'model-textproxy-wrong-offsetintext', this );
}
if ( length < 0 || offsetInText + length > textNode.offsetSize ) {
/**
* Given `length` value is incorrect.
*
* @error model-textproxy-wrong-length
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_0__["default"]( 'model-textproxy-wrong-length', this );
}
/**
* Text data represented by this text proxy.
*
* @readonly
* @member {String}
*/
this.data = textNode.data.substring( offsetInText, offsetInText + length );
/**
* Offset in {@link module:engine/model/textproxy~TextProxy#textNode text node} from which the text proxy starts.
*
* @readonly
* @member {Number}
*/
this.offsetInText = offsetInText;
}
/**
* Offset at which this text proxy starts in it's parent.
*
* @see module:engine/model/node~Node#startOffset
* @readonly
* @type {Number}
*/
get startOffset() {
return this.textNode.startOffset !== null ? this.textNode.startOffset + this.offsetInText : null;
}
/**
* Offset size of this text proxy. Equal to the number of characters represented by the text proxy.
*
* @see module:engine/model/node~Node#offsetSize
* @readonly
* @type {Number}
*/
get offsetSize() {
return this.data.length;
}
/**
* Offset at which this text proxy ends in it's parent.
*
* @see module:engine/model/node~Node#endOffset
* @readonly
* @type {Number}
*/
get endOffset() {
return this.startOffset !== null ? this.startOffset + this.offsetSize : null;
}
/**
* Flag indicating whether `TextProxy` instance covers only part of the original {@link module:engine/model/text~Text text node}
* (`true`) or the whole text node (`false`).
*
* This is `false` when text proxy starts at the very beginning of {@link module:engine/model/textproxy~TextProxy#textNode textNode}
* ({@link module:engine/model/textproxy~TextProxy#offsetInText offsetInText} equals `0`) and text proxy sizes is equal to
* text node size.
*
* @readonly
* @type {Boolean}
*/
get isPartial() {
return this.offsetSize !== this.textNode.offsetSize;
}
/**
* Parent of this text proxy, which is same as parent of text node represented by this text proxy.
*
* @readonly
* @type {module:engine/model/element~Element|module:engine/model/documentfragment~DocumentFragment|null}
*/
get parent() {
return this.textNode.parent;
}
/**
* Root of this text proxy, which is same as root of text node represented by this text proxy.
*
* @readonly
* @type {module:engine/model/node~Node|module:engine/model/documentfragment~DocumentFragment}
*/
get root() {
return this.textNode.root;
}
/**
* Checks whether this object is of the given.
*
* textProxy.is( '$textProxy' ); // -> true
* textProxy.is( 'model:$textProxy' ); // -> true
*
* textProxy.is( 'view:$textProxy' ); // -> false
* textProxy.is( 'range' ); // -> false
*
* {@link module:engine/model/node~Node#is Check the entire list of model objects} which implement the `is()` method.
*
* **Note:** Until version 20.0.0 this method wasn't accepting `'$textProxy'` type. The legacy `'textProxt'` type is still
* accepted for backward compatibility.
*
* @param {String} type Type to check.
* @returns {Boolean}
*/
is( type ) {
return type === '$textProxy' || type === 'model:$textProxy' ||
// This are legacy values kept for backward compatibility.
type === 'textProxy' || type === 'model:textProxy';
}
/**
* Gets path to this text proxy.
*
* @see module:engine/model/node~Node#getPath
* @returns {Array.<Number>}
*/
getPath() {
const path = this.textNode.getPath();
if ( path.length > 0 ) {
path[ path.length - 1 ] += this.offsetInText;
}
return path;
}
/**
* Returns ancestors array of this text proxy.
*
* @param {Object} options Options object.
* @param {Boolean} [options.includeSelf=false] When set to `true` this text proxy will be also included in parent's array.
* @param {Boolean} [options.parentFirst=false] When set to `true`, array will be sorted from text proxy parent to root element,
* otherwise root element will be the first item in the array.
* @returns {Array} Array with ancestors.
*/
getAncestors( options = { includeSelf: false, parentFirst: false } ) {
const ancestors = [];
let parent = options.includeSelf ? this : this.parent;
while ( parent ) {
ancestors[ options.parentFirst ? 'push' : 'unshift' ]( parent );
parent = parent.parent;
}
return ancestors;
}
/**
* Checks if this text proxy has an attribute for given key.
*
* @param {String} key Key of attribute to check.
* @returns {Boolean} `true` if attribute with given key is set on text proxy, `false` otherwise.
*/
hasAttribute( key ) {
return this.textNode.hasAttribute( key );
}
/**
* Gets an attribute value for given key or `undefined` if that attribute is not set on text proxy.
*
* @param {String} key Key of attribute to look for.
* @returns {*} Attribute value or `undefined`.
*/
getAttribute( key ) {
return this.textNode.getAttribute( key );
}
/**
* Returns iterator that iterates over this node's attributes. Attributes are returned as arrays containing two
* items. First one is attribute key and second is attribute value.
*
* This format is accepted by native `Map` object and also can be passed in `Node` constructor.
*
* @returns {Iterable.<*>}
*/
getAttributes() {
return this.textNode.getAttributes();
}
/**
* Returns iterator that iterates over this node's attribute keys.
*
* @returns {Iterable.<String>}
*/
getAttributeKeys() {
return this.textNode.getAttributeKeys();
}
// @if CK_DEBUG_ENGINE // toString() {
// @if CK_DEBUG_ENGINE // return `#${ this.data }`;
// @if CK_DEBUG_ENGINE // }
// @if CK_DEBUG_ENGINE // log() {
// @if CK_DEBUG_ENGINE // console.log( 'ModelTextProxy: ' + this );
// @if CK_DEBUG_ENGINE // }
// @if CK_DEBUG_ENGINE // logExtended() {
// @if CK_DEBUG_ENGINE // console.log( `ModelTextProxy: ${ this }, ` +
// @if CK_DEBUG_ENGINE // `attrs: ${ convertMapToStringifiedObject( this.getAttributes() ) }` );
// @if CK_DEBUG_ENGINE // }
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/model/treewalker.js":
/*!*************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/model/treewalker.js ***!
\*************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ TreeWalker)
/* harmony export */ });
/* harmony import */ var _text__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./text */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/text.js");
/* harmony import */ var _textproxy__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./textproxy */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/textproxy.js");
/* harmony import */ var _element__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./element */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/element.js");
/* harmony import */ var _position__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./position */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/position.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/model/treewalker
*/
/**
* Position iterator class. It allows to iterate forward and backward over the document.
*/
class TreeWalker {
/**
* Creates a range iterator. All parameters are optional, but you have to specify either `boundaries` or `startPosition`.
*
* @constructor
* @param {Object} [options={}] Object with configuration.
* @param {'forward'|'backward'} [options.direction='forward'] Walking direction.
* @param {module:engine/model/range~Range} [options.boundaries=null] Range to define boundaries of the iterator.
* @param {module:engine/model/position~Position} [options.startPosition] Starting position.
* @param {Boolean} [options.singleCharacters=false] Flag indicating whether all consecutive characters with the same attributes
* should be returned one by one as multiple {@link module:engine/model/textproxy~TextProxy} (`true`) objects or as one
* {@link module:engine/model/textproxy~TextProxy} (`false`).
* @param {Boolean} [options.shallow=false] Flag indicating whether iterator should enter elements or not. If the
* iterator is shallow child nodes of any iterated node will not be returned along with `elementEnd` tag.
* @param {Boolean} [options.ignoreElementEnd=false] Flag indicating whether iterator should ignore `elementEnd`
* tags. If the option is true walker will not return a parent node of start position. If this option is `true`
* each {@link module:engine/model/element~Element} will be returned once, while if the option is `false` they might be returned
* twice: for `'elementStart'` and `'elementEnd'`.
*/
constructor( options = {} ) {
if ( !options.boundaries && !options.startPosition ) {
/**
* Neither boundaries nor starting position of a `TreeWalker` have been defined.
*
* @error model-tree-walker-no-start-position
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_4__["default"](
'model-tree-walker-no-start-position',
null
);
}
const direction = options.direction || 'forward';
if ( direction != 'forward' && direction != 'backward' ) {
/**
* Only `backward` and `forward` direction allowed.
*
* @error model-tree-walker-unknown-direction
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_4__["default"]( 'model-tree-walker-unknown-direction', options, { direction } );
}
/**
* Walking direction. Defaults `'forward'`.
*
* @readonly
* @member {'backward'|'forward'} module:engine/model/treewalker~TreeWalker#direction
*/
this.direction = direction;
/**
* Iterator boundaries.
*
* When the iterator is walking `'forward'` on the end of boundary or is walking `'backward'`
* on the start of boundary, then `{ done: true }` is returned.
*
* If boundaries are not defined they are set before first and after last child of the root node.
*
* @readonly
* @member {module:engine/model/range~Range} module:engine/model/treewalker~TreeWalker#boundaries
*/
this.boundaries = options.boundaries || null;
/**
* Iterator position. This is always static position, even if the initial position was a
* {@link module:engine/model/liveposition~LivePosition live position}. If start position is not defined then position depends
* on {@link #direction}. If direction is `'forward'` position starts form the beginning, when direction
* is `'backward'` position starts from the end.
*
* @readonly
* @member {module:engine/model/position~Position} module:engine/model/treewalker~TreeWalker#position
*/
if ( options.startPosition ) {
this.position = options.startPosition.clone();
} else {
this.position = _position__WEBPACK_IMPORTED_MODULE_3__["default"]._createAt( this.boundaries[ this.direction == 'backward' ? 'end' : 'start' ] );
}
// Reset position stickiness in case it was set to other value, as the stickiness is kept after cloning.
this.position.stickiness = 'toNone';
/**
* Flag indicating whether all consecutive characters with the same attributes should be
* returned as one {@link module:engine/model/textproxy~TextProxy} (`true`) or one by one (`false`).
*
* @readonly
* @member {Boolean} module:engine/model/treewalker~TreeWalker#singleCharacters
*/
this.singleCharacters = !!options.singleCharacters;
/**
* Flag indicating whether iterator should enter elements or not. If the iterator is shallow child nodes of any
* iterated node will not be returned along with `elementEnd` tag.
*
* @readonly
* @member {Boolean} module:engine/model/treewalker~TreeWalker#shallow
*/
this.shallow = !!options.shallow;
/**
* Flag indicating whether iterator should ignore `elementEnd` tags. If the option is true walker will not
* return a parent node of the start position. If this option is `true` each {@link module:engine/model/element~Element} will
* be returned once, while if the option is `false` they might be returned twice:
* for `'elementStart'` and `'elementEnd'`.
*
* @readonly
* @member {Boolean} module:engine/model/treewalker~TreeWalker#ignoreElementEnd
*/
this.ignoreElementEnd = !!options.ignoreElementEnd;
/**
* Start boundary cached for optimization purposes.
*
* @private
* @member {module:engine/model/element~Element} module:engine/model/treewalker~TreeWalker#_boundaryStartParent
*/
this._boundaryStartParent = this.boundaries ? this.boundaries.start.parent : null;
/**
* End boundary cached for optimization purposes.
*
* @private
* @member {module:engine/model/element~Element} module:engine/model/treewalker~TreeWalker#_boundaryEndParent
*/
this._boundaryEndParent = this.boundaries ? this.boundaries.end.parent : null;
/**
* Parent of the most recently visited node. Cached for optimization purposes.
*
* @private
* @member {module:engine/model/element~Element|module:engine/model/documentfragment~DocumentFragment}
* module:engine/model/treewalker~TreeWalker#_visitedParent
*/
this._visitedParent = this.position.parent;
}
/**
* Iterable interface.
*
* @returns {Iterable.<module:engine/model/treewalker~TreeWalkerValue>}
*/
[ Symbol.iterator ]() {
return this;
}
/**
* Moves {@link #position} in the {@link #direction} skipping values as long as the callback function returns `true`.
*
* For example:
*
* walker.skip( value => value.type == 'text' ); // <paragraph>[]foo</paragraph> -> <paragraph>foo[]</paragraph>
* walker.skip( () => true ); // Move the position to the end: <paragraph>[]foo</paragraph> -> <paragraph>foo</paragraph>[]
* walker.skip( () => false ); // Do not move the position.
*
* @param {Function} skip Callback function. Gets {@link module:engine/model/treewalker~TreeWalkerValue} and should
* return `true` if the value should be skipped or `false` if not.
*/
skip( skip ) {
let done, value, prevPosition, prevVisitedParent;
do {
prevPosition = this.position;
prevVisitedParent = this._visitedParent;
( { done, value } = this.next() );
} while ( !done && skip( value ) );
if ( !done ) {
this.position = prevPosition;
this._visitedParent = prevVisitedParent;
}
}
/**
* Gets the next tree walker's value.
*
* @returns {module:engine/model/treewalker~TreeWalkerValue} Next tree walker's value.
*/
next() {
if ( this.direction == 'forward' ) {
return this._next();
} else {
return this._previous();
}
}
/**
* Makes a step forward in model. Moves the {@link #position} to the next position and returns the encountered value.
*
* @private
* @returns {Object}
* @returns {Boolean} return.done True if iterator is done.
* @returns {module:engine/model/treewalker~TreeWalkerValue} return.value Information about taken step.
*/
_next() {
const previousPosition = this.position;
const position = this.position.clone();
const parent = this._visitedParent;
// We are at the end of the root.
if ( parent.parent === null && position.offset === parent.maxOffset ) {
return { done: true };
}
// We reached the walker boundary.
if ( parent === this._boundaryEndParent && position.offset == this.boundaries.end.offset ) {
return { done: true };
}
// Get node just after the current position.
// Use a highly optimized version instead of checking the text node first and then getting the node after. See #6582.
const positionParent = position.parent;
const textNodeAtPosition = (0,_position__WEBPACK_IMPORTED_MODULE_3__.getTextNodeAtPosition)( position, positionParent );
const node = textNodeAtPosition ? textNodeAtPosition : (0,_position__WEBPACK_IMPORTED_MODULE_3__.getNodeAfterPosition)( position, positionParent, textNodeAtPosition );
if ( node instanceof _element__WEBPACK_IMPORTED_MODULE_2__["default"] ) {
if ( !this.shallow ) {
// Manual operations on path internals for optimization purposes. Here and in the rest of the method.
position.path.push( 0 );
this._visitedParent = node;
} else {
position.offset++;
}
this.position = position;
return formatReturnValue( 'elementStart', node, previousPosition, position, 1 );
} else if ( node instanceof _text__WEBPACK_IMPORTED_MODULE_0__["default"] ) {
let charactersCount;
if ( this.singleCharacters ) {
charactersCount = 1;
} else {
let offset = node.endOffset;
if ( this._boundaryEndParent == parent && this.boundaries.end.offset < offset ) {
offset = this.boundaries.end.offset;
}
charactersCount = offset - position.offset;
}
const offsetInTextNode = position.offset - node.startOffset;
const item = new _textproxy__WEBPACK_IMPORTED_MODULE_1__["default"]( node, offsetInTextNode, charactersCount );
position.offset += charactersCount;
this.position = position;
return formatReturnValue( 'text', item, previousPosition, position, charactersCount );
} else {
// `node` is not set, we reached the end of current `parent`.
position.path.pop();
position.offset++;
this.position = position;
this._visitedParent = parent.parent;
if ( this.ignoreElementEnd ) {
return this._next();
} else {
return formatReturnValue( 'elementEnd', parent, previousPosition, position );
}
}
}
/**
* Makes a step backward in model. Moves the {@link #position} to the previous position and returns the encountered value.
*
* @private
* @returns {Object}
* @returns {Boolean} return.done True if iterator is done.
* @returns {module:engine/model/treewalker~TreeWalkerValue} return.value Information about taken step.
*/
_previous() {
const previousPosition = this.position;
const position = this.position.clone();
const parent = this._visitedParent;
// We are at the beginning of the root.
if ( parent.parent === null && position.offset === 0 ) {
return { done: true };
}
// We reached the walker boundary.
if ( parent == this._boundaryStartParent && position.offset == this.boundaries.start.offset ) {
return { done: true };
}
// Get node just before the current position.
// Use a highly optimized version instead of checking the text node first and then getting the node before. See #6582.
const positionParent = position.parent;
const textNodeAtPosition = (0,_position__WEBPACK_IMPORTED_MODULE_3__.getTextNodeAtPosition)( position, positionParent );
const node = textNodeAtPosition ? textNodeAtPosition : (0,_position__WEBPACK_IMPORTED_MODULE_3__.getNodeBeforePosition)( position, positionParent, textNodeAtPosition );
if ( node instanceof _element__WEBPACK_IMPORTED_MODULE_2__["default"] ) {
position.offset--;
if ( !this.shallow ) {
position.path.push( node.maxOffset );
this.position = position;
this._visitedParent = node;
if ( this.ignoreElementEnd ) {
return this._previous();
} else {
return formatReturnValue( 'elementEnd', node, previousPosition, position );
}
} else {
this.position = position;
return formatReturnValue( 'elementStart', node, previousPosition, position, 1 );
}
} else if ( node instanceof _text__WEBPACK_IMPORTED_MODULE_0__["default"] ) {
let charactersCount;
if ( this.singleCharacters ) {
charactersCount = 1;
} else {
let offset = node.startOffset;
if ( this._boundaryStartParent == parent && this.boundaries.start.offset > offset ) {
offset = this.boundaries.start.offset;
}
charactersCount = position.offset - offset;
}
const offsetInTextNode = position.offset - node.startOffset;
const item = new _textproxy__WEBPACK_IMPORTED_MODULE_1__["default"]( node, offsetInTextNode - charactersCount, charactersCount );
position.offset -= charactersCount;
this.position = position;
return formatReturnValue( 'text', item, previousPosition, position, charactersCount );
} else {
// `node` is not set, we reached the beginning of current `parent`.
position.path.pop();
this.position = position;
this._visitedParent = parent.parent;
return formatReturnValue( 'elementStart', parent, previousPosition, position, 1 );
}
}
}
function formatReturnValue( type, item, previousPosition, nextPosition, length ) {
return {
done: false,
value: {
type,
item,
previousPosition,
nextPosition,
length
}
};
}
/**
* Type of the step made by {@link module:engine/model/treewalker~TreeWalker}.
* Possible values: `'elementStart'` if walker is at the beginning of a node, `'elementEnd'` if walker is at the end of node,
* or `'text'` if walker traversed over text.
*
* @typedef {'elementStart'|'elementEnd'|'text'} module:engine/model/treewalker~TreeWalkerValueType
*/
/**
* Object returned by {@link module:engine/model/treewalker~TreeWalker} when traversing tree model.
*
* @typedef {Object} module:engine/model/treewalker~TreeWalkerValue
* @property {module:engine/model/treewalker~TreeWalkerValueType} type
* @property {module:engine/model/item~Item} item Item between old and new positions of {@link module:engine/model/treewalker~TreeWalker}.
* @property {module:engine/model/position~Position} previousPosition Previous position of the iterator.
* * Forward iteration: For `'elementEnd'` it is the last position inside the element. For all other types it is the
* position before the item.
* * Backward iteration: For `'elementStart'` it is the first position inside the element. For all other types it is
* the position after item.
* @property {module:engine/model/position~Position} nextPosition Next position of the iterator.
* * Forward iteration: For `'elementStart'` it is the first position inside the element. For all other types it is
* the position after the item.
* * Backward iteration: For `'elementEnd'` it is last position inside element. For all other types it is the position
* before the item.
* @property {Number} [length] Length of the item. For `'elementStart'` it is 1. For `'text'` it is
* the length of the text. For `'elementEnd'` it is `undefined`.
*/
/**
* Tree walking directions.
*
* @typedef {'forward'|'backward'} module:engine/model/treewalker~TreeWalkerDirection
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/model/utils/autoparagraphing.js":
/*!*************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/model/utils/autoparagraphing.js ***!
\*************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "autoParagraphEmptyRoots": () => (/* binding */ autoParagraphEmptyRoots),
/* harmony export */ "isParagraphable": () => (/* binding */ isParagraphable),
/* harmony export */ "wrapInParagraph": () => (/* binding */ wrapInParagraph)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/model/utils/autoparagraphing
*/
/**
* Fixes all empty roots.
*
* @protected
* @param {module:engine/model/writer~Writer} writer The model writer.
* @returns {Boolean} `true` if any change has been applied, `false` otherwise.
*/
function autoParagraphEmptyRoots( writer ) {
const { schema, document } = writer.model;
for ( const rootName of document.getRootNames() ) {
const root = document.getRoot( rootName );
if ( root.isEmpty && !schema.checkChild( root, '$text' ) ) {
// If paragraph element is allowed in the root, create paragraph element.
if ( schema.checkChild( root, 'paragraph' ) ) {
writer.insertElement( 'paragraph', root );
// Other roots will get fixed in the next post-fixer round. Those will be triggered
// in the same batch no matter if this method was triggered by the post-fixing or not
// (the above insertElement call will trigger the post-fixers).
return true;
}
}
}
return false;
}
/**
* Checks if the given node wrapped with a paragraph would be accepted by the schema in the given position.
*
* @protected
* @param {module:engine/model/position~Position} position The position at which to check.
* @param {module:engine/model/node~Node|String} nodeOrType The child node or child type to check.
* @param {module:engine/model/schema~Schema} schema A schema instance used for element validation.
*/
function isParagraphable( position, nodeOrType, schema ) {
const context = schema.createContext( position );
// When paragraph is allowed in this context...
if ( !schema.checkChild( context, 'paragraph' ) ) {
return false;
}
// And a node would be allowed in this paragraph...
if ( !schema.checkChild( context.push( 'paragraph' ), nodeOrType ) ) {
return false;
}
return true;
}
/**
* Inserts a new paragraph at the given position and returns a position inside that paragraph.
*
* @protected
* @param {module:engine/model/position~Position} position The position where a paragraph should be inserted.
* @param {module:engine/model/writer~Writer} writer The model writer.
* @returns {module:engine/model/position~Position} Position inside the created paragraph.
*/
function wrapInParagraph( position, writer ) {
const paragraph = writer.createElement( 'paragraph' );
writer.insert( paragraph, position );
return writer.createPositionAt( paragraph, 0 );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/model/utils/deletecontent.js":
/*!**********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/model/utils/deletecontent.js ***!
\**********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ deleteContent)
/* harmony export */ });
/* harmony import */ var _liveposition__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../liveposition */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/liveposition.js");
/* harmony import */ var _range__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../range */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/range.js");
/* harmony import */ var _documentselection__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../documentselection */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/documentselection.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/model/utils/deletecontent
*/
/**
* Deletes content of the selection and merge siblings. The resulting selection is always collapsed.
*
* **Note:** Use {@link module:engine/model/model~Model#deleteContent} instead of this function.
* This function is only exposed to be reusable in algorithms
* which change the {@link module:engine/model/model~Model#deleteContent}
* method's behavior.
*
* @param {module:engine/model/model~Model} model The model in context of which the insertion
* should be performed.
* @param {module:engine/model/selection~Selection|module:engine/model/documentselection~DocumentSelection} selection
* Selection of which the content should be deleted.
* @param {Object} [options]
* @param {Boolean} [options.leaveUnmerged=false] Whether to merge elements after removing the content of the selection.
*
* For example `<heading>x[x</heading><paragraph>y]y</paragraph>` will become:
*
* * `<heading>x^y</heading>` with the option disabled (`leaveUnmerged == false`)
* * `<heading>x^</heading><paragraph>y</paragraph>` with enabled (`leaveUnmerged == true`).
*
* Note: {@link module:engine/model/schema~Schema#isObject object} and {@link module:engine/model/schema~Schema#isLimit limit}
* elements will not be merged.
*
* @param {Boolean} [options.doNotResetEntireContent=false] Whether to skip replacing the entire content with a
* paragraph when the entire content was selected.
*
* For example `<heading>[x</heading><paragraph>y]</paragraph>` will become:
*
* * `<paragraph>^</paragraph>` with the option disabled (`doNotResetEntireContent == false`)
* * `<heading>^</heading>` with enabled (`doNotResetEntireContent == true`).
*
* @param {Boolean} [options.doNotAutoparagraph=false] Whether to create a paragraph if after content deletion selection is moved
* to a place where text cannot be inserted.
*
* For example `<paragraph>x</paragraph>[<imageBlock src="foo.jpg"></imageBlock>]` will become:
*
* * `<paragraph>x</paragraph><paragraph>[]</paragraph>` with the option disabled (`doNotAutoparagraph == false`)
* * `<paragraph>x</paragraph>[]` with the option enabled (`doNotAutoparagraph == true`).
*
* If you use this option you need to make sure to handle invalid selections yourself or leave
* them to the selection post-fixer (may not always work).
*
* **Note:** If there is no valid position for the selection, the paragraph will always be created:
*
* `[<imageBlock src="foo.jpg"></imageBlock>]` -> `<paragraph>[]</paragraph>`.
*/
function deleteContent( model, selection, options = {} ) {
if ( selection.isCollapsed ) {
return;
}
const selRange = selection.getFirstRange();
// If the selection is already removed, don't do anything.
if ( selRange.root.rootName == '$graveyard' ) {
return;
}
const schema = model.schema;
model.change( writer => {
// 1. Replace the entire content with paragraph.
// See: https://github.com/ckeditor/ckeditor5-engine/issues/1012#issuecomment-315017594.
if ( !options.doNotResetEntireContent && shouldEntireContentBeReplacedWithParagraph( schema, selection ) ) {
replaceEntireContentWithParagraph( writer, selection, schema );
return;
}
// Get the live positions for the range adjusted to span only blocks selected from the user perspective.
const [ startPosition, endPosition ] = getLivePositionsForSelectedBlocks( selRange );
// 2. Remove the content if there is any.
if ( !startPosition.isTouching( endPosition ) ) {
writer.remove( writer.createRange( startPosition, endPosition ) );
}
// 3. Merge elements in the right branch to the elements in the left branch.
// The only reasonable (in terms of data and selection correctness) case in which we need to do that is:
//
// <heading type=1>Fo[</heading><paragraph>]ar</paragraph> => <heading type=1>Fo^ar</heading>
//
// However, the algorithm supports also merging deeper structures (up to the depth of the shallower branch),
// as it's hard to imagine what should actually be the default behavior. Usually, specific features will
// want to override that behavior anyway.
if ( !options.leaveUnmerged ) {
mergeBranches( writer, startPosition, endPosition );
// TMP this will be replaced with a postfixer.
// We need to check and strip disallowed attributes in all nested nodes because after merge
// some attributes could end up in a path where are disallowed.
//
// e.g. bold is disallowed for <H1>
// <h1>Fo{o</h1><p>b}a<b>r</b><p> -> <h1>Fo{}a<b>r</b><h1> -> <h1>Fo{}ar<h1>.
schema.removeDisallowedAttributes( startPosition.parent.getChildren(), writer );
}
collapseSelectionAt( writer, selection, startPosition );
// 4. Add a paragraph to set selection in it.
// Check if a text is allowed in the new container. If not, try to create a new paragraph (if it's allowed here).
// If autoparagraphing is off, we assume that you know what you do so we leave the selection wherever it was.
if ( !options.doNotAutoparagraph && shouldAutoparagraph( schema, startPosition ) ) {
insertParagraph( writer, startPosition, selection );
}
startPosition.detach();
endPosition.detach();
} );
}
// Returns the live positions for the range adjusted to span only blocks selected from the user perspective. Example:
//
// <heading1>[foo</heading1>
// <paragraph>bar</paragraph>
// <heading1>]abc</heading1> <-- this block is not considered as selected
//
// This is the same behavior as in Selection#getSelectedBlocks() "special case".
function getLivePositionsForSelectedBlocks( range ) {
const model = range.root.document.model;
const startPosition = range.start;
let endPosition = range.end;
// If the end of selection is at the start position of last block in the selection, then
// shrink it to not include that trailing block. Note that this should happen only for not empty selection.
if ( model.hasContent( range, { ignoreMarkers: true } ) ) {
const endBlock = getParentBlock( endPosition );
if ( endBlock && endPosition.isTouching( model.createPositionAt( endBlock, 0 ) ) ) {
// Create forward selection as a probe to find a valid position after excluding last block from the range.
const selection = model.createSelection( range );
// Modify the forward selection in backward direction to shrink it and remove first position of following block from it.
// This is how modifySelection works and here we are making use of it.
model.modifySelection( selection, { direction: 'backward' } );
const newEndPosition = selection.getLastPosition();
// For such a model and selection:
// <paragraph>A[</paragraph><imageBlock></imageBlock><paragraph>]B</paragraph>
//
// After modifySelection(), we would end up with this:
// <paragraph>A[</paragraph>]<imageBlock></imageBlock><paragraph>B</paragraph>
//
// So we need to check if there is no content in the skipped range (because we want to include the <imageBlock>).
const skippedRange = model.createRange( newEndPosition, endPosition );
if ( !model.hasContent( skippedRange, { ignoreMarkers: true } ) ) {
endPosition = newEndPosition;
}
}
}
return [
_liveposition__WEBPACK_IMPORTED_MODULE_0__["default"].fromPosition( startPosition, 'toPrevious' ),
_liveposition__WEBPACK_IMPORTED_MODULE_0__["default"].fromPosition( endPosition, 'toNext' )
];
}
// Finds the lowest element in position's ancestors which is a block.
// Returns null if a limit element is encountered before reaching a block element.
function getParentBlock( position ) {
const element = position.parent;
const schema = element.root.document.model.schema;
const ancestors = element.getAncestors( { parentFirst: true, includeSelf: true } );
for ( const element of ancestors ) {
if ( schema.isLimit( element ) ) {
return null;
}
if ( schema.isBlock( element ) ) {
return element;
}
}
}
// This function is a result of reaching the Ballmer's peak for just the right amount of time.
// Even I had troubles documenting it after a while and after reading it again I couldn't believe that it really works.
function mergeBranches( writer, startPosition, endPosition ) {
const model = writer.model;
// Verify if there is a need and possibility to merge.
if ( !checkShouldMerge( writer.model.schema, startPosition, endPosition ) ) {
return;
}
// If the start element on the common ancestor level is empty, and the end element on the same level is not empty
// then merge those to the right element so that it's properties are preserved (name, attributes).
// Because of OT merging is used instead of removing elements.
//
// Merge left:
// <heading1>foo[</heading1> -> <heading1>foo[]bar</heading1>
// <paragraph>]bar</paragraph> -> --^
//
// Merge right:
// <heading1>[</heading1> ->
// <paragraph>]bar</paragraph> -> <paragraph>[]bar</paragraph>
//
// Merge left:
// <blockQuote> -> <blockQuote>
// <heading1>foo[</heading1> -> <heading1>foo[]bar</heading1>
// <paragraph>]bar</paragraph> -> --^
// </blockQuote> -> </blockQuote>
//
// Merge right:
// <blockQuote> -> <blockQuote>
// <heading1>[</heading1> ->
// <paragraph>]bar</paragraph> -> <paragraph>[]bar</paragraph>
// </blockQuote> -> </blockQuote>
// Merging should not go deeper than common ancestor.
const [ startAncestor, endAncestor ] = getAncestorsJustBelowCommonAncestor( startPosition, endPosition );
// Branches can't be merged if one of the positions is directly inside a common ancestor.
//
// Example:
// <blockQuote>
// <paragraph>[foo</paragraph>]
// <table> ... </table>
// <blockQuote>
//
if ( !startAncestor || !endAncestor ) {
return;
}
if ( !model.hasContent( startAncestor, { ignoreMarkers: true } ) && model.hasContent( endAncestor, { ignoreMarkers: true } ) ) {
mergeBranchesRight( writer, startPosition, endPosition, startAncestor.parent );
} else {
mergeBranchesLeft( writer, startPosition, endPosition, startAncestor.parent );
}
}
// Merging blocks to the left (properties of the left block are preserved).
// Simple example:
// <heading1>foo[</heading1> -> <heading1>foo[bar</heading1>]
// <paragraph>]bar</paragraph> -> --^
//
// Nested example:
// <blockQuote> -> <blockQuote>
// <heading1>foo[</heading1> -> <heading1>foo[bar</heading1>
// </blockQuote> -> </blockQuote>] ^
// <blockBlock> -> |
// <paragraph>]bar</paragraph> -> ---
// </blockBlock> ->
//
function mergeBranchesLeft( writer, startPosition, endPosition, commonAncestor ) {
const startElement = startPosition.parent;
const endElement = endPosition.parent;
// Merging reached the common ancestor element, stop here.
if ( startElement == commonAncestor || endElement == commonAncestor ) {
return;
}
// Remember next positions to merge in next recursive step (also used as modification points pointers).
startPosition = writer.createPositionAfter( startElement );
endPosition = writer.createPositionBefore( endElement );
// Move endElement just after startElement if they aren't siblings.
if ( !endPosition.isEqual( startPosition ) ) {
//
// <blockQuote> -> <blockQuote>
// <heading1>foo[</heading1> -> <heading1>foo</heading1>[<paragraph>bar</paragraph>
// </blockQuote> -> </blockQuote> ^
// <blockBlock> -> <blockBlock> |
// <paragraph>]bar</paragraph> -> ] ---
// </blockBlock> -> </blockBlock>
//
writer.insert( endElement, startPosition );
}
// Merge two siblings (nodes on sides of startPosition):
//
// <blockQuote> -> <blockQuote>
// <heading1>foo</heading1>[<paragraph>bar</paragraph> -> <heading1>foo[bar</heading1>
// </blockQuote> -> </blockQuote>
// <blockBlock> -> <blockBlock>
// ] -> ]
// </blockBlock> -> </blockBlock>
//
// Or in simple case (without moving elements in above if):
// <heading1>foo</heading1>[<paragraph>bar</paragraph>] -> <heading1>foo[bar</heading1>]
//
writer.merge( startPosition );
// Remove empty end ancestors:
//
// <blockQuote> -> <blockQuote>
// <heading1>foo[bar</heading1> -> <heading1>foo[bar</heading1>
// </blockQuote> -> </blockQuote>
// <blockBlock> ->
// ] -> ]
// </blockBlock> ->
//
while ( endPosition.parent.isEmpty ) {
const parentToRemove = endPosition.parent;
endPosition = writer.createPositionBefore( parentToRemove );
writer.remove( parentToRemove );
}
// Verify if there is a need and possibility to merge next level.
if ( !checkShouldMerge( writer.model.schema, startPosition, endPosition ) ) {
return;
}
// Continue merging next level (blockQuote with blockBlock in the examples above if it would not be empty and got removed).
mergeBranchesLeft( writer, startPosition, endPosition, commonAncestor );
}
// Merging blocks to the right (properties of the right block are preserved).
// Simple example:
// <heading1>foo[</heading1> -> --v
// <paragraph>]bar</paragraph> -> [<paragraph>foo]bar</paragraph>
//
// Nested example:
// <blockQuote> ->
// <heading1>foo[</heading1> -> ---
// </blockQuote> -> |
// <blockBlock> -> [<blockBlock> v
// <paragraph>]bar</paragraph> -> <paragraph>foo]bar</paragraph>
// </blockBlock> -> </blockBlock>
//
function mergeBranchesRight( writer, startPosition, endPosition, commonAncestor ) {
const startElement = startPosition.parent;
const endElement = endPosition.parent;
// Merging reached the common ancestor element, stop here.
if ( startElement == commonAncestor || endElement == commonAncestor ) {
return;
}
// Remember next positions to merge in next recursive step (also used as modification points pointers).
startPosition = writer.createPositionAfter( startElement );
endPosition = writer.createPositionBefore( endElement );
// Move startElement just before endElement if they aren't siblings.
if ( !endPosition.isEqual( startPosition ) ) {
//
// <blockQuote> -> <blockQuote>
// <heading1>foo[</heading1> -> [ ---
// </blockQuote> -> </blockQuote> |
// <blockBlock> -> <blockBlock> v
// <paragraph>]bar</paragraph> -> <heading1>foo</heading1>]<paragraph>bar</paragraph>
// </blockBlock> -> </blockBlock>
//
writer.insert( startElement, endPosition );
}
// Remove empty end ancestors:
//
// <blockQuote> ->
// [ -> [
// </blockQuote> ->
// <blockBlock> -> <blockBlock>
// <heading1>foo</heading1>]<paragraph>bar</paragraph> -> <heading1>foo</heading1>]<paragraph>bar</paragraph>
// </blockBlock> -> </blockBlock>
//
while ( startPosition.parent.isEmpty ) {
const parentToRemove = startPosition.parent;
startPosition = writer.createPositionBefore( parentToRemove );
writer.remove( parentToRemove );
}
// Update endPosition after inserting and removing elements.
endPosition = writer.createPositionBefore( endElement );
// Merge right two siblings (nodes on sides of endPosition):
// ->
// [ -> [
// ->
// <blockBlock> -> <blockBlock>
// <heading1>foo</heading1>]<paragraph>bar</paragraph> -> <paragraph>foo]bar</paragraph>
// </blockBlock> -> </blockBlock>
//
// Or in simple case (without moving elements in above if):
// [<heading1>foo</heading1>]<paragraph>bar</paragraph> -> [<heading1>foo]bar</heading1>
//
mergeRight( writer, endPosition );
// Verify if there is a need and possibility to merge next level.
if ( !checkShouldMerge( writer.model.schema, startPosition, endPosition ) ) {
return;
}
// Continue merging next level (blockQuote with blockBlock in the examples above if it would not be empty and got removed).
mergeBranchesRight( writer, startPosition, endPosition, commonAncestor );
}
// There is no right merge operation so we need to simulate it.
function mergeRight( writer, position ) {
const startElement = position.nodeBefore;
const endElement = position.nodeAfter;
if ( startElement.name != endElement.name ) {
writer.rename( startElement, endElement.name );
}
writer.clearAttributes( startElement );
writer.setAttributes( Object.fromEntries( endElement.getAttributes() ), startElement );
writer.merge( position );
}
// Verifies if merging is needed and possible. It's not needed if both positions are in the same element
// and it's not possible if some element is a limit or the range crosses a limit element.
function checkShouldMerge( schema, startPosition, endPosition ) {
const startElement = startPosition.parent;
const endElement = endPosition.parent;
// If both positions ended up in the same parent, then there's nothing more to merge:
// <$root><p>x[</p><p>]y</p></$root> => <$root><p>xy</p>[]</$root>
if ( startElement == endElement ) {
return false;
}
// If one of the positions is a limit element, then there's nothing to merge because we don't want to cross the limit boundaries.
if ( schema.isLimit( startElement ) || schema.isLimit( endElement ) ) {
return false;
}
// Check if operations we'll need to do won't need to cross object or limit boundaries.
// E.g., we can't merge endElement into startElement in this case:
// <limit><startElement>x[</startElement></limit><endElement>]</endElement>
return isCrossingLimitElement( startPosition, endPosition, schema );
}
// Returns the elements that are the ancestors of the provided positions that are direct children of the common ancestor.
function getAncestorsJustBelowCommonAncestor( positionA, positionB ) {
const ancestorsA = positionA.getAncestors();
const ancestorsB = positionB.getAncestors();
let i = 0;
while ( ancestorsA[ i ] && ancestorsA[ i ] == ancestorsB[ i ] ) {
i++;
}
return [ ancestorsA[ i ], ancestorsB[ i ] ];
}
function shouldAutoparagraph( schema, position ) {
const isTextAllowed = schema.checkChild( position, '$text' );
const isParagraphAllowed = schema.checkChild( position, 'paragraph' );
return !isTextAllowed && isParagraphAllowed;
}
// Check if parents of two positions can be merged by checking if there are no limit/object
// boundaries between those two positions.
//
// E.g. in <bQ><p>x[]</p></bQ><widget><caption>{}</caption></widget>
// we'll check <p>, <bQ>, <widget> and <caption>.
// Usually, widget and caption are marked as objects/limits in the schema, so in this case merging will be blocked.
function isCrossingLimitElement( leftPos, rightPos, schema ) {
const rangeToCheck = new _range__WEBPACK_IMPORTED_MODULE_1__["default"]( leftPos, rightPos );
for ( const value of rangeToCheck.getWalker() ) {
if ( schema.isLimit( value.item ) ) {
return false;
}
}
return true;
}
function insertParagraph( writer, position, selection ) {
const paragraph = writer.createElement( 'paragraph' );
writer.insert( paragraph, position );
collapseSelectionAt( writer, selection, writer.createPositionAt( paragraph, 0 ) );
}
function replaceEntireContentWithParagraph( writer, selection ) {
const limitElement = writer.model.schema.getLimitElement( selection );
writer.remove( writer.createRangeIn( limitElement ) );
insertParagraph( writer, writer.createPositionAt( limitElement, 0 ), selection );
}
// We want to replace the entire content with a paragraph when:
// * the entire content is selected,
// * selection contains at least two elements,
// * whether the paragraph is allowed in schema in the common ancestor.
function shouldEntireContentBeReplacedWithParagraph( schema, selection ) {
const limitElement = schema.getLimitElement( selection );
if ( !selection.containsEntireContent( limitElement ) ) {
return false;
}
const range = selection.getFirstRange();
if ( range.start.parent == range.end.parent ) {
return false;
}
return schema.checkChild( limitElement, 'paragraph' );
}
// Helper function that sets the selection. Depending whether given `selection` is a document selection or not,
// uses a different method to set it.
function collapseSelectionAt( writer, selection, positionOrRange ) {
if ( selection instanceof _documentselection__WEBPACK_IMPORTED_MODULE_2__["default"] ) {
writer.setSelection( positionOrRange );
} else {
selection.setTo( positionOrRange );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/model/utils/getselectedcontent.js":
/*!***************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/model/utils/getselectedcontent.js ***!
\***************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ getSelectedContent)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/model/utils/getselectedcontent
*/
/**
* Gets a clone of the selected content.
*
* For example, for the following selection:
*
* ```html
* <p>x</p><quote><p>y</p><h>fir[st</h></quote><p>se]cond</p><p>z</p>
* ```
*
* It will return a document fragment with such a content:
*
* ```html
* <quote><h>st</h></quote><p>se</p>
* ```
*
* @param {module:engine/model/model~Model} model The model in context of which
* the selection modification should be performed.
* @param {module:engine/model/selection~Selection|module:engine/model/documentselection~DocumentSelection} selection
* The selection of which content will be returned.
* @returns {module:engine/model/documentfragment~DocumentFragment}
*/
function getSelectedContent( model, selection ) {
return model.change( writer => {
const frag = writer.createDocumentFragment();
const range = selection.getFirstRange();
if ( !range || range.isCollapsed ) {
return frag;
}
const root = range.start.root;
const commonPath = range.start.getCommonPath( range.end );
const commonParent = root.getNodeByPath( commonPath );
// ## 1st step
//
// First, we'll clone a fragment represented by a minimal flat range
// containing the original range to be cloned.
// E.g. let's consider such a range:
//
// <p>x</p><quote><p>y</p><h>fir[st</h></quote><p>se]cond</p><p>z</p>
//
// A minimal flat range containing this one is:
//
// <p>x</p>[<quote><p>y</p><h>first</h></quote><p>second</p>]<p>z</p>
//
// We can easily clone this structure, preserving e.g. the <quote> element.
let flatSubtreeRange;
if ( range.start.parent == range.end.parent ) {
// The original range is flat, so take it.
flatSubtreeRange = range;
} else {
flatSubtreeRange = writer.createRange(
writer.createPositionAt( commonParent, range.start.path[ commonPath.length ] ),
writer.createPositionAt( commonParent, range.end.path[ commonPath.length ] + 1 )
);
}
const howMany = flatSubtreeRange.end.offset - flatSubtreeRange.start.offset;
// Clone the whole contents.
for ( const item of flatSubtreeRange.getItems( { shallow: true } ) ) {
if ( item.is( '$textProxy' ) ) {
writer.appendText( item.data, item.getAttributes(), frag );
} else {
writer.append( writer.cloneElement( item, true ), frag );
}
}
// ## 2nd step
//
// If the original range wasn't flat, then we need to remove the excess nodes from the both ends of the cloned fragment.
//
// For example, for the range shown in the 1st step comment, we need to remove these pieces:
//
// <quote>[<p>y</p>]<h>[fir]st</h></quote><p>se[cond]</p>
//
// So this will be the final copied content:
//
// <quote><h>st</h></quote><p>se</p>
//
// In order to do that, we remove content from these two ranges:
//
// [<quote><p>y</p><h>fir]st</h></quote><p>se[cond</p>]
if ( flatSubtreeRange != range ) {
// Find the position of the original range in the cloned fragment.
const newRange = range._getTransformedByMove( flatSubtreeRange.start, writer.createPositionAt( frag, 0 ), howMany )[ 0 ];
const leftExcessRange = writer.createRange( writer.createPositionAt( frag, 0 ), newRange.start );
const rightExcessRange = writer.createRange( newRange.end, writer.createPositionAt( frag, 'end' ) );
removeRangeContent( rightExcessRange, writer );
removeRangeContent( leftExcessRange, writer );
}
return frag;
} );
}
// After https://github.com/ckeditor/ckeditor5-engine/issues/690 is fixed,
// this function will, most likely, be able to rewritten using getMinimalFlatRanges().
function removeRangeContent( range, writer ) {
const parentsToCheck = [];
Array.from( range.getItems( { direction: 'backward' } ) )
// We should better store ranges because text proxies will lose integrity
// with the text nodes when we'll start removing content.
.map( item => writer.createRangeOn( item ) )
// Filter only these items which are fully contained in the passed range.
//
// E.g. for the following range: [<quote><p>y</p><h>fir]st</h>
// the walker will return the entire <h> element, when only the "fir" item inside it is fully contained.
.filter( itemRange => {
// We should be able to use Range.containsRange, but https://github.com/ckeditor/ckeditor5-engine/issues/691.
const contained =
( itemRange.start.isAfter( range.start ) || itemRange.start.isEqual( range.start ) ) &&
( itemRange.end.isBefore( range.end ) || itemRange.end.isEqual( range.end ) );
return contained;
} )
.forEach( itemRange => {
parentsToCheck.push( itemRange.start.parent );
writer.remove( itemRange );
} );
// Remove ancestors of the removed items if they turned to be empty now
// (their whole content was contained in the range).
parentsToCheck.forEach( parentToCheck => {
let parent = parentToCheck;
while ( parent.parent && parent.isEmpty ) {
const removeRange = writer.createRangeOn( parent );
parent = parent.parent;
writer.remove( removeRange );
}
} );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/model/utils/insertcontent.js":
/*!**********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/model/utils/insertcontent.js ***!
\**********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ insertContent)
/* harmony export */ });
/* harmony import */ var _position__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../position */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/position.js");
/* harmony import */ var _liveposition__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../liveposition */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/liveposition.js");
/* harmony import */ var _element__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../element */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/element.js");
/* harmony import */ var _range__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../range */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/range.js");
/* harmony import */ var _documentselection__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../documentselection */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/documentselection.js");
/* harmony import */ var _selection__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../selection */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/selection.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/model/utils/insertcontent
*/
/**
* Inserts content into the editor (specified selection) as one would expect the paste functionality to work.
*
* It takes care of removing the selected content, splitting elements (if needed), inserting elements and merging elements appropriately.
*
* Some examples:
*
* <p>x^</p> + <p>y</p> => <p>x</p><p>y</p> => <p>xy[]</p>
* <p>x^y</p> + <p>z</p> => <p>x</p>^<p>y</p> + <p>z</p> => <p>x</p><p>z</p><p>y</p> => <p>xz[]y</p>
* <p>x^y</p> + <img /> => <p>x</p>^<p>y</p> + <img /> => <p>x</p><img /><p>y</p>
* <p>x</p><p>^</p><p>z</p> + <p>y</p> => <p>x</p><p>y[]</p><p>z</p> (no merging)
* <p>x</p>[<img />]<p>z</p> + <p>y</p> => <p>x</p>^<p>z</p> + <p>y</p> => <p>x</p><p>y[]</p><p>z</p>
*
* If an instance of {@link module:engine/model/selection~Selection} is passed as `selectable` it will be modified
* to the insertion selection (equal to a range to be selected after insertion).
*
* If `selectable` is not passed, the content will be inserted using the current selection of the model document.
*
* **Note:** Use {@link module:engine/model/model~Model#insertContent} instead of this function.
* This function is only exposed to be reusable in algorithms which change the {@link module:engine/model/model~Model#insertContent}
* method's behavior.
*
* @param {module:engine/model/model~Model} model The model in context of which the insertion
* should be performed.
* @param {module:engine/model/documentfragment~DocumentFragment|module:engine/model/item~Item} content The content to insert.
* @param {module:engine/model/selection~Selectable} [selectable=model.document.selection]
* Selection into which the content should be inserted.
* @param {Number|'before'|'end'|'after'|'on'|'in'} [placeOrOffset] Sets place or offset of the selection.
* @returns {module:engine/model/range~Range} Range which contains all the performed changes. This is a range that, if removed,
* would return the model to the state before the insertion. If no changes were preformed by `insertContent`, returns a range collapsed
* at the insertion position.
*/
function insertContent( model, content, selectable, placeOrOffset ) {
return model.change( writer => {
let selection;
if ( !selectable ) {
selection = model.document.selection;
} else if ( selectable instanceof _selection__WEBPACK_IMPORTED_MODULE_5__["default"] || selectable instanceof _documentselection__WEBPACK_IMPORTED_MODULE_4__["default"] ) {
selection = selectable;
} else {
selection = writer.createSelection( selectable, placeOrOffset );
}
if ( !selection.isCollapsed ) {
model.deleteContent( selection, { doNotAutoparagraph: true } );
}
const insertion = new Insertion( model, writer, selection.anchor );
let nodesToInsert;
if ( content.is( 'documentFragment' ) ) {
nodesToInsert = content.getChildren();
} else {
nodesToInsert = [ content ];
}
insertion.handleNodes( nodesToInsert );
const newRange = insertion.getSelectionRange();
/* istanbul ignore else */
if ( newRange ) {
if ( selection instanceof _documentselection__WEBPACK_IMPORTED_MODULE_4__["default"] ) {
writer.setSelection( newRange );
} else {
selection.setTo( newRange );
}
} else {
// We are not testing else because it's a safe check for unpredictable edge cases:
// an insertion without proper range to select.
//
// @if CK_DEBUG // console.warn( 'Cannot determine a proper selection range after insertion.' );
}
const affectedRange = insertion.getAffectedRange() || model.createRange( selection.anchor );
insertion.destroy();
return affectedRange;
} );
}
/**
* Utility class for performing content insertion.
*
* @private
*/
class Insertion {
constructor( model, writer, position ) {
/**
* The model in context of which the insertion should be performed.
*
* @member {module:engine/model~Model} #model
*/
this.model = model;
/**
* Batch to which operations will be added.
*
* @member {module:engine/controller/writer~Batch} #writer
*/
this.writer = writer;
/**
* The position at which (or near which) the next node will be inserted.
*
* @member {module:engine/model/position~Position} #position
*/
this.position = position;
/**
* Elements with which the inserted elements can be merged.
*
* <p>x^</p><p>y</p> + <p>z</p> (can merge to <p>x</p>)
* <p>x</p><p>^y</p> + <p>z</p> (can merge to <p>y</p>)
* <p>x^y</p> + <p>z</p> (can merge to <p>xy</p> which will be split during the action,
* so both its pieces will be added to this set)
*
*
* @member {Set} #canMergeWith
*/
this.canMergeWith = new Set( [ this.position.parent ] );
/**
* Schema of the model.
*
* @member {module:engine/model/schema~Schema} #schema
*/
this.schema = model.schema;
/**
* The temporary DocumentFragment used for grouping multiple nodes for single insert operation.
*
* @private
* @type {module:engine/model/documentfragment~DocumentFragment}
*/
this._documentFragment = writer.createDocumentFragment();
/**
* The current position in the temporary DocumentFragment.
*
* @private
* @type {module:engine/model/position~Position}
*/
this._documentFragmentPosition = writer.createPositionAt( this._documentFragment, 0 );
/**
* The reference to the first inserted node.
*
* @private
* @type {module:engine/model/node~Node}
*/
this._firstNode = null;
/**
* The reference to the last inserted node.
*
* @private
* @type {module:engine/model/node~Node}
*/
this._lastNode = null;
/**
* The reference to the last auto paragraph node.
*
* @private
* @type {module:engine/model/node~Node}
*/
this._lastAutoParagraph = null;
/**
* The array of nodes that should be cleaned of not allowed attributes.
*
* @private
* @type {Array.<module:engine/model/node~Node>}
*/
this._filterAttributesOf = [];
/**
* Beginning of the affected range. See {@link module:engine/model/utils/insertcontent~Insertion#getAffectedRange}.
*
* @private
* @member {module:engine/model/liveposition~LivePosition|null} #_affectedStart
*/
this._affectedStart = null;
/**
* End of the affected range. See {@link module:engine/model/utils/insertcontent~Insertion#getAffectedRange}.
*
* @private
* @member {module:engine/model/liveposition~LivePosition|null} #_affectedEnd
*/
this._affectedEnd = null;
}
/**
* Handles insertion of a set of nodes.
*
* @param {Iterable.<module:engine/model/node~Node>} nodes Nodes to insert.
*/
handleNodes( nodes ) {
for ( const node of Array.from( nodes ) ) {
this._handleNode( node );
}
// Insert nodes collected in temporary DocumentFragment.
this._insertPartialFragment();
// If there was an auto paragraph then we might need to adjust the end of insertion.
if ( this._lastAutoParagraph ) {
this._updateLastNodeFromAutoParagraph( this._lastAutoParagraph );
}
// After the content was inserted we may try to merge it with its next sibling if the selection was in it initially.
// Merging with the previous sibling was performed just after inserting the first node to the document.
this._mergeOnRight();
// TMP this will become a post-fixer.
this.schema.removeDisallowedAttributes( this._filterAttributesOf, this.writer );
this._filterAttributesOf = [];
}
/**
* Updates the last node after the auto paragraphing.
*
* @private
* @param {module:engine/model/node~Node} node The last auto paragraphing node.
*/
_updateLastNodeFromAutoParagraph( node ) {
const positionAfterLastNode = this.writer.createPositionAfter( this._lastNode );
const positionAfterNode = this.writer.createPositionAfter( node );
// If the real end was after the last auto paragraph then update relevant properties.
if ( positionAfterNode.isAfter( positionAfterLastNode ) ) {
this._lastNode = node;
/* istanbul ignore if */
if ( this.position.parent != node || !this.position.isAtEnd ) {
// Algorithm's correctness check. We should never end up here but it's good to know that we did.
// At this point the insertion position should be at the end of the last auto paragraph.
// Note: This error is documented in other place in this file.
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_6__["default"]( 'insertcontent-invalid-insertion-position', this );
}
this.position = positionAfterNode;
this._setAffectedBoundaries( this.position );
}
}
/**
* Returns range to be selected after insertion.
* Returns `null` if there is no valid range to select after insertion.
*
* @returns {module:engine/model/range~Range|null}
*/
getSelectionRange() {
if ( this.nodeToSelect ) {
return _range__WEBPACK_IMPORTED_MODULE_3__["default"]._createOn( this.nodeToSelect );
}
return this.model.schema.getNearestSelectionRange( this.position );
}
/**
* Returns a range which contains all the performed changes. This is a range that, if removed, would return the model to the state
* before the insertion. Returns `null` if no changes were done.
*
* @returns {module:engine/model/range~Range|null}
*/
getAffectedRange() {
if ( !this._affectedStart ) {
return null;
}
return new _range__WEBPACK_IMPORTED_MODULE_3__["default"]( this._affectedStart, this._affectedEnd );
}
/**
* Destroys `Insertion` instance.
*/
destroy() {
if ( this._affectedStart ) {
this._affectedStart.detach();
}
if ( this._affectedEnd ) {
this._affectedEnd.detach();
}
}
/**
* Handles insertion of a single node.
*
* @private
* @param {module:engine/model/node~Node} node
*/
_handleNode( node ) {
// Let's handle object in a special way.
// * They should never be merged with other elements.
// * If they are not allowed in any of the selection ancestors, they could be either autoparagraphed or totally removed.
if ( this.schema.isObject( node ) ) {
this._handleObject( node );
return;
}
// Try to find a place for the given node.
// Check if a node can be inserted in the given position or it would be accepted if a paragraph would be inserted.
// Inserts the auto paragraph if it would allow for insertion.
let isAllowed = this._checkAndAutoParagraphToAllowedPosition( node );
if ( !isAllowed ) {
// Split the position.parent's branch up to a point where the node can be inserted.
// If it isn't allowed in the whole branch, then of course don't split anything.
isAllowed = this._checkAndSplitToAllowedPosition( node );
if ( !isAllowed ) {
this._handleDisallowedNode( node );
return;
}
}
// Add node to the current temporary DocumentFragment.
this._appendToFragment( node );
// Store the first and last nodes for easy access for merging with sibling nodes.
if ( !this._firstNode ) {
this._firstNode = node;
}
this._lastNode = node;
}
/**
* Inserts the temporary DocumentFragment into the model.
*
* @private
*/
_insertPartialFragment() {
if ( this._documentFragment.isEmpty ) {
return;
}
const livePosition = _liveposition__WEBPACK_IMPORTED_MODULE_1__["default"].fromPosition( this.position, 'toNext' );
this._setAffectedBoundaries( this.position );
// If the very first node of the whole insertion process is inserted, insert it separately for OT reasons (undo).
// Note: there can be multiple calls to `_insertPartialFragment()` during one insertion process.
// Note: only the very first node can be merged so we have to do separate operation only for it.
if ( this._documentFragment.getChild( 0 ) == this._firstNode ) {
this.writer.insert( this._firstNode, this.position );
// We must merge the first node just after inserting it to avoid problems with OT.
// (See: https://github.com/ckeditor/ckeditor5/pull/8773#issuecomment-760945652).
this._mergeOnLeft();
this.position = livePosition.toPosition();
}
// Insert the remaining nodes from document fragment.
if ( !this._documentFragment.isEmpty ) {
this.writer.insert( this._documentFragment, this.position );
}
this._documentFragmentPosition = this.writer.createPositionAt( this._documentFragment, 0 );
this.position = livePosition.toPosition();
livePosition.detach();
}
/**
* @private
* @param {module:engine/model/element~Element} node The object element.
*/
_handleObject( node ) {
// Try finding it a place in the tree.
if ( this._checkAndSplitToAllowedPosition( node ) ) {
this._appendToFragment( node );
}
// Try autoparagraphing.
else {
this._tryAutoparagraphing( node );
}
}
/**
* @private
* @param {module:engine/model/node~Node} node The disallowed node which needs to be handled.
*/
_handleDisallowedNode( node ) {
// If the node is an element, try inserting its children (strip the parent).
if ( node.is( 'element' ) ) {
this.handleNodes( node.getChildren() );
}
// If text is not allowed, try autoparagraphing it.
else {
this._tryAutoparagraphing( node );
}
}
/**
* Append a node to the temporary DocumentFragment.
*
* @private
* @param {module:engine/model/node~Node} node The node to insert.
*/
_appendToFragment( node ) {
/* istanbul ignore if */
if ( !this.schema.checkChild( this.position, node ) ) {
// Algorithm's correctness check. We should never end up here but it's good to know that we did.
// Note that it would often be a silent issue if we insert node in a place where it's not allowed.
/**
* Given node cannot be inserted on the given position.
*
* @error insertcontent-wrong-position
* @param {module:engine/model/node~Node} node Node to insert.
* @param {module:engine/model/position~Position} position Position to insert the node at.
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_6__["default"](
'insertcontent-wrong-position',
this,
{ node, position: this.position }
);
}
this.writer.insert( node, this._documentFragmentPosition );
this._documentFragmentPosition = this._documentFragmentPosition.getShiftedBy( node.offsetSize );
// The last inserted object should be selected because we can't put a collapsed selection after it.
if ( this.schema.isObject( node ) && !this.schema.checkChild( this.position, '$text' ) ) {
this.nodeToSelect = node;
} else {
this.nodeToSelect = null;
}
this._filterAttributesOf.push( node );
}
/**
* Sets `_affectedStart` and `_affectedEnd` to the given `position`. Should be used before a change is done during insertion process to
* mark the affected range.
*
* This method is used before inserting a node or splitting a parent node. `_affectedStart` and `_affectedEnd` are also changed
* during merging, but the logic there is more complicated so it is left out of this function.
*
* @private
* @param {module:engine/model/position~Position} position
*/
_setAffectedBoundaries( position ) {
// Set affected boundaries stickiness so that those position will "expand" when something is inserted in between them:
// <paragraph>Foo][bar</paragraph> -> <paragraph>Foo]xx[bar</paragraph>
// This is why it cannot be a range but two separate positions.
if ( !this._affectedStart ) {
this._affectedStart = _liveposition__WEBPACK_IMPORTED_MODULE_1__["default"].fromPosition( position, 'toPrevious' );
}
// If `_affectedEnd` is before the new boundary position, expand `_affectedEnd`. This can happen if first inserted node was
// inserted into the parent but the next node is moved-out of that parent:
// (1) <paragraph>Foo][</paragraph> -> <paragraph>Foo]xx[</paragraph>
// (2) <paragraph>Foo]xx[</paragraph> -> <paragraph>Foo]xx</paragraph><widget></widget>[
if ( !this._affectedEnd || this._affectedEnd.isBefore( position ) ) {
if ( this._affectedEnd ) {
this._affectedEnd.detach();
}
this._affectedEnd = _liveposition__WEBPACK_IMPORTED_MODULE_1__["default"].fromPosition( position, 'toNext' );
}
}
/**
* Merges the previous sibling of the first node if it should be merged.
*
* After the content was inserted we may try to merge it with its siblings.
* This should happen only if the selection was in those elements initially.
*
* @private
*/
_mergeOnLeft() {
const node = this._firstNode;
if ( !( node instanceof _element__WEBPACK_IMPORTED_MODULE_2__["default"] ) ) {
return;
}
if ( !this._canMergeLeft( node ) ) {
return;
}
const mergePosLeft = _liveposition__WEBPACK_IMPORTED_MODULE_1__["default"]._createBefore( node );
mergePosLeft.stickiness = 'toNext';
const livePosition = _liveposition__WEBPACK_IMPORTED_MODULE_1__["default"].fromPosition( this.position, 'toNext' );
// If `_affectedStart` is sames as merge position, it means that the element "marked" by `_affectedStart` is going to be
// removed and its contents will be moved. This won't transform `LivePosition` so `_affectedStart` needs to be moved
// by hand to properly reflect affected range. (Due to `_affectedStart` and `_affectedEnd` stickiness, the "range" is
// shown as `][`).
//
// Example - insert `<paragraph>Abc</paragraph><paragraph>Xyz</paragraph>` at the end of `<paragraph>Foo^</paragraph>`:
//
// <paragraph>Foo</paragraph><paragraph>Bar</paragraph> -->
// <paragraph>Foo</paragraph>]<paragraph>Abc</paragraph><paragraph>Xyz</paragraph>[<paragraph>Bar</paragraph> -->
// <paragraph>Foo]Abc</paragraph><paragraph>Xyz</paragraph>[<paragraph>Bar</paragraph>
//
// Note, that if we are here then something must have been inserted, so `_affectedStart` and `_affectedEnd` have to be set.
if ( this._affectedStart.isEqual( mergePosLeft ) ) {
this._affectedStart.detach();
this._affectedStart = _liveposition__WEBPACK_IMPORTED_MODULE_1__["default"]._createAt( mergePosLeft.nodeBefore, 'end', 'toPrevious' );
}
// We need to update the references to the first and last nodes if they will be merged into the previous sibling node
// because the reference would point to the removed node.
//
// <p>A^A</p> + <p>X</p>
//
// <p>A</p>^<p>A</p>
// <p>A</p><p>X</p><p>A</p>
// <p>AX</p><p>A</p>
// <p>AXA</p>
if ( this._firstNode === this._lastNode ) {
this._firstNode = mergePosLeft.nodeBefore;
this._lastNode = mergePosLeft.nodeBefore;
}
this.writer.merge( mergePosLeft );
// If only one element (the merged one) is in the "affected range", also move the affected range end appropriately.
//
// Example - insert `<paragraph>Abc</paragraph>` at the of `<paragraph>Foo^</paragraph>`:
//
// <paragraph>Foo</paragraph><paragraph>Bar</paragraph> -->
// <paragraph>Foo</paragraph>]<paragraph>Abc</paragraph>[<paragraph>Bar</paragraph> -->
// <paragraph>Foo]Abc</paragraph>[<paragraph>Bar</paragraph> -->
// <paragraph>Foo]Abc[</paragraph><paragraph>Bar</paragraph>
if ( mergePosLeft.isEqual( this._affectedEnd ) && this._firstNode === this._lastNode ) {
this._affectedEnd.detach();
this._affectedEnd = _liveposition__WEBPACK_IMPORTED_MODULE_1__["default"]._createAt( mergePosLeft.nodeBefore, 'end', 'toNext' );
}
this.position = livePosition.toPosition();
livePosition.detach();
// After merge elements that were marked by _insert() to be filtered might be gone so
// we need to mark the new container.
this._filterAttributesOf.push( this.position.parent );
mergePosLeft.detach();
}
/**
* Merges the next sibling of the last node if it should be merged.
*
* After the content was inserted we may try to merge it with its siblings.
* This should happen only if the selection was in those elements initially.
*
* @private
*/
_mergeOnRight() {
const node = this._lastNode;
if ( !( node instanceof _element__WEBPACK_IMPORTED_MODULE_2__["default"] ) ) {
return;
}
if ( !this._canMergeRight( node ) ) {
return;
}
const mergePosRight = _liveposition__WEBPACK_IMPORTED_MODULE_1__["default"]._createAfter( node );
mergePosRight.stickiness = 'toNext';
/* istanbul ignore if */
if ( !this.position.isEqual( mergePosRight ) ) {
// Algorithm's correctness check. We should never end up here but it's good to know that we did.
// At this point the insertion position should be after the node we'll merge. If it isn't,
// it should need to be secured as in the left merge case.
/**
* An internal error occurred when merging inserted content with its siblings.
* The insertion position should equal the merge position.
*
* If you encountered this error, report it back to the CKEditor 5 team
* with as many details as possible regarding the content being inserted and the insertion position.
*
* @error insertcontent-invalid-insertion-position
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_6__["default"]( 'insertcontent-invalid-insertion-position', this );
}
// Move the position to the previous node, so it isn't moved to the graveyard on merge.
// <p>x</p>[]<p>y</p> => <p>x[]</p><p>y</p>
this.position = _position__WEBPACK_IMPORTED_MODULE_0__["default"]._createAt( mergePosRight.nodeBefore, 'end' );
// Explanation of setting position stickiness to `'toPrevious'`:
// OK: <p>xx[]</p> + <p>yy</p> => <p>xx[]yy</p> (when sticks to previous)
// NOK: <p>xx[]</p> + <p>yy</p> => <p>xxyy[]</p> (when sticks to next)
const livePosition = _liveposition__WEBPACK_IMPORTED_MODULE_1__["default"].fromPosition( this.position, 'toPrevious' );
// See comment in `_mergeOnLeft()` on moving `_affectedStart`.
if ( this._affectedEnd.isEqual( mergePosRight ) ) {
this._affectedEnd.detach();
this._affectedEnd = _liveposition__WEBPACK_IMPORTED_MODULE_1__["default"]._createAt( mergePosRight.nodeBefore, 'end', 'toNext' );
}
// We need to update the references to the first and last nodes if they will be merged into the previous sibling node
// because the reference would point to the removed node.
//
// <p>A^A</p> + <p>X</p>
//
// <p>A</p>^<p>A</p>
// <p>A</p><p>X</p><p>A</p>
// <p>AX</p><p>A</p>
// <p>AXA</p>
if ( this._firstNode === this._lastNode ) {
this._firstNode = mergePosRight.nodeBefore;
this._lastNode = mergePosRight.nodeBefore;
}
this.writer.merge( mergePosRight );
// See comment in `_mergeOnLeft()` on moving `_affectedStart`.
if ( mergePosRight.getShiftedBy( -1 ).isEqual( this._affectedStart ) && this._firstNode === this._lastNode ) {
this._affectedStart.detach();
this._affectedStart = _liveposition__WEBPACK_IMPORTED_MODULE_1__["default"]._createAt( mergePosRight.nodeBefore, 0, 'toPrevious' );
}
this.position = livePosition.toPosition();
livePosition.detach();
// After merge elements that were marked by _insert() to be filtered might be gone so
// we need to mark the new container.
this._filterAttributesOf.push( this.position.parent );
mergePosRight.detach();
}
/**
* Checks whether specified node can be merged with previous sibling element.
*
* @private
* @param {module:engine/model/node~Node} node The node which could potentially be merged.
* @returns {Boolean}
*/
_canMergeLeft( node ) {
const previousSibling = node.previousSibling;
return ( previousSibling instanceof _element__WEBPACK_IMPORTED_MODULE_2__["default"] ) &&
this.canMergeWith.has( previousSibling ) &&
this.model.schema.checkMerge( previousSibling, node );
}
/**
* Checks whether specified node can be merged with next sibling element.
*
* @private
* @param {module:engine/model/node~Node} node The node which could potentially be merged.
* @returns {Boolean}
*/
_canMergeRight( node ) {
const nextSibling = node.nextSibling;
return ( nextSibling instanceof _element__WEBPACK_IMPORTED_MODULE_2__["default"] ) &&
this.canMergeWith.has( nextSibling ) &&
this.model.schema.checkMerge( node, nextSibling );
}
/**
* Tries wrapping the node in a new paragraph and inserting it this way.
*
* @private
* @param {module:engine/model/node~Node} node The node which needs to be autoparagraphed.
*/
_tryAutoparagraphing( node ) {
const paragraph = this.writer.createElement( 'paragraph' );
// Do not autoparagraph if the paragraph won't be allowed there,
// cause that would lead to an infinite loop. The paragraph would be rejected in
// the next _handleNode() call and we'd be here again.
if ( this._getAllowedIn( this.position.parent, paragraph ) && this.schema.checkChild( paragraph, node ) ) {
paragraph._appendChild( node );
this._handleNode( paragraph );
}
}
/**
* Checks if a node can be inserted in the given position or it would be accepted if a paragraph would be inserted.
* It also handles inserting the paragraph.
*
* @private
* @param {module:engine/model/node~Node} node The node.
* @returns {Boolean} Whether an allowed position was found.
* `false` is returned if the node isn't allowed at the current position or in auto paragraph, `true` if was.
*/
_checkAndAutoParagraphToAllowedPosition( node ) {
if ( this.schema.checkChild( this.position.parent, node ) ) {
return true;
}
// Do not auto paragraph if the paragraph won't be allowed there,
// cause that would lead to an infinite loop. The paragraph would be rejected in
// the next _handleNode() call and we'd be here again.
if ( !this.schema.checkChild( this.position.parent, 'paragraph' ) || !this.schema.checkChild( 'paragraph', node ) ) {
return false;
}
// Insert nodes collected in temporary DocumentFragment if the position parent needs change to process further nodes.
this._insertPartialFragment();
// Insert a paragraph and move insertion position to it.
const paragraph = this.writer.createElement( 'paragraph' );
this.writer.insert( paragraph, this.position );
this._setAffectedBoundaries( this.position );
this._lastAutoParagraph = paragraph;
this.position = this.writer.createPositionAt( paragraph, 0 );
return true;
}
/**
* @private
* @param {module:engine/model/node~Node} node
* @returns {Boolean} Whether an allowed position was found.
* `false` is returned if the node isn't allowed at any position up in the tree, `true` if was.
*/
_checkAndSplitToAllowedPosition( node ) {
const allowedIn = this._getAllowedIn( this.position.parent, node );
if ( !allowedIn ) {
return false;
}
// Insert nodes collected in temporary DocumentFragment if the position parent needs change to process further nodes.
if ( allowedIn != this.position.parent ) {
this._insertPartialFragment();
}
while ( allowedIn != this.position.parent ) {
if ( this.position.isAtStart ) {
// If insertion position is at the beginning of the parent, move it out instead of splitting.
// <p>^Foo</p> -> ^<p>Foo</p>
const parent = this.position.parent;
this.position = this.writer.createPositionBefore( parent );
// Special case – parent is empty (<p>^</p>).
//
// 1. parent.isEmpty
// We can remove the element after moving insertion position out of it.
//
// 2. parent.parent === allowedIn
// However parent should remain in place when allowed element is above limit element in document tree.
// For example there shouldn't be allowed to remove empty paragraph from tableCell, when is pasted
// content allowed in $root.
if ( parent.isEmpty && parent.parent === allowedIn ) {
this.writer.remove( parent );
}
} else if ( this.position.isAtEnd ) {
// If insertion position is at the end of the parent, move it out instead of splitting.
// <p>Foo^</p> -> <p>Foo</p>^
this.position = this.writer.createPositionAfter( this.position.parent );
} else {
const tempPos = this.writer.createPositionAfter( this.position.parent );
this._setAffectedBoundaries( this.position );
this.writer.split( this.position );
this.position = tempPos;
this.canMergeWith.add( this.position.nodeAfter );
}
}
return true;
}
/**
* Gets the element in which the given node is allowed. It checks the passed element and all its ancestors.
*
* @private
* @param {module:engine/model/element~Element} contextElement The element in which context the node should be checked.
* @param {module:engine/model/node~Node} childNode The node to check.
* @returns {module:engine/model/element~Element|null}
*/
_getAllowedIn( contextElement, childNode ) {
if ( this.schema.checkChild( contextElement, childNode ) ) {
return contextElement;
}
// If the child wasn't allowed in the context element and the element is a limit there's no point in
// checking any further towards the root. This is it: the limit is unsplittable and there's nothing
// we can do about it. Without this check, the algorithm will analyze parent of the limit and may create
// an illusion of the child being allowed. There's no way to insert it down there, though. It results in
// infinite loops.
if ( this.schema.isLimit( contextElement ) ) {
return null;
}
return this._getAllowedIn( contextElement.parent, childNode );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/model/utils/modifyselection.js":
/*!************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/model/utils/modifyselection.js ***!
\************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ modifySelection)
/* harmony export */ });
/* harmony import */ var _position__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../position */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/position.js");
/* harmony import */ var _treewalker__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../treewalker */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/treewalker.js");
/* harmony import */ var _range__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../range */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/range.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_unicode__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/unicode */ "./node_modules/@ckeditor/ckeditor5-utils/src/unicode.js");
/* harmony import */ var _documentselection__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../documentselection */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/documentselection.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/model/utils/modifyselection
*/
const wordBoundaryCharacters = ' ,.?!:;"-()';
/**
* Modifies the selection. Currently, the supported modifications are:
*
* * Extending. The selection focus is moved in the specified `options.direction` with a step specified in `options.unit`.
* Possible values for `unit` are:
* * `'character'` (default) - moves selection by one user-perceived character. In most cases this means moving by one
* character in `String` sense. However, unicode also defines "combing marks". These are special symbols, that combines
* with a symbol before it ("base character") to create one user-perceived character. For example, `q̣̇` is a normal
* letter `q` with two "combining marks": upper dot (`Ux0307`) and lower dot (`Ux0323`). For most actions, i.e. extending
* selection by one position, it is correct to include both "base character" and all of it's "combining marks". That is
* why `'character'` value is most natural and common method of modifying selection.
* * `'codePoint'` - moves selection by one unicode code point. In contrary to, `'character'` unit, this will insert
* selection between "base character" and "combining mark", because "combining marks" have their own unicode code points.
* However, for technical reasons, unicode code points with values above `UxFFFF` are represented in native `String` by
* two characters, called "surrogate pairs". Halves of "surrogate pairs" have a meaning only when placed next to each other.
* For example `𨭎` is represented in `String` by `\uD862\uDF4E`. Both `\uD862` and `\uDF4E` do not have any meaning
* outside the pair (are rendered as ? when alone). Position between them would be incorrect. In this case, selection
* extension will include whole "surrogate pair".
* * `'word'` - moves selection by a whole word.
*
* **Note:** if you extend a forward selection in a backward direction you will in fact shrink it.
*
* **Note:** Use {@link module:engine/model/model~Model#modifySelection} instead of this function.
* This function is only exposed to be reusable in algorithms
* which change the {@link module:engine/model/model~Model#modifySelection}
* method's behavior.
*
* @param {module:engine/model/model~Model} model The model in context of which
* the selection modification should be performed.
* @param {module:engine/model/selection~Selection|module:engine/model/documentselection~DocumentSelection} selection
* The selection to modify.
* @param {Object} [options]
* @param {'forward'|'backward'} [options.direction='forward'] The direction in which the selection should be modified.
* @param {'character'|'codePoint'|'word'} [options.unit='character'] The unit by which selection should be modified.
* @param {Boolean} [options.treatEmojiAsSingleUnit=false] Whether multi-characer emoji sequences should be handled as single unit.
*/
function modifySelection( model, selection, options = {} ) {
const schema = model.schema;
const isForward = options.direction != 'backward';
const unit = options.unit ? options.unit : 'character';
const treatEmojiAsSingleUnit = !!options.treatEmojiAsSingleUnit;
const focus = selection.focus;
const walker = new _treewalker__WEBPACK_IMPORTED_MODULE_1__["default"]( {
boundaries: getSearchRange( focus, isForward ),
singleCharacters: true,
direction: isForward ? 'forward' : 'backward'
} );
const data = { walker, schema, isForward, unit, treatEmojiAsSingleUnit };
let next;
while ( ( next = walker.next() ) ) {
if ( next.done ) {
return;
}
const position = tryExtendingTo( data, next.value );
if ( position ) {
if ( selection instanceof _documentselection__WEBPACK_IMPORTED_MODULE_4__["default"] ) {
model.change( writer => {
writer.setSelectionFocus( position );
} );
} else {
selection.setFocus( position );
}
return;
}
}
}
// Checks whether the selection can be extended to the the walker's next value (next position).
// @param {{ walker, unit, isForward, schema, treatEmojiAsSingleUnit }} data
// @param {module:engine/view/treewalker~TreeWalkerValue} value
function tryExtendingTo( data, value ) {
const { isForward, walker, unit, schema, treatEmojiAsSingleUnit } = data;
const { type, item, nextPosition } = value;
// If found text, we can certainly put the focus in it. Let's just find a correct position
// based on the unit.
if ( type == 'text' ) {
if ( data.unit === 'word' ) {
return getCorrectWordBreakPosition( walker, isForward );
}
return getCorrectPosition( walker, unit, treatEmojiAsSingleUnit );
}
// Entering an element.
if ( type == ( isForward ? 'elementStart' : 'elementEnd' ) ) {
// If it's a selectable, we can select it now.
if ( schema.isSelectable( item ) ) {
return _position__WEBPACK_IMPORTED_MODULE_0__["default"]._createAt( item, isForward ? 'after' : 'before' );
}
// If text allowed on this position, extend to this place.
if ( schema.checkChild( nextPosition, '$text' ) ) {
return nextPosition;
}
}
// Leaving an element.
else {
// If leaving a limit element, stop.
if ( schema.isLimit( item ) ) {
// NOTE: Fast-forward the walker until the end.
walker.skip( () => true );
return;
}
// If text allowed on this position, extend to this place.
if ( schema.checkChild( nextPosition, '$text' ) ) {
return nextPosition;
}
}
}
// Finds a correct position by walking in a text node and checking whether selection can be extended to given position
// or should be extended further.
//
// @param {module:engine/model/treewalker~TreeWalker} walker
// @param {String} unit The unit by which selection should be modified.
// @param {Boolean} treatEmojiAsSingleUnit
function getCorrectPosition( walker, unit, treatEmojiAsSingleUnit ) {
const textNode = walker.position.textNode;
if ( textNode ) {
const data = textNode.data;
let offset = walker.position.offset - textNode.startOffset;
while (
(0,_ckeditor_ckeditor5_utils_src_unicode__WEBPACK_IMPORTED_MODULE_3__.isInsideSurrogatePair)( data, offset ) ||
( unit == 'character' && (0,_ckeditor_ckeditor5_utils_src_unicode__WEBPACK_IMPORTED_MODULE_3__.isInsideCombinedSymbol)( data, offset ) ) ||
( treatEmojiAsSingleUnit && (0,_ckeditor_ckeditor5_utils_src_unicode__WEBPACK_IMPORTED_MODULE_3__.isInsideEmojiSequence)( data, offset ) )
) {
walker.next();
offset = walker.position.offset - textNode.startOffset;
}
}
return walker.position;
}
// Finds a correct position of a word break by walking in a text node and checking whether selection can be extended to given position
// or should be extended further.
//
// @param {module:engine/model/treewalker~TreeWalker} walker
// @param {Boolean} isForward Is the direction in which the selection should be modified is forward.
function getCorrectWordBreakPosition( walker, isForward ) {
let textNode = walker.position.textNode;
if ( textNode ) {
let offset = walker.position.offset - textNode.startOffset;
while ( !isAtWordBoundary( textNode.data, offset, isForward ) && !isAtNodeBoundary( textNode, offset, isForward ) ) {
walker.next();
// Check of adjacent text nodes with different attributes (like BOLD).
// Example : 'foofoo []bar<$text bold="true">bar</$text> bazbaz'
// should expand to : 'foofoo [bar<$text bold="true">bar</$text>] bazbaz'.
const nextNode = isForward ? walker.position.nodeAfter : walker.position.nodeBefore;
// Scan only text nodes. Ignore inline elements (like `<softBreak>`).
if ( nextNode && nextNode.is( '$text' ) ) {
// Check boundary char of an adjacent text node.
const boundaryChar = nextNode.data.charAt( isForward ? 0 : nextNode.data.length - 1 );
// Go to the next node if the character at the boundary of that node belongs to the same word.
if ( !wordBoundaryCharacters.includes( boundaryChar ) ) {
// If adjacent text node belongs to the same word go to it & reset values.
walker.next();
textNode = walker.position.textNode;
}
}
offset = walker.position.offset - textNode.startOffset;
}
}
return walker.position;
}
function getSearchRange( start, isForward ) {
const root = start.root;
const searchEnd = _position__WEBPACK_IMPORTED_MODULE_0__["default"]._createAt( root, isForward ? 'end' : 0 );
if ( isForward ) {
return new _range__WEBPACK_IMPORTED_MODULE_2__["default"]( start, searchEnd );
} else {
return new _range__WEBPACK_IMPORTED_MODULE_2__["default"]( searchEnd, start );
}
}
// Checks if selection is on word boundary.
//
// @param {String} data The text node value to investigate.
// @param {Number} offset Position offset.
// @param {Boolean} isForward Is the direction in which the selection should be modified is forward.
function isAtWordBoundary( data, offset, isForward ) {
// The offset to check depends on direction.
const offsetToCheck = offset + ( isForward ? 0 : -1 );
return wordBoundaryCharacters.includes( data.charAt( offsetToCheck ) );
}
// Checks if selection is on node boundary.
//
// @param {module:engine/model/text~Text} textNode The text node to investigate.
// @param {Number} offset Position offset.
// @param {Boolean} isForward Is the direction in which the selection should be modified is forward.
function isAtNodeBoundary( textNode, offset, isForward ) {
return offset === ( isForward ? textNode.endOffset : 0 );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/model/utils/selection-post-fixer.js":
/*!*****************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/model/utils/selection-post-fixer.js ***!
\*****************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "injectSelectionPostFixer": () => (/* binding */ injectSelectionPostFixer),
/* harmony export */ "mergeIntersectingRanges": () => (/* binding */ mergeIntersectingRanges)
/* harmony export */ });
/* harmony import */ var _range__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../range */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/range.js");
/* harmony import */ var _position__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../position */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/position.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/model/utils/selection-post-fixer
*/
/**
* Injects selection post-fixer to the model.
*
* The role of the selection post-fixer is to ensure that the selection is in a correct place
* after a {@link module:engine/model/model~Model#change `change()`} block was executed.
*
* The correct position means that:
*
* * All collapsed selection ranges are in a place where the {@link module:engine/model/schema~Schema}
* allows a `$text`.
* * None of the selection's non-collapsed ranges crosses a {@link module:engine/model/schema~Schema#isLimit limit element}
* boundary (a range must be rooted within one limit element).
* * Only {@link module:engine/model/schema~Schema#isSelectable selectable elements} can be selected from the outside
* (e.g. `[<paragraph>foo</paragraph>]` is invalid). This rule applies independently to both selection ends, so this
* selection is correct: `<paragraph>f[oo</paragraph><imageBlock></imageBlock>]`.
*
* If the position is not correct, the post-fixer will automatically correct it.
*
* ## Fixing a non-collapsed selection
*
* See as an example a selection that starts in a P1 element and ends inside the text of a TD element
* (`[` and `]` are range boundaries and `(l)` denotes an element defined as `isLimit=true`):
*
* root
* |- element P1
* | |- "foo" root
* |- element TABLE (l) P1 TABLE P2
* | |- element TR (l) f o[o TR TR b a r
* | | |- element TD (l) TD TD
* | | |- "aaa" a]a a b b b
* | |- element TR (l)
* | | |- element TD (l) ||
* | | |- "bbb" ||
* |- element P2 VV
* | |- "bar"
* root
* P1 TABLE] P2
* f o[o TR TR b a r
* TD TD
* a a a b b b
*
* In the example above, the TABLE, TR and TD are defined as `isLimit=true` in the schema. The range which is not contained within
* a single limit element must be expanded to select the outermost limit element. The range end is inside the text node of the TD element.
* As the TD element is a child of the TR and TABLE elements, where both are defined as `isLimit=true` in the schema, the range must be
* expanded to select the whole TABLE element.
*
* **Note** If the selection contains multiple ranges, the method returns a minimal set of ranges that are not intersecting after expanding
* them to select `isLimit=true` elements.
*
* @param {module:engine/model/model~Model} model
*/
function injectSelectionPostFixer( model ) {
model.document.registerPostFixer( writer => selectionPostFixer( writer, model ) );
}
// The selection post-fixer.
//
// @param {module:engine/model/writer~Writer} writer
// @param {module:engine/model/model~Model} model
function selectionPostFixer( writer, model ) {
const selection = model.document.selection;
const schema = model.schema;
const ranges = [];
let wasFixed = false;
for ( const modelRange of selection.getRanges() ) {
// Go through all ranges in selection and try fixing each of them.
// Those ranges might overlap but will be corrected later.
const correctedRange = tryFixingRange( modelRange, schema );
// "Selection fixing" algorithms sometimes get lost. In consequence, it may happen
// that a new range is returned but, in fact, it has the same positions as the original
// range anyway. If this range is not discarded, a new selection will be set and that,
// for instance, would destroy the selection attributes. Let's make sure that the post-fixer
// actually worked first before setting a new selection.
//
// https://github.com/ckeditor/ckeditor5/issues/6693
if ( correctedRange && !correctedRange.isEqual( modelRange ) ) {
ranges.push( correctedRange );
wasFixed = true;
} else {
ranges.push( modelRange );
}
}
// If any of ranges were corrected update the selection.
if ( wasFixed ) {
writer.setSelection( mergeIntersectingRanges( ranges ), { backward: selection.isBackward } );
}
}
// Tries fixing a range if it's incorrect.
//
// @param {module:engine/model/range~Range} range
// @param {module:engine/model/schema~Schema} schema
// @returns {module:engine/model/range~Range|null} Returns fixed range or null if range is valid.
function tryFixingRange( range, schema ) {
if ( range.isCollapsed ) {
return tryFixingCollapsedRange( range, schema );
}
return tryFixingNonCollapsedRage( range, schema );
}
// Tries to fix collapsed ranges.
//
// * Fixes situation when a range is in a place where $text is not allowed
//
// @param {module:engine/model/range~Range} range Collapsed range to fix.
// @param {module:engine/model/schema~Schema} schema
// @returns {module:engine/model/range~Range|null} Returns fixed range or null if range is valid.
function tryFixingCollapsedRange( range, schema ) {
const originalPosition = range.start;
const nearestSelectionRange = schema.getNearestSelectionRange( originalPosition );
// This might be null, i.e. when the editor data is empty or the selection is inside a limit element
// that doesn't allow text inside.
// In the first case, there is no need to fix the selection range.
// In the second, let's go up to the outer selectable element
if ( !nearestSelectionRange ) {
const ancestorObject = originalPosition.getAncestors().reverse().find( item => schema.isObject( item ) );
if ( ancestorObject ) {
return _range__WEBPACK_IMPORTED_MODULE_0__["default"]._createOn( ancestorObject );
}
return null;
}
if ( !nearestSelectionRange.isCollapsed ) {
return nearestSelectionRange;
}
const fixedPosition = nearestSelectionRange.start;
// Fixed position is the same as original - no need to return corrected range.
if ( originalPosition.isEqual( fixedPosition ) ) {
return null;
}
return new _range__WEBPACK_IMPORTED_MODULE_0__["default"]( fixedPosition );
}
// Tries to fix an expanded range.
//
// @param {module:engine/model/range~Range} range Expanded range to fix.
// @param {module:engine/model/schema~Schema} schema
// @returns {module:engine/model/range~Range|null} Returns fixed range or null if range is valid.
function tryFixingNonCollapsedRage( range, schema ) {
const { start, end } = range;
const isTextAllowedOnStart = schema.checkChild( start, '$text' );
const isTextAllowedOnEnd = schema.checkChild( end, '$text' );
const startLimitElement = schema.getLimitElement( start );
const endLimitElement = schema.getLimitElement( end );
// Ranges which both end are inside the same limit element (or root) might needs only minor fix.
if ( startLimitElement === endLimitElement ) {
// Range is valid when both position allows to place a text:
// - <block>f[oobarba]z</block>
// This would be "fixed" by a next check but as it will be the same it's better to return null so the selection stays the same.
if ( isTextAllowedOnStart && isTextAllowedOnEnd ) {
return null;
}
// Range that is on non-limit element (or is partially) must be fixed so it is placed inside the block around $text:
// - [<block>foo</block>] -> <block>[foo]</block>
// - [<block>foo]</block> -> <block>[foo]</block>
// - <block>f[oo</block>] -> <block>f[oo]</block>
// - [<block>foo</block><selectable></selectable>] -> <block>[foo</block><selectable></selectable>]
if ( checkSelectionOnNonLimitElements( start, end, schema ) ) {
const isStartBeforeSelectable = start.nodeAfter && schema.isSelectable( start.nodeAfter );
const fixedStart = isStartBeforeSelectable ? null : schema.getNearestSelectionRange( start, 'forward' );
const isEndAfterSelectable = end.nodeBefore && schema.isSelectable( end.nodeBefore );
const fixedEnd = isEndAfterSelectable ? null : schema.getNearestSelectionRange( end, 'backward' );
// The schema.getNearestSelectionRange might return null - if that happens use original position.
const rangeStart = fixedStart ? fixedStart.start : start;
const rangeEnd = fixedEnd ? fixedEnd.end : end;
return new _range__WEBPACK_IMPORTED_MODULE_0__["default"]( rangeStart, rangeEnd );
}
}
const isStartInLimit = startLimitElement && !startLimitElement.is( 'rootElement' );
const isEndInLimit = endLimitElement && !endLimitElement.is( 'rootElement' );
// At this point we eliminated valid positions on text nodes so if one of range positions is placed inside a limit element
// then the range crossed limit element boundaries and needs to be fixed.
if ( isStartInLimit || isEndInLimit ) {
const bothInSameParent = ( start.nodeAfter && end.nodeBefore ) && start.nodeAfter.parent === end.nodeBefore.parent;
const expandStart = isStartInLimit && ( !bothInSameParent || !isSelectable( start.nodeAfter, schema ) );
const expandEnd = isEndInLimit && ( !bothInSameParent || !isSelectable( end.nodeBefore, schema ) );
// Although we've already found limit element on start/end positions we must find the outer-most limit element.
// as limit elements might be nested directly inside (ie table > tableRow > tableCell).
let fixedStart = start;
let fixedEnd = end;
if ( expandStart ) {
fixedStart = _position__WEBPACK_IMPORTED_MODULE_1__["default"]._createBefore( findOutermostLimitAncestor( startLimitElement, schema ) );
}
if ( expandEnd ) {
fixedEnd = _position__WEBPACK_IMPORTED_MODULE_1__["default"]._createAfter( findOutermostLimitAncestor( endLimitElement, schema ) );
}
return new _range__WEBPACK_IMPORTED_MODULE_0__["default"]( fixedStart, fixedEnd );
}
// Range was not fixed at this point so it is valid - ie it was placed around limit element already.
return null;
}
// Finds the outer-most ancestor.
//
// @param {module:engine/model/node~Node} startingNode
// @param {module:engine/model/schema~Schema} schema
// @param {String} expandToDirection Direction of expansion - either 'start' or 'end' of the range.
// @returns {module:engine/model/node~Node}
function findOutermostLimitAncestor( startingNode, schema ) {
let isLimitNode = startingNode;
let parent = isLimitNode;
// Find outer most isLimit block as such blocks might be nested (ie. in tables).
while ( schema.isLimit( parent ) && parent.parent ) {
isLimitNode = parent;
parent = parent.parent;
}
return isLimitNode;
}
// Checks whether any of range boundaries is placed around non-limit elements.
//
// @param {module:engine/model/position~Position} start
// @param {module:engine/model/position~Position} end
// @param {module:engine/model/schema~Schema} schema
// @returns {Boolean}
function checkSelectionOnNonLimitElements( start, end, schema ) {
const startIsOnBlock = ( start.nodeAfter && !schema.isLimit( start.nodeAfter ) ) || schema.checkChild( start, '$text' );
const endIsOnBlock = ( end.nodeBefore && !schema.isLimit( end.nodeBefore ) ) || schema.checkChild( end, '$text' );
// We should fix such selection when one of those nodes needs fixing.
return startIsOnBlock || endIsOnBlock;
}
/**
* Returns a minimal non-intersecting array of ranges without duplicates.
*
* @param {Array.<module:engine/model/range~Range>} Ranges to merge.
* @returns {Array.<module:engine/model/range~Range>} Array of unique and nonIntersecting ranges.
*/
function mergeIntersectingRanges( ranges ) {
const rangesToMerge = [ ...ranges ];
const rangeIndexesToRemove = new Set();
let currentRangeIndex = 1;
while ( currentRangeIndex < rangesToMerge.length ) {
const currentRange = rangesToMerge[ currentRangeIndex ];
const previousRanges = rangesToMerge.slice( 0, currentRangeIndex );
for ( const [ previousRangeIndex, previousRange ] of previousRanges.entries() ) {
if ( rangeIndexesToRemove.has( previousRangeIndex ) ) {
continue;
}
if ( currentRange.isEqual( previousRange ) ) {
rangeIndexesToRemove.add( previousRangeIndex );
} else if ( currentRange.isIntersecting( previousRange ) ) {
rangeIndexesToRemove.add( previousRangeIndex );
rangeIndexesToRemove.add( currentRangeIndex );
const mergedRange = currentRange.getJoined( previousRange );
rangesToMerge.push( mergedRange );
}
}
currentRangeIndex++;
}
const nonIntersectingRanges = rangesToMerge.filter( ( _, index ) => !rangeIndexesToRemove.has( index ) );
return nonIntersectingRanges;
}
// Checks if node exists and if it's a selectable.
//
// @param {module:engine/model/node~Node} node
// @param {module:engine/model/schema~Schema} schema
// @returns {Boolean}
function isSelectable( node, schema ) {
return node && schema.isSelectable( node );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/model/writer.js":
/*!*********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/model/writer.js ***!
\*********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ Writer)
/* harmony export */ });
/* harmony import */ var _operation_attributeoperation__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./operation/attributeoperation */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/attributeoperation.js");
/* harmony import */ var _operation_detachoperation__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./operation/detachoperation */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/detachoperation.js");
/* harmony import */ var _operation_insertoperation__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./operation/insertoperation */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/insertoperation.js");
/* harmony import */ var _operation_markeroperation__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./operation/markeroperation */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/markeroperation.js");
/* harmony import */ var _operation_moveoperation__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./operation/moveoperation */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/moveoperation.js");
/* harmony import */ var _operation_renameoperation__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./operation/renameoperation */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/renameoperation.js");
/* harmony import */ var _operation_rootattributeoperation__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./operation/rootattributeoperation */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/rootattributeoperation.js");
/* harmony import */ var _operation_splitoperation__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./operation/splitoperation */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/splitoperation.js");
/* harmony import */ var _operation_mergeoperation__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./operation/mergeoperation */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/mergeoperation.js");
/* harmony import */ var _documentfragment__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./documentfragment */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/documentfragment.js");
/* harmony import */ var _text__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./text */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/text.js");
/* harmony import */ var _element__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ./element */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/element.js");
/* harmony import */ var _rootelement__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! ./rootelement */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/rootelement.js");
/* harmony import */ var _position__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(/*! ./position */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/position.js");
/* harmony import */ var _range_js__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(/*! ./range.js */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/range.js");
/* harmony import */ var _documentselection__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(/*! ./documentselection */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/documentselection.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_tomap__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/tomap */ "./node_modules/@ckeditor/ckeditor5-utils/src/tomap.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/model/writer
*/
/**
* The model can only be modified by using the writer. It should be used whenever you want to create a node, modify
* child nodes, attributes or text, set the selection's position and its attributes.
*
* The instance of the writer is only available in the {@link module:engine/model/model~Model#change `change()`} or
* {@link module:engine/model/model~Model#enqueueChange `enqueueChange()`}.
*
* model.change( writer => {
* writer.insertText( 'foo', paragraph, 'end' );
* } );
*
* Note that the writer should never be stored and used outside of the `change()` and
* `enqueueChange()` blocks.
*
* Note that writer's methods do not check the {@link module:engine/model/schema~Schema}. It is possible
* to create incorrect model structures by using the writer. Read more about in
* {@glink framework/guides/deep-dive/schema#who-checks-the-schema "Who checks the schema?"}.
*
* @see module:engine/model/model~Model#change
* @see module:engine/model/model~Model#enqueueChange
*/
class Writer {
/**
* Creates a writer instance.
*
* **Note:** It is not recommended to use it directly. Use {@link module:engine/model/model~Model#change `Model#change()`} or
* {@link module:engine/model/model~Model#enqueueChange `Model#enqueueChange()`} instead.
*
* @protected
* @param {module:engine/model/model~Model} model
* @param {module:engine/model/batch~Batch} batch
*/
constructor( model, batch ) {
/**
* Instance of the model on which this writer operates.
*
* @readonly
* @type {module:engine/model/model~Model}
*/
this.model = model;
/**
* The batch to which this writer will add changes.
*
* @readonly
* @type {module:engine/model/batch~Batch}
*/
this.batch = batch;
}
/**
* Creates a new {@link module:engine/model/text~Text text node}.
*
* writer.createText( 'foo' );
* writer.createText( 'foo', { bold: true } );
*
* @param {String} data Text data.
* @param {Object} [attributes] Text attributes.
* @returns {module:engine/model/text~Text} Created text node.
*/
createText( data, attributes ) {
return new _text__WEBPACK_IMPORTED_MODULE_10__["default"]( data, attributes );
}
/**
* Creates a new {@link module:engine/model/element~Element element}.
*
* writer.createElement( 'paragraph' );
* writer.createElement( 'paragraph', { alignment: 'center' } );
*
* @param {String} name Name of the element.
* @param {Object} [attributes] Elements attributes.
* @returns {module:engine/model/element~Element} Created element.
*/
createElement( name, attributes ) {
return new _element__WEBPACK_IMPORTED_MODULE_11__["default"]( name, attributes );
}
/**
* Creates a new {@link module:engine/model/documentfragment~DocumentFragment document fragment}.
*
* @returns {module:engine/model/documentfragment~DocumentFragment} Created document fragment.
*/
createDocumentFragment() {
return new _documentfragment__WEBPACK_IMPORTED_MODULE_9__["default"]();
}
/**
* Creates a copy of the element and returns it. Created element has the same name and attributes as the original element.
* If clone is deep, the original element's children are also cloned. If not, then empty element is returned.
*
* @param {module:engine/model/element~Element} element The element to clone.
* @param {Boolean} [deep=true] If set to `true` clones element and all its children recursively. When set to `false`,
* element will be cloned without any child.
*/
cloneElement( element, deep = true ) {
return element._clone( deep );
}
/**
* Inserts item on given position.
*
* const paragraph = writer.createElement( 'paragraph' );
* writer.insert( paragraph, position );
*
* Instead of using position you can use parent and offset:
*
* const text = writer.createText( 'foo' );
* writer.insert( text, paragraph, 5 );
*
* You can also use `end` instead of the offset to insert at the end:
*
* const text = writer.createText( 'foo' );
* writer.insert( text, paragraph, 'end' );
*
* Or insert before or after another element:
*
* const paragraph = writer.createElement( 'paragraph' );
* writer.insert( paragraph, anotherParagraph, 'after' );
*
* These parameters works the same way as {@link #createPositionAt `writer.createPositionAt()`}.
*
* Note that if the item already has parent it will be removed from the previous parent.
*
* Note that you cannot re-insert a node from a document to a different document or a document fragment. In this case,
* `model-writer-insert-forbidden-move` is thrown.
*
* If you want to move {@link module:engine/model/range~Range range} instead of an
* {@link module:engine/model/item~Item item} use {@link module:engine/model/writer~Writer#move `Writer#move()`}.
*
* **Note:** For a paste-like content insertion mechanism see
* {@link module:engine/model/model~Model#insertContent `model.insertContent()`}.
*
* @param {module:engine/model/item~Item|module:engine/model/documentfragment~DocumentFragment} item Item or document
* fragment to insert.
* @param {module:engine/model/item~Item|module:engine/model/position~Position} itemOrPosition
* @param {Number|'end'|'before'|'after'} [offset] Offset or one of the flags. Used only when
* second parameter is a {@link module:engine/model/item~Item model item}.
*/
insert( item, itemOrPosition, offset = 0 ) {
this._assertWriterUsedCorrectly();
if ( item instanceof _text__WEBPACK_IMPORTED_MODULE_10__["default"] && item.data == '' ) {
return;
}
const position = _position__WEBPACK_IMPORTED_MODULE_13__["default"]._createAt( itemOrPosition, offset );
// If item has a parent already.
if ( item.parent ) {
// We need to check if item is going to be inserted within the same document.
if ( isSameTree( item.root, position.root ) ) {
// If it's we just need to move it.
this.move( _range_js__WEBPACK_IMPORTED_MODULE_14__["default"]._createOn( item ), position );
return;
}
// If it isn't the same root.
else {
if ( item.root.document ) {
/**
* Cannot move a node from a document to a different tree.
* It is forbidden to move a node that was already in a document outside of it.
*
* @error model-writer-insert-forbidden-move
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_17__["default"](
'model-writer-insert-forbidden-move',
this
);
} else {
// Move between two different document fragments or from document fragment to a document is possible.
// In that case, remove the item from it's original parent.
this.remove( item );
}
}
}
const version = position.root.document ? position.root.document.version : null;
const insert = new _operation_insertoperation__WEBPACK_IMPORTED_MODULE_2__["default"]( position, item, version );
if ( item instanceof _text__WEBPACK_IMPORTED_MODULE_10__["default"] ) {
insert.shouldReceiveAttributes = true;
}
this.batch.addOperation( insert );
this.model.applyOperation( insert );
// When element is a DocumentFragment we need to move its markers to Document#markers.
if ( item instanceof _documentfragment__WEBPACK_IMPORTED_MODULE_9__["default"] ) {
for ( const [ markerName, markerRange ] of item.markers ) {
// We need to migrate marker range from DocumentFragment to Document.
const rangeRootPosition = _position__WEBPACK_IMPORTED_MODULE_13__["default"]._createAt( markerRange.root, 0 );
const range = new _range_js__WEBPACK_IMPORTED_MODULE_14__["default"](
markerRange.start._getCombined( rangeRootPosition, position ),
markerRange.end._getCombined( rangeRootPosition, position )
);
const options = { range, usingOperation: true, affectsData: true };
if ( this.model.markers.has( markerName ) ) {
this.updateMarker( markerName, options );
} else {
this.addMarker( markerName, options );
}
}
}
}
/**
* Creates and inserts text on given position. You can optionally set text attributes:
*
* writer.insertText( 'foo', position );
* writer.insertText( 'foo', { bold: true }, position );
*
* Instead of using position you can use parent and offset or define that text should be inserted at the end
* or before or after other node:
*
* // Inserts 'foo' in paragraph, at offset 5:
* writer.insertText( 'foo', paragraph, 5 );
* // Inserts 'foo' at the end of a paragraph:
* writer.insertText( 'foo', paragraph, 'end' );
* // Inserts 'foo' after an image:
* writer.insertText( 'foo', image, 'after' );
*
* These parameters work in the same way as {@link #createPositionAt `writer.createPositionAt()`}.
*
* @param {String} data Text data.
* @param {Object} [attributes] Text attributes.
* @param {module:engine/model/item~Item|module:engine/model/position~Position} itemOrPosition
* @param {Number|'end'|'before'|'after'} [offset] Offset or one of the flags. Used only when
* third parameter is a {@link module:engine/model/item~Item model item}.
*/
insertText( text, attributes, itemOrPosition, offset ) {
if ( attributes instanceof _documentfragment__WEBPACK_IMPORTED_MODULE_9__["default"] || attributes instanceof _element__WEBPACK_IMPORTED_MODULE_11__["default"] || attributes instanceof _position__WEBPACK_IMPORTED_MODULE_13__["default"] ) {
this.insert( this.createText( text ), attributes, itemOrPosition );
} else {
this.insert( this.createText( text, attributes ), itemOrPosition, offset );
}
}
/**
* Creates and inserts element on given position. You can optionally set attributes:
*
* writer.insertElement( 'paragraph', position );
* writer.insertElement( 'paragraph', { alignment: 'center' }, position );
*
* Instead of using position you can use parent and offset or define that text should be inserted at the end
* or before or after other node:
*
* // Inserts paragraph in the root at offset 5:
* writer.insertElement( 'paragraph', root, 5 );
* // Inserts paragraph at the end of a blockquote:
* writer.insertElement( 'paragraph', blockquote, 'end' );
* // Inserts after an image:
* writer.insertElement( 'paragraph', image, 'after' );
*
* These parameters works the same way as {@link #createPositionAt `writer.createPositionAt()`}.
*
* @param {String} name Name of the element.
* @param {Object} [attributes] Elements attributes.
* @param {module:engine/model/item~Item|module:engine/model/position~Position} itemOrPosition
* @param {Number|'end'|'before'|'after'} [offset] Offset or one of the flags. Used only when
* third parameter is a {@link module:engine/model/item~Item model item}.
*/
insertElement( name, attributes, itemOrPosition, offset ) {
if ( attributes instanceof _documentfragment__WEBPACK_IMPORTED_MODULE_9__["default"] || attributes instanceof _element__WEBPACK_IMPORTED_MODULE_11__["default"] || attributes instanceof _position__WEBPACK_IMPORTED_MODULE_13__["default"] ) {
this.insert( this.createElement( name ), attributes, itemOrPosition );
} else {
this.insert( this.createElement( name, attributes ), itemOrPosition, offset );
}
}
/**
* Inserts item at the end of the given parent.
*
* const paragraph = writer.createElement( 'paragraph' );
* writer.append( paragraph, root );
*
* Note that if the item already has parent it will be removed from the previous parent.
*
* If you want to move {@link module:engine/model/range~Range range} instead of an
* {@link module:engine/model/item~Item item} use {@link module:engine/model/writer~Writer#move `Writer#move()`}.
*
* @param {module:engine/model/item~Item|module:engine/model/documentfragment~DocumentFragment}
* item Item or document fragment to insert.
* @param {module:engine/model/element~Element|module:engine/model/documentfragment~DocumentFragment} parent
*/
append( item, parent ) {
this.insert( item, parent, 'end' );
}
/**
* Creates text node and inserts it at the end of the parent. You can optionally set text attributes:
*
* writer.appendText( 'foo', paragraph );
* writer.appendText( 'foo', { bold: true }, paragraph );
*
* @param {String} text Text data.
* @param {Object} [attributes] Text attributes.
* @param {module:engine/model/element~Element|module:engine/model/documentfragment~DocumentFragment} parent
*/
appendText( text, attributes, parent ) {
if ( attributes instanceof _documentfragment__WEBPACK_IMPORTED_MODULE_9__["default"] || attributes instanceof _element__WEBPACK_IMPORTED_MODULE_11__["default"] ) {
this.insert( this.createText( text ), attributes, 'end' );
} else {
this.insert( this.createText( text, attributes ), parent, 'end' );
}
}
/**
* Creates element and inserts it at the end of the parent. You can optionally set attributes:
*
* writer.appendElement( 'paragraph', root );
* writer.appendElement( 'paragraph', { alignment: 'center' }, root );
*
* @param {String} name Name of the element.
* @param {Object} [attributes] Elements attributes.
* @param {module:engine/model/element~Element|module:engine/model/documentfragment~DocumentFragment} parent
*/
appendElement( name, attributes, parent ) {
if ( attributes instanceof _documentfragment__WEBPACK_IMPORTED_MODULE_9__["default"] || attributes instanceof _element__WEBPACK_IMPORTED_MODULE_11__["default"] ) {
this.insert( this.createElement( name ), attributes, 'end' );
} else {
this.insert( this.createElement( name, attributes ), parent, 'end' );
}
}
/**
* Sets value of the attribute with given key on a {@link module:engine/model/item~Item model item}
* or on a {@link module:engine/model/range~Range range}.
*
* @param {String} key Attribute key.
* @param {*} value Attribute new value.
* @param {module:engine/model/item~Item|module:engine/model/range~Range} itemOrRange
* Model item or range on which the attribute will be set.
*/
setAttribute( key, value, itemOrRange ) {
this._assertWriterUsedCorrectly();
if ( itemOrRange instanceof _range_js__WEBPACK_IMPORTED_MODULE_14__["default"] ) {
const ranges = itemOrRange.getMinimalFlatRanges();
for ( const range of ranges ) {
setAttributeOnRange( this, key, value, range );
}
} else {
setAttributeOnItem( this, key, value, itemOrRange );
}
}
/**
* Sets values of attributes on a {@link module:engine/model/item~Item model item}
* or on a {@link module:engine/model/range~Range range}.
*
* writer.setAttributes( {
* bold: true,
* italic: true
* }, range );
*
* @param {Object} attributes Attributes keys and values.
* @param {module:engine/model/item~Item|module:engine/model/range~Range} itemOrRange
* Model item or range on which the attributes will be set.
*/
setAttributes( attributes, itemOrRange ) {
for ( const [ key, val ] of (0,_ckeditor_ckeditor5_utils_src_tomap__WEBPACK_IMPORTED_MODULE_16__["default"])( attributes ) ) {
this.setAttribute( key, val, itemOrRange );
}
}
/**
* Removes an attribute with given key from a {@link module:engine/model/item~Item model item}
* or from a {@link module:engine/model/range~Range range}.
*
* @param {String} key Attribute key.
* @param {module:engine/model/item~Item|module:engine/model/range~Range} itemOrRange
* Model item or range from which the attribute will be removed.
*/
removeAttribute( key, itemOrRange ) {
this._assertWriterUsedCorrectly();
if ( itemOrRange instanceof _range_js__WEBPACK_IMPORTED_MODULE_14__["default"] ) {
const ranges = itemOrRange.getMinimalFlatRanges();
for ( const range of ranges ) {
setAttributeOnRange( this, key, null, range );
}
} else {
setAttributeOnItem( this, key, null, itemOrRange );
}
}
/**
* Removes all attributes from all elements in the range or from the given item.
*
* @param {module:engine/model/item~Item|module:engine/model/range~Range} itemOrRange
* Model item or range from which all attributes will be removed.
*/
clearAttributes( itemOrRange ) {
this._assertWriterUsedCorrectly();
const removeAttributesFromItem = item => {
for ( const attribute of item.getAttributeKeys() ) {
this.removeAttribute( attribute, item );
}
};
if ( !( itemOrRange instanceof _range_js__WEBPACK_IMPORTED_MODULE_14__["default"] ) ) {
removeAttributesFromItem( itemOrRange );
} else {
for ( const item of itemOrRange.getItems() ) {
removeAttributesFromItem( item );
}
}
}
/**
* Moves all items in the source range to the target position.
*
* writer.move( sourceRange, targetPosition );
*
* Instead of the target position you can use parent and offset or define that range should be moved to the end
* or before or after chosen item:
*
* // Moves all items in the range to the paragraph at offset 5:
* writer.move( sourceRange, paragraph, 5 );
* // Moves all items in the range to the end of a blockquote:
* writer.move( sourceRange, blockquote, 'end' );
* // Moves all items in the range to a position after an image:
* writer.move( sourceRange, image, 'after' );
*
* These parameters works the same way as {@link #createPositionAt `writer.createPositionAt()`}.
*
* Note that items can be moved only within the same tree. It means that you can move items within the same root
* (element or document fragment) or between {@link module:engine/model/document~Document#roots documents roots},
* but you can not move items from document fragment to the document or from one detached element to another. Use
* {@link module:engine/model/writer~Writer#insert} in such cases.
*
* @param {module:engine/model/range~Range} range Source range.
* @param {module:engine/model/item~Item|module:engine/model/position~Position} itemOrPosition
* @param {Number|'end'|'before'|'after'} [offset] Offset or one of the flags. Used only when
* second parameter is a {@link module:engine/model/item~Item model item}.
*/
move( range, itemOrPosition, offset ) {
this._assertWriterUsedCorrectly();
if ( !( range instanceof _range_js__WEBPACK_IMPORTED_MODULE_14__["default"] ) ) {
/**
* Invalid range to move.
*
* @error writer-move-invalid-range
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_17__["default"]( 'writer-move-invalid-range', this );
}
if ( !range.isFlat ) {
/**
* Range to move is not flat.
*
* @error writer-move-range-not-flat
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_17__["default"]( 'writer-move-range-not-flat', this );
}
const position = _position__WEBPACK_IMPORTED_MODULE_13__["default"]._createAt( itemOrPosition, offset );
// Do not move anything if the move target is same as moved range start.
if ( position.isEqual( range.start ) ) {
return;
}
// If part of the marker is removed, create additional marker operation for undo purposes.
this._addOperationForAffectedMarkers( 'move', range );
if ( !isSameTree( range.root, position.root ) ) {
/**
* Range is going to be moved within not the same document. Please use
* {@link module:engine/model/writer~Writer#insert insert} instead.
*
* @error writer-move-different-document
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_17__["default"]( 'writer-move-different-document', this );
}
const version = range.root.document ? range.root.document.version : null;
const operation = new _operation_moveoperation__WEBPACK_IMPORTED_MODULE_4__["default"]( range.start, range.end.offset - range.start.offset, position, version );
this.batch.addOperation( operation );
this.model.applyOperation( operation );
}
/**
* Removes given model {@link module:engine/model/item~Item item} or {@link module:engine/model/range~Range range}.
*
* @param {module:engine/model/item~Item|module:engine/model/range~Range} itemOrRange Model item or range to remove.
*/
remove( itemOrRange ) {
this._assertWriterUsedCorrectly();
const rangeToRemove = itemOrRange instanceof _range_js__WEBPACK_IMPORTED_MODULE_14__["default"] ? itemOrRange : _range_js__WEBPACK_IMPORTED_MODULE_14__["default"]._createOn( itemOrRange );
const ranges = rangeToRemove.getMinimalFlatRanges().reverse();
for ( const flat of ranges ) {
// If part of the marker is removed, create additional marker operation for undo purposes.
this._addOperationForAffectedMarkers( 'move', flat );
applyRemoveOperation( flat.start, flat.end.offset - flat.start.offset, this.batch, this.model );
}
}
/**
* Merges two siblings at the given position.
*
* Node before and after the position have to be an element. Otherwise `writer-merge-no-element-before` or
* `writer-merge-no-element-after` error will be thrown.
*
* @param {module:engine/model/position~Position} position Position between merged elements.
*/
merge( position ) {
this._assertWriterUsedCorrectly();
const nodeBefore = position.nodeBefore;
const nodeAfter = position.nodeAfter;
// If part of the marker is removed, create additional marker operation for undo purposes.
this._addOperationForAffectedMarkers( 'merge', position );
if ( !( nodeBefore instanceof _element__WEBPACK_IMPORTED_MODULE_11__["default"] ) ) {
/**
* Node before merge position must be an element.
*
* @error writer-merge-no-element-before
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_17__["default"]( 'writer-merge-no-element-before', this );
}
if ( !( nodeAfter instanceof _element__WEBPACK_IMPORTED_MODULE_11__["default"] ) ) {
/**
* Node after merge position must be an element.
*
* @error writer-merge-no-element-after
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_17__["default"]( 'writer-merge-no-element-after', this );
}
if ( !position.root.document ) {
this._mergeDetached( position );
} else {
this._merge( position );
}
}
/**
* Shortcut for {@link module:engine/model/model~Model#createPositionFromPath `Model#createPositionFromPath()`}.
*
* @param {module:engine/model/element~Element|module:engine/model/documentfragment~DocumentFragment} root Root of the position.
* @param {Array.<Number>} path Position path. See {@link module:engine/model/position~Position#path}.
* @param {module:engine/model/position~PositionStickiness} [stickiness='toNone'] Position stickiness.
* See {@link module:engine/model/position~PositionStickiness}.
* @returns {module:engine/model/position~Position}
*/
createPositionFromPath( root, path, stickiness ) {
return this.model.createPositionFromPath( root, path, stickiness );
}
/**
* Shortcut for {@link module:engine/model/model~Model#createPositionAt `Model#createPositionAt()`}.
*
* @param {module:engine/model/item~Item|module:engine/model/position~Position} itemOrPosition
* @param {Number|'end'|'before'|'after'} [offset] Offset or one of the flags. Used only when
* first parameter is a {@link module:engine/model/item~Item model item}.
* @returns {module:engine/model/position~Position}
*/
createPositionAt( itemOrPosition, offset ) {
return this.model.createPositionAt( itemOrPosition, offset );
}
/**
* Shortcut for {@link module:engine/model/model~Model#createPositionAfter `Model#createPositionAfter()`}.
*
* @param {module:engine/model/item~Item} item Item after which the position should be placed.
* @returns {module:engine/model/position~Position}
*/
createPositionAfter( item ) {
return this.model.createPositionAfter( item );
}
/**
* Shortcut for {@link module:engine/model/model~Model#createPositionBefore `Model#createPositionBefore()`}.
*
* @param {module:engine/model/item~Item} item Item after which the position should be placed.
* @returns {module:engine/model/position~Position}
*/
createPositionBefore( item ) {
return this.model.createPositionBefore( item );
}
/**
* Shortcut for {@link module:engine/model/model~Model#createRange `Model#createRange()`}.
*
* @param {module:engine/model/position~Position} start Start position.
* @param {module:engine/model/position~Position} [end] End position. If not set, range will be collapsed at `start` position.
* @returns {module:engine/model/range~Range}
*/
createRange( start, end ) {
return this.model.createRange( start, end );
}
/**
* Shortcut for {@link module:engine/model/model~Model#createRangeIn `Model#createRangeIn()`}.
*
* @param {module:engine/model/element~Element} element Element which is a parent for the range.
* @returns {module:engine/model/range~Range}
*/
createRangeIn( element ) {
return this.model.createRangeIn( element );
}
/**
* Shortcut for {@link module:engine/model/model~Model#createRangeOn `Model#createRangeOn()`}.
*
* @param {module:engine/model/element~Element} element Element which is a parent for the range.
* @returns {module:engine/model/range~Range}
*/
createRangeOn( element ) {
return this.model.createRangeOn( element );
}
/**
* Shortcut for {@link module:engine/model/model~Model#createSelection `Model#createSelection()`}.
*
* @param {module:engine/model/selection~Selectable} selectable
* @param {Number|'before'|'end'|'after'|'on'|'in'} [placeOrOffset] Sets place or offset of the selection.
* @param {Object} [options]
* @param {Boolean} [options.backward] Sets this selection instance to be backward.
* @returns {module:engine/model/selection~Selection}
*/
createSelection( selectable, placeOrOffset, options ) {
return this.model.createSelection( selectable, placeOrOffset, options );
}
/**
* Performs merge action in a detached tree.
*
* @private
* @param {module:engine/model/position~Position} position Position between merged elements.
*/
_mergeDetached( position ) {
const nodeBefore = position.nodeBefore;
const nodeAfter = position.nodeAfter;
this.move( _range_js__WEBPACK_IMPORTED_MODULE_14__["default"]._createIn( nodeAfter ), _position__WEBPACK_IMPORTED_MODULE_13__["default"]._createAt( nodeBefore, 'end' ) );
this.remove( nodeAfter );
}
/**
* Performs merge action in a non-detached tree.
*
* @private
* @param {module:engine/model/position~Position} position Position between merged elements.
*/
_merge( position ) {
const targetPosition = _position__WEBPACK_IMPORTED_MODULE_13__["default"]._createAt( position.nodeBefore, 'end' );
const sourcePosition = _position__WEBPACK_IMPORTED_MODULE_13__["default"]._createAt( position.nodeAfter, 0 );
const graveyard = position.root.document.graveyard;
const graveyardPosition = new _position__WEBPACK_IMPORTED_MODULE_13__["default"]( graveyard, [ 0 ] );
const version = position.root.document.version;
const merge = new _operation_mergeoperation__WEBPACK_IMPORTED_MODULE_8__["default"]( sourcePosition, position.nodeAfter.maxOffset, targetPosition, graveyardPosition, version );
this.batch.addOperation( merge );
this.model.applyOperation( merge );
}
/**
* Renames the given element.
*
* @param {module:engine/model/element~Element} element The element to rename.
* @param {String} newName New element name.
*/
rename( element, newName ) {
this._assertWriterUsedCorrectly();
if ( !( element instanceof _element__WEBPACK_IMPORTED_MODULE_11__["default"] ) ) {
/**
* Trying to rename an object which is not an instance of Element.
*
* @error writer-rename-not-element-instance
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_17__["default"](
'writer-rename-not-element-instance',
this
);
}
const version = element.root.document ? element.root.document.version : null;
const renameOperation = new _operation_renameoperation__WEBPACK_IMPORTED_MODULE_5__["default"]( _position__WEBPACK_IMPORTED_MODULE_13__["default"]._createBefore( element ), element.name, newName, version );
this.batch.addOperation( renameOperation );
this.model.applyOperation( renameOperation );
}
/**
* Splits elements starting from the given position and going to the top of the model tree as long as given
* `limitElement` is reached. When `limitElement` is not defined then only the parent of the given position will be split.
*
* The element needs to have a parent. It cannot be a root element nor a document fragment.
* The `writer-split-element-no-parent` error will be thrown if you try to split an element with no parent.
*
* @param {module:engine/model/position~Position} position Position of split.
* @param {module:engine/model/node~Node} [limitElement] Stop splitting when this element will be reached.
* @returns {Object} result Split result.
* @returns {module:engine/model/position~Position} result.position Position between split elements.
* @returns {module:engine/model/range~Range} result.range Range that stars from the end of the first split element and ends
* at the beginning of the first copy element.
*/
split( position, limitElement ) {
this._assertWriterUsedCorrectly();
let splitElement = position.parent;
if ( !splitElement.parent ) {
/**
* Element with no parent can not be split.
*
* @error writer-split-element-no-parent
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_17__["default"]( 'writer-split-element-no-parent', this );
}
// When limit element is not defined lets set splitElement parent as limit.
if ( !limitElement ) {
limitElement = splitElement.parent;
}
if ( !position.parent.getAncestors( { includeSelf: true } ).includes( limitElement ) ) {
/**
* Limit element is not a position ancestor.
*
* @error writer-split-invalid-limit-element
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_17__["default"]( 'writer-split-invalid-limit-element', this );
}
// We need to cache elements that will be created as a result of the first split because
// we need to create a range from the end of the first split element to the beginning of the
// first copy element. This should be handled by LiveRange but it doesn't work on detached nodes.
let firstSplitElement, firstCopyElement;
do {
const version = splitElement.root.document ? splitElement.root.document.version : null;
const howMany = splitElement.maxOffset - position.offset;
const insertionPosition = _operation_splitoperation__WEBPACK_IMPORTED_MODULE_7__["default"].getInsertionPosition( position );
const split = new _operation_splitoperation__WEBPACK_IMPORTED_MODULE_7__["default"]( position, howMany, insertionPosition, null, version );
this.batch.addOperation( split );
this.model.applyOperation( split );
// Cache result of the first split.
if ( !firstSplitElement && !firstCopyElement ) {
firstSplitElement = splitElement;
firstCopyElement = position.parent.nextSibling;
}
position = this.createPositionAfter( position.parent );
splitElement = position.parent;
} while ( splitElement !== limitElement );
return {
position,
range: new _range_js__WEBPACK_IMPORTED_MODULE_14__["default"]( _position__WEBPACK_IMPORTED_MODULE_13__["default"]._createAt( firstSplitElement, 'end' ), _position__WEBPACK_IMPORTED_MODULE_13__["default"]._createAt( firstCopyElement, 0 ) )
};
}
/**
* Wraps the given range with the given element or with a new element (if a string was passed).
*
* **Note:** range to wrap should be a "flat range" (see {@link module:engine/model/range~Range#isFlat `Range#isFlat`}).
* If not, an error will be thrown.
*
* @param {module:engine/model/range~Range} range Range to wrap.
* @param {module:engine/model/element~Element|String} elementOrString Element or name of element to wrap the range with.
*/
wrap( range, elementOrString ) {
this._assertWriterUsedCorrectly();
if ( !range.isFlat ) {
/**
* Range to wrap is not flat.
*
* @error writer-wrap-range-not-flat
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_17__["default"]( 'writer-wrap-range-not-flat', this );
}
const element = elementOrString instanceof _element__WEBPACK_IMPORTED_MODULE_11__["default"] ? elementOrString : new _element__WEBPACK_IMPORTED_MODULE_11__["default"]( elementOrString );
if ( element.childCount > 0 ) {
/**
* Element to wrap with is not empty.
*
* @error writer-wrap-element-not-empty
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_17__["default"]( 'writer-wrap-element-not-empty', this );
}
if ( element.parent !== null ) {
/**
* Element to wrap with is already attached to a tree model.
*
* @error writer-wrap-element-attached
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_17__["default"]( 'writer-wrap-element-attached', this );
}
this.insert( element, range.start );
// Shift the range-to-wrap because we just inserted an element before that range.
const shiftedRange = new _range_js__WEBPACK_IMPORTED_MODULE_14__["default"]( range.start.getShiftedBy( 1 ), range.end.getShiftedBy( 1 ) );
this.move( shiftedRange, _position__WEBPACK_IMPORTED_MODULE_13__["default"]._createAt( element, 0 ) );
}
/**
* Unwraps children of the given element – all its children are moved before it and then the element is removed.
* Throws error if you try to unwrap an element which does not have a parent.
*
* @param {module:engine/model/element~Element} element Element to unwrap.
*/
unwrap( element ) {
this._assertWriterUsedCorrectly();
if ( element.parent === null ) {
/**
* Trying to unwrap an element which has no parent.
*
* @error writer-unwrap-element-no-parent
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_17__["default"]( 'writer-unwrap-element-no-parent', this );
}
this.move( _range_js__WEBPACK_IMPORTED_MODULE_14__["default"]._createIn( element ), this.createPositionAfter( element ) );
this.remove( element );
}
/**
* Adds a {@link module:engine/model/markercollection~Marker marker}. Marker is a named range, which tracks
* changes in the document and updates its range automatically, when model tree changes.
*
* As the first parameter you can set marker name.
*
* The required `options.usingOperation` parameter lets you decide if the marker should be managed by operations or not. See
* {@link module:engine/model/markercollection~Marker marker class description} to learn about the difference between
* markers managed by operations and not-managed by operations.
*
* The `options.affectsData` parameter, which defaults to `false`, allows you to define if a marker affects the data. It should be
* `true` when the marker change changes the data returned by the
* {@link module:core/editor/utils/dataapimixin~DataApi#getData `editor.getData()`} method.
* When set to `true` it fires the {@link module:engine/model/document~Document#event:change:data `change:data`} event.
* When set to `false` it fires the {@link module:engine/model/document~Document#event:change `change`} event.
*
* Create marker directly base on marker's name:
*
* addMarker( markerName, { range, usingOperation: false } );
*
* Create marker using operation:
*
* addMarker( markerName, { range, usingOperation: true } );
*
* Create marker that affects the editor data:
*
* addMarker( markerName, { range, usingOperation: false, affectsData: true } );
*
* Note: For efficiency reasons, it's best to create and keep as little markers as possible.
*
* @see module:engine/model/markercollection~Marker
* @param {String} name Name of a marker to create - must be unique.
* @param {Object} options
* @param {Boolean} options.usingOperation Flag indicating that the marker should be added by MarkerOperation.
* See {@link module:engine/model/markercollection~Marker#managedUsingOperations}.
* @param {module:engine/model/range~Range} options.range Marker range.
* @param {Boolean} [options.affectsData=false] Flag indicating that the marker changes the editor data.
* @returns {module:engine/model/markercollection~Marker} Marker that was set.
*/
addMarker( name, options ) {
this._assertWriterUsedCorrectly();
if ( !options || typeof options.usingOperation != 'boolean' ) {
/**
* The `options.usingOperation` parameter is required when adding a new marker.
*
* @error writer-addmarker-no-usingoperation
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_17__["default"]( 'writer-addmarker-no-usingoperation', this );
}
const usingOperation = options.usingOperation;
const range = options.range;
const affectsData = options.affectsData === undefined ? false : options.affectsData;
if ( this.model.markers.has( name ) ) {
/**
* Marker with provided name already exists.
*
* @error writer-addmarker-marker-exists
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_17__["default"]( 'writer-addmarker-marker-exists', this );
}
if ( !range ) {
/**
* Range parameter is required when adding a new marker.
*
* @error writer-addmarker-no-range
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_17__["default"]( 'writer-addmarker-no-range', this );
}
if ( !usingOperation ) {
return this.model.markers._set( name, range, usingOperation, affectsData );
}
applyMarkerOperation( this, name, null, range, affectsData );
return this.model.markers.get( name );
}
/**
* Adds, updates or refreshes a {@link module:engine/model/markercollection~Marker marker}. Marker is a named range, which tracks
* changes in the document and updates its range automatically, when model tree changes. Still, it is possible to change the
* marker's range directly using this method.
*
* As the first parameter you can set marker name or instance. If none of them is provided, new marker, with a unique
* name is created and returned.
*
* **Note**: If you want to change the {@link module:engine/view/element~Element view element} of the marker while its data in the model
* remains the same, use the dedicated {@link module:engine/controller/editingcontroller~EditingController#reconvertMarker} method.
*
* The `options.usingOperation` parameter lets you change if the marker should be managed by operations or not. See
* {@link module:engine/model/markercollection~Marker marker class description} to learn about the difference between
* markers managed by operations and not-managed by operations. It is possible to change this option for an existing marker.
*
* The `options.affectsData` parameter, which defaults to `false`, allows you to define if a marker affects the data. It should be
* `true` when the marker change changes the data returned by
* the {@link module:core/editor/utils/dataapimixin~DataApi#getData `editor.getData()`} method.
* When set to `true` it fires the {@link module:engine/model/document~Document#event:change:data `change:data`} event.
* When set to `false` it fires the {@link module:engine/model/document~Document#event:change `change`} event.
*
* Update marker directly base on marker's name:
*
* updateMarker( markerName, { range } );
*
* Update marker using operation:
*
* updateMarker( marker, { range, usingOperation: true } );
* updateMarker( markerName, { range, usingOperation: true } );
*
* Change marker's option (start using operations to manage it):
*
* updateMarker( marker, { usingOperation: true } );
*
* Change marker's option (inform the engine, that the marker does not affect the data anymore):
*
* updateMarker( markerName, { affectsData: false } );
*
* @see module:engine/model/markercollection~Marker
* @param {String|module:engine/model/markercollection~Marker} markerOrName Name of a marker to update, or a marker instance.
* @param {Object} [options] If options object is not defined then marker will be refreshed by triggering
* downcast conversion for this marker with the same data.
* @param {module:engine/model/range~Range} [options.range] Marker range to update.
* @param {Boolean} [options.usingOperation] Flag indicated whether the marker should be added by MarkerOperation.
* See {@link module:engine/model/markercollection~Marker#managedUsingOperations}.
* @param {Boolean} [options.affectsData] Flag indicating that the marker changes the editor data.
*/
updateMarker( markerOrName, options ) {
this._assertWriterUsedCorrectly();
const markerName = typeof markerOrName == 'string' ? markerOrName : markerOrName.name;
const currentMarker = this.model.markers.get( markerName );
if ( !currentMarker ) {
/**
* Marker with provided name does not exist and will not be updated.
*
* @error writer-updatemarker-marker-not-exists
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_17__["default"]( 'writer-updatemarker-marker-not-exists', this );
}
if ( !options ) {
/**
* The usage of `writer.updateMarker()` only to reconvert (refresh) a
* {@link module:engine/model/markercollection~Marker model marker} was deprecated and may not work in the future.
* Please update your code to use
* {@link module:engine/controller/editingcontroller~EditingController#reconvertMarker `editor.editing.reconvertMarker()`}
* instead.
*
* @error writer-updatemarker-reconvert-using-editingcontroller
* @param {String} markerName The name of the updated marker.
*/
(0,_ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_17__.logWarning)( 'writer-updatemarker-reconvert-using-editingcontroller', { markerName } );
this.model.markers._refresh( currentMarker );
return;
}
const hasUsingOperationDefined = typeof options.usingOperation == 'boolean';
const affectsDataDefined = typeof options.affectsData == 'boolean';
// Use previously defined marker's affectsData if the property is not provided.
const affectsData = affectsDataDefined ? options.affectsData : currentMarker.affectsData;
if ( !hasUsingOperationDefined && !options.range && !affectsDataDefined ) {
/**
* One of the options is required - provide range, usingOperations or affectsData.
*
* @error writer-updatemarker-wrong-options
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_17__["default"]( 'writer-updatemarker-wrong-options', this );
}
const currentRange = currentMarker.getRange();
const updatedRange = options.range ? options.range : currentRange;
if ( hasUsingOperationDefined && options.usingOperation !== currentMarker.managedUsingOperations ) {
// The marker type is changed so it's necessary to create proper operations.
if ( options.usingOperation ) {
// If marker changes to a managed one treat this as synchronizing existing marker.
// Create `MarkerOperation` with `oldRange` set to `null`, so reverse operation will remove the marker.
applyMarkerOperation( this, markerName, null, updatedRange, affectsData );
} else {
// If marker changes to a marker that do not use operations then we need to create additional operation
// that removes that marker first.
applyMarkerOperation( this, markerName, currentRange, null, affectsData );
// Although not managed the marker itself should stay in model and its range should be preserver or changed to passed range.
this.model.markers._set( markerName, updatedRange, undefined, affectsData );
}
return;
}
// Marker's type doesn't change so update it accordingly.
if ( currentMarker.managedUsingOperations ) {
applyMarkerOperation( this, markerName, currentRange, updatedRange, affectsData );
} else {
this.model.markers._set( markerName, updatedRange, undefined, affectsData );
}
}
/**
* Removes given {@link module:engine/model/markercollection~Marker marker} or marker with given name.
* The marker is removed accordingly to how it has been created, so if the marker was created using operation,
* it will be destroyed using operation.
*
* @param {module:engine/model/markercollection~Marker|String} markerOrName Marker or marker name to remove.
*/
removeMarker( markerOrName ) {
this._assertWriterUsedCorrectly();
const name = typeof markerOrName == 'string' ? markerOrName : markerOrName.name;
if ( !this.model.markers.has( name ) ) {
/**
* Trying to remove marker which does not exist.
*
* @error writer-removemarker-no-marker
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_17__["default"]( 'writer-removemarker-no-marker', this );
}
const marker = this.model.markers.get( name );
if ( !marker.managedUsingOperations ) {
this.model.markers._remove( name );
return;
}
const oldRange = marker.getRange();
applyMarkerOperation( this, name, oldRange, null, marker.affectsData );
}
/**
* Sets the document's selection (ranges and direction) to the specified location based on the given
* {@link module:engine/model/selection~Selectable selectable} or creates an empty selection if no arguments were passed.
*
* // Sets selection to the given range.
* const range = writer.createRange( start, end );
* writer.setSelection( range );
*
* // Sets selection to given ranges.
* const ranges = [ writer.createRange( start1, end2 ), writer.createRange( star2, end2 ) ];
* writer.setSelection( ranges );
*
* // Sets selection to other selection.
* const otherSelection = writer.createSelection();
* writer.setSelection( otherSelection );
*
* // Sets selection to the given document selection.
* const documentSelection = model.document.selection;
* writer.setSelection( documentSelection );
*
* // Sets collapsed selection at the given position.
* const position = writer.createPosition( root, path );
* writer.setSelection( position );
*
* // Sets collapsed selection at the position of the given node and an offset.
* writer.setSelection( paragraph, offset );
*
* Creates a range inside an {@link module:engine/model/element~Element element} which starts before the first child of
* that element and ends after the last child of that element.
*
* writer.setSelection( paragraph, 'in' );
*
* Creates a range on an {@link module:engine/model/item~Item item} which starts before the item and ends just after the item.
*
* writer.setSelection( paragraph, 'on' );
*
* // Removes all selection's ranges.
* writer.setSelection( null );
*
* `Writer#setSelection()` allow passing additional options (`backward`) as the last argument.
*
* // Sets selection as backward.
* writer.setSelection( range, { backward: true } );
*
* Throws `writer-incorrect-use` error when the writer is used outside the `change()` block.
*
* @param {module:engine/model/selection~Selectable} selectable
* @param {Number|'before'|'end'|'after'|'on'|'in'} [placeOrOffset] Sets place or offset of the selection.
* @param {Object} [options]
* @param {Boolean} [options.backward] Sets this selection instance to be backward.
*/
setSelection( selectable, placeOrOffset, options ) {
this._assertWriterUsedCorrectly();
this.model.document.selection._setTo( selectable, placeOrOffset, options );
}
/**
* Moves {@link module:engine/model/documentselection~DocumentSelection#focus} to the specified location.
*
* The location can be specified in the same form as
* {@link #createPositionAt `writer.createPositionAt()`} parameters.
*
* @param {module:engine/model/item~Item|module:engine/model/position~Position} itemOrPosition
* @param {Number|'end'|'before'|'after'} [offset=0] Offset or one of the flags. Used only when
* first parameter is a {@link module:engine/model/item~Item model item}.
*/
setSelectionFocus( itemOrPosition, offset ) {
this._assertWriterUsedCorrectly();
this.model.document.selection._setFocus( itemOrPosition, offset );
}
/**
* Sets attribute(s) on the selection. If attribute with the same key already is set, it's value is overwritten.
*
* Using key and value pair:
*
* writer.setSelectionAttribute( 'italic', true );
*
* Using key-value object:
*
* writer.setSelectionAttribute( { italic: true, bold: false } );
*
* Using iterable object:
*
* writer.setSelectionAttribute( new Map( [ [ 'italic', true ] ] ) );
*
* @param {String|Object|Iterable.<*>} keyOrObjectOrIterable Key of the attribute to set
* or object / iterable of key => value attribute pairs.
* @param {*} [value] Attribute value.
*/
setSelectionAttribute( keyOrObjectOrIterable, value ) {
this._assertWriterUsedCorrectly();
if ( typeof keyOrObjectOrIterable === 'string' ) {
this._setSelectionAttribute( keyOrObjectOrIterable, value );
} else {
for ( const [ key, value ] of (0,_ckeditor_ckeditor5_utils_src_tomap__WEBPACK_IMPORTED_MODULE_16__["default"])( keyOrObjectOrIterable ) ) {
this._setSelectionAttribute( key, value );
}
}
}
/**
* Removes attribute(s) with given key(s) from the selection.
*
* Remove one attribute:
*
* writer.removeSelectionAttribute( 'italic' );
*
* Remove multiple attributes:
*
* writer.removeSelectionAttribute( [ 'italic', 'bold' ] );
*
* @param {String|Iterable.<String>} keyOrIterableOfKeys Key of the attribute to remove or an iterable of attribute keys to remove.
*/
removeSelectionAttribute( keyOrIterableOfKeys ) {
this._assertWriterUsedCorrectly();
if ( typeof keyOrIterableOfKeys === 'string' ) {
this._removeSelectionAttribute( keyOrIterableOfKeys );
} else {
for ( const key of keyOrIterableOfKeys ) {
this._removeSelectionAttribute( key );
}
}
}
/**
* Temporarily changes the {@link module:engine/model/documentselection~DocumentSelection#isGravityOverridden gravity}
* of the selection from left to right.
*
* The gravity defines from which direction the selection inherits its attributes. If it's the default left gravity,
* then the selection (after being moved by the user) inherits attributes from its left-hand side.
* This method allows to temporarily override this behavior by forcing the gravity to the right.
*
* For the following model fragment:
*
* <$text bold="true" linkHref="url">bar[]</$text><$text bold="true">biz</$text>
*
* * Default gravity: selection will have the `bold` and `linkHref` attributes.
* * Overridden gravity: selection will have `bold` attribute.
*
* **Note**: It returns an unique identifier which is required to restore the gravity. It guarantees the symmetry
* of the process.
*
* @returns {String} The unique id which allows restoring the gravity.
*/
overrideSelectionGravity() {
return this.model.document.selection._overrideGravity();
}
/**
* Restores {@link ~Writer#overrideSelectionGravity} gravity to default.
*
* Restoring the gravity is only possible using the unique identifier returned by
* {@link ~Writer#overrideSelectionGravity}. Note that the gravity remains overridden as long as won't be restored
* the same number of times it was overridden.
*
* @param {String} uid The unique id returned by {@link ~Writer#overrideSelectionGravity}.
*/
restoreSelectionGravity( uid ) {
this.model.document.selection._restoreGravity( uid );
}
/**
* @private
* @param {String} key Key of the attribute to remove.
* @param {*} value Attribute value.
*/
_setSelectionAttribute( key, value ) {
const selection = this.model.document.selection;
// Store attribute in parent element if the selection is collapsed in an empty node.
if ( selection.isCollapsed && selection.anchor.parent.isEmpty ) {
const storeKey = _documentselection__WEBPACK_IMPORTED_MODULE_15__["default"]._getStoreAttributeKey( key );
this.setAttribute( storeKey, value, selection.anchor.parent );
}
selection._setAttribute( key, value );
}
/**
* @private
* @param {String} key Key of the attribute to remove.
*/
_removeSelectionAttribute( key ) {
const selection = this.model.document.selection;
// Remove stored attribute from parent element if the selection is collapsed in an empty node.
if ( selection.isCollapsed && selection.anchor.parent.isEmpty ) {
const storeKey = _documentselection__WEBPACK_IMPORTED_MODULE_15__["default"]._getStoreAttributeKey( key );
this.removeAttribute( storeKey, selection.anchor.parent );
}
selection._removeAttribute( key );
}
/**
* Throws `writer-detached-writer-tries-to-modify-model` error when the writer is used outside of the `change()` block.
*
* @private
*/
_assertWriterUsedCorrectly() {
/**
* Trying to use a writer outside a {@link module:engine/model/model~Model#change `change()`} or
* {@link module:engine/model/model~Model#enqueueChange `enqueueChange()`} blocks.
*
* The writer can only be used inside these blocks which ensures that the model
* can only be changed during such "sessions".
*
* @error writer-incorrect-use
*/
if ( this.model._currentWriter !== this ) {
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_17__["default"]( 'writer-incorrect-use', this );
}
}
/**
* For given action `type` and `positionOrRange` where the action happens, this function finds all affected markers
* and applies a marker operation with the new marker range equal to the current range. Thanks to this, the marker range
* can be later correctly processed during undo.
*
* @private
* @param {'move'|'merge'} type Writer action type.
* @param {module:engine/model/position~Position|module:engine/model/range~Range} positionOrRange Position or range
* where the writer action happens.
*/
_addOperationForAffectedMarkers( type, positionOrRange ) {
for ( const marker of this.model.markers ) {
if ( !marker.managedUsingOperations ) {
continue;
}
const markerRange = marker.getRange();
let isAffected = false;
if ( type === 'move' ) {
isAffected =
positionOrRange.containsPosition( markerRange.start ) ||
positionOrRange.start.isEqual( markerRange.start ) ||
positionOrRange.containsPosition( markerRange.end ) ||
positionOrRange.end.isEqual( markerRange.end );
} else {
// if type === 'merge'.
const elementBefore = positionOrRange.nodeBefore;
const elementAfter = positionOrRange.nodeAfter;
// Start: <p>Foo[</p><p>Bar]</p>
// After merge: <p>Foo[Bar]</p>
// After undoing split: <p>Foo</p><p>[Bar]</p> <-- incorrect, needs remembering for undo.
//
const affectedInLeftElement = markerRange.start.parent == elementBefore && markerRange.start.isAtEnd;
// Start: <p>[Foo</p><p>]Bar</p>
// After merge: <p>[Foo]Bar</p>
// After undoing split: <p>[Foo]</p><p>Bar</p> <-- incorrect, needs remembering for undo.
//
const affectedInRightElement = markerRange.end.parent == elementAfter && markerRange.end.offset == 0;
// Start: <p>[Foo</p>]<p>Bar</p>
// After merge: <p>[Foo]Bar</p>
// After undoing split: <p>[Foo]</p><p>Bar</p> <-- incorrect, needs remembering for undo.
//
const affectedAfterLeftElement = markerRange.end.nodeAfter == elementAfter;
// Start: <p>Foo</p>[<p>Bar]</p>
// After merge: <p>Foo[Bar]</p>
// After undoing split: <p>Foo</p><p>[Bar]</p> <-- incorrect, needs remembering for undo.
//
const affectedBeforeRightElement = markerRange.start.nodeAfter == elementAfter;
isAffected = affectedInLeftElement || affectedInRightElement || affectedAfterLeftElement || affectedBeforeRightElement;
}
if ( isAffected ) {
this.updateMarker( marker.name, { range: markerRange } );
}
}
}
}
// Sets given attribute to each node in given range. When attribute value is null then attribute will be removed.
//
// Because attribute operation needs to have the same attribute value on the whole range, this function splits
// the range into smaller parts.
//
// Given `range` must be flat.
//
// @private
// @param {module:engine/model/writer~Writer} writer
// @param {String} key Attribute key.
// @param {*} value Attribute new value.
// @param {module:engine/model/range~Range} range Model range on which the attribute will be set.
function setAttributeOnRange( writer, key, value, range ) {
const model = writer.model;
const doc = model.document;
// Position of the last split, the beginning of the new range.
let lastSplitPosition = range.start;
// Currently position in the scanning range. Because we need value after the position, it is not a current
// position of the iterator but the previous one (we need to iterate one more time to get the value after).
let position;
// Value before the currently position.
let valueBefore;
// Value after the currently position.
let valueAfter;
for ( const val of range.getWalker( { shallow: true } ) ) {
valueAfter = val.item.getAttribute( key );
// At the first run of the iterator the position in undefined. We also do not have a valueBefore, but
// because valueAfter may be null, valueBefore may be equal valueAfter ( undefined == null ).
if ( position && valueBefore != valueAfter ) {
// if valueBefore == value there is nothing to change, so we add operation only if these values are different.
if ( valueBefore != value ) {
addOperation();
}
lastSplitPosition = position;
}
position = val.nextPosition;
valueBefore = valueAfter;
}
// Because position in the loop is not the iterator position (see let position comment), the last position in
// the while loop will be last but one position in the range. We need to check the last position manually.
if ( position instanceof _position__WEBPACK_IMPORTED_MODULE_13__["default"] && position != lastSplitPosition && valueBefore != value ) {
addOperation();
}
function addOperation() {
const range = new _range_js__WEBPACK_IMPORTED_MODULE_14__["default"]( lastSplitPosition, position );
const version = range.root.document ? doc.version : null;
const operation = new _operation_attributeoperation__WEBPACK_IMPORTED_MODULE_0__["default"]( range, key, valueBefore, value, version );
writer.batch.addOperation( operation );
model.applyOperation( operation );
}
}
// Sets given attribute to the given node. When attribute value is null then attribute will be removed.
//
// @private
// @param {module:engine/model/writer~Writer} writer
// @param {String} key Attribute key.
// @param {*} value Attribute new value.
// @param {module:engine/model/item~Item} item Model item on which the attribute will be set.
function setAttributeOnItem( writer, key, value, item ) {
const model = writer.model;
const doc = model.document;
const previousValue = item.getAttribute( key );
let range, operation;
if ( previousValue != value ) {
const isRootChanged = item.root === item;
if ( isRootChanged ) {
// If we change attributes of root element, we have to use `RootAttributeOperation`.
const version = item.document ? doc.version : null;
operation = new _operation_rootattributeoperation__WEBPACK_IMPORTED_MODULE_6__["default"]( item, key, previousValue, value, version );
} else {
range = new _range_js__WEBPACK_IMPORTED_MODULE_14__["default"]( _position__WEBPACK_IMPORTED_MODULE_13__["default"]._createBefore( item ), writer.createPositionAfter( item ) );
const version = range.root.document ? doc.version : null;
operation = new _operation_attributeoperation__WEBPACK_IMPORTED_MODULE_0__["default"]( range, key, previousValue, value, version );
}
writer.batch.addOperation( operation );
model.applyOperation( operation );
}
}
// Creates and applies marker operation to {@link module:engine/model/operation/operation~Operation operation}.
//
// @private
// @param {module:engine/model/writer~Writer} writer
// @param {String} name Marker name.
// @param {module:engine/model/range~Range} oldRange Marker range before the change.
// @param {module:engine/model/range~Range} newRange Marker range after the change.
// @param {Boolean} affectsData
function applyMarkerOperation( writer, name, oldRange, newRange, affectsData ) {
const model = writer.model;
const doc = model.document;
const operation = new _operation_markeroperation__WEBPACK_IMPORTED_MODULE_3__["default"]( name, oldRange, newRange, model.markers, affectsData, doc.version );
writer.batch.addOperation( operation );
model.applyOperation( operation );
}
// Creates `MoveOperation` or `DetachOperation` that removes `howMany` nodes starting from `position`.
// The operation will be applied on given model instance and added to given operation instance.
//
// @private
// @param {module:engine/model/position~Position} position Position from which nodes are removed.
// @param {Number} howMany Number of nodes to remove.
// @param {Batch} batch Batch to which the operation will be added.
// @param {module:engine/model/model~Model} model Model instance on which operation will be applied.
function applyRemoveOperation( position, howMany, batch, model ) {
let operation;
if ( position.root.document ) {
const doc = model.document;
const graveyardPosition = new _position__WEBPACK_IMPORTED_MODULE_13__["default"]( doc.graveyard, [ 0 ] );
operation = new _operation_moveoperation__WEBPACK_IMPORTED_MODULE_4__["default"]( position, howMany, graveyardPosition, doc.version );
} else {
operation = new _operation_detachoperation__WEBPACK_IMPORTED_MODULE_1__["default"]( position, howMany );
}
batch.addOperation( operation );
model.applyOperation( operation );
}
// Returns `true` if both root elements are the same element or both are documents root elements.
//
// Elements in the same tree can be moved (for instance you can move element form one documents root to another, or
// within the same document fragment), but when element supposed to be moved from document fragment to the document, or
// to another document it should be removed and inserted to avoid problems with OT. This is because features like undo or
// collaboration may track changes on the document but ignore changes on detached fragments and should not get
// unexpected `move` operation.
function isSameTree( rootA, rootB ) {
// If it is the same root this is the same tree.
if ( rootA === rootB ) {
return true;
}
// If both roots are documents root it is operation within the document what we still treat as the same tree.
if ( rootA instanceof _rootelement__WEBPACK_IMPORTED_MODULE_12__["default"] && rootB instanceof _rootelement__WEBPACK_IMPORTED_MODULE_12__["default"] ) {
return true;
}
return false;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/view/attributeelement.js":
/*!******************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/view/attributeelement.js ***!
\******************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ AttributeElement)
/* harmony export */ });
/* harmony import */ var _element__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./element */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/element.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/view/attributeelement
*/
// Default attribute priority.
const DEFAULT_PRIORITY = 10;
/**
* Attribute elements are used to represent formatting elements in the view (think – `<b>`, `<span style="font-size: 2em">`, etc.).
* Most often they are created when downcasting model text attributes.
*
* Editing engine does not define a fixed HTML DTD. This is why a feature developer needs to choose between various
* types (container element, {@link module:engine/view/attributeelement~AttributeElement attribute element},
* {@link module:engine/view/emptyelement~EmptyElement empty element}, etc) when developing a feature.
*
* To create a new attribute element instance use the
* {@link module:engine/view/downcastwriter~DowncastWriter#createAttributeElement `DowncastWriter#createAttributeElement()`} method.
*
* **Note:** Attribute elements by default can wrap {@link module:engine/view/text~Text},
* {@link module:engine/view/emptyelement~EmptyElement}, {@link module:engine/view/uielement~UIElement},
* {@link module:engine/view/rawelement~RawElement} and other attribute elements with higher priority. Other elements while placed inside
* an attribute element will split it (or nest in case of an `AttributeElement`). This behavior can be modified by changing
* the `isAllowedInsideAttributeElement` option while creating
* {@link module:engine/view/downcastwriter~DowncastWriter#createContainerElement},
* {@link module:engine/view/downcastwriter~DowncastWriter#createEmptyElement},
* {@link module:engine/view/downcastwriter~DowncastWriter#createUIElement} or
* {@link module:engine/view/downcastwriter~DowncastWriter#createRawElement}.
*
* @extends module:engine/view/element~Element
*/
class AttributeElement extends _element__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* Creates an attribute element.
*
* @see module:engine/view/downcastwriter~DowncastWriter#createAttributeElement
* @see module:engine/view/element~Element
* @protected
* @param {module:engine/view/document~Document} document The document instance to which this element belongs.
* @param {String} name Node name.
* @param {Object|Iterable} [attrs] Collection of attributes.
* @param {module:engine/view/node~Node|Iterable.<module:engine/view/node~Node>} [children]
* A list of nodes to be inserted into created element.
*/
constructor( document, name, attrs, children ) {
super( document, name, attrs, children );
/**
* Returns block {@link module:engine/view/filler filler} offset or `null` if block filler is not needed.
*
* @method #getFillerOffset
* @returns {Number|null} Block filler offset or `null` if block filler is not needed.
*/
this.getFillerOffset = getFillerOffset;
/**
* Element priority. Decides in what order elements are wrapped by {@link module:engine/view/downcastwriter~DowncastWriter}.
*
* @protected
* @member {Number}
*/
this._priority = DEFAULT_PRIORITY;
/**
* Element identifier. If set, it is used by {@link module:engine/view/element~Element#isSimilar},
* and then two elements are considered similar if, and only if they have the same `_id`.
*
* @protected
* @member {String|Number}
*/
this._id = null;
/**
* Keeps all the attribute elements that have the same {@link module:engine/view/attributeelement~AttributeElement#id ids}
* and still exist in the view tree.
*
* This property is managed by {@link module:engine/view/downcastwriter~DowncastWriter}.
*
* @protected
* @member {Set.<module:engine/view/attributeelement~AttributeElement>|null}
*/
this._clonesGroup = null;
}
/**
* Element priority. Decides in what order elements are wrapped by {@link module:engine/view/downcastwriter~DowncastWriter}.
*
* @readonly
* @type {Number}
*/
get priority() {
return this._priority;
}
/**
* Element identifier. If set, it is used by {@link module:engine/view/element~Element#isSimilar},
* and then two elements are considered similar if, and only if they have the same `id`.
*
* @readonly
* @type {String|Number}
*/
get id() {
return this._id;
}
/**
* Returns all {@link module:engine/view/attributeelement~AttributeElement attribute elements} that has the
* same {@link module:engine/view/attributeelement~AttributeElement#id id} and are in the view tree (were not removed).
*
* Note: If this element has been removed from the tree, returned set will not include it.
*
* Throws {@link module:utils/ckeditorerror~CKEditorError attribute-element-get-elements-with-same-id-no-id}
* if this element has no `id`.
*
* @returns {Set.<module:engine/view/attributeelement~AttributeElement>} Set containing all the attribute elements
* with the same `id` that were added and not removed from the view tree.
*/
getElementsWithSameId() {
if ( this.id === null ) {
/**
* Cannot get elements with the same id for an attribute element without id.
*
* @error attribute-element-get-elements-with-same-id-no-id
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_1__["default"](
'attribute-element-get-elements-with-same-id-no-id',
this
);
}
return new Set( this._clonesGroup );
}
/**
* Checks whether this object is of the given.
*
* attributeElement.is( 'attributeElement' ); // -> true
* attributeElement.is( 'element' ); // -> true
* attributeElement.is( 'node' ); // -> true
* attributeElement.is( 'view:attributeElement' ); // -> true
* attributeElement.is( 'view:element' ); // -> true
* attributeElement.is( 'view:node' ); // -> true
*
* attributeElement.is( 'model:element' ); // -> false
* attributeElement.is( 'documentFragment' ); // -> false
*
* Assuming that the object being checked is an attribute element, you can also check its
* {@link module:engine/view/attributeelement~AttributeElement#name name}:
*
* attributeElement.is( 'element', 'b' ); // -> true if this is a bold element
* attributeElement.is( 'attributeElement', 'b' ); // -> same as above
* text.is( 'element', 'b' ); -> false
*
* {@link module:engine/view/node~Node#is Check the entire list of view objects} which implement the `is()` method.
*
* @param {String} type Type to check.
* @param {String} [name] Element name.
* @returns {Boolean}
*/
is( type, name = null ) {
if ( !name ) {
return type === 'attributeElement' || type === 'view:attributeElement' ||
// From super.is(). This is highly utilised method and cannot call super. See ckeditor/ckeditor5#6529.
type === 'element' || type === 'view:element' ||
type === 'node' || type === 'view:node';
} else {
return name === this.name && (
type === 'attributeElement' || type === 'view:attributeElement' ||
// From super.is(). This is highly utilised method and cannot call super. See ckeditor/ckeditor5#6529.
type === 'element' || type === 'view:element'
);
}
}
/**
* Checks if this element is similar to other element.
*
* If none of elements has set {@link module:engine/view/attributeelement~AttributeElement#id}, then both elements
* should have the same name, attributes and priority to be considered as similar. Two similar elements can contain
* different set of children nodes.
*
* If at least one element has {@link module:engine/view/attributeelement~AttributeElement#id} set, then both
* elements have to have the same {@link module:engine/view/attributeelement~AttributeElement#id} value to be
* considered similar.
*
* Similarity is important for {@link module:engine/view/downcastwriter~DowncastWriter}. For example:
*
* * two following similar elements can be merged together into one, longer element,
* * {@link module:engine/view/downcastwriter~DowncastWriter#unwrap} checks similarity of passed element and processed element to
* decide whether processed element should be unwrapped,
* * etc.
*
* @param {module:engine/view/element~Element} otherElement
* @returns {Boolean}
*/
isSimilar( otherElement ) {
// If any element has an `id` set, just compare the ids.
if ( this.id !== null || otherElement.id !== null ) {
return this.id === otherElement.id;
}
return super.isSimilar( otherElement ) && this.priority == otherElement.priority;
}
/**
* Clones provided element with priority.
*
* @protected
* @param {Boolean} deep If set to `true` clones element and all its children recursively. When set to `false`,
* element will be cloned without any children.
* @returns {module:engine/view/attributeelement~AttributeElement} Clone of this element.
*/
_clone( deep ) {
const cloned = super._clone( deep );
// Clone priority too.
cloned._priority = this._priority;
// And id too.
cloned._id = this._id;
return cloned;
}
}
/**
* Default attribute priority.
*
* @member {Number} module:engine/view/attributeelement~AttributeElement.DEFAULT_PRIORITY
*/
AttributeElement.DEFAULT_PRIORITY = DEFAULT_PRIORITY;
// Returns block {@link module:engine/view/filler~Filler filler} offset or `null` if block filler is not needed.
//
// @returns {Number|null} Block filler offset or `null` if block filler is not needed.
function getFillerOffset() {
// <b>foo</b> does not need filler.
if ( nonUiChildrenCount( this ) ) {
return null;
}
let element = this.parent;
// <p><b></b></p> needs filler -> <p><b><br></b></p>
while ( element && element.is( 'attributeElement' ) ) {
if ( nonUiChildrenCount( element ) > 1 ) {
return null;
}
element = element.parent;
}
if ( !element || nonUiChildrenCount( element ) > 1 ) {
return null;
}
// Render block filler at the end of element (after all ui elements).
return this.childCount;
}
// Returns total count of children that are not {@link module:engine/view/uielement~UIElement UIElements}.
//
// @param {module:engine/view/element~Element} element
// @returns {Number}
function nonUiChildrenCount( element ) {
return Array.from( element.getChildren() ).filter( element => !element.is( 'uiElement' ) ).length;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/view/containerelement.js":
/*!******************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/view/containerelement.js ***!
\******************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ContainerElement),
/* harmony export */ "getFillerOffset": () => (/* binding */ getFillerOffset)
/* harmony export */ });
/* harmony import */ var _element__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./element */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/element.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/view/containerelement
*/
/**
* Containers are elements which define document structure. They define boundaries for
* {@link module:engine/view/attributeelement~AttributeElement attributes}. They are mostly used for block elements like `<p>` or `<div>`.
*
* Editing engine does not define a fixed HTML DTD. This is why a feature developer needs to choose between various
* types (container element, {@link module:engine/view/attributeelement~AttributeElement attribute element},
* {@link module:engine/view/emptyelement~EmptyElement empty element}, etc) when developing a feature.
*
* The container element should be your default choice when writing a converter, unless:
*
* * this element represents a model text attribute (then use {@link module:engine/view/attributeelement~AttributeElement}),
* * this is an empty element like `<img>` (then use {@link module:engine/view/emptyelement~EmptyElement}),
* * this is a root element,
* * this is a nested editable element (then use {@link module:engine/view/editableelement~EditableElement}).
*
* To create a new container element instance use the
* {@link module:engine/view/downcastwriter~DowncastWriter#createContainerElement `DowncastWriter#createContainerElement()`}
* method.
*
* @extends module:engine/view/element~Element
*/
class ContainerElement extends _element__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* Creates a container element.
*
* @see module:engine/view/downcastwriter~DowncastWriter#createContainerElement
* @see module:engine/view/element~Element
* @protected
* @param {module:engine/view/document~Document} document The document instance to which this element belongs.
* @param {String} name Node name.
* @param {Object|Iterable} [attrs] Collection of attributes.
* @param {module:engine/view/node~Node|Iterable.<module:engine/view/node~Node>} [children]
* A list of nodes to be inserted into created element.
*/
constructor( document, name, attrs, children ) {
super( document, name, attrs, children );
/**
* Returns block {@link module:engine/view/filler filler} offset or `null` if block filler is not needed.
*
* @method #getFillerOffset
* @returns {Number|null} Block filler offset or `null` if block filler is not needed.
*/
this.getFillerOffset = getFillerOffset;
}
/**
* Checks whether this object is of the given.
*
* containerElement.is( 'containerElement' ); // -> true
* containerElement.is( 'element' ); // -> true
* containerElement.is( 'node' ); // -> true
* containerElement.is( 'view:containerElement' ); // -> true
* containerElement.is( 'view:element' ); // -> true
* containerElement.is( 'view:node' ); // -> true
*
* containerElement.is( 'model:element' ); // -> false
* containerElement.is( 'documentFragment' ); // -> false
*
* Assuming that the object being checked is a container element, you can also check its
* {@link module:engine/view/containerelement~ContainerElement#name name}:
*
* containerElement.is( 'element', 'div' ); // -> true if this is a div container element
* containerElement.is( 'contaienrElement', 'div' ); // -> same as above
* text.is( 'element', 'div' ); -> false
*
* {@link module:engine/view/node~Node#is Check the entire list of view objects} which implement the `is()` method.
*
* @param {String} type Type to check.
* @param {String} [name] Element name.
* @returns {Boolean}
*/
is( type, name = null ) {
if ( !name ) {
return type === 'containerElement' || type === 'view:containerElement' ||
// From super.is(). This is highly utilised method and cannot call super. See ckeditor/ckeditor5#6529.
type === 'element' || type === 'view:element' ||
type === 'node' || type === 'view:node';
} else {
return name === this.name && (
type === 'containerElement' || type === 'view:containerElement' ||
// From super.is(). This is highly utilised method and cannot call super. See ckeditor/ckeditor5#6529.
type === 'element' || type === 'view:element'
);
}
}
}
/**
* Returns block {@link module:engine/view/filler filler} offset or `null` if block filler is not needed.
*
* @returns {Number|null} Block filler offset or `null` if block filler is not needed.
*/
function getFillerOffset() {
const children = [ ...this.getChildren() ];
const lastChild = children[ this.childCount - 1 ];
// Block filler is required after a `<br>` if it's the last element in its container. See #1422.
if ( lastChild && lastChild.is( 'element', 'br' ) ) {
return this.childCount;
}
for ( const child of children ) {
// If there's any non-UI element – don't render the bogus.
if ( !child.is( 'uiElement' ) ) {
return null;
}
}
// If there are only UI elements – render the bogus at the end of the element.
return this.childCount;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/view/document.js":
/*!**********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/view/document.js ***!
\**********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ Document)
/* harmony export */ });
/* harmony import */ var _documentselection__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./documentselection */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/documentselection.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_collection__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/collection */ "./node_modules/@ckeditor/ckeditor5-utils/src/collection.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/mix */ "./node_modules/@ckeditor/ckeditor5-utils/src/mix.js");
/* harmony import */ var _observer_bubblingemittermixin__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./observer/bubblingemittermixin */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/bubblingemittermixin.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_observablemixin__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/observablemixin */ "./node_modules/@ckeditor/ckeditor5-utils/src/observablemixin.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/view/document
*/
// @if CK_DEBUG_ENGINE // const { logDocument } = require( '../dev-utils/utils' );
/**
* Document class creates an abstract layer over the content editable area, contains a tree of view elements and
* {@link module:engine/view/documentselection~DocumentSelection view selection} associated with this document.
*
* @mixes module:engine/view/observer/bubblingemittermixin~BubblingEmitterMixin
* @mixes module:utils/observablemixin~ObservableMixin
*/
class Document {
/**
* Creates a Document instance.
*
* @param {module:engine/view/stylesmap~StylesProcessor} stylesProcessor The styles processor instance.
*/
constructor( stylesProcessor ) {
/**
* Selection done on this document.
*
* @readonly
* @member {module:engine/view/documentselection~DocumentSelection} module:engine/view/document~Document#selection
*/
this.selection = new _documentselection__WEBPACK_IMPORTED_MODULE_0__["default"]();
/**
* Roots of the view tree. Collection of the {@link module:engine/view/element~Element view elements}.
*
* View roots are created as a result of binding between {@link module:engine/view/document~Document#roots} and
* {@link module:engine/model/document~Document#roots} and this is handled by
* {@link module:engine/controller/editingcontroller~EditingController}, so to create view root we need to create
* model root using {@link module:engine/model/document~Document#createRoot}.
*
* @readonly
* @member {module:utils/collection~Collection} module:engine/view/document~Document#roots
*/
this.roots = new _ckeditor_ckeditor5_utils_src_collection__WEBPACK_IMPORTED_MODULE_1__["default"]( { idProperty: 'rootName' } );
/**
* The styles processor instance used by this document when normalizing styles.
*
* @readonly
* @member {module:engine/view/stylesmap~StylesProcessor}
*/
this.stylesProcessor = stylesProcessor;
/**
* Defines whether document is in read-only mode.
*
* When document is read-ony then all roots are read-only as well and caret placed inside this root is hidden.
*
* @observable
* @member {Boolean} #isReadOnly
*/
this.set( 'isReadOnly', false );
/**
* True if document is focused.
*
* This property is updated by the {@link module:engine/view/observer/focusobserver~FocusObserver}.
* If the {@link module:engine/view/observer/focusobserver~FocusObserver} is disabled this property will not change.
*
* @readonly
* @observable
* @member {Boolean} module:engine/view/document~Document#isFocused
*/
this.set( 'isFocused', false );
/**
* `true` while the user is making a selection in the document (e.g. holding the mouse button and moving the cursor).
* When they stop selecting, the property goes back to `false`.
*
* This property is updated by the {@link module:engine/view/observer/selectionobserver~SelectionObserver}.
*
* @readonly
* @observable
* @member {Boolean} module:engine/view/document~Document#isSelecting
*/
this.set( 'isSelecting', false );
/**
* True if composition is in progress inside the document.
*
* This property is updated by the {@link module:engine/view/observer/compositionobserver~CompositionObserver}.
* If the {@link module:engine/view/observer/compositionobserver~CompositionObserver} is disabled this property will not change.
*
* @readonly
* @observable
* @member {Boolean} module:engine/view/document~Document#isComposing
*/
this.set( 'isComposing', false );
/**
* Post-fixer callbacks registered to the view document.
*
* @private
* @member {Set}
*/
this._postFixers = new Set();
}
/**
* Gets a {@link module:engine/view/document~Document#roots view root element} with the specified name. If the name is not
* specific "main" root is returned.
*
* @param {String} [name='main'] Name of the root.
* @returns {module:engine/view/rooteditableelement~RootEditableElement|null} The view root element with the specified name
* or null when there is no root of given name.
*/
getRoot( name = 'main' ) {
return this.roots.get( name );
}
/**
* Allows registering post-fixer callbacks. A post-fixers mechanism allows to update the view tree just before it is rendered
* to the DOM.
*
* Post-fixers are executed right after all changes from the outermost change block were applied but
* before the {@link module:engine/view/view~View#event:render render event} is fired. If a post-fixer callback made
* a change, it should return `true`. When this happens, all post-fixers are fired again to check if something else should
* not be fixed in the new document tree state.
*
* View post-fixers are useful when you want to apply some fixes whenever the view structure changes. Keep in mind that
* changes executed in a view post-fixer should not break model-view mapping.
*
* The types of changes which should be safe:
*
* * adding or removing attribute from elements,
* * changes inside of {@link module:engine/view/uielement~UIElement UI elements},
* * {@link module:engine/controller/editingcontroller~EditingController#reconvertItem marking some of the model elements to be
* re-converted}.
*
* Try to avoid changes which touch view structure:
*
* * you should not add or remove nor wrap or unwrap any view elements,
* * you should not change the editor data model in a view post-fixer.
*
* As a parameter, a post-fixer callback receives a {@link module:engine/view/downcastwriter~DowncastWriter downcast writer}.
*
* Typically, a post-fixer will look like this:
*
* editor.editing.view.document.registerPostFixer( writer => {
* if ( checkSomeCondition() ) {
* writer.doSomething();
*
* // Let other post-fixers know that something changed.
* return true;
* }
* } );
*
* Note that nothing happens right after you register a post-fixer (e.g. execute such a code in the console).
* That is because adding a post-fixer does not execute it.
* The post-fixer will be executed as soon as any change in the document needs to cause its rendering.
* If you want to re-render the editor's view after registering the post-fixer then you should do it manually by calling
* {@link module:engine/view/view~View#forceRender `view.forceRender()`}.
*
* If you need to register a callback which is executed when DOM elements are already updated,
* use {@link module:engine/view/view~View#event:render render event}.
*
* @param {Function} postFixer
*/
registerPostFixer( postFixer ) {
this._postFixers.add( postFixer );
}
/**
* Destroys this instance. Makes sure that all observers are destroyed and listeners removed.
*/
destroy() {
this.roots.map( root => root.destroy() );
this.stopListening();
}
/**
* Performs post-fixer loops. Executes post-fixer callbacks as long as none of them has done any changes to the model.
*
* @protected
* @param {module:engine/view/downcastwriter~DowncastWriter} writer
*/
_callPostFixers( writer ) {
let wasFixed = false;
do {
for ( const callback of this._postFixers ) {
wasFixed = callback( writer );
if ( wasFixed ) {
break;
}
}
} while ( wasFixed );
}
/**
* Event fired whenever document content layout changes. It is fired whenever content is
* {@link module:engine/view/view~View#event:render rendered}, but should be also fired by observers in case of
* other actions which may change layout, for instance when image loads.
*
* @event layoutChanged
*/
// @if CK_DEBUG_ENGINE // log( version ) {
// @if CK_DEBUG_ENGINE // logDocument( this, version );
// @if CK_DEBUG_ENGINE // }
}
(0,_ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_2__["default"])( Document, _observer_bubblingemittermixin__WEBPACK_IMPORTED_MODULE_3__["default"] );
(0,_ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_2__["default"])( Document, _ckeditor_ckeditor5_utils_src_observablemixin__WEBPACK_IMPORTED_MODULE_4__["default"] );
/**
* Enum representing type of the change.
*
* Possible values:
*
* * `children` - for child list changes,
* * `attributes` - for element attributes changes,
* * `text` - for text nodes changes.
*
* @typedef {String} module:engine/view/document~ChangeType
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/view/documentfragment.js":
/*!******************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/view/documentfragment.js ***!
\******************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ DocumentFragment)
/* harmony export */ });
/* harmony import */ var _text__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./text */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/text.js");
/* harmony import */ var _textproxy__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./textproxy */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/textproxy.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/mix */ "./node_modules/@ckeditor/ckeditor5-utils/src/mix.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_isiterable__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/isiterable */ "./node_modules/@ckeditor/ckeditor5-utils/src/isiterable.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_emittermixin__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/emittermixin */ "./node_modules/@ckeditor/ckeditor5-utils/src/emittermixin.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/view/documentfragment
*/
/**
* Document fragment.
*
* To create a new document fragment instance use the
* {@link module:engine/view/upcastwriter~UpcastWriter#createDocumentFragment `UpcastWriter#createDocumentFragment()`}
* method.
*/
class DocumentFragment {
/**
* Creates new DocumentFragment instance.
*
* @protected
* @param {module:engine/view/document~Document} document The document to which this document fragment belongs.
* @param {module:engine/view/node~Node|Iterable.<module:engine/view/node~Node>} [children]
* A list of nodes to be inserted into the created document fragment.
*/
constructor( document, children ) {
/**
* The document to which this document fragment belongs.
*
* @readonly
* @member {module:engine/view/document~Document}
*/
this.document = document;
/**
* Array of child nodes.
*
* @protected
* @member {Array.<module:engine/view/element~Element>} module:engine/view/documentfragment~DocumentFragment#_children
*/
this._children = [];
if ( children ) {
this._insertChild( 0, children );
}
}
/**
* Iterable interface.
*
* Iterates over nodes added to this document fragment.
*
* @returns {Iterable.<module:engine/view/node~Node>}
*/
[ Symbol.iterator ]() {
return this._children[ Symbol.iterator ]();
}
/**
* Number of child nodes in this document fragment.
*
* @readonly
* @type {Number}
*/
get childCount() {
return this._children.length;
}
/**
* Is `true` if there are no nodes inside this document fragment, `false` otherwise.
*
* @readonly
* @type {Boolean}
*/
get isEmpty() {
return this.childCount === 0;
}
/**
* Artificial root of `DocumentFragment`. Returns itself. Added for compatibility reasons.
*
* @readonly
* @type {module:engine/model/documentfragment~DocumentFragment}
*/
get root() {
return this;
}
/**
* Artificial parent of `DocumentFragment`. Returns `null`. Added for compatibility reasons.
*
* @readonly
* @type {null}
*/
get parent() {
return null;
}
/**
* Checks whether this object is of the given type.
*
* docFrag.is( 'documentFragment' ); // -> true
* docFrag.is( 'view:documentFragment' ); // -> true
*
* docFrag.is( 'model:documentFragment' ); // -> false
* docFrag.is( 'element' ); // -> false
* docFrag.is( 'node' ); // -> false
*
* {@link module:engine/view/node~Node#is Check the entire list of view objects} which implement the `is()` method.
*
* @param {String} type
* @returns {Boolean}
*/
is( type ) {
return type === 'documentFragment' || type === 'view:documentFragment';
}
/**
* {@link module:engine/view/documentfragment~DocumentFragment#_insertChild Insert} a child node or a list of child nodes at the end
* and sets the parent of these nodes to this fragment.
*
* @param {module:engine/view/item~Item|Iterable.<module:engine/view/item~Item>} items Items to be inserted.
* @returns {Number} Number of appended nodes.
*/
_appendChild( items ) {
return this._insertChild( this.childCount, items );
}
/**
* Gets child at the given index.
*
* @param {Number} index Index of child.
* @returns {module:engine/view/node~Node} Child node.
*/
getChild( index ) {
return this._children[ index ];
}
/**
* Gets index of the given child node. Returns `-1` if child node is not found.
*
* @param {module:engine/view/node~Node} node Child node.
* @returns {Number} Index of the child node.
*/
getChildIndex( node ) {
return this._children.indexOf( node );
}
/**
* Gets child nodes iterator.
*
* @returns {Iterable.<module:engine/view/node~Node>} Child nodes iterator.
*/
getChildren() {
return this._children[ Symbol.iterator ]();
}
/**
* Inserts a child node or a list of child nodes on the given index and sets the parent of these nodes to
* this fragment.
*
* @param {Number} index Position where nodes should be inserted.
* @param {module:engine/view/item~Item|Iterable.<module:engine/view/item~Item>} items Items to be inserted.
* @returns {Number} Number of inserted nodes.
*/
_insertChild( index, items ) {
this._fireChange( 'children', this );
let count = 0;
const nodes = normalize( this.document, items );
for ( const node of nodes ) {
// If node that is being added to this element is already inside another element, first remove it from the old parent.
if ( node.parent !== null ) {
node._remove();
}
node.parent = this;
this._children.splice( index, 0, node );
index++;
count++;
}
return count;
}
/**
* Removes number of child nodes starting at the given index and set the parent of these nodes to `null`.
*
* @param {Number} index Number of the first node to remove.
* @param {Number} [howMany=1] Number of nodes to remove.
* @returns {Array.<module:engine/view/node~Node>} The array of removed nodes.
*/
_removeChildren( index, howMany = 1 ) {
this._fireChange( 'children', this );
for ( let i = index; i < index + howMany; i++ ) {
this._children[ i ].parent = null;
}
return this._children.splice( index, howMany );
}
/**
* Fires `change` event with given type of the change.
*
* @private
* @param {module:engine/view/document~ChangeType} type Type of the change.
* @param {module:engine/view/node~Node} node Changed node.
* @fires module:engine/view/node~Node#change
*/
_fireChange( type, node ) {
this.fire( 'change:' + type, node );
}
// @if CK_DEBUG_ENGINE // printTree() {
// @if CK_DEBUG_ENGINE // let string = 'ViewDocumentFragment: [';
// @if CK_DEBUG_ENGINE // for ( const child of this.getChildren() ) {
// @if CK_DEBUG_ENGINE // if ( child.is( '$text' ) ) {
// @if CK_DEBUG_ENGINE // string += '\n' + '\t'.repeat( 1 ) + child.data;
// @if CK_DEBUG_ENGINE // } else {
// @if CK_DEBUG_ENGINE // string += '\n' + child.printTree( 1 );
// @if CK_DEBUG_ENGINE // }
// @if CK_DEBUG_ENGINE // }
// @if CK_DEBUG_ENGINE // string += '\n]';
// @if CK_DEBUG_ENGINE // return string;
// @if CK_DEBUG_ENGINE // }
// @if CK_DEBUG_ENGINE // logTree() {
// @if CK_DEBUG_ENGINE // console.log( this.printTree() );
// @if CK_DEBUG_ENGINE // }
}
(0,_ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_2__["default"])( DocumentFragment, _ckeditor_ckeditor5_utils_src_emittermixin__WEBPACK_IMPORTED_MODULE_4__["default"] );
// Converts strings to Text and non-iterables to arrays.
//
// @param {String|module:engine/view/item~Item|Iterable.<String|module:engine/view/item~Item>}
// @returns {Iterable.<module:engine/view/node~Node>}
function normalize( document, nodes ) {
// Separate condition because string is iterable.
if ( typeof nodes == 'string' ) {
return [ new _text__WEBPACK_IMPORTED_MODULE_0__["default"]( document, nodes ) ];
}
if ( !(0,_ckeditor_ckeditor5_utils_src_isiterable__WEBPACK_IMPORTED_MODULE_3__["default"])( nodes ) ) {
nodes = [ nodes ];
}
// Array.from to enable .map() on non-arrays.
return Array.from( nodes )
.map( node => {
if ( typeof node == 'string' ) {
return new _text__WEBPACK_IMPORTED_MODULE_0__["default"]( document, node );
}
if ( node instanceof _textproxy__WEBPACK_IMPORTED_MODULE_1__["default"] ) {
return new _text__WEBPACK_IMPORTED_MODULE_0__["default"]( document, node.data );
}
return node;
} );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/view/documentselection.js":
/*!*******************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/view/documentselection.js ***!
\*******************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ DocumentSelection)
/* harmony export */ });
/* harmony import */ var _selection__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./selection */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/selection.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/mix */ "./node_modules/@ckeditor/ckeditor5-utils/src/mix.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_emittermixin__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/emittermixin */ "./node_modules/@ckeditor/ckeditor5-utils/src/emittermixin.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/view/documentselection
*/
/**
* Class representing the document selection in the view.
*
* Its instance is available in {@link module:engine/view/document~Document#selection `Document#selection`}.
*
* It is similar to {@link module:engine/view/selection~Selection} but
* it has a read-only API and can be modified only by the writer available in
* the {@link module:engine/view/view~View#change `View#change()`} block
* (so via {@link module:engine/view/downcastwriter~DowncastWriter#setSelection `DowncastWriter#setSelection()`}).
*/
class DocumentSelection {
/**
* Creates new DocumentSelection instance.
*
* // Creates empty selection without ranges.
* const selection = new DocumentSelection();
*
* // Creates selection at the given range.
* const range = writer.createRange( start, end );
* const selection = new DocumentSelection( range );
*
* // Creates selection at the given ranges
* const ranges = [ writer.createRange( start1, end2 ), writer.createRange( start2, end2 ) ];
* const selection = new DocumentSelection( ranges );
*
* // Creates selection from the other selection.
* const otherSelection = writer.createSelection();
* const selection = new DocumentSelection( otherSelection );
*
* // Creates selection at the given position.
* const position = writer.createPositionAt( root, offset );
* const selection = new DocumentSelection( position );
*
* // Creates collapsed selection at the position of given item and offset.
* const paragraph = writer.createContainerElement( 'paragraph' );
* const selection = new DocumentSelection( paragraph, offset );
*
* // Creates a range inside an {@link module:engine/view/element~Element element} which starts before the
* // first child of that element and ends after the last child of that element.
* const selection = new DocumentSelection( paragraph, 'in' );
*
* // Creates a range on an {@link module:engine/view/item~Item item} which starts before the item and ends
* // just after the item.
* const selection = new DocumentSelection( paragraph, 'on' );
*
* `Selection`'s constructor allow passing additional options (`backward`, `fake` and `label`) as the last argument.
*
* // Creates backward selection.
* const selection = new DocumentSelection( range, { backward: true } );
*
* Fake selection does not render as browser native selection over selected elements and is hidden to the user.
* This way, no native selection UI artifacts are displayed to the user and selection over elements can be
* represented in other way, for example by applying proper CSS class.
*
* Additionally fake's selection label can be provided. It will be used to describe fake selection in DOM
* (and be properly handled by screen readers).
*
* // Creates fake selection with label.
* const selection = new DocumentSelection( range, { fake: true, label: 'foo' } );
*
* @param {module:engine/view/selection~Selectable} [selectable=null]
* @param {Number|'before'|'end'|'after'|'on'|'in'} [placeOrOffset] Offset or place when selectable is an `Item`.
* @param {Object} [options]
* @param {Boolean} [options.backward] Sets this selection instance to be backward.
* @param {Boolean} [options.fake] Sets this selection instance to be marked as `fake`.
* @param {String} [options.label] Label for the fake selection.
*/
constructor( selectable = null, placeOrOffset, options ) {
/**
* Selection is used internally (`DocumentSelection` is a proxy to that selection).
*
* @private
* @member {module:engine/view/selection~Selection}
*/
this._selection = new _selection__WEBPACK_IMPORTED_MODULE_0__["default"]();
// Delegate change event to be fired on DocumentSelection instance.
this._selection.delegate( 'change' ).to( this );
// Set selection data.
this._selection.setTo( selectable, placeOrOffset, options );
}
/**
* Returns true if selection instance is marked as `fake`.
*
* @see #_setTo
* @type {Boolean}
*/
get isFake() {
return this._selection.isFake;
}
/**
* Returns fake selection label.
*
* @see #_setTo
* @type {String}
*/
get fakeSelectionLabel() {
return this._selection.fakeSelectionLabel;
}
/**
* Selection anchor. Anchor may be described as a position where the selection starts. Together with
* {@link #focus focus} they define the direction of selection, which is important
* when expanding/shrinking selection. Anchor is always the start or end of the most recent added range.
* It may be a bit unintuitive when there are multiple ranges in selection.
*
* @see #focus
* @type {module:engine/view/position~Position}
*/
get anchor() {
return this._selection.anchor;
}
/**
* Selection focus. Focus is a position where the selection ends.
*
* @see #anchor
* @type {module:engine/view/position~Position}
*/
get focus() {
return this._selection.focus;
}
/**
* Returns whether the selection is collapsed. Selection is collapsed when there is exactly one range which is
* collapsed.
*
* @type {Boolean}
*/
get isCollapsed() {
return this._selection.isCollapsed;
}
/**
* Returns number of ranges in selection.
*
* @type {Number}
*/
get rangeCount() {
return this._selection.rangeCount;
}
/**
* Specifies whether the {@link #focus} precedes {@link #anchor}.
*
* @type {Boolean}
*/
get isBackward() {
return this._selection.isBackward;
}
/**
* {@link module:engine/view/editableelement~EditableElement EditableElement} instance that contains this selection, or `null`
* if the selection is not inside an editable element.
*
* @type {module:engine/view/editableelement~EditableElement|null}
*/
get editableElement() {
return this._selection.editableElement;
}
/**
* Used for the compatibility with the {@link module:engine/view/selection~Selection#isEqual} method.
*
* @protected
*/
get _ranges() {
return this._selection._ranges;
}
/**
* Returns an iterable that contains copies of all ranges added to the selection.
*
* @returns {Iterable.<module:engine/view/range~Range>}
*/
* getRanges() {
yield* this._selection.getRanges();
}
/**
* Returns copy of the first range in the selection. First range is the one which
* {@link module:engine/view/range~Range#start start} position {@link module:engine/view/position~Position#isBefore is before} start
* position of all other ranges (not to confuse with the first range added to the selection).
* Returns `null` if no ranges are added to selection.
*
* @returns {module:engine/view/range~Range|null}
*/
getFirstRange() {
return this._selection.getFirstRange();
}
/**
* Returns copy of the last range in the selection. Last range is the one which {@link module:engine/view/range~Range#end end}
* position {@link module:engine/view/position~Position#isAfter is after} end position of all other ranges (not to confuse
* with the last range added to the selection). Returns `null` if no ranges are added to selection.
*
* @returns {module:engine/view/range~Range|null}
*/
getLastRange() {
return this._selection.getLastRange();
}
/**
* Returns copy of the first position in the selection. First position is the position that
* {@link module:engine/view/position~Position#isBefore is before} any other position in the selection ranges.
* Returns `null` if no ranges are added to selection.
*
* @returns {module:engine/view/position~Position|null}
*/
getFirstPosition() {
return this._selection.getFirstPosition();
}
/**
* Returns copy of the last position in the selection. Last position is the position that
* {@link module:engine/view/position~Position#isAfter is after} any other position in the selection ranges.
* Returns `null` if no ranges are added to selection.
*
* @returns {module:engine/view/position~Position|null}
*/
getLastPosition() {
return this._selection.getLastPosition();
}
/**
* Returns the selected element. {@link module:engine/view/element~Element Element} is considered as selected if there is only
* one range in the selection, and that range contains exactly one element.
* Returns `null` if there is no selected element.
*
* @returns {module:engine/view/element~Element|null}
*/
getSelectedElement() {
return this._selection.getSelectedElement();
}
/**
* Checks whether, this selection is equal to given selection. Selections are equal if they have same directions,
* same number of ranges and all ranges from one selection equal to a range from other selection.
*
* @param {module:engine/view/selection~Selection|module:engine/view/documentselection~DocumentSelection} otherSelection
* Selection to compare with.
* @returns {Boolean} `true` if selections are equal, `false` otherwise.
*/
isEqual( otherSelection ) {
return this._selection.isEqual( otherSelection );
}
/**
* Checks whether this selection is similar to given selection. Selections are similar if they have same directions, same
* number of ranges, and all {@link module:engine/view/range~Range#getTrimmed trimmed} ranges from one selection are
* equal to any trimmed range from other selection.
*
* @param {module:engine/view/selection~Selection|module:engine/view/documentselection~DocumentSelection} otherSelection
* Selection to compare with.
* @returns {Boolean} `true` if selections are similar, `false` otherwise.
*/
isSimilar( otherSelection ) {
return this._selection.isSimilar( otherSelection );
}
/**
* Checks whether this object is of the given type.
*
* docSelection.is( 'selection' ); // -> true
* docSelection.is( 'documentSelection' ); // -> true
* docSelection.is( 'view:selection' ); // -> true
* docSelection.is( 'view:documentSelection' ); // -> true
*
* docSelection.is( 'model:documentSelection' ); // -> false
* docSelection.is( 'element' ); // -> false
* docSelection.is( 'node' ); // -> false
*
* {@link module:engine/view/node~Node#is Check the entire list of view objects} which implement the `is()` method.
*
* @param {String} type
* @returns {Boolean}
*/
is( type ) {
return type === 'selection' ||
type == 'documentSelection' ||
type == 'view:selection' ||
type == 'view:documentSelection';
}
/**
* Sets this selection's ranges and direction to the specified location based on the given
* {@link module:engine/view/selection~Selectable selectable}.
*
* // Sets selection to the given range.
* const range = writer.createRange( start, end );
* documentSelection._setTo( range );
*
* // Sets selection to given ranges.
* const ranges = [ writer.createRange( start1, end2 ), writer.createRange( start2, end2 ) ];
* documentSelection._setTo( range );
*
* // Sets selection to the other selection.
* const otherSelection = writer.createSelection();
* documentSelection._setTo( otherSelection );
*
* // Sets collapsed selection at the given position.
* const position = writer.createPositionAt( root, offset );
* documentSelection._setTo( position );
*
* // Sets collapsed selection at the position of given item and offset.
* documentSelection._setTo( paragraph, offset );
*
* Creates a range inside an {@link module:engine/view/element~Element element} which starts before the first child of
* that element and ends after the last child of that element.
*
* documentSelection._setTo( paragraph, 'in' );
*
* Creates a range on an {@link module:engine/view/item~Item item} which starts before the item and ends just after the item.
*
* documentSelection._setTo( paragraph, 'on' );
*
* // Clears selection. Removes all ranges.
* documentSelection._setTo( null );
*
* `Selection#_setTo()` method allow passing additional options (`backward`, `fake` and `label`) as the last argument.
*
* // Sets selection as backward.
* documentSelection._setTo( range, { backward: true } );
*
* Fake selection does not render as browser native selection over selected elements and is hidden to the user.
* This way, no native selection UI artifacts are displayed to the user and selection over elements can be
* represented in other way, for example by applying proper CSS class.
*
* Additionally fake's selection label can be provided. It will be used to des cribe fake selection in DOM
* (and be properly handled by screen readers).
*
* // Creates fake selection with label.
* documentSelection._setTo( range, { fake: true, label: 'foo' } );
*
* @protected
* @fires change
* @param {module:engine/view/selection~Selectable} selectable
* @param {Number|'before'|'end'|'after'|'on'|'in'} [placeOrOffset] Sets place or offset of the selection.
* @param {Object} [options]
* @param {Boolean} [options.backward] Sets this selection instance to be backward.
* @param {Boolean} [options.fake] Sets this selection instance to be marked as `fake`.
* @param {String} [options.label] Label for the fake selection.
*/
_setTo( selectable, placeOrOffset, options ) {
this._selection.setTo( selectable, placeOrOffset, options );
}
/**
* Moves {@link #focus} to the specified location.
*
* The location can be specified in the same form as {@link module:engine/view/view~View#createPositionAt view.createPositionAt()}
* parameters.
*
* @protected
* @fires change
* @param {module:engine/view/item~Item|module:engine/view/position~Position} itemOrPosition
* @param {Number|'end'|'before'|'after'} [offset] Offset or one of the flags. Used only when
* first parameter is a {@link module:engine/view/item~Item view item}.
*/
_setFocus( itemOrPosition, offset ) {
this._selection.setFocus( itemOrPosition, offset );
}
/**
* Fired whenever selection ranges are changed through {@link ~DocumentSelection Selection API}.
*
* @event change
*/
}
(0,_ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_1__["default"])( DocumentSelection, _ckeditor_ckeditor5_utils_src_emittermixin__WEBPACK_IMPORTED_MODULE_2__["default"] );
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/view/domconverter.js":
/*!**************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/view/domconverter.js ***!
\**************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ DomConverter)
/* harmony export */ });
/* harmony import */ var _text__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./text */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/text.js");
/* harmony import */ var _element__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./element */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/element.js");
/* harmony import */ var _uielement__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./uielement */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/uielement.js");
/* harmony import */ var _position__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./position */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/position.js");
/* harmony import */ var _range__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./range */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/range.js");
/* harmony import */ var _selection__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./selection */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/selection.js");
/* harmony import */ var _documentfragment__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./documentfragment */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/documentfragment.js");
/* harmony import */ var _treewalker__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./treewalker */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/treewalker.js");
/* harmony import */ var _matcher__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./matcher */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/matcher.js");
/* harmony import */ var _filler__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./filler */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/filler.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_dom_global__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/dom/global */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/global.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_dom_indexof__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/dom/indexof */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/indexof.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_dom_getancestors__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/dom/getancestors */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/getancestors.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_dom_istext__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/dom/istext */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/istext.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_dom_iscomment__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/dom/iscomment */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/iscomment.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/view/domconverter
*/
/* globals document, Node, NodeFilter, DOMParser, Text */
const BR_FILLER_REF = (0,_filler__WEBPACK_IMPORTED_MODULE_9__.BR_FILLER)( document ); // eslint-disable-line new-cap
const NBSP_FILLER_REF = (0,_filler__WEBPACK_IMPORTED_MODULE_9__.NBSP_FILLER)( document ); // eslint-disable-line new-cap
const MARKED_NBSP_FILLER_REF = (0,_filler__WEBPACK_IMPORTED_MODULE_9__.MARKED_NBSP_FILLER)( document ); // eslint-disable-line new-cap
const UNSAFE_ATTRIBUTE_NAME_PREFIX = 'data-ck-unsafe-attribute-';
const UNSAFE_ELEMENT_REPLACEMENT_ATTRIBUTE = 'data-ck-unsafe-element';
const UNSAFE_ELEMENTS = [ 'script', 'style' ];
/**
* `DomConverter` is a set of tools to do transformations between DOM nodes and view nodes. It also handles
* {@link module:engine/view/domconverter~DomConverter#bindElements bindings} between these nodes.
*
* An instance of the DOM converter is available under
* {@link module:engine/view/view~View#domConverter `editor.editing.view.domConverter`}.
*
* The DOM converter does not check which nodes should be rendered (use {@link module:engine/view/renderer~Renderer}), does not keep the
* state of a tree nor keeps the synchronization between the tree view and the DOM tree (use {@link module:engine/view/document~Document}).
*
* The DOM converter keeps DOM elements to view element bindings, so when the converter gets destroyed, the bindings are lost.
* Two converters will keep separate binding maps, so one tree view can be bound with two DOM trees.
*/
class DomConverter {
/**
* Creates a DOM converter.
*
* @param {module:engine/view/document~Document} document The view document instance.
* @param {Object} options An object with configuration options.
* @param {module:engine/view/filler~BlockFillerMode} [options.blockFillerMode] The type of the block filler to use.
* Default value depends on the options.renderingMode:
* 'nbsp' when options.renderingMode == 'data',
* 'br' when options.renderingMode == 'editing'.
* @param {'data'|'editing'} [options.renderingMode='editing'] Whether to leave the View-to-DOM conversion result unchanged
* or improve editing experience by filtering out interactive data.
*/
constructor( document, options = {} ) {
/**
* @readonly
* @type {module:engine/view/document~Document}
*/
this.document = document;
/**
* Whether to leave the View-to-DOM conversion result unchanged or improve editing experience by filtering out interactive data.
*
* @member {'data'|'editing'} module:engine/view/domconverter~DomConverter#renderingMode
*/
this.renderingMode = options.renderingMode || 'editing';
/**
* The mode of a block filler used by the DOM converter.
*
* @member {'br'|'nbsp'|'markedNbsp'} module:engine/view/domconverter~DomConverter#blockFillerMode
*/
this.blockFillerMode = options.blockFillerMode || ( this.renderingMode === 'editing' ? 'br' : 'nbsp' );
/**
* Elements which are considered pre-formatted elements.
*
* @readonly
* @member {Array.<String>} module:engine/view/domconverter~DomConverter#preElements
*/
this.preElements = [ 'pre' ];
/**
* Elements which are considered block elements (and hence should be filled with a
* {@link #isBlockFiller block filler}).
*
* Whether an element is considered a block element also affects handling of trailing whitespaces.
*
* You can extend this array if you introduce support for block elements which are not yet recognized here.
*
* @readonly
* @member {Array.<String>} module:engine/view/domconverter~DomConverter#blockElements
*/
this.blockElements = [
'address', 'article', 'aside', 'blockquote', 'caption', 'center', 'dd', 'details', 'dir', 'div',
'dl', 'dt', 'fieldset', 'figcaption', 'figure', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header',
'hgroup', 'legend', 'li', 'main', 'menu', 'nav', 'ol', 'p', 'pre', 'section', 'summary', 'table', 'tbody',
'td', 'tfoot', 'th', 'thead', 'tr', 'ul'
];
/**
* A list of elements that exist inline (in text) but their inner structure cannot be edited because
* of the way they are rendered by the browser. They are mostly HTML form elements but there are other
* elements such as `<img>` or `<iframe>` that also have non-editable children or no children whatsoever.
*
* Whether an element is considered an inline object has an impact on white space rendering (trimming)
* around (and inside of it). In short, white spaces in text nodes next to inline objects are not trimmed.
*
* You can extend this array if you introduce support for inline object elements which are not yet recognized here.
*
* @readonly
* @member {Array.<String>} module:engine/view/domconverter~DomConverter#inlineObjectElements
*/
this.inlineObjectElements = [
'object', 'iframe', 'input', 'button', 'textarea', 'select', 'option', 'video', 'embed', 'audio', 'img', 'canvas'
];
/**
* The DOM-to-view mapping.
*
* @private
* @member {WeakMap} module:engine/view/domconverter~DomConverter#_domToViewMapping
*/
this._domToViewMapping = new WeakMap();
/**
* The view-to-DOM mapping.
*
* @private
* @member {WeakMap} module:engine/view/domconverter~DomConverter#_viewToDomMapping
*/
this._viewToDomMapping = new WeakMap();
/**
* Holds the mapping between fake selection containers and corresponding view selections.
*
* @private
* @member {WeakMap} module:engine/view/domconverter~DomConverter#_fakeSelectionMapping
*/
this._fakeSelectionMapping = new WeakMap();
/**
* Matcher for view elements whose content should be treated as raw data
* and not processed during the conversion from DOM nodes to view elements.
*
* @private
* @type {module:engine/view/matcher~Matcher}
*/
this._rawContentElementMatcher = new _matcher__WEBPACK_IMPORTED_MODULE_8__["default"]();
/**
* A set of encountered raw content DOM nodes. It is used for preventing left trimming of the following text node.
*
* @private
* @type {WeakSet.<Node>}
*/
this._encounteredRawContentDomNodes = new WeakSet();
}
/**
* Binds a given DOM element that represents fake selection to a **position** of a
* {@link module:engine/view/documentselection~DocumentSelection document selection}.
* Document selection copy is stored and can be retrieved by the
* {@link module:engine/view/domconverter~DomConverter#fakeSelectionToView} method.
*
* @param {HTMLElement} domElement
* @param {module:engine/view/documentselection~DocumentSelection} viewDocumentSelection
*/
bindFakeSelection( domElement, viewDocumentSelection ) {
this._fakeSelectionMapping.set( domElement, new _selection__WEBPACK_IMPORTED_MODULE_5__["default"]( viewDocumentSelection ) );
}
/**
* Returns a {@link module:engine/view/selection~Selection view selection} instance corresponding to a given
* DOM element that represents fake selection. Returns `undefined` if binding to the given DOM element does not exist.
*
* @param {HTMLElement} domElement
* @returns {module:engine/view/selection~Selection|undefined}
*/
fakeSelectionToView( domElement ) {
return this._fakeSelectionMapping.get( domElement );
}
/**
* Binds DOM and view elements, so it will be possible to get corresponding elements using
* {@link module:engine/view/domconverter~DomConverter#mapDomToView} and
* {@link module:engine/view/domconverter~DomConverter#mapViewToDom}.
*
* @param {HTMLElement} domElement The DOM element to bind.
* @param {module:engine/view/element~Element} viewElement The view element to bind.
*/
bindElements( domElement, viewElement ) {
this._domToViewMapping.set( domElement, viewElement );
this._viewToDomMapping.set( viewElement, domElement );
}
/**
* Unbinds a given DOM element from the view element it was bound to. Unbinding is deep, meaning that all children of
* the DOM element will be unbound too.
*
* @param {HTMLElement} domElement The DOM element to unbind.
*/
unbindDomElement( domElement ) {
const viewElement = this._domToViewMapping.get( domElement );
if ( viewElement ) {
this._domToViewMapping.delete( domElement );
this._viewToDomMapping.delete( viewElement );
for ( const child of domElement.childNodes ) {
this.unbindDomElement( child );
}
}
}
/**
* Binds DOM and view document fragments, so it will be possible to get corresponding document fragments using
* {@link module:engine/view/domconverter~DomConverter#mapDomToView} and
* {@link module:engine/view/domconverter~DomConverter#mapViewToDom}.
*
* @param {DocumentFragment} domFragment The DOM document fragment to bind.
* @param {module:engine/view/documentfragment~DocumentFragment} viewFragment The view document fragment to bind.
*/
bindDocumentFragments( domFragment, viewFragment ) {
this._domToViewMapping.set( domFragment, viewFragment );
this._viewToDomMapping.set( viewFragment, domFragment );
}
/**
* Decides whether a given pair of attribute key and value should be passed further down the pipeline.
*
* @param {String} attributeKey
* @param {String} attributeValue
* @param {String} elementName Element name in lower case.
* @returns {Boolean}
*/
shouldRenderAttribute( attributeKey, attributeValue, elementName ) {
if ( this.renderingMode === 'data' ) {
return true;
}
attributeKey = attributeKey.toLowerCase();
if ( attributeKey.startsWith( 'on' ) ) {
return false;
}
if (
attributeKey === 'srcdoc' &&
attributeValue.match( /\bon\S+\s*=|javascript:|<\s*\/*script/i )
) {
return false;
}
if (
elementName === 'img' &&
( attributeKey === 'src' || attributeKey === 'srcset' )
) {
return true;
}
if ( elementName === 'source' && attributeKey === 'srcset' ) {
return true;
}
if ( attributeValue.match( /^\s*(javascript:|data:(image\/svg|text\/x?html))/i ) ) {
return false;
}
return true;
}
/**
* Set `domElement`'s content using provided `html` argument. Apply necessary filtering for the editing pipeline.
*
* @param {Element} domElement DOM element that should have `html` set as its content.
* @param {String} html Textual representation of the HTML that will be set on `domElement`.
*/
setContentOf( domElement, html ) {
// For data pipeline we pass the HTML as-is.
if ( this.renderingMode === 'data' ) {
domElement.innerHTML = html;
return;
}
const document = new DOMParser().parseFromString( html, 'text/html' );
const fragment = document.createDocumentFragment();
const bodyChildNodes = document.body.childNodes;
while ( bodyChildNodes.length > 0 ) {
fragment.appendChild( bodyChildNodes[ 0 ] );
}
const treeWalker = document.createTreeWalker( fragment, NodeFilter.SHOW_ELEMENT );
const nodes = [];
let currentNode;
// eslint-disable-next-line no-cond-assign
while ( currentNode = treeWalker.nextNode() ) {
nodes.push( currentNode );
}
for ( const currentNode of nodes ) {
// Go through nodes to remove those that are prohibited in editing pipeline.
for ( const attributeName of currentNode.getAttributeNames() ) {
this.setDomElementAttribute( currentNode, attributeName, currentNode.getAttribute( attributeName ) );
}
const elementName = currentNode.tagName.toLowerCase();
// There are certain nodes, that should be renamed to <span> in editing pipeline.
if ( this._shouldRenameElement( elementName ) ) {
_logUnsafeElement( elementName );
currentNode.replaceWith( this._createReplacementDomElement( elementName, currentNode ) );
}
}
// Empty the target element.
while ( domElement.firstChild ) {
domElement.firstChild.remove();
}
domElement.append( fragment );
}
/**
* Converts the view to the DOM. For all text nodes, not bound elements and document fragments new items will
* be created. For bound elements and document fragments the method will return corresponding items.
*
* @param {module:engine/view/node~Node|module:engine/view/documentfragment~DocumentFragment} viewNode
* View node or document fragment to transform.
* @param {Document} domDocument Document which will be used to create DOM nodes.
* @param {Object} [options] Conversion options.
* @param {Boolean} [options.bind=false] Determines whether new elements will be bound.
* @param {Boolean} [options.withChildren=true] If `true`, node's and document fragment's children will be converted too.
* @returns {Node|DocumentFragment} Converted node or DocumentFragment.
*/
viewToDom( viewNode, domDocument, options = {} ) {
if ( viewNode.is( '$text' ) ) {
const textData = this._processDataFromViewText( viewNode );
return domDocument.createTextNode( textData );
} else {
if ( this.mapViewToDom( viewNode ) ) {
return this.mapViewToDom( viewNode );
}
let domElement;
if ( viewNode.is( 'documentFragment' ) ) {
// Create DOM document fragment.
domElement = domDocument.createDocumentFragment();
if ( options.bind ) {
this.bindDocumentFragments( domElement, viewNode );
}
} else if ( viewNode.is( 'uiElement' ) ) {
if ( viewNode.name === '$comment' ) {
domElement = domDocument.createComment( viewNode.getCustomProperty( '$rawContent' ) );
} else {
// UIElement has its own render() method (see #799).
domElement = viewNode.render( domDocument, this );
}
if ( options.bind ) {
this.bindElements( domElement, viewNode );
}
return domElement;
} else {
// Create DOM element.
if ( this._shouldRenameElement( viewNode.name ) ) {
_logUnsafeElement( viewNode.name );
domElement = this._createReplacementDomElement( viewNode.name );
} else if ( viewNode.hasAttribute( 'xmlns' ) ) {
domElement = domDocument.createElementNS( viewNode.getAttribute( 'xmlns' ), viewNode.name );
} else {
domElement = domDocument.createElement( viewNode.name );
}
// RawElement take care of their children in RawElement#render() method which can be customized
// (see https://github.com/ckeditor/ckeditor5/issues/4469).
if ( viewNode.is( 'rawElement' ) ) {
viewNode.render( domElement, this );
}
if ( options.bind ) {
this.bindElements( domElement, viewNode );
}
// Copy element's attributes.
for ( const key of viewNode.getAttributeKeys() ) {
this.setDomElementAttribute( domElement, key, viewNode.getAttribute( key ), viewNode );
}
}
if ( options.withChildren !== false ) {
for ( const child of this.viewChildrenToDom( viewNode, domDocument, options ) ) {
domElement.appendChild( child );
}
}
return domElement;
}
}
/**
* Sets the attribute on a DOM element.
*
* **Note**: To remove the attribute, use {@link #removeDomElementAttribute}.
*
* @param {HTMLElement} domElement The DOM element the attribute should be set on.
* @param {String} key The name of the attribute.
* @param {String} value The value of the attribute.
* @param {module:engine/view/element~Element} [relatedViewElement] The view element related to the `domElement` (if there is any).
* It helps decide whether the attribute set is unsafe. For instance, view elements created via the
* {@link module:engine/view/downcastwriter~DowncastWriter} methods can allow certain attributes that would normally be filtered out.
*/
setDomElementAttribute( domElement, key, value, relatedViewElement = null ) {
const shouldRenderAttribute = this.shouldRenderAttribute( key, value, domElement.tagName.toLowerCase() ) ||
relatedViewElement && relatedViewElement.shouldRenderUnsafeAttribute( key );
if ( !shouldRenderAttribute ) {
(0,_ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_11__.logWarning)( 'domconverter-unsafe-attribute-detected', { domElement, key, value } );
}
// The old value was safe but the new value is unsafe.
if ( domElement.hasAttribute( key ) && !shouldRenderAttribute ) {
domElement.removeAttribute( key );
}
// The old value was unsafe (but prefixed) but the new value will be safe (will be unprefixed).
else if ( domElement.hasAttribute( UNSAFE_ATTRIBUTE_NAME_PREFIX + key ) && shouldRenderAttribute ) {
domElement.removeAttribute( UNSAFE_ATTRIBUTE_NAME_PREFIX + key );
}
// If the attribute should not be rendered, rename it (instead of removing) to give developers some idea of what
// is going on (https://github.com/ckeditor/ckeditor5/issues/10801).
domElement.setAttribute( shouldRenderAttribute ? key : UNSAFE_ATTRIBUTE_NAME_PREFIX + key, value );
}
/**
* Removes an attribute from a DOM element.
*
* **Note**: To set the attribute, use {@link #setDomElementAttribute}.
*
* @param {HTMLElement} domElement The DOM element the attribute should be removed from.
* @param {String} key The name of the attribute.
*/
removeDomElementAttribute( domElement, key ) {
// See #_createReplacementDomElement() to learn what this is.
if ( key == UNSAFE_ELEMENT_REPLACEMENT_ATTRIBUTE ) {
return;
}
domElement.removeAttribute( key );
// See setDomElementAttribute() to learn what this is.
domElement.removeAttribute( UNSAFE_ATTRIBUTE_NAME_PREFIX + key );
}
/**
* Converts children of the view element to DOM using the
* {@link module:engine/view/domconverter~DomConverter#viewToDom} method.
* Additionally, this method adds block {@link module:engine/view/filler filler} to the list of children, if needed.
*
* @param {module:engine/view/element~Element|module:engine/view/documentfragment~DocumentFragment} viewElement Parent view element.
* @param {Document} domDocument Document which will be used to create DOM nodes.
* @param {Object} options See {@link module:engine/view/domconverter~DomConverter#viewToDom} options parameter.
* @returns {Iterable.<Node>} DOM nodes.
*/
* viewChildrenToDom( viewElement, domDocument, options = {} ) {
const fillerPositionOffset = viewElement.getFillerOffset && viewElement.getFillerOffset();
let offset = 0;
for ( const childView of viewElement.getChildren() ) {
if ( fillerPositionOffset === offset ) {
yield this._getBlockFiller( domDocument );
}
yield this.viewToDom( childView, domDocument, options );
offset++;
}
if ( fillerPositionOffset === offset ) {
yield this._getBlockFiller( domDocument );
}
}
/**
* Converts view {@link module:engine/view/range~Range} to DOM range.
* Inline and block {@link module:engine/view/filler fillers} are handled during the conversion.
*
* @param {module:engine/view/range~Range} viewRange View range.
* @returns {Range} DOM range.
*/
viewRangeToDom( viewRange ) {
const domStart = this.viewPositionToDom( viewRange.start );
const domEnd = this.viewPositionToDom( viewRange.end );
const domRange = document.createRange();
domRange.setStart( domStart.parent, domStart.offset );
domRange.setEnd( domEnd.parent, domEnd.offset );
return domRange;
}
/**
* Converts view {@link module:engine/view/position~Position} to DOM parent and offset.
*
* Inline and block {@link module:engine/view/filler fillers} are handled during the conversion.
* If the converted position is directly before inline filler it is moved inside the filler.
*
* @param {module:engine/view/position~Position} viewPosition View position.
* @returns {Object|null} position DOM position or `null` if view position could not be converted to DOM.
* @returns {Node} position.parent DOM position parent.
* @returns {Number} position.offset DOM position offset.
*/
viewPositionToDom( viewPosition ) {
const viewParent = viewPosition.parent;
if ( viewParent.is( '$text' ) ) {
const domParent = this.findCorrespondingDomText( viewParent );
if ( !domParent ) {
// Position is in a view text node that has not been rendered to DOM yet.
return null;
}
let offset = viewPosition.offset;
if ( (0,_filler__WEBPACK_IMPORTED_MODULE_9__.startsWithFiller)( domParent ) ) {
offset += _filler__WEBPACK_IMPORTED_MODULE_9__.INLINE_FILLER_LENGTH;
}
return { parent: domParent, offset };
} else {
// viewParent is instance of ViewElement.
let domParent, domBefore, domAfter;
if ( viewPosition.offset === 0 ) {
domParent = this.mapViewToDom( viewParent );
if ( !domParent ) {
// Position is in a view element that has not been rendered to DOM yet.
return null;
}
domAfter = domParent.childNodes[ 0 ];
} else {
const nodeBefore = viewPosition.nodeBefore;
domBefore = nodeBefore.is( '$text' ) ?
this.findCorrespondingDomText( nodeBefore ) :
this.mapViewToDom( viewPosition.nodeBefore );
if ( !domBefore ) {
// Position is after a view element that has not been rendered to DOM yet.
return null;
}
domParent = domBefore.parentNode;
domAfter = domBefore.nextSibling;
}
// If there is an inline filler at position return position inside the filler. We should never return
// the position before the inline filler.
if ( (0,_ckeditor_ckeditor5_utils_src_dom_istext__WEBPACK_IMPORTED_MODULE_14__["default"])( domAfter ) && (0,_filler__WEBPACK_IMPORTED_MODULE_9__.startsWithFiller)( domAfter ) ) {
return { parent: domAfter, offset: _filler__WEBPACK_IMPORTED_MODULE_9__.INLINE_FILLER_LENGTH };
}
const offset = domBefore ? (0,_ckeditor_ckeditor5_utils_src_dom_indexof__WEBPACK_IMPORTED_MODULE_12__["default"])( domBefore ) + 1 : 0;
return { parent: domParent, offset };
}
}
/**
* Converts DOM to view. For all text nodes, not bound elements and document fragments new items will
* be created. For bound elements and document fragments function will return corresponding items. For
* {@link module:engine/view/filler fillers} `null` will be returned.
* For all DOM elements rendered by {@link module:engine/view/uielement~UIElement} that UIElement will be returned.
*
* @param {Node|DocumentFragment} domNode DOM node or document fragment to transform.
* @param {Object} [options] Conversion options.
* @param {Boolean} [options.bind=false] Determines whether new elements will be bound.
* @param {Boolean} [options.withChildren=true] If `true`, node's and document fragment's children will be converted too.
* @param {Boolean} [options.keepOriginalCase=false] If `false`, node's tag name will be converted to lower case.
* @param {Boolean} [options.skipComments=false] If `false`, comment nodes will be converted to `$comment`
* {@link module:engine/view/uielement~UIElement view UI elements}.
* @returns {module:engine/view/node~Node|module:engine/view/documentfragment~DocumentFragment|null} Converted node or document fragment
* or `null` if DOM node is a {@link module:engine/view/filler filler} or the given node is an empty text node.
*/
domToView( domNode, options = {} ) {
if ( this.isBlockFiller( domNode ) ) {
return null;
}
// When node is inside a UIElement or a RawElement return that parent as it's view representation.
const hostElement = this.getHostViewElement( domNode );
if ( hostElement ) {
return hostElement;
}
if ( (0,_ckeditor_ckeditor5_utils_src_dom_iscomment__WEBPACK_IMPORTED_MODULE_15__["default"])( domNode ) && options.skipComments ) {
return null;
}
if ( (0,_ckeditor_ckeditor5_utils_src_dom_istext__WEBPACK_IMPORTED_MODULE_14__["default"])( domNode ) ) {
if ( (0,_filler__WEBPACK_IMPORTED_MODULE_9__.isInlineFiller)( domNode ) ) {
return null;
} else {
const textData = this._processDataFromDomText( domNode );
return textData === '' ? null : new _text__WEBPACK_IMPORTED_MODULE_0__["default"]( this.document, textData );
}
} else {
if ( this.mapDomToView( domNode ) ) {
return this.mapDomToView( domNode );
}
let viewElement;
if ( this.isDocumentFragment( domNode ) ) {
// Create view document fragment.
viewElement = new _documentfragment__WEBPACK_IMPORTED_MODULE_6__["default"]( this.document );
if ( options.bind ) {
this.bindDocumentFragments( domNode, viewElement );
}
} else {
// Create view element.
viewElement = this._createViewElement( domNode, options );
if ( options.bind ) {
this.bindElements( domNode, viewElement );
}
// Copy element's attributes.
const attrs = domNode.attributes;
if ( attrs ) {
for ( let i = attrs.length - 1; i >= 0; i-- ) {
viewElement._setAttribute( attrs[ i ].name, attrs[ i ].value );
}
}
// Treat this element's content as a raw data if it was registered as such.
// Comment node is also treated as an element with raw data.
if ( this._isViewElementWithRawContent( viewElement, options ) || (0,_ckeditor_ckeditor5_utils_src_dom_iscomment__WEBPACK_IMPORTED_MODULE_15__["default"])( domNode ) ) {
const rawContent = (0,_ckeditor_ckeditor5_utils_src_dom_iscomment__WEBPACK_IMPORTED_MODULE_15__["default"])( domNode ) ? domNode.data : domNode.innerHTML;
viewElement._setCustomProperty( '$rawContent', rawContent );
// Store a DOM node to prevent left trimming of the following text node.
this._encounteredRawContentDomNodes.add( domNode );
return viewElement;
}
}
if ( options.withChildren !== false ) {
for ( const child of this.domChildrenToView( domNode, options ) ) {
viewElement._appendChild( child );
}
}
return viewElement;
}
}
/**
* Converts children of the DOM element to view nodes using
* the {@link module:engine/view/domconverter~DomConverter#domToView} method.
* Additionally this method omits block {@link module:engine/view/filler filler}, if it exists in the DOM parent.
*
* @param {HTMLElement} domElement Parent DOM element.
* @param {Object} options See {@link module:engine/view/domconverter~DomConverter#domToView} options parameter.
* @returns {Iterable.<module:engine/view/node~Node>} View nodes.
*/
* domChildrenToView( domElement, options = {} ) {
for ( let i = 0; i < domElement.childNodes.length; i++ ) {
const domChild = domElement.childNodes[ i ];
const viewChild = this.domToView( domChild, options );
if ( viewChild !== null ) {
yield viewChild;
}
}
}
/**
* Converts DOM selection to view {@link module:engine/view/selection~Selection}.
* Ranges which cannot be converted will be omitted.
*
* @param {Selection} domSelection DOM selection.
* @returns {module:engine/view/selection~Selection} View selection.
*/
domSelectionToView( domSelection ) {
// DOM selection might be placed in fake selection container.
// If container contains fake selection - return corresponding view selection.
if ( domSelection.rangeCount === 1 ) {
let container = domSelection.getRangeAt( 0 ).startContainer;
// The DOM selection might be moved to the text node inside the fake selection container.
if ( (0,_ckeditor_ckeditor5_utils_src_dom_istext__WEBPACK_IMPORTED_MODULE_14__["default"])( container ) ) {
container = container.parentNode;
}
const viewSelection = this.fakeSelectionToView( container );
if ( viewSelection ) {
return viewSelection;
}
}
const isBackward = this.isDomSelectionBackward( domSelection );
const viewRanges = [];
for ( let i = 0; i < domSelection.rangeCount; i++ ) {
// DOM Range have correct start and end, no matter what is the DOM Selection direction. So we don't have to fix anything.
const domRange = domSelection.getRangeAt( i );
const viewRange = this.domRangeToView( domRange );
if ( viewRange ) {
viewRanges.push( viewRange );
}
}
return new _selection__WEBPACK_IMPORTED_MODULE_5__["default"]( viewRanges, { backward: isBackward } );
}
/**
* Converts DOM Range to view {@link module:engine/view/range~Range}.
* If the start or end position can not be converted `null` is returned.
*
* @param {Range} domRange DOM range.
* @returns {module:engine/view/range~Range|null} View range.
*/
domRangeToView( domRange ) {
const viewStart = this.domPositionToView( domRange.startContainer, domRange.startOffset );
const viewEnd = this.domPositionToView( domRange.endContainer, domRange.endOffset );
if ( viewStart && viewEnd ) {
return new _range__WEBPACK_IMPORTED_MODULE_4__["default"]( viewStart, viewEnd );
}
return null;
}
/**
* Converts DOM parent and offset to view {@link module:engine/view/position~Position}.
*
* If the position is inside a {@link module:engine/view/filler filler} which has no corresponding view node,
* position of the filler will be converted and returned.
*
* If the position is inside DOM element rendered by {@link module:engine/view/uielement~UIElement}
* that position will be converted to view position before that UIElement.
*
* If structures are too different and it is not possible to find corresponding position then `null` will be returned.
*
* @param {Node} domParent DOM position parent.
* @param {Number} [domOffset=0] DOM position offset. You can skip it when converting the inline filler node.
* @returns {module:engine/view/position~Position} viewPosition View position.
*/
domPositionToView( domParent, domOffset = 0 ) {
if ( this.isBlockFiller( domParent ) ) {
return this.domPositionToView( domParent.parentNode, (0,_ckeditor_ckeditor5_utils_src_dom_indexof__WEBPACK_IMPORTED_MODULE_12__["default"])( domParent ) );
}
// If position is somewhere inside UIElement or a RawElement - return position before that element.
const viewElement = this.mapDomToView( domParent );
if ( viewElement && ( viewElement.is( 'uiElement' ) || viewElement.is( 'rawElement' ) ) ) {
return _position__WEBPACK_IMPORTED_MODULE_3__["default"]._createBefore( viewElement );
}
if ( (0,_ckeditor_ckeditor5_utils_src_dom_istext__WEBPACK_IMPORTED_MODULE_14__["default"])( domParent ) ) {
if ( (0,_filler__WEBPACK_IMPORTED_MODULE_9__.isInlineFiller)( domParent ) ) {
return this.domPositionToView( domParent.parentNode, (0,_ckeditor_ckeditor5_utils_src_dom_indexof__WEBPACK_IMPORTED_MODULE_12__["default"])( domParent ) );
}
const viewParent = this.findCorrespondingViewText( domParent );
let offset = domOffset;
if ( !viewParent ) {
return null;
}
if ( (0,_filler__WEBPACK_IMPORTED_MODULE_9__.startsWithFiller)( domParent ) ) {
offset -= _filler__WEBPACK_IMPORTED_MODULE_9__.INLINE_FILLER_LENGTH;
offset = offset < 0 ? 0 : offset;
}
return new _position__WEBPACK_IMPORTED_MODULE_3__["default"]( viewParent, offset );
}
// domParent instanceof HTMLElement.
else {
if ( domOffset === 0 ) {
const viewParent = this.mapDomToView( domParent );
if ( viewParent ) {
return new _position__WEBPACK_IMPORTED_MODULE_3__["default"]( viewParent, 0 );
}
} else {
const domBefore = domParent.childNodes[ domOffset - 1 ];
const viewBefore = (0,_ckeditor_ckeditor5_utils_src_dom_istext__WEBPACK_IMPORTED_MODULE_14__["default"])( domBefore ) ?
this.findCorrespondingViewText( domBefore ) :
this.mapDomToView( domBefore );
// TODO #663
if ( viewBefore && viewBefore.parent ) {
return new _position__WEBPACK_IMPORTED_MODULE_3__["default"]( viewBefore.parent, viewBefore.index + 1 );
}
}
return null;
}
}
/**
* Returns corresponding view {@link module:engine/view/element~Element Element} or
* {@link module:engine/view/documentfragment~DocumentFragment} for provided DOM element or
* document fragment. If there is no view item {@link module:engine/view/domconverter~DomConverter#bindElements bound}
* to the given DOM - `undefined` is returned.
*
* For all DOM elements rendered by a {@link module:engine/view/uielement~UIElement} or
* a {@link module:engine/view/rawelement~RawElement}, the parent `UIElement` or `RawElement` will be returned.
*
* @param {DocumentFragment|Element} domElementOrDocumentFragment DOM element or document fragment.
* @returns {module:engine/view/element~Element|module:engine/view/documentfragment~DocumentFragment|undefined}
* Corresponding view element, document fragment or `undefined` if no element was bound.
*/
mapDomToView( domElementOrDocumentFragment ) {
const hostElement = this.getHostViewElement( domElementOrDocumentFragment );
return hostElement || this._domToViewMapping.get( domElementOrDocumentFragment );
}
/**
* Finds corresponding text node. Text nodes are not {@link module:engine/view/domconverter~DomConverter#bindElements bound},
* corresponding text node is returned based on the sibling or parent.
*
* If the directly previous sibling is a {@link module:engine/view/domconverter~DomConverter#bindElements bound} element, it is used
* to find the corresponding text node.
*
* If this is a first child in the parent and the parent is a {@link module:engine/view/domconverter~DomConverter#bindElements bound}
* element, it is used to find the corresponding text node.
*
* For all text nodes rendered by a {@link module:engine/view/uielement~UIElement} or
* a {@link module:engine/view/rawelement~RawElement}, the parent `UIElement` or `RawElement` will be returned.
*
* Otherwise `null` is returned.
*
* Note that for the block or inline {@link module:engine/view/filler filler} this method returns `null`.
*
* @param {Text} domText DOM text node.
* @returns {module:engine/view/text~Text|null} Corresponding view text node or `null`, if it was not possible to find a
* corresponding node.
*/
findCorrespondingViewText( domText ) {
if ( (0,_filler__WEBPACK_IMPORTED_MODULE_9__.isInlineFiller)( domText ) ) {
return null;
}
// If DOM text was rendered by a UIElement or a RawElement - return this parent element.
const hostElement = this.getHostViewElement( domText );
if ( hostElement ) {
return hostElement;
}
const previousSibling = domText.previousSibling;
// Try to use previous sibling to find the corresponding text node.
if ( previousSibling ) {
if ( !( this.isElement( previousSibling ) ) ) {
// The previous is text or comment.
return null;
}
const viewElement = this.mapDomToView( previousSibling );
if ( viewElement ) {
const nextSibling = viewElement.nextSibling;
// It might be filler which has no corresponding view node.
if ( nextSibling instanceof _text__WEBPACK_IMPORTED_MODULE_0__["default"] ) {
return viewElement.nextSibling;
} else {
return null;
}
}
}
// Try to use parent to find the corresponding text node.
else {
const viewElement = this.mapDomToView( domText.parentNode );
if ( viewElement ) {
const firstChild = viewElement.getChild( 0 );
// It might be filler which has no corresponding view node.
if ( firstChild instanceof _text__WEBPACK_IMPORTED_MODULE_0__["default"] ) {
return firstChild;
} else {
return null;
}
}
}
return null;
}
/**
* Returns corresponding DOM item for provided {@link module:engine/view/element~Element Element} or
* {@link module:engine/view/documentfragment~DocumentFragment DocumentFragment}.
* To find a corresponding text for {@link module:engine/view/text~Text view Text instance}
* use {@link #findCorrespondingDomText}.
*
* @param {module:engine/view/element~Element|module:engine/view/documentfragment~DocumentFragment} viewNode
* View element or document fragment.
* @returns {Node|DocumentFragment|undefined} Corresponding DOM node or document fragment.
*/
mapViewToDom( documentFragmentOrElement ) {
return this._viewToDomMapping.get( documentFragmentOrElement );
}
/**
* Finds corresponding text node. Text nodes are not {@link module:engine/view/domconverter~DomConverter#bindElements bound},
* corresponding text node is returned based on the sibling or parent.
*
* If the directly previous sibling is a {@link module:engine/view/domconverter~DomConverter#bindElements bound} element, it is used
* to find the corresponding text node.
*
* If this is a first child in the parent and the parent is a {@link module:engine/view/domconverter~DomConverter#bindElements bound}
* element, it is used to find the corresponding text node.
*
* Otherwise `null` is returned.
*
* @param {module:engine/view/text~Text} viewText View text node.
* @returns {Text|null} Corresponding DOM text node or `null`, if it was not possible to find a corresponding node.
*/
findCorrespondingDomText( viewText ) {
const previousSibling = viewText.previousSibling;
// Try to use previous sibling to find the corresponding text node.
if ( previousSibling && this.mapViewToDom( previousSibling ) ) {
return this.mapViewToDom( previousSibling ).nextSibling;
}
// If this is a first node, try to use parent to find the corresponding text node.
if ( !previousSibling && viewText.parent && this.mapViewToDom( viewText.parent ) ) {
return this.mapViewToDom( viewText.parent ).childNodes[ 0 ];
}
return null;
}
/**
* Focuses DOM editable that is corresponding to provided {@link module:engine/view/editableelement~EditableElement}.
*
* @param {module:engine/view/editableelement~EditableElement} viewEditable
*/
focus( viewEditable ) {
const domEditable = this.mapViewToDom( viewEditable );
if ( domEditable && domEditable.ownerDocument.activeElement !== domEditable ) {
// Save the scrollX and scrollY positions before the focus.
const { scrollX, scrollY } = _ckeditor_ckeditor5_utils_src_dom_global__WEBPACK_IMPORTED_MODULE_10__["default"].window;
const scrollPositions = [];
// Save all scrollLeft and scrollTop values starting from domEditable up to
// document#documentElement.
forEachDomNodeAncestor( domEditable, node => {
const { scrollLeft, scrollTop } = node;
scrollPositions.push( [ scrollLeft, scrollTop ] );
} );
domEditable.focus();
// Restore scrollLeft and scrollTop values starting from domEditable up to
// document#documentElement.
// https://github.com/ckeditor/ckeditor5-engine/issues/951
// https://github.com/ckeditor/ckeditor5-engine/issues/957
forEachDomNodeAncestor( domEditable, node => {
const [ scrollLeft, scrollTop ] = scrollPositions.shift();
node.scrollLeft = scrollLeft;
node.scrollTop = scrollTop;
} );
// Restore the scrollX and scrollY positions after the focus.
// https://github.com/ckeditor/ckeditor5-engine/issues/951
_ckeditor_ckeditor5_utils_src_dom_global__WEBPACK_IMPORTED_MODULE_10__["default"].window.scrollTo( scrollX, scrollY );
}
}
/**
* Returns `true` when `node.nodeType` equals `Node.ELEMENT_NODE`.
*
* @param {Node} node Node to check.
* @returns {Boolean}
*/
isElement( node ) {
return node && node.nodeType == Node.ELEMENT_NODE;
}
/**
* Returns `true` when `node.nodeType` equals `Node.DOCUMENT_FRAGMENT_NODE`.
*
* @param {Node} node Node to check.
* @returns {Boolean}
*/
isDocumentFragment( node ) {
return node && node.nodeType == Node.DOCUMENT_FRAGMENT_NODE;
}
/**
* Checks if the node is an instance of the block filler for this DOM converter.
*
* const converter = new DomConverter( viewDocument, { blockFillerMode: 'br' } );
*
* converter.isBlockFiller( BR_FILLER( document ) ); // true
* converter.isBlockFiller( NBSP_FILLER( document ) ); // false
*
* **Note:**: For the `'nbsp'` mode the method also checks context of a node so it cannot be a detached node.
*
* **Note:** A special case in the `'nbsp'` mode exists where the `<br>` in `<p><br></p>` is treated as a block filler.
*
* @param {Node} domNode DOM node to check.
* @returns {Boolean} True if a node is considered a block filler for given mode.
*/
isBlockFiller( domNode ) {
if ( this.blockFillerMode == 'br' ) {
return domNode.isEqualNode( BR_FILLER_REF );
}
// Special case for <p><br></p> in which <br> should be treated as filler even when we are not in the 'br' mode. See ckeditor5#5564.
if ( domNode.tagName === 'BR' && hasBlockParent( domNode, this.blockElements ) && domNode.parentNode.childNodes.length === 1 ) {
return true;
}
// If not in 'br' mode, try recognizing both marked and regular nbsp block fillers.
return domNode.isEqualNode( MARKED_NBSP_FILLER_REF ) || isNbspBlockFiller( domNode, this.blockElements );
}
/**
* Returns `true` if given selection is a backward selection, that is, if it's `focus` is before `anchor`.
*
* @param {Selection} DOM Selection instance to check.
* @returns {Boolean}
*/
isDomSelectionBackward( selection ) {
if ( selection.isCollapsed ) {
return false;
}
// Since it takes multiple lines of code to check whether a "DOM Position" is before/after another "DOM Position",
// we will use the fact that range will collapse if it's end is before it's start.
const range = document.createRange();
range.setStart( selection.anchorNode, selection.anchorOffset );
range.setEnd( selection.focusNode, selection.focusOffset );
const backward = range.collapsed;
range.detach();
return backward;
}
/**
* Returns a parent {@link module:engine/view/uielement~UIElement} or {@link module:engine/view/rawelement~RawElement}
* that hosts the provided DOM node. Returns `null` if there is no such parent.
*
* @param {Node} domNode
* @returns {module:engine/view/uielement~UIElement|module:engine/view/rawelement~RawElement|null}
*/
getHostViewElement( domNode ) {
const ancestors = (0,_ckeditor_ckeditor5_utils_src_dom_getancestors__WEBPACK_IMPORTED_MODULE_13__["default"])( domNode );
// Remove domNode from the list.
ancestors.pop();
while ( ancestors.length ) {
const domNode = ancestors.pop();
const viewNode = this._domToViewMapping.get( domNode );
if ( viewNode && ( viewNode.is( 'uiElement' ) || viewNode.is( 'rawElement' ) ) ) {
return viewNode;
}
}
return null;
}
/**
* Checks if the given selection's boundaries are at correct places.
*
* The following places are considered as incorrect for selection boundaries:
*
* * before or in the middle of an inline filler sequence,
* * inside a DOM element which represents {@link module:engine/view/uielement~UIElement a view UI element},
* * inside a DOM element which represents {@link module:engine/view/rawelement~RawElement a view raw element}.
*
* @param {Selection} domSelection The DOM selection object to be checked.
* @returns {Boolean} `true` if the given selection is at a correct place, `false` otherwise.
*/
isDomSelectionCorrect( domSelection ) {
return this._isDomSelectionPositionCorrect( domSelection.anchorNode, domSelection.anchorOffset ) &&
this._isDomSelectionPositionCorrect( domSelection.focusNode, domSelection.focusOffset );
}
/**
* Registers a {@link module:engine/view/matcher~MatcherPattern} for view elements whose content should be treated as raw data
* and not processed during the conversion from DOM nodes to view elements.
*
* This is affecting how {@link module:engine/view/domconverter~DomConverter#domToView} and
* {@link module:engine/view/domconverter~DomConverter#domChildrenToView} process DOM nodes.
*
* The raw data can be later accessed by a
* {@link module:engine/view/element~Element#getCustomProperty custom property of a view element} called `"$rawContent"`.
*
* @param {module:engine/view/matcher~MatcherPattern} pattern Pattern matching a view element whose content should
* be treated as raw data.
*/
registerRawContentMatcher( pattern ) {
this._rawContentElementMatcher.add( pattern );
}
/**
* Returns the block {@link module:engine/view/filler filler} node based on the current {@link #blockFillerMode} setting.
*
* @private
* @params {Document} domDocument
* @returns {Node} filler
*/
_getBlockFiller( domDocument ) {
switch ( this.blockFillerMode ) {
case 'nbsp':
return (0,_filler__WEBPACK_IMPORTED_MODULE_9__.NBSP_FILLER)( domDocument ); // eslint-disable-line new-cap
case 'markedNbsp':
return (0,_filler__WEBPACK_IMPORTED_MODULE_9__.MARKED_NBSP_FILLER)( domDocument ); // eslint-disable-line new-cap
case 'br':
return (0,_filler__WEBPACK_IMPORTED_MODULE_9__.BR_FILLER)( domDocument ); // eslint-disable-line new-cap
}
}
/**
* Checks if the given DOM position is a correct place for selection boundary. See {@link #isDomSelectionCorrect}.
*
* @private
* @param {Element} domParent Position parent.
* @param {Number} offset Position offset.
* @returns {Boolean} `true` if given position is at a correct place for selection boundary, `false` otherwise.
*/
_isDomSelectionPositionCorrect( domParent, offset ) {
// If selection is before or in the middle of inline filler string, it is incorrect.
if ( (0,_ckeditor_ckeditor5_utils_src_dom_istext__WEBPACK_IMPORTED_MODULE_14__["default"])( domParent ) && (0,_filler__WEBPACK_IMPORTED_MODULE_9__.startsWithFiller)( domParent ) && offset < _filler__WEBPACK_IMPORTED_MODULE_9__.INLINE_FILLER_LENGTH ) {
// Selection in a text node, at wrong position (before or in the middle of filler).
return false;
}
if ( this.isElement( domParent ) && (0,_filler__WEBPACK_IMPORTED_MODULE_9__.startsWithFiller)( domParent.childNodes[ offset ] ) ) {
// Selection in an element node, before filler text node.
return false;
}
const viewParent = this.mapDomToView( domParent );
// The position is incorrect when anchored inside a UIElement or a RawElement.
// Note: In case of UIElement and RawElement, mapDomToView() returns a parent element for any DOM child
// so there's no need to perform any additional checks.
if ( viewParent && ( viewParent.is( 'uiElement' ) || viewParent.is( 'rawElement' ) ) ) {
return false;
}
return true;
}
/**
* Takes text data from a given {@link module:engine/view/text~Text#data} and processes it so
* it is correctly displayed in the DOM.
*
* Following changes are done:
*
* * a space at the beginning is changed to ` ` if this is the first text node in its container
* element or if a previous text node ends with a space character,
* * space at the end of the text node is changed to ` ` if there are two spaces at the end of a node or if next node
* starts with a space or if it is the last text node in its container,
* * remaining spaces are replaced to a chain of spaces and ` ` (e.g. `'x x'` becomes `'x x'`).
*
* Content of {@link #preElements} is not processed.
*
* @private
* @param {module:engine/view/text~Text} node View text node to process.
* @returns {String} Processed text data.
*/
_processDataFromViewText( node ) {
let data = node.data;
// If any of node ancestors has a name which is in `preElements` array, then currently processed
// view text node is (will be) in preformatted element. We should not change whitespaces then.
if ( node.getAncestors().some( parent => this.preElements.includes( parent.name ) ) ) {
return data;
}
// 1. Replace the first space with a nbsp if the previous node ends with a space or there is no previous node
// (container element boundary).
if ( data.charAt( 0 ) == ' ' ) {
const prevNode = this._getTouchingInlineViewNode( node, false );
const prevEndsWithSpace = prevNode && prevNode.is( '$textProxy' ) && this._nodeEndsWithSpace( prevNode );
if ( prevEndsWithSpace || !prevNode ) {
data = '\u00A0' + data.substr( 1 );
}
}
// 2. Replace the last space with nbsp if there are two spaces at the end or if the next node starts with space or there is no
// next node (container element boundary).
//
// Keep in mind that Firefox prefers $nbsp; before tag, not inside it:
//
// Foo <span> bar</span> <-- bad.
// Foo <span> bar</span> <-- good.
//
// More here: https://github.com/ckeditor/ckeditor5-engine/issues/1747.
if ( data.charAt( data.length - 1 ) == ' ' ) {
const nextNode = this._getTouchingInlineViewNode( node, true );
const nextStartsWithSpace = nextNode && nextNode.is( '$textProxy' ) && nextNode.data.charAt( 0 ) == ' ';
if ( data.charAt( data.length - 2 ) == ' ' || !nextNode || nextStartsWithSpace ) {
data = data.substr( 0, data.length - 1 ) + '\u00A0';
}
}
// 3. Create space+nbsp pairs.
return data.replace( / {2}/g, ' \u00A0' );
}
/**
* Checks whether given node ends with a space character after changing appropriate space characters to ` `s.
*
* @private
* @param {module:engine/view/text~Text} node Node to check.
* @returns {Boolean} `true` if given `node` ends with space, `false` otherwise.
*/
_nodeEndsWithSpace( node ) {
if ( node.getAncestors().some( parent => this.preElements.includes( parent.name ) ) ) {
return false;
}
const data = this._processDataFromViewText( node );
return data.charAt( data.length - 1 ) == ' ';
}
/**
* Takes text data from native `Text` node and processes it to a correct {@link module:engine/view/text~Text view text node} data.
*
* Following changes are done:
*
* * multiple whitespaces are replaced to a single space,
* * space at the beginning of a text node is removed if it is the first text node in its container
* element or if the previous text node ends with a space character,
* * space at the end of the text node is removed if there are two spaces at the end of a node or if next node
* starts with a space or if it is the last text node in its container
* * nbsps are converted to spaces.
*
* @param {Node} node DOM text node to process.
* @returns {String} Processed data.
* @private
*/
_processDataFromDomText( node ) {
let data = node.data;
if ( _hasDomParentOfType( node, this.preElements ) ) {
return (0,_filler__WEBPACK_IMPORTED_MODULE_9__.getDataWithoutFiller)( node );
}
// Change all consecutive whitespace characters (from the [ \n\t\r] set –
// see https://github.com/ckeditor/ckeditor5-engine/issues/822#issuecomment-311670249) to a single space character.
// That's how multiple whitespaces are treated when rendered, so we normalize those whitespaces.
// We're replacing 1+ (and not 2+) to also normalize singular \n\t\r characters (#822).
data = data.replace( /[ \n\t\r]{1,}/g, ' ' );
const prevNode = this._getTouchingInlineDomNode( node, false );
const nextNode = this._getTouchingInlineDomNode( node, true );
const shouldLeftTrim = this._checkShouldLeftTrimDomText( node, prevNode );
const shouldRightTrim = this._checkShouldRightTrimDomText( node, nextNode );
// If the previous dom text node does not exist or it ends by whitespace character, remove space character from the beginning
// of this text node. Such space character is treated as a whitespace.
if ( shouldLeftTrim ) {
data = data.replace( /^ /, '' );
}
// If the next text node does not exist remove space character from the end of this text node.
if ( shouldRightTrim ) {
data = data.replace( / $/, '' );
}
// At the beginning and end of a block element, Firefox inserts normal space + <br> instead of non-breaking space.
// This means that the text node starts/end with normal space instead of non-breaking space.
// This causes a problem because the normal space would be removed in `.replace` calls above. To prevent that,
// the inline filler is removed only after the data is initially processed (by the `.replace` above). See ckeditor5#692.
data = (0,_filler__WEBPACK_IMPORTED_MODULE_9__.getDataWithoutFiller)( new Text( data ) );
// At this point we should have removed all whitespaces from DOM text data.
//
// Now, We will reverse the process that happens in `_processDataFromViewText`.
//
// We have to change chars, that were in DOM text data because of rendering reasons, to spaces.
// First, change all ` \u00A0` pairs (space + ) to two spaces. DOM converter changes two spaces from model/view to
// ` \u00A0` to ensure proper rendering. Since here we convert back, we recognize those pairs and change them back to ` `.
data = data.replace( / \u00A0/g, ' ' );
const isNextNodeInlineObjectElement = nextNode && this.isElement( nextNode ) && nextNode.tagName != 'BR';
const isNextNodeStartingWithSpace = nextNode && (0,_ckeditor_ckeditor5_utils_src_dom_istext__WEBPACK_IMPORTED_MODULE_14__["default"])( nextNode ) && nextNode.data.charAt( 0 ) == ' ';
// Then, let's change the last nbsp to a space.
if ( /( |\u00A0)\u00A0$/.test( data ) || !nextNode || isNextNodeInlineObjectElement || isNextNodeStartingWithSpace ) {
data = data.replace( /\u00A0$/, ' ' );
}
// Then, change character that is at the beginning of the text node to space character.
// We do that replacement only if this is the first node or the previous node ends on whitespace character.
if ( shouldLeftTrim || prevNode && this.isElement( prevNode ) && prevNode.tagName != 'BR' ) {
data = data.replace( /^\u00A0/, ' ' );
}
// At this point, all whitespaces should be removed and all created for rendering reasons should be
// changed to normal space. All left are inserted intentionally.
return data;
}
/**
* Helper function which checks if a DOM text node, preceded by the given `prevNode` should
* be trimmed from the left side.
*
* @private
* @param {Node} node
* @param {Node} prevNode Either DOM text or `<br>` or one of `#inlineObjectElements`.
*/
_checkShouldLeftTrimDomText( node, prevNode ) {
if ( !prevNode ) {
return true;
}
if ( this.isElement( prevNode ) ) {
return prevNode.tagName === 'BR';
}
// Shouldn't left trim if previous node is a node that was encountered as a raw content node.
if ( this._encounteredRawContentDomNodes.has( node.previousSibling ) ) {
return false;
}
return /[^\S\u00A0]/.test( prevNode.data.charAt( prevNode.data.length - 1 ) );
}
/**
* Helper function which checks if a DOM text node, succeeded by the given `nextNode` should
* be trimmed from the right side.
*
* @private
* @param {Node} node
* @param {Node} nextNode Either DOM text or `<br>` or one of `#inlineObjectElements`.
*/
_checkShouldRightTrimDomText( node, nextNode ) {
if ( nextNode ) {
return false;
}
return !(0,_filler__WEBPACK_IMPORTED_MODULE_9__.startsWithFiller)( node );
}
/**
* Helper function. For given {@link module:engine/view/text~Text view text node}, it finds previous or next sibling
* that is contained in the same container element. If there is no such sibling, `null` is returned.
*
* @private
* @param {module:engine/view/text~Text} node Reference node.
* @param {Boolean} getNext
* @returns {module:engine/view/text~Text|module:engine/view/element~Element|null} Touching text node, an inline object
* or `null` if there is no next or previous touching text node.
*/
_getTouchingInlineViewNode( node, getNext ) {
const treeWalker = new _treewalker__WEBPACK_IMPORTED_MODULE_7__["default"]( {
startPosition: getNext ? _position__WEBPACK_IMPORTED_MODULE_3__["default"]._createAfter( node ) : _position__WEBPACK_IMPORTED_MODULE_3__["default"]._createBefore( node ),
direction: getNext ? 'forward' : 'backward'
} );
for ( const value of treeWalker ) {
// Found an inline object (for example an image).
if ( value.item.is( 'element' ) && this.inlineObjectElements.includes( value.item.name ) ) {
return value.item;
}
// ViewContainerElement is found on a way to next ViewText node, so given `node` was first/last
// text node in its container element.
else if ( value.item.is( 'containerElement' ) ) {
return null;
}
// <br> found – it works like a block boundary, so do not scan further.
else if ( value.item.is( 'element', 'br' ) ) {
return null;
}
// Found a text node in the same container element.
else if ( value.item.is( '$textProxy' ) ) {
return value.item;
}
}
return null;
}
/**
* Helper function. For the given text node, it finds the closest touching node which is either
* a text, `<br>` or an {@link #inlineObjectElements inline object}.
*
* If no such node is found, `null` is returned.
*
* For instance, in the following DOM structure:
*
* <p>foo<b>bar</b><br>bom</p>
*
* * `foo` doesn't have its previous touching inline node (`null` is returned),
* * `foo`'s next touching inline node is `bar`
* * `bar`'s next touching inline node is `<br>`
*
* This method returns text nodes and `<br>` elements because these types of nodes affect how
* spaces in the given text node need to be converted.
*
* @private
* @param {Text} node
* @param {Boolean} getNext
* @returns {Text|Element|null}
*/
_getTouchingInlineDomNode( node, getNext ) {
if ( !node.parentNode ) {
return null;
}
const stepInto = getNext ? 'firstChild' : 'lastChild';
const stepOver = getNext ? 'nextSibling' : 'previousSibling';
let skipChildren = true;
do {
if ( !skipChildren && node[ stepInto ] ) {
node = node[ stepInto ];
} else if ( node[ stepOver ] ) {
node = node[ stepOver ];
skipChildren = false;
} else {
node = node.parentNode;
skipChildren = true;
}
if ( !node || this._isBlockElement( node ) ) {
return null;
}
} while (
!( (0,_ckeditor_ckeditor5_utils_src_dom_istext__WEBPACK_IMPORTED_MODULE_14__["default"])( node ) || node.tagName == 'BR' || this._isInlineObjectElement( node ) )
);
return node;
}
/**
* Returns `true` if a DOM node belongs to {@link #blockElements}. `false` otherwise.
*
* @private
* @param {Node} node
* @returns {Boolean}
*/
_isBlockElement( node ) {
return this.isElement( node ) && this.blockElements.includes( node.tagName.toLowerCase() );
}
/**
* Returns `true` if a DOM node belongs to {@link #inlineObjectElements}. `false` otherwise.
*
* @private
* @param {Node} node
* @returns {Boolean}
*/
_isInlineObjectElement( node ) {
return this.isElement( node ) && this.inlineObjectElements.includes( node.tagName.toLowerCase() );
}
/**
* Creates view element basing on the node type.
*
* @private
* @param {Node} node DOM node to check.
* @param {Object} options Conversion options. See {@link module:engine/view/domconverter~DomConverter#domToView} options parameter.
* @returns {Element}
*/
_createViewElement( node, options ) {
if ( (0,_ckeditor_ckeditor5_utils_src_dom_iscomment__WEBPACK_IMPORTED_MODULE_15__["default"])( node ) ) {
return new _uielement__WEBPACK_IMPORTED_MODULE_2__["default"]( this.document, '$comment' );
}
const viewName = options.keepOriginalCase ? node.tagName : node.tagName.toLowerCase();
return new _element__WEBPACK_IMPORTED_MODULE_1__["default"]( this.document, viewName );
}
/**
* Checks if view element's content should be treated as a raw data.
*
* @private
* @param {Element} viewElement View element to check.
* @param {Object} options Conversion options. See {@link module:engine/view/domconverter~DomConverter#domToView} options parameter.
* @returns {Boolean}
*/
_isViewElementWithRawContent( viewElement, options ) {
return options.withChildren !== false && this._rawContentElementMatcher.match( viewElement );
}
/**
* Checks whether a given element name should be renamed in a current rendering mode.
*
* @private
* @param {String} elementName The name of view element.
* @returns {Boolean}
*/
_shouldRenameElement( elementName ) {
const name = elementName.toLowerCase();
return this.renderingMode === 'editing' && UNSAFE_ELEMENTS.includes( name );
}
/**
* Return a <span> element with a special attribute holding the name of the original element.
* Optionally, copy all the attributes of the original element if that element is provided.
*
* @private
* @param {String} elementName The name of view element.
* @param {Element} [originalDomElement] The original DOM element to copy attributes and content from.
* @returns {Element}
*/
_createReplacementDomElement( elementName, originalDomElement = null ) {
const newDomElement = document.createElement( 'span' );
// Mark the span replacing a script as hidden.
newDomElement.setAttribute( UNSAFE_ELEMENT_REPLACEMENT_ATTRIBUTE, elementName );
if ( originalDomElement ) {
while ( originalDomElement.firstChild ) {
newDomElement.appendChild( originalDomElement.firstChild );
}
for ( const attributeName of originalDomElement.getAttributeNames() ) {
newDomElement.setAttribute( attributeName, originalDomElement.getAttribute( attributeName ) );
}
}
return newDomElement;
}
}
// Helper function.
// Used to check if given native `Element` or `Text` node has parent with tag name from `types` array.
//
// @param {Node} node
// @param {Array.<String>} types
// @returns {Boolean} `true` if such parent exists or `false` if it does not.
function _hasDomParentOfType( node, types ) {
const parents = (0,_ckeditor_ckeditor5_utils_src_dom_getancestors__WEBPACK_IMPORTED_MODULE_13__["default"])( node );
return parents.some( parent => parent.tagName && types.includes( parent.tagName.toLowerCase() ) );
}
// A helper that executes given callback for each DOM node's ancestor, starting from the given node
// and ending in document#documentElement.
//
// @param {Node} node
// @param {Function} callback A callback to be executed for each ancestor.
function forEachDomNodeAncestor( node, callback ) {
while ( node && node != _ckeditor_ckeditor5_utils_src_dom_global__WEBPACK_IMPORTED_MODULE_10__["default"].document ) {
callback( node );
node = node.parentNode;
}
}
// Checks if given node is a nbsp block filler.
//
// A is a block filler only if it is a single child of a block element.
//
// @param {Node} domNode DOM node.
// @param {Array.<String>} blockElements
// @returns {Boolean}
function isNbspBlockFiller( domNode, blockElements ) {
const isNBSP = domNode.isEqualNode( NBSP_FILLER_REF );
return isNBSP && hasBlockParent( domNode, blockElements ) && domNode.parentNode.childNodes.length === 1;
}
// Checks if domNode has block parent.
//
// @param {Node} domNode DOM node.
// @param {Array.<String>} blockElements
// @returns {Boolean}
function hasBlockParent( domNode, blockElements ) {
const parent = domNode.parentNode;
return parent && parent.tagName && blockElements.includes( parent.tagName.toLowerCase() );
}
// Log to console the information about element that was replaced.
// Check UNSAFE_ELEMENTS for all recognized unsafe elements.
//
// @param {String} elementName The name of the view element
function _logUnsafeElement( elementName ) {
if ( elementName === 'script' ) {
(0,_ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_11__.logWarning)( 'domconverter-unsafe-script-element-detected' );
}
if ( elementName === 'style' ) {
(0,_ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_11__.logWarning)( 'domconverter-unsafe-style-element-detected' );
}
}
/**
* Enum representing the type of the block filler.
*
* Possible values:
*
* * `br` – For the `<br data-cke-filler="true">` block filler used in the editing view.
* * `nbsp` – For the ` ` block fillers used in the data.
* * `markedNbsp` – For the ` ` block fillers wrapped in `<span>` elements: `<span data-cke-filler="true"> </span>`
* used in the data.
*
* @typedef {String} module:engine/view/filler~BlockFillerMode
*/
/**
* While rendering the editor content, the {@link module:engine/view/domconverter~DomConverter} detected a `<script>` element that may
* disrupt the editing experience. To avoid this, the `<script>` element was replaced with `<span data-ck-unsafe-element="script"></span>`.
*
* @error domconverter-unsafe-script-element-detected
*/
/**
* While rendering the editor content, the {@link module:engine/view/domconverter~DomConverter} detected a `<style>` element that may affect
* the editing experience. To avoid this, the `<style>` element was replaced with `<span data-ck-unsafe-element="style"></span>`.
*
* @error domconverter-unsafe-style-element-detected
*/
/**
* The {@link module:engine/view/domconverter~DomConverter} detected an interactive attribute in the
* {@glink framework/guides/architecture/editing-engine#editing-pipeline editing pipeline}. For the best
* editing experience, the attribute was renamed to `data-ck-unsafe-attribute-[original attribute name]`.
*
* If you are the author of the plugin that generated this attribute and you want it to be preserved
* in the editing pipeline, you can configure this when creating the element
* using {@link module:engine/view/downcastwriter~DowncastWriter} during the
* {@glink framework/guides/architecture/editing-engine#conversion model–view conversion}. Methods such as
* {@link module:engine/view/downcastwriter~DowncastWriter#createContainerElement},
* {@link module:engine/view/downcastwriter~DowncastWriter#createAttributeElement}, or
* {@link module:engine/view/downcastwriter~DowncastWriter#createEmptyElement}
* accept an option that will disable filtering of specific attributes:
*
* const paragraph = writer.createContainerElement( 'p',
* {
* class: 'clickable-paragraph',
* onclick: 'alert( "Paragraph clicked!" )'
* },
* {
* // Make sure the "onclick" attribute will pass through.
* renderUnsafeAttributes: [ 'onclick' ]
* }
* );
*
* @error domconverter-unsafe-attribute-detected
* @param {HTMLElement} domElement The DOM element the attribute was set on.
* @param {String} key The original name of the attribute
* @param {String} value The value of the original attribute
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/view/downcastwriter.js":
/*!****************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/view/downcastwriter.js ***!
\****************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ DowncastWriter)
/* harmony export */ });
/* harmony import */ var _position__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./position */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/position.js");
/* harmony import */ var _range__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./range */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/range.js");
/* harmony import */ var _selection__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./selection */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/selection.js");
/* harmony import */ var _containerelement__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./containerelement */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/containerelement.js");
/* harmony import */ var _attributeelement__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./attributeelement */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/attributeelement.js");
/* harmony import */ var _emptyelement__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./emptyelement */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/emptyelement.js");
/* harmony import */ var _uielement__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./uielement */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/uielement.js");
/* harmony import */ var _rawelement__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./rawelement */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/rawelement.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/* harmony import */ var _documentfragment__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./documentfragment */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/documentfragment.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_isiterable__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/isiterable */ "./node_modules/@ckeditor/ckeditor5-utils/src/isiterable.js");
/* harmony import */ var _text__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ./text */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/text.js");
/* harmony import */ var _editableelement__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! ./editableelement */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/editableelement.js");
/* harmony import */ var lodash_es__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(/*! lodash-es */ "./node_modules/lodash-es/isPlainObject.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module module:engine/view/downcastwriter
*/
/**
* View downcast writer.
*
* It provides a set of methods used to manipulate view nodes.
*
* Do not create an instance of this writer manually. To modify a view structure, use
* the {@link module:engine/view/view~View#change `View#change()`} block.
*
* The `DowncastWriter` is designed to work with semantic views which are the views that were/are being downcasted from the model.
* To work with ordinary views (e.g. parsed from a pasted content) use the
* {@link module:engine/view/upcastwriter~UpcastWriter upcast writer}.
*
* Read more about changing the view in the {@glink framework/guides/architecture/editing-engine#changing-the-view Changing the view}
* section of the {@glink framework/guides/architecture/editing-engine Editing engine architecture} guide.
*/
class DowncastWriter {
/**
* @param {module:engine/view/document~Document} document The view document instance.
*/
constructor( document ) {
/**
* The view document instance in which this writer operates.
*
* @readonly
* @type {module:engine/view/document~Document}
*/
this.document = document;
/**
* Holds references to the attribute groups that share the same {@link module:engine/view/attributeelement~AttributeElement#id id}.
* The keys are `id`s, the values are `Set`s holding {@link module:engine/view/attributeelement~AttributeElement}s.
*
* @private
* @type {Map.<String,Set>}
*/
this._cloneGroups = new Map();
/**
* The slot factory used by the `elementToStructure` downcast helper.
*
* @private
* @type {Function|null}
*/
this._slotFactory = null;
}
/**
* Sets {@link module:engine/view/documentselection~DocumentSelection selection's} ranges and direction to the
* specified location based on the given {@link module:engine/view/selection~Selectable selectable}.
*
* Usage:
*
* // Sets selection to the given range.
* const range = writer.createRange( start, end );
* writer.setSelection( range );
*
* // Sets backward selection to the given range.
* const range = writer.createRange( start, end );
* writer.setSelection( range );
*
* // Sets selection to given ranges.
* const ranges = [ writer.createRange( start1, end2 ), writer.createRange( start2, end2 ) ];
* writer.setSelection( range );
*
* // Sets selection to the other selection.
* const otherSelection = writer.createSelection();
* writer.setSelection( otherSelection );
*
* // Sets collapsed selection at the given position.
* const position = writer.createPositionFromPath( root, path );
* writer.setSelection( position );
*
* // Sets collapsed selection at the position of given item and offset.
* const paragraph = writer.createContainerElement( 'p' );
* writer.setSelection( paragraph, offset );
*
* Creates a range inside an {@link module:engine/view/element~Element element} which starts before the first child of
* that element and ends after the last child of that element.
*
* writer.setSelection( paragraph, 'in' );
*
* Creates a range on the {@link module:engine/view/item~Item item} which starts before the item and ends just after the item.
*
* writer.setSelection( paragraph, 'on' );
*
* // Removes all ranges.
* writer.setSelection( null );
*
* `DowncastWriter#setSelection()` allow passing additional options (`backward`, `fake` and `label`) as the last argument.
*
* // Sets selection as backward.
* writer.setSelection( range, { backward: true } );
*
* // Sets selection as fake.
* // Fake selection does not render as browser native selection over selected elements and is hidden to the user.
* // This way, no native selection UI artifacts are displayed to the user and selection over elements can be
* // represented in other way, for example by applying proper CSS class.
* writer.setSelection( range, { fake: true } );
*
* // Additionally fake's selection label can be provided. It will be used to describe fake selection in DOM
* // (and be properly handled by screen readers).
* writer.setSelection( range, { fake: true, label: 'foo' } );
*
* @param {module:engine/view/selection~Selectable} selectable
* @param {Number|'before'|'end'|'after'|'on'|'in'} [placeOrOffset] Sets place or offset of the selection.
* @param {Object} [options]
* @param {Boolean} [options.backward] Sets this selection instance to be backward.
* @param {Boolean} [options.fake] Sets this selection instance to be marked as `fake`.
* @param {String} [options.label] Label for the fake selection.
*/
setSelection( selectable, placeOrOffset, options ) {
this.document.selection._setTo( selectable, placeOrOffset, options );
}
/**
* Moves {@link module:engine/view/documentselection~DocumentSelection#focus selection's focus} to the specified location.
*
* The location can be specified in the same form as {@link module:engine/view/view~View#createPositionAt view.createPositionAt()}
* parameters.
*
* @param {module:engine/view/item~Item|module:engine/view/position~Position} itemOrPosition
* @param {Number|'end'|'before'|'after'} [offset] Offset or one of the flags. Used only when
* first parameter is a {@link module:engine/view/item~Item view item}.
*/
setSelectionFocus( itemOrPosition, offset ) {
this.document.selection._setFocus( itemOrPosition, offset );
}
/**
* Creates a new {@link module:engine/view/documentfragment~DocumentFragment} instance.
*
* @param {module:engine/view/node~Node|Iterable.<module:engine/view/node~Node>} [children]
* A list of nodes to be inserted into the created document fragment.
* @returns {module:engine/view/documentfragment~DocumentFragment} The created document fragment.
*/
createDocumentFragment( children ) {
return new _documentfragment__WEBPACK_IMPORTED_MODULE_9__["default"]( this.document, children );
}
/**
* Creates a new {@link module:engine/view/text~Text text node}.
*
* writer.createText( 'foo' );
*
* @param {String} data The text's data.
* @returns {module:engine/view/text~Text} The created text node.
*/
createText( data ) {
return new _text__WEBPACK_IMPORTED_MODULE_11__["default"]( this.document, data );
}
/**
* Creates a new {@link module:engine/view/attributeelement~AttributeElement}.
*
* writer.createAttributeElement( 'strong' );
* writer.createAttributeElement( 'a', { href: 'foo.bar' } );
*
* // Make `<a>` element contain other attributes element so the `<a>` element is not broken.
* writer.createAttributeElement( 'a', { href: 'foo.bar' }, { priority: 5 } );
*
* // Set `id` of a marker element so it is not joined or merged with "normal" elements.
* writer.createAttributeElement( 'span', { class: 'my-marker' }, { id: 'marker:my' } );
*
* **Note:** By default an `AttributeElement` is split by a
* {@link module:engine/view/containerelement~ContainerElement `ContainerElement`} but this behavior can be modified
* with `isAllowedInsideAttributeElement` option set while {@link #createContainerElement creating the element}.
*
* @param {String} name Name of the element.
* @param {Object} [attributes] Element's attributes.
* @param {Object} [options] Element's options.
* @param {Number} [options.priority] Element's {@link module:engine/view/attributeelement~AttributeElement#priority priority}.
* @param {Number|String} [options.id] Element's {@link module:engine/view/attributeelement~AttributeElement#id id}.
* @param {Array.<String>} [options.renderUnsafeAttributes] A list of attribute names that should be rendered in the editing
* pipeline even though they would normally be filtered out by unsafe attribute detection mechanisms.
* @returns {module:engine/view/attributeelement~AttributeElement} Created element.
*/
createAttributeElement( name, attributes, options = {} ) {
const attributeElement = new _attributeelement__WEBPACK_IMPORTED_MODULE_4__["default"]( this.document, name, attributes );
if ( typeof options.priority === 'number' ) {
attributeElement._priority = options.priority;
}
if ( options.id ) {
attributeElement._id = options.id;
}
if ( options.renderUnsafeAttributes ) {
attributeElement._unsafeAttributesToRender.push( ...options.renderUnsafeAttributes );
}
return attributeElement;
}
/**
* Creates a new {@link module:engine/view/containerelement~ContainerElement}.
*
* writer.createContainerElement( 'p' );
*
* // Create element with custom attributes.
* writer.createContainerElement( 'div', { id: 'foo-bar', 'data-baz': '123' } );
*
* // Create element with custom styles.
* writer.createContainerElement( 'p', { style: 'font-weight: bold; padding-bottom: 10px' } );
*
* // Create element with custom classes.
* writer.createContainerElement( 'p', { class: 'foo bar baz' } );
*
* // Create element with children.
* writer.createContainerElement( 'figure', { class: 'image' }, [
* writer.createEmptyElement( 'img' ),
* writer.createContainerElement( 'figcaption' )
* ] );
*
* // Create element with specific options.
* writer.createContainerElement( 'span', { class: 'placeholder' }, { isAllowedInsideAttributeElement: true } );
*
* @param {String} name Name of the element.
* @param {Object} [attributes] Elements attributes.
* @param {module:engine/view/node~Node|Iterable.<module:engine/view/node~Node>|Object} [childrenOrOptions]
* A node or a list of nodes to be inserted into the created element. If no children were specified, element's `options`
* can be passed in this argument.
* @param {Object} [options] Element's options.
* @param {Boolean} [options.isAllowedInsideAttributeElement=false] Whether an element is
* {@link module:engine/view/element~Element#isAllowedInsideAttributeElement allowed inside an AttributeElement} and can be wrapped
* with {@link module:engine/view/attributeelement~AttributeElement} by {@link module:engine/view/downcastwriter~DowncastWriter}.
* @param {Array.<String>} [options.renderUnsafeAttributes] A list of attribute names that should be rendered in the editing
* pipeline even though they would normally be filtered out by unsafe attribute detection mechanisms.
* @returns {module:engine/view/containerelement~ContainerElement} Created element.
*/
createContainerElement( name, attributes, childrenOrOptions = {}, options = {} ) {
let children = null;
if ( (0,lodash_es__WEBPACK_IMPORTED_MODULE_13__["default"])( childrenOrOptions ) ) {
options = childrenOrOptions;
} else {
children = childrenOrOptions;
}
const containerElement = new _containerelement__WEBPACK_IMPORTED_MODULE_3__["default"]( this.document, name, attributes, children );
if ( options.isAllowedInsideAttributeElement !== undefined ) {
containerElement._isAllowedInsideAttributeElement = options.isAllowedInsideAttributeElement;
}
if ( options.renderUnsafeAttributes ) {
containerElement._unsafeAttributesToRender.push( ...options.renderUnsafeAttributes );
}
return containerElement;
}
/**
* Creates a new {@link module:engine/view/editableelement~EditableElement}.
*
* writer.createEditableElement( 'div' );
* writer.createEditableElement( 'div', { id: 'foo-1234' } );
*
* Note: The editable element is to be used in the editing pipeline. Usually, together with
* {@link module:widget/utils~toWidgetEditable `toWidgetEditable()`}.
*
* @param {String} name Name of the element.
* @param {Object} [attributes] Elements attributes.
* @param {Object} [options] Element's options.
* @param {Array.<String>} [options.renderUnsafeAttributes] A list of attribute names that should be rendered in the editing
* pipeline even though they would normally be filtered out by unsafe attribute detection mechanisms.
* @returns {module:engine/view/editableelement~EditableElement} Created element.
*/
createEditableElement( name, attributes, options = {} ) {
const editableElement = new _editableelement__WEBPACK_IMPORTED_MODULE_12__["default"]( this.document, name, attributes );
editableElement._document = this.document;
if ( options.renderUnsafeAttributes ) {
editableElement._unsafeAttributesToRender.push( ...options.renderUnsafeAttributes );
}
return editableElement;
}
/**
* Creates a new {@link module:engine/view/emptyelement~EmptyElement}.
*
* writer.createEmptyElement( 'img' );
* writer.createEmptyElement( 'img', { id: 'foo-1234' } );
*
* @param {String} name Name of the element.
* @param {Object} [attributes] Elements attributes.
* @param {Object} [options] Element's options.
* @param {Boolean} [options.isAllowedInsideAttributeElement=true] Whether an element is
* {@link module:engine/view/element~Element#isAllowedInsideAttributeElement allowed inside an AttributeElement} and can be wrapped
* with {@link module:engine/view/attributeelement~AttributeElement} by {@link module:engine/view/downcastwriter~DowncastWriter}.
* @param {Array.<String>} [options.renderUnsafeAttributes] A list of attribute names that should be rendered in the editing
* pipeline even though they would normally be filtered out by unsafe attribute detection mechanisms.
* @returns {module:engine/view/emptyelement~EmptyElement} Created element.
*/
createEmptyElement( name, attributes, options = {} ) {
const emptyElement = new _emptyelement__WEBPACK_IMPORTED_MODULE_5__["default"]( this.document, name, attributes );
if ( options.isAllowedInsideAttributeElement !== undefined ) {
emptyElement._isAllowedInsideAttributeElement = options.isAllowedInsideAttributeElement;
}
if ( options.renderUnsafeAttributes ) {
emptyElement._unsafeAttributesToRender.push( ...options.renderUnsafeAttributes );
}
return emptyElement;
}
/**
* Creates a new {@link module:engine/view/uielement~UIElement}.
*
* writer.createUIElement( 'span' );
* writer.createUIElement( 'span', { id: 'foo-1234' } );
*
* A custom render function can be provided as the third parameter:
*
* writer.createUIElement( 'span', null, function( domDocument ) {
* const domElement = this.toDomElement( domDocument );
* domElement.innerHTML = '<b>this is ui element</b>';
*
* return domElement;
* } );
*
* Unlike {@link #createRawElement raw elements}, UI elements are by no means editor content, for instance,
* they are ignored by the editor selection system.
*
* You should not use UI elements as data containers. Check out {@link #createRawElement} instead.
*
* @param {String} name The name of the element.
* @param {Object} [attributes] Element attributes.
* @param {Function} [renderFunction] A custom render function.
* @param {Object} [options] Element's options.
* @param {Boolean} [options.isAllowedInsideAttributeElement=true] Whether an element is
* {@link module:engine/view/element~Element#isAllowedInsideAttributeElement allowed inside an AttributeElement} and can be wrapped
* with {@link module:engine/view/attributeelement~AttributeElement} by {@link module:engine/view/downcastwriter~DowncastWriter}.
* @returns {module:engine/view/uielement~UIElement} The created element.
*/
createUIElement( name, attributes, renderFunction, options = {} ) {
const uiElement = new _uielement__WEBPACK_IMPORTED_MODULE_6__["default"]( this.document, name, attributes );
if ( renderFunction ) {
uiElement.render = renderFunction;
}
if ( options.isAllowedInsideAttributeElement !== undefined ) {
uiElement._isAllowedInsideAttributeElement = options.isAllowedInsideAttributeElement;
}
return uiElement;
}
/**
* Creates a new {@link module:engine/view/rawelement~RawElement}.
*
* writer.createRawElement( 'span', { id: 'foo-1234' }, function( domElement ) {
* domElement.innerHTML = '<b>This is the raw content of the raw element.</b>';
* } );
*
* Raw elements work as data containers ("wrappers", "sandboxes") but their children are not managed or
* even recognized by the editor. This encapsulation allows integrations to maintain custom DOM structures
* in the editor content without, for instance, worrying about compatibility with other editor features.
* Raw elements are a perfect tool for integration with external frameworks and data sources.
*
* Unlike {@link #createUIElement UI elements}, raw elements act like "real" editor content (similar to
* {@link module:engine/view/containerelement~ContainerElement} or {@link module:engine/view/emptyelement~EmptyElement}),
* and they are considered by the editor selection.
*
* You should not use raw elements to render the UI in the editor content. Check out {@link #createUIElement `#createUIElement()`}
* instead.
*
* @param {String} name The name of the element.
* @param {Object} [attributes] Element attributes.
* @param {Function} [renderFunction] A custom render function.
* @param {Object} [options] Element's options.
* @param {Boolean} [options.isAllowedInsideAttributeElement=true] Whether an element is
* {@link module:engine/view/element~Element#isAllowedInsideAttributeElement allowed inside an AttributeElement} and can be wrapped
* with {@link module:engine/view/attributeelement~AttributeElement} by {@link module:engine/view/downcastwriter~DowncastWriter}.
* @param {Array.<String>} [options.renderUnsafeAttributes] A list of attribute names that should be rendered in the editing
* pipeline even though they would normally be filtered out by unsafe attribute detection mechanisms.
* @returns {module:engine/view/rawelement~RawElement} The created element.
*/
createRawElement( name, attributes, renderFunction, options = {} ) {
const rawElement = new _rawelement__WEBPACK_IMPORTED_MODULE_7__["default"]( this.document, name, attributes );
rawElement.render = renderFunction || ( () => {} );
if ( options.isAllowedInsideAttributeElement !== undefined ) {
rawElement._isAllowedInsideAttributeElement = options.isAllowedInsideAttributeElement;
}
if ( options.renderUnsafeAttributes ) {
rawElement._unsafeAttributesToRender.push( ...options.renderUnsafeAttributes );
}
return rawElement;
}
/**
* Adds or overwrites the element's attribute with a specified key and value.
*
* writer.setAttribute( 'href', 'http://ckeditor.com', linkElement );
*
* @param {String} key The attribute key.
* @param {String} value The attribute value.
* @param {module:engine/view/element~Element} element
*/
setAttribute( key, value, element ) {
element._setAttribute( key, value );
}
/**
* Removes attribute from the element.
*
* writer.removeAttribute( 'href', linkElement );
*
* @param {String} key Attribute key.
* @param {module:engine/view/element~Element} element
*/
removeAttribute( key, element ) {
element._removeAttribute( key );
}
/**
* Adds specified class to the element.
*
* writer.addClass( 'foo', linkElement );
* writer.addClass( [ 'foo', 'bar' ], linkElement );
*
* @param {Array.<String>|String} className
* @param {module:engine/view/element~Element} element
*/
addClass( className, element ) {
element._addClass( className );
}
/**
* Removes specified class from the element.
*
* writer.removeClass( 'foo', linkElement );
* writer.removeClass( [ 'foo', 'bar' ], linkElement );
*
* @param {Array.<String>|String} className
* @param {module:engine/view/element~Element} element
*/
removeClass( className, element ) {
element._removeClass( className );
}
/**
* Adds style to the element.
*
* writer.setStyle( 'color', 'red', element );
* writer.setStyle( {
* color: 'red',
* position: 'fixed'
* }, element );
*
* **Note**: The passed style can be normalized if
* {@link module:engine/controller/datacontroller~DataController#addStyleProcessorRules a particular style processor rule is enabled}.
* See {@link module:engine/view/stylesmap~StylesMap#set `StylesMap#set()`} for details.
*
* @param {String|Object} property Property name or object with key - value pairs.
* @param {String} [value] Value to set. This parameter is ignored if object is provided as the first parameter.
* @param {module:engine/view/element~Element} element Element to set styles on.
*/
setStyle( property, value, element ) {
if ( (0,lodash_es__WEBPACK_IMPORTED_MODULE_13__["default"])( property ) && element === undefined ) {
element = value;
}
element._setStyle( property, value );
}
/**
* Removes specified style from the element.
*
* writer.removeStyle( 'color', element ); // Removes 'color' style.
* writer.removeStyle( [ 'color', 'border-top' ], element ); // Removes both 'color' and 'border-top' styles.
*
* **Note**: This method can work with normalized style names if
* {@link module:engine/controller/datacontroller~DataController#addStyleProcessorRules a particular style processor rule is enabled}.
* See {@link module:engine/view/stylesmap~StylesMap#remove `StylesMap#remove()`} for details.
*
* @param {Array.<String>|String} property
* @param {module:engine/view/element~Element} element
*/
removeStyle( property, element ) {
element._removeStyle( property );
}
/**
* Sets a custom property on element. Unlike attributes, custom properties are not rendered to the DOM,
* so they can be used to add special data to elements.
*
* @param {String|Symbol} key
* @param {*} value
* @param {module:engine/view/element~Element} element
*/
setCustomProperty( key, value, element ) {
element._setCustomProperty( key, value );
}
/**
* Removes a custom property stored under the given key.
*
* @param {String|Symbol} key
* @param {module:engine/view/element~Element} element
* @returns {Boolean} Returns true if property was removed.
*/
removeCustomProperty( key, element ) {
return element._removeCustomProperty( key );
}
/**
* Breaks attribute elements at the provided position or at the boundaries of a provided range. It breaks attribute elements
* up to their first ancestor that is a container element.
*
* In following examples `<p>` is a container, `<b>` and `<u>` are attribute elements:
*
* <p>foo<b><u>bar{}</u></b></p> -> <p>foo<b><u>bar</u></b>[]</p>
* <p>foo<b><u>{}bar</u></b></p> -> <p>foo{}<b><u>bar</u></b></p>
* <p>foo<b><u>b{}ar</u></b></p> -> <p>foo<b><u>b</u></b>[]<b><u>ar</u></b></p>
* <p><b>fo{o</b><u>ba}r</u></p> -> <p><b>fo</b><b>o</b><u>ba</u><u>r</u></b></p>
*
* **Note:** {@link module:engine/view/documentfragment~DocumentFragment DocumentFragment} is treated like a container.
*
* **Note:** The difference between {@link module:engine/view/downcastwriter~DowncastWriter#breakAttributes breakAttributes()} and
* {@link module:engine/view/downcastwriter~DowncastWriter#breakContainer breakContainer()} is that `breakAttributes()` breaks all
* {@link module:engine/view/attributeelement~AttributeElement attribute elements} that are ancestors of a given `position`,
* up to the first encountered {@link module:engine/view/containerelement~ContainerElement container element}.
* `breakContainer()` assumes that a given `position` is directly in the container element and breaks that container element.
*
* Throws the `view-writer-invalid-range-container` {@link module:utils/ckeditorerror~CKEditorError CKEditorError}
* when the {@link module:engine/view/range~Range#start start}
* and {@link module:engine/view/range~Range#end end} positions of a passed range are not placed inside same parent container.
*
* Throws the `view-writer-cannot-break-empty-element` {@link module:utils/ckeditorerror~CKEditorError CKEditorError}
* when trying to break attributes inside an {@link module:engine/view/emptyelement~EmptyElement EmptyElement}.
*
* Throws the `view-writer-cannot-break-ui-element` {@link module:utils/ckeditorerror~CKEditorError CKEditorError}
* when trying to break attributes inside a {@link module:engine/view/uielement~UIElement UIElement}.
*
* @see module:engine/view/attributeelement~AttributeElement
* @see module:engine/view/containerelement~ContainerElement
* @see module:engine/view/downcastwriter~DowncastWriter#breakContainer
* @param {module:engine/view/position~Position|module:engine/view/range~Range} positionOrRange The position where
* to break attribute elements.
* @returns {module:engine/view/position~Position|module:engine/view/range~Range} The new position or range, after breaking the
* attribute elements.
*/
breakAttributes( positionOrRange ) {
if ( positionOrRange instanceof _position__WEBPACK_IMPORTED_MODULE_0__["default"] ) {
return this._breakAttributes( positionOrRange );
} else {
return this._breakAttributesRange( positionOrRange );
}
}
/**
* Breaks a {@link module:engine/view/containerelement~ContainerElement container view element} into two, at the given position.
* The position has to be directly inside the container element and cannot be in the root. It does not break the conrainer view element
* if the position is at the beginning or at the end of its parent element.
*
* <p>foo^bar</p> -> <p>foo</p><p>bar</p>
* <div><p>foo</p>^<p>bar</p></div> -> <div><p>foo</p></div><div><p>bar</p></div>
* <p>^foobar</p> -> ^<p>foobar</p>
* <p>foobar^</p> -> <p>foobar</p>^
*
* **Note:** The difference between {@link module:engine/view/downcastwriter~DowncastWriter#breakAttributes breakAttributes()} and
* {@link module:engine/view/downcastwriter~DowncastWriter#breakContainer breakContainer()} is that `breakAttributes()` breaks all
* {@link module:engine/view/attributeelement~AttributeElement attribute elements} that are ancestors of a given `position`,
* up to the first encountered {@link module:engine/view/containerelement~ContainerElement container element}.
* `breakContainer()` assumes that the given `position` is directly in the container element and breaks that container element.
*
* @see module:engine/view/attributeelement~AttributeElement
* @see module:engine/view/containerelement~ContainerElement
* @see module:engine/view/downcastwriter~DowncastWriter#breakAttributes
* @param {module:engine/view/position~Position} position The position where to break the element.
* @returns {module:engine/view/position~Position} The position between broken elements. If an element has not been broken,
* the returned position is placed either before or after it.
*/
breakContainer( position ) {
const element = position.parent;
if ( !( element.is( 'containerElement' ) ) ) {
/**
* Trying to break an element which is not a container element.
*
* @error view-writer-break-non-container-element
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_8__["default"]( 'view-writer-break-non-container-element', this.document );
}
if ( !element.parent ) {
/**
* Trying to break root element.
*
* @error view-writer-break-root
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_8__["default"]( 'view-writer-break-root', this.document );
}
if ( position.isAtStart ) {
return _position__WEBPACK_IMPORTED_MODULE_0__["default"]._createBefore( element );
} else if ( !position.isAtEnd ) {
const newElement = element._clone( false );
this.insert( _position__WEBPACK_IMPORTED_MODULE_0__["default"]._createAfter( element ), newElement );
const sourceRange = new _range__WEBPACK_IMPORTED_MODULE_1__["default"]( position, _position__WEBPACK_IMPORTED_MODULE_0__["default"]._createAt( element, 'end' ) );
const targetPosition = new _position__WEBPACK_IMPORTED_MODULE_0__["default"]( newElement, 0 );
this.move( sourceRange, targetPosition );
}
return _position__WEBPACK_IMPORTED_MODULE_0__["default"]._createAfter( element );
}
/**
* Merges {@link module:engine/view/attributeelement~AttributeElement attribute elements}. It also merges text nodes if needed.
* Only {@link module:engine/view/attributeelement~AttributeElement#isSimilar similar} attribute elements can be merged.
*
* In following examples `<p>` is a container and `<b>` is an attribute element:
*
* <p>foo[]bar</p> -> <p>foo{}bar</p>
* <p><b>foo</b>[]<b>bar</b></p> -> <p><b>foo{}bar</b></p>
* <p><b foo="bar">a</b>[]<b foo="baz">b</b></p> -> <p><b foo="bar">a</b>[]<b foo="baz">b</b></p>
*
* It will also take care about empty attributes when merging:
*
* <p><b>[]</b></p> -> <p>[]</p>
* <p><b>foo</b><i>[]</i><b>bar</b></p> -> <p><b>foo{}bar</b></p>
*
* **Note:** Difference between {@link module:engine/view/downcastwriter~DowncastWriter#mergeAttributes mergeAttributes} and
* {@link module:engine/view/downcastwriter~DowncastWriter#mergeContainers mergeContainers} is that `mergeAttributes` merges two
* {@link module:engine/view/attributeelement~AttributeElement attribute elements} or {@link module:engine/view/text~Text text nodes}
* while `mergeContainer` merges two {@link module:engine/view/containerelement~ContainerElement container elements}.
*
* @see module:engine/view/attributeelement~AttributeElement
* @see module:engine/view/containerelement~ContainerElement
* @see module:engine/view/downcastwriter~DowncastWriter#mergeContainers
* @param {module:engine/view/position~Position} position Merge position.
* @returns {module:engine/view/position~Position} Position after merge.
*/
mergeAttributes( position ) {
const positionOffset = position.offset;
const positionParent = position.parent;
// When inside text node - nothing to merge.
if ( positionParent.is( '$text' ) ) {
return position;
}
// When inside empty attribute - remove it.
if ( positionParent.is( 'attributeElement' ) && positionParent.childCount === 0 ) {
const parent = positionParent.parent;
const offset = positionParent.index;
positionParent._remove();
this._removeFromClonedElementsGroup( positionParent );
return this.mergeAttributes( new _position__WEBPACK_IMPORTED_MODULE_0__["default"]( parent, offset ) );
}
const nodeBefore = positionParent.getChild( positionOffset - 1 );
const nodeAfter = positionParent.getChild( positionOffset );
// Position should be placed between two nodes.
if ( !nodeBefore || !nodeAfter ) {
return position;
}
// When position is between two text nodes.
if ( nodeBefore.is( '$text' ) && nodeAfter.is( '$text' ) ) {
return mergeTextNodes( nodeBefore, nodeAfter );
}
// When position is between two same attribute elements.
else if ( nodeBefore.is( 'attributeElement' ) && nodeAfter.is( 'attributeElement' ) && nodeBefore.isSimilar( nodeAfter ) ) {
// Move all children nodes from node placed after selection and remove that node.
const count = nodeBefore.childCount;
nodeBefore._appendChild( nodeAfter.getChildren() );
nodeAfter._remove();
this._removeFromClonedElementsGroup( nodeAfter );
// New position is located inside the first node, before new nodes.
// Call this method recursively to merge again if needed.
return this.mergeAttributes( new _position__WEBPACK_IMPORTED_MODULE_0__["default"]( nodeBefore, count ) );
}
return position;
}
/**
* Merges two {@link module:engine/view/containerelement~ContainerElement container elements} that are before and after given position.
* Precisely, the element after the position is removed and it's contents are moved to element before the position.
*
* <p>foo</p>^<p>bar</p> -> <p>foo^bar</p>
* <div>foo</div>^<p>bar</p> -> <div>foo^bar</div>
*
* **Note:** Difference between {@link module:engine/view/downcastwriter~DowncastWriter#mergeAttributes mergeAttributes} and
* {@link module:engine/view/downcastwriter~DowncastWriter#mergeContainers mergeContainers} is that `mergeAttributes` merges two
* {@link module:engine/view/attributeelement~AttributeElement attribute elements} or {@link module:engine/view/text~Text text nodes}
* while `mergeContainer` merges two {@link module:engine/view/containerelement~ContainerElement container elements}.
*
* @see module:engine/view/attributeelement~AttributeElement
* @see module:engine/view/containerelement~ContainerElement
* @see module:engine/view/downcastwriter~DowncastWriter#mergeAttributes
* @param {module:engine/view/position~Position} position Merge position.
* @returns {module:engine/view/position~Position} Position after merge.
*/
mergeContainers( position ) {
const prev = position.nodeBefore;
const next = position.nodeAfter;
if ( !prev || !next || !prev.is( 'containerElement' ) || !next.is( 'containerElement' ) ) {
/**
* Element before and after given position cannot be merged.
*
* @error view-writer-merge-containers-invalid-position
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_8__["default"]( 'view-writer-merge-containers-invalid-position', this.document );
}
const lastChild = prev.getChild( prev.childCount - 1 );
const newPosition = lastChild instanceof _text__WEBPACK_IMPORTED_MODULE_11__["default"] ? _position__WEBPACK_IMPORTED_MODULE_0__["default"]._createAt( lastChild, 'end' ) : _position__WEBPACK_IMPORTED_MODULE_0__["default"]._createAt( prev, 'end' );
this.move( _range__WEBPACK_IMPORTED_MODULE_1__["default"]._createIn( next ), _position__WEBPACK_IMPORTED_MODULE_0__["default"]._createAt( prev, 'end' ) );
this.remove( _range__WEBPACK_IMPORTED_MODULE_1__["default"]._createOn( next ) );
return newPosition;
}
/**
* Inserts a node or nodes at specified position. Takes care about breaking attributes before insertion
* and merging them afterwards.
*
* Throws {@link module:utils/ckeditorerror~CKEditorError CKEditorError} `view-writer-insert-invalid-node` when nodes to insert
* contains instances that are not {@link module:engine/view/text~Text Texts},
* {@link module:engine/view/attributeelement~AttributeElement AttributeElements},
* {@link module:engine/view/containerelement~ContainerElement ContainerElements},
* {@link module:engine/view/emptyelement~EmptyElement EmptyElements},
* {@link module:engine/view/rawelement~RawElement RawElements} or
* {@link module:engine/view/uielement~UIElement UIElements}.
*
* @param {module:engine/view/position~Position} position Insertion position.
* @param {module:engine/view/text~Text|module:engine/view/attributeelement~AttributeElement|
* module:engine/view/containerelement~ContainerElement|module:engine/view/emptyelement~EmptyElement|
* module:engine/view/rawelement~RawElement|module:engine/view/uielement~UIElement|
* Iterable.<module:engine/view/text~Text|
* module:engine/view/attributeelement~AttributeElement|module:engine/view/containerelement~ContainerElement|
* module:engine/view/emptyelement~EmptyElement|module:engine/view/rawelement~RawElement|
* module:engine/view/uielement~UIElement>} nodes Node or nodes to insert.
* @returns {module:engine/view/range~Range} Range around inserted nodes.
*/
insert( position, nodes ) {
nodes = (0,_ckeditor_ckeditor5_utils_src_isiterable__WEBPACK_IMPORTED_MODULE_10__["default"])( nodes ) ? [ ...nodes ] : [ nodes ];
// Check if nodes to insert are instances of AttributeElements, ContainerElements, EmptyElements, UIElements or Text.
validateNodesToInsert( nodes, this.document );
// Group nodes in batches of nodes that require or do not require breaking an AttributeElements.
const nodeGroups = nodes.reduce( ( groups, node ) => {
const lastGroup = groups[ groups.length - 1 ];
// Break attributes on nodes that do exist in the model tree so they can have attributes, other elements
// can't have an attribute in model and won't get wrapped with an AttributeElement while down-casted.
const breakAttributes = !( node.is( 'uiElement' ) && node.isAllowedInsideAttributeElement );
if ( !lastGroup || lastGroup.breakAttributes != breakAttributes ) {
groups.push( {
breakAttributes,
nodes: [ node ]
} );
} else {
lastGroup.nodes.push( node );
}
return groups;
}, [] );
// Insert nodes in batches.
let start = null;
let end = position;
for ( const { nodes, breakAttributes } of nodeGroups ) {
const range = this._insertNodes( end, nodes, breakAttributes );
if ( !start ) {
start = range.start;
}
end = range.end;
}
// When no nodes were inserted - return collapsed range.
if ( !start ) {
return new _range__WEBPACK_IMPORTED_MODULE_1__["default"]( position );
}
return new _range__WEBPACK_IMPORTED_MODULE_1__["default"]( start, end );
}
/**
* Removes provided range from the container.
*
* Throws {@link module:utils/ckeditorerror~CKEditorError CKEditorError} `view-writer-invalid-range-container` when
* {@link module:engine/view/range~Range#start start} and {@link module:engine/view/range~Range#end end} positions are not placed inside
* same parent container.
*
* @param {module:engine/view/range~Range|module:engine/view/item~Item} rangeOrItem Range to remove from container
* or an {@link module:engine/view/item~Item item} to remove. If range is provided, after removing, it will be updated
* to a collapsed range showing the new position.
* @returns {module:engine/view/documentfragment~DocumentFragment} Document fragment containing removed nodes.
*/
remove( rangeOrItem ) {
const range = rangeOrItem instanceof _range__WEBPACK_IMPORTED_MODULE_1__["default"] ? rangeOrItem : _range__WEBPACK_IMPORTED_MODULE_1__["default"]._createOn( rangeOrItem );
validateRangeContainer( range, this.document );
// If range is collapsed - nothing to remove.
if ( range.isCollapsed ) {
return new _documentfragment__WEBPACK_IMPORTED_MODULE_9__["default"]( this.document );
}
// Break attributes at range start and end.
const { start: breakStart, end: breakEnd } = this._breakAttributesRange( range, true );
const parentContainer = breakStart.parent;
const count = breakEnd.offset - breakStart.offset;
// Remove nodes in range.
const removed = parentContainer._removeChildren( breakStart.offset, count );
for ( const node of removed ) {
this._removeFromClonedElementsGroup( node );
}
// Merge after removing.
const mergePosition = this.mergeAttributes( breakStart );
range.start = mergePosition;
range.end = mergePosition.clone();
// Return removed nodes.
return new _documentfragment__WEBPACK_IMPORTED_MODULE_9__["default"]( this.document, removed );
}
/**
* Removes matching elements from given range.
*
* Throws {@link module:utils/ckeditorerror~CKEditorError CKEditorError} `view-writer-invalid-range-container` when
* {@link module:engine/view/range~Range#start start} and {@link module:engine/view/range~Range#end end} positions are not placed inside
* same parent container.
*
* @param {module:engine/view/range~Range} range Range to clear.
* @param {module:engine/view/element~Element} element Element to remove.
*/
clear( range, element ) {
validateRangeContainer( range, this.document );
// Create walker on given range.
// We walk backward because when we remove element during walk it modifies range end position.
const walker = range.getWalker( {
direction: 'backward',
ignoreElementEnd: true
} );
// Let's walk.
for ( const current of walker ) {
const item = current.item;
let rangeToRemove;
// When current item matches to the given element.
if ( item.is( 'element' ) && element.isSimilar( item ) ) {
// Create range on this element.
rangeToRemove = _range__WEBPACK_IMPORTED_MODULE_1__["default"]._createOn( item );
// When range starts inside Text or TextProxy element.
} else if ( !current.nextPosition.isAfter( range.start ) && item.is( '$textProxy' ) ) {
// We need to check if parent of this text matches to given element.
const parentElement = item.getAncestors().find( ancestor => {
return ancestor.is( 'element' ) && element.isSimilar( ancestor );
} );
// If it is then create range inside this element.
if ( parentElement ) {
rangeToRemove = _range__WEBPACK_IMPORTED_MODULE_1__["default"]._createIn( parentElement );
}
}
// If we have found element to remove.
if ( rangeToRemove ) {
// We need to check if element range stick out of the given range and truncate if it is.
if ( rangeToRemove.end.isAfter( range.end ) ) {
rangeToRemove.end = range.end;
}
if ( rangeToRemove.start.isBefore( range.start ) ) {
rangeToRemove.start = range.start;
}
// At the end we remove range with found element.
this.remove( rangeToRemove );
}
}
}
/**
* Moves nodes from provided range to target position.
*
* Throws {@link module:utils/ckeditorerror~CKEditorError CKEditorError} `view-writer-invalid-range-container` when
* {@link module:engine/view/range~Range#start start} and {@link module:engine/view/range~Range#end end} positions are not placed inside
* same parent container.
*
* @param {module:engine/view/range~Range} sourceRange Range containing nodes to move.
* @param {module:engine/view/position~Position} targetPosition Position to insert.
* @returns {module:engine/view/range~Range} Range in target container. Inserted nodes are placed between
* {@link module:engine/view/range~Range#start start} and {@link module:engine/view/range~Range#end end} positions.
*/
move( sourceRange, targetPosition ) {
let nodes;
if ( targetPosition.isAfter( sourceRange.end ) ) {
targetPosition = this._breakAttributes( targetPosition, true );
const parent = targetPosition.parent;
const countBefore = parent.childCount;
sourceRange = this._breakAttributesRange( sourceRange, true );
nodes = this.remove( sourceRange );
targetPosition.offset += ( parent.childCount - countBefore );
} else {
nodes = this.remove( sourceRange );
}
return this.insert( targetPosition, nodes );
}
/**
* Wraps elements within range with provided {@link module:engine/view/attributeelement~AttributeElement AttributeElement}.
* If a collapsed range is provided, it will be wrapped only if it is equal to view selection.
*
* If a collapsed range was passed and is same as selection, the selection
* will be moved to the inside of the wrapped attribute element.
*
* Throws {@link module:utils/ckeditorerror~CKEditorError} `view-writer-invalid-range-container`
* when {@link module:engine/view/range~Range#start}
* and {@link module:engine/view/range~Range#end} positions are not placed inside same parent container.
*
* Throws {@link module:utils/ckeditorerror~CKEditorError} `view-writer-wrap-invalid-attribute` when passed attribute element is not
* an instance of {@link module:engine/view/attributeelement~AttributeElement AttributeElement}.
*
* Throws {@link module:utils/ckeditorerror~CKEditorError} `view-writer-wrap-nonselection-collapsed-range` when passed range
* is collapsed and different than view selection.
*
* **Note:** Attribute elements by default can wrap {@link module:engine/view/text~Text},
* {@link module:engine/view/emptyelement~EmptyElement}, {@link module:engine/view/uielement~UIElement},
* {@link module:engine/view/rawelement~RawElement} and other attribute elements with higher priority. Other elements while placed
* inside an attribute element will split it (or nest it in case of an `AttributeElement`). This behavior can be modified by changing
* the `isAllowedInsideAttributeElement` option while using
* {@link module:engine/view/downcastwriter~DowncastWriter#createContainerElement},
* {@link module:engine/view/downcastwriter~DowncastWriter#createEmptyElement},
* {@link module:engine/view/downcastwriter~DowncastWriter#createUIElement} or
* {@link module:engine/view/downcastwriter~DowncastWriter#createRawElement}.
*
* @param {module:engine/view/range~Range} range Range to wrap.
* @param {module:engine/view/attributeelement~AttributeElement} attribute Attribute element to use as wrapper.
* @returns {module:engine/view/range~Range} range Range after wrapping, spanning over wrapping attribute element.
*/
wrap( range, attribute ) {
if ( !( attribute instanceof _attributeelement__WEBPACK_IMPORTED_MODULE_4__["default"] ) ) {
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_8__["default"](
'view-writer-wrap-invalid-attribute',
this.document
);
}
validateRangeContainer( range, this.document );
if ( !range.isCollapsed ) {
// Non-collapsed range. Wrap it with the attribute element.
return this._wrapRange( range, attribute );
} else {
// Collapsed range. Wrap position.
let position = range.start;
if ( position.parent.is( 'element' ) && !_hasNonUiChildren( position.parent ) ) {
position = position.getLastMatchingPosition( value => value.item.is( 'uiElement' ) );
}
position = this._wrapPosition( position, attribute );
const viewSelection = this.document.selection;
// If wrapping position is equal to view selection, move view selection inside wrapping attribute element.
if ( viewSelection.isCollapsed && viewSelection.getFirstPosition().isEqual( range.start ) ) {
this.setSelection( position );
}
return new _range__WEBPACK_IMPORTED_MODULE_1__["default"]( position );
}
}
/**
* Unwraps nodes within provided range from attribute element.
*
* Throws {@link module:utils/ckeditorerror~CKEditorError CKEditorError} `view-writer-invalid-range-container` when
* {@link module:engine/view/range~Range#start start} and {@link module:engine/view/range~Range#end end} positions are not placed inside
* same parent container.
*
* @param {module:engine/view/range~Range} range
* @param {module:engine/view/attributeelement~AttributeElement} attribute
*/
unwrap( range, attribute ) {
if ( !( attribute instanceof _attributeelement__WEBPACK_IMPORTED_MODULE_4__["default"] ) ) {
/**
* The `attribute` passed to {@link module:engine/view/downcastwriter~DowncastWriter#unwrap `DowncastWriter#unwrap()`}
* must be an instance of {@link module:engine/view/attributeelement~AttributeElement `AttributeElement`}.
*
* @error view-writer-unwrap-invalid-attribute
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_8__["default"](
'view-writer-unwrap-invalid-attribute',
this.document
);
}
validateRangeContainer( range, this.document );
// If range is collapsed - nothing to unwrap.
if ( range.isCollapsed ) {
return range;
}
// Break attributes at range start and end.
const { start: breakStart, end: breakEnd } = this._breakAttributesRange( range, true );
const parentContainer = breakStart.parent;
// Unwrap children located between break points.
const newRange = this._unwrapChildren( parentContainer, breakStart.offset, breakEnd.offset, attribute );
// Merge attributes at the both ends and return a new range.
const start = this.mergeAttributes( newRange.start );
// If start position was merged - move end position back.
if ( !start.isEqual( newRange.start ) ) {
newRange.end.offset--;
}
const end = this.mergeAttributes( newRange.end );
return new _range__WEBPACK_IMPORTED_MODULE_1__["default"]( start, end );
}
/**
* Renames element by creating a copy of renamed element but with changed name and then moving contents of the
* old element to the new one. Keep in mind that this will invalidate all {@link module:engine/view/position~Position positions} which
* has renamed element as {@link module:engine/view/position~Position#parent a parent}.
*
* New element has to be created because `Element#tagName` property in DOM is readonly.
*
* Since this function creates a new element and removes the given one, the new element is returned to keep reference.
*
* @param {String} newName New name for element.
* @param {module:engine/view/containerelement~ContainerElement} viewElement Element to be renamed.
* @returns {module:engine/view/containerelement~ContainerElement} Element created due to rename.
*/
rename( newName, viewElement ) {
const newElement = new _containerelement__WEBPACK_IMPORTED_MODULE_3__["default"]( this.document, newName, viewElement.getAttributes() );
this.insert( _position__WEBPACK_IMPORTED_MODULE_0__["default"]._createAfter( viewElement ), newElement );
this.move( _range__WEBPACK_IMPORTED_MODULE_1__["default"]._createIn( viewElement ), _position__WEBPACK_IMPORTED_MODULE_0__["default"]._createAt( newElement, 0 ) );
this.remove( _range__WEBPACK_IMPORTED_MODULE_1__["default"]._createOn( viewElement ) );
return newElement;
}
/**
* Cleans up memory by removing obsolete cloned elements group from the writer.
*
* Should be used whenever all {@link module:engine/view/attributeelement~AttributeElement attribute elements}
* with the same {@link module:engine/view/attributeelement~AttributeElement#id id} are going to be removed from the view and
* the group will no longer be needed.
*
* Cloned elements group are not removed automatically in case if the group is still needed after all its elements
* were removed from the view.
*
* Keep in mind that group names are equal to the `id` property of the attribute element.
*
* @param {String} groupName Name of the group to clear.
*/
clearClonedElementsGroup( groupName ) {
this._cloneGroups.delete( groupName );
}
/**
* Creates position at the given location. The location can be specified as:
*
* * a {@link module:engine/view/position~Position position},
* * parent element and offset (offset defaults to `0`),
* * parent element and `'end'` (sets position at the end of that element),
* * {@link module:engine/view/item~Item view item} and `'before'` or `'after'` (sets position before or after given view item).
*
* This method is a shortcut to other constructors such as:
*
* * {@link #createPositionBefore},
* * {@link #createPositionAfter},
*
* @param {module:engine/view/item~Item|module:engine/model/position~Position} itemOrPosition
* @param {Number|'end'|'before'|'after'} [offset] Offset or one of the flags. Used only when
* first parameter is a {@link module:engine/view/item~Item view item}.
* @returns {module:engine/view/position~Position}
*/
createPositionAt( itemOrPosition, offset ) {
return _position__WEBPACK_IMPORTED_MODULE_0__["default"]._createAt( itemOrPosition, offset );
}
/**
* Creates a new position after given view item.
*
* @param {module:engine/view/item~Item} item View item after which the position should be located.
* @returns {module:engine/view/position~Position}
*/
createPositionAfter( item ) {
return _position__WEBPACK_IMPORTED_MODULE_0__["default"]._createAfter( item );
}
/**
* Creates a new position before given view item.
*
* @param {module:engine/view/item~Item} item View item before which the position should be located.
* @returns {module:engine/view/position~Position}
*/
createPositionBefore( item ) {
return _position__WEBPACK_IMPORTED_MODULE_0__["default"]._createBefore( item );
}
/**
* Creates a range spanning from `start` position to `end` position.
*
* **Note:** This factory method creates its own {@link module:engine/view/position~Position} instances basing on passed values.
*
* @param {module:engine/view/position~Position} start Start position.
* @param {module:engine/view/position~Position} [end] End position. If not set, range will be collapsed at `start` position.
* @returns {module:engine/view/range~Range}
*/
createRange( start, end ) {
return new _range__WEBPACK_IMPORTED_MODULE_1__["default"]( start, end );
}
/**
* Creates a range that starts before given {@link module:engine/view/item~Item view item} and ends after it.
*
* @param {module:engine/view/item~Item} item
* @returns {module:engine/view/range~Range}
*/
createRangeOn( item ) {
return _range__WEBPACK_IMPORTED_MODULE_1__["default"]._createOn( item );
}
/**
* Creates a range inside an {@link module:engine/view/element~Element element} which starts before the first child of
* that element and ends after the last child of that element.
*
* @param {module:engine/view/element~Element} element Element which is a parent for the range.
* @returns {module:engine/view/range~Range}
*/
createRangeIn( element ) {
return _range__WEBPACK_IMPORTED_MODULE_1__["default"]._createIn( element );
}
/**
* Creates new {@link module:engine/view/selection~Selection} instance.
*
* // Creates empty selection without ranges.
* const selection = writer.createSelection();
*
* // Creates selection at the given range.
* const range = writer.createRange( start, end );
* const selection = writer.createSelection( range );
*
* // Creates selection at the given ranges
* const ranges = [ writer.createRange( start1, end2 ), writer.createRange( star2, end2 ) ];
* const selection = writer.createSelection( ranges );
*
* // Creates selection from the other selection.
* const otherSelection = writer.createSelection();
* const selection = writer.createSelection( otherSelection );
*
* // Creates selection from the document selection.
* const selection = writer.createSelection( editor.editing.view.document.selection );
*
* // Creates selection at the given position.
* const position = writer.createPositionFromPath( root, path );
* const selection = writer.createSelection( position );
*
* // Creates collapsed selection at the position of given item and offset.
* const paragraph = writer.createContainerElement( 'p' );
* const selection = writer.createSelection( paragraph, offset );
*
* // Creates a range inside an {@link module:engine/view/element~Element element} which starts before the
* // first child of that element and ends after the last child of that element.
* const selection = writer.createSelection( paragraph, 'in' );
*
* // Creates a range on an {@link module:engine/view/item~Item item} which starts before the item and ends
* // just after the item.
* const selection = writer.createSelection( paragraph, 'on' );
*
* `Selection`'s constructor allow passing additional options (`backward`, `fake` and `label`) as the last argument.
*
* // Creates backward selection.
* const selection = writer.createSelection( range, { backward: true } );
*
* Fake selection does not render as browser native selection over selected elements and is hidden to the user.
* This way, no native selection UI artifacts are displayed to the user and selection over elements can be
* represented in other way, for example by applying proper CSS class.
*
* Additionally fake's selection label can be provided. It will be used to describe fake selection in DOM
* (and be properly handled by screen readers).
*
* // Creates fake selection with label.
* const selection = writer.createSelection( range, { fake: true, label: 'foo' } );
*
* @param {module:engine/view/selection~Selectable} [selectable=null]
* @param {Number|'before'|'end'|'after'|'on'|'in'} [placeOrOffset] Offset or place when selectable is an `Item`.
* @param {Object} [options]
* @param {Boolean} [options.backward] Sets this selection instance to be backward.
* @param {Boolean} [options.fake] Sets this selection instance to be marked as `fake`.
* @param {String} [options.label] Label for the fake selection.
* @returns {module:engine/view/selection~Selection}
*/
createSelection( selectable, placeOrOffset, options ) {
return new _selection__WEBPACK_IMPORTED_MODULE_2__["default"]( selectable, placeOrOffset, options );
}
/**
* Creates placeholders for child elements of the {@link module:engine/conversion/downcasthelpers~DowncastHelpers#elementToStructure
* `elementToStructure()`} conversion helper.
*
* const viewSlot = conversionApi.writer.createSlot();
* const viewPosition = conversionApi.writer.createPositionAt( viewElement, 0 );
*
* conversionApi.writer.insert( viewPosition, viewSlot );
*
* It could be filtered down to a specific subset of children (only `<foo>` model elements in this case):
*
* const viewSlot = conversionApi.writer.createSlot( node => node.is( 'element', 'foo' ) );
* const viewPosition = conversionApi.writer.createPositionAt( viewElement, 0 );
*
* conversionApi.writer.insert( viewPosition, viewSlot );
*
* While providing a filtered slot, make sure to provide slots for all child nodes. A single node can not be downcasted into
* multiple slots.
*
* **Note**: You should not change the order of nodes. View elements should be in the same order as model nodes.
*
* @param {'children'|module:engine/conversion/downcasthelpers~SlotFilter} [modeOrFilter='children'] The filter for child nodes.
* @returns {module:engine/view/element~Element} The slot element to be placed in to the view structure while processing
* {@link module:engine/conversion/downcasthelpers~DowncastHelpers#elementToStructure `elementToStructure()`}.
*/
createSlot( modeOrFilter ) {
if ( !this._slotFactory ) {
/**
* The `createSlot()` method is only allowed inside the `elementToStructure` downcast helper callback.
*
* @error view-writer-invalid-create-slot-context
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_8__["default"]( 'view-writer-invalid-create-slot-context', this.document );
}
return this._slotFactory( this, modeOrFilter );
}
/**
* Registers a slot factory.
*
* @protected
* @param {Function} slotFactory The slot factory.
*/
_registerSlotFactory( slotFactory ) {
this._slotFactory = slotFactory;
}
/**
* Clears the registered slot factory.
*
* @protected
*/
_clearSlotFactory() {
this._slotFactory = null;
}
/**
* Inserts a node or nodes at the specified position. Takes care of breaking attributes before insertion
* and merging them afterwards if requested by the breakAttributes param.
*
* @private
* @param {module:engine/view/position~Position} position Insertion position.
* @param {module:engine/view/text~Text|module:engine/view/attributeelement~AttributeElement|
* module:engine/view/containerelement~ContainerElement|module:engine/view/emptyelement~EmptyElement|
* module:engine/view/rawelement~RawElement|module:engine/view/uielement~UIElement|
* Iterable.<module:engine/view/text~Text|
* module:engine/view/attributeelement~AttributeElement|module:engine/view/containerelement~ContainerElement|
* module:engine/view/emptyelement~EmptyElement|module:engine/view/rawelement~RawElement|
* module:engine/view/uielement~UIElement>} nodes Node or nodes to insert.
* @param {Boolean} breakAttributes Whether attributes should be broken.
* @returns {module:engine/view/range~Range} Range around inserted nodes.
*/
_insertNodes( position, nodes, breakAttributes ) {
let parentElement;
// Break attributes on nodes that do exist in the model tree so they can have attributes, other elements
// can't have an attribute in model and won't get wrapped with an AttributeElement while down-casted.
if ( breakAttributes ) {
parentElement = getParentContainer( position );
} else {
parentElement = position.parent.is( '$text' ) ? position.parent.parent : position.parent;
}
if ( !parentElement ) {
/**
* Position's parent container cannot be found.
*
* @error view-writer-invalid-position-container
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_8__["default"](
'view-writer-invalid-position-container',
this.document
);
}
let insertionPosition;
if ( breakAttributes ) {
insertionPosition = this._breakAttributes( position, true );
} else {
insertionPosition = position.parent.is( '$text' ) ? breakTextNode( position ) : position;
}
const length = parentElement._insertChild( insertionPosition.offset, nodes );
for ( const node of nodes ) {
this._addToClonedElementsGroup( node );
}
const endPosition = insertionPosition.getShiftedBy( length );
const start = this.mergeAttributes( insertionPosition );
// If start position was merged - move end position.
if ( !start.isEqual( insertionPosition ) ) {
endPosition.offset--;
}
const end = this.mergeAttributes( endPosition );
return new _range__WEBPACK_IMPORTED_MODULE_1__["default"]( start, end );
}
/**
* Wraps children with provided `wrapElement`. Only children contained in `parent` element between
* `startOffset` and `endOffset` will be wrapped.
*
* @private
* @param {module:engine/view/element~Element} parent
* @param {Number} startOffset
* @param {Number} endOffset
* @param {module:engine/view/element~Element} wrapElement
*/
_wrapChildren( parent, startOffset, endOffset, wrapElement ) {
let i = startOffset;
const wrapPositions = [];
while ( i < endOffset ) {
const child = parent.getChild( i );
const isText = child.is( '$text' );
const isAttribute = child.is( 'attributeElement' );
const isAllowedInsideAttributeElement = child.isAllowedInsideAttributeElement;
//
// (In all examples, assume that `wrapElement` is `<span class="foo">` element.)
//
// Check if `wrapElement` can be joined with the wrapped element. One of requirements is having same name.
// If possible, join elements.
//
// <p><span class="bar">abc</span></p> --> <p><span class="foo bar">abc</span></p>
//
if ( isAttribute && this._wrapAttributeElement( wrapElement, child ) ) {
wrapPositions.push( new _position__WEBPACK_IMPORTED_MODULE_0__["default"]( parent, i ) );
}
//
// Wrap the child if it is not an attribute element or if it is an attribute element that should be inside
// `wrapElement` (due to priority).
//
// <p>abc</p> --> <p><span class="foo">abc</span></p>
// <p><strong>abc</strong></p> --> <p><span class="foo"><strong>abc</strong></span></p>
else if ( isText || isAllowedInsideAttributeElement || ( isAttribute && shouldABeOutsideB( wrapElement, child ) ) ) {
// Clone attribute.
const newAttribute = wrapElement._clone();
// Wrap current node with new attribute.
child._remove();
newAttribute._appendChild( child );
parent._insertChild( i, newAttribute );
this._addToClonedElementsGroup( newAttribute );
wrapPositions.push( new _position__WEBPACK_IMPORTED_MODULE_0__["default"]( parent, i ) );
}
//
// If other nested attribute is found and it wasn't wrapped (see above), continue wrapping inside it.
//
// <p><a href="foo.html">abc</a></p> --> <p><a href="foo.html"><span class="foo">abc</span></a></p>
//
else if ( isAttribute ) {
this._wrapChildren( child, 0, child.childCount, wrapElement );
}
i++;
}
// Merge at each wrap.
let offsetChange = 0;
for ( const position of wrapPositions ) {
position.offset -= offsetChange;
// Do not merge with elements outside selected children.
if ( position.offset == startOffset ) {
continue;
}
const newPosition = this.mergeAttributes( position );
// If nodes were merged - other merge offsets will change.
if ( !newPosition.isEqual( position ) ) {
offsetChange++;
endOffset--;
}
}
return _range__WEBPACK_IMPORTED_MODULE_1__["default"]._createFromParentsAndOffsets( parent, startOffset, parent, endOffset );
}
/**
* Unwraps children from provided `unwrapElement`. Only children contained in `parent` element between
* `startOffset` and `endOffset` will be unwrapped.
*
* @private
* @param {module:engine/view/element~Element} parent
* @param {Number} startOffset
* @param {Number} endOffset
* @param {module:engine/view/element~Element} unwrapElement
*/
_unwrapChildren( parent, startOffset, endOffset, unwrapElement ) {
let i = startOffset;
const unwrapPositions = [];
// Iterate over each element between provided offsets inside parent.
// We don't use tree walker or range iterator because we will be removing and merging potentially multiple nodes,
// so it could get messy. It is safer to it manually in this case.
while ( i < endOffset ) {
const child = parent.getChild( i );
// Skip all text nodes. There should be no container element's here either.
if ( !child.is( 'attributeElement' ) ) {
i++;
continue;
}
//
// (In all examples, assume that `unwrapElement` is `<span class="foo">` element.)
//
// If the child is similar to the given attribute element, unwrap it - it will be completely removed.
//
// <p><span class="foo">abc</span>xyz</p> --> <p>abcxyz</p>
//
if ( child.isSimilar( unwrapElement ) ) {
const unwrapped = child.getChildren();
const count = child.childCount;
// Replace wrapper element with its children
child._remove();
parent._insertChild( i, unwrapped );
this._removeFromClonedElementsGroup( child );
// Save start and end position of moved items.
unwrapPositions.push(
new _position__WEBPACK_IMPORTED_MODULE_0__["default"]( parent, i ),
new _position__WEBPACK_IMPORTED_MODULE_0__["default"]( parent, i + count )
);
// Skip elements that were unwrapped. Assuming there won't be another element to unwrap in child elements.
i += count;
endOffset += count - 1;
continue;
}
//
// If the child is not similar but is an attribute element, try partial unwrapping - remove the same attributes/styles/classes.
// Partial unwrapping will happen only if the elements have the same name.
//
// <p><span class="foo bar">abc</span>xyz</p> --> <p><span class="bar">abc</span>xyz</p>
// <p><i class="foo">abc</i>xyz</p> --> <p><i class="foo">abc</i>xyz</p>
//
if ( this._unwrapAttributeElement( unwrapElement, child ) ) {
unwrapPositions.push(
new _position__WEBPACK_IMPORTED_MODULE_0__["default"]( parent, i ),
new _position__WEBPACK_IMPORTED_MODULE_0__["default"]( parent, i + 1 )
);
i++;
continue;
}
//
// If other nested attribute is found, look through it's children for elements to unwrap.
//
// <p><i><span class="foo">abc</span></i><p> --> <p><i>abc</i><p>
//
this._unwrapChildren( child, 0, child.childCount, unwrapElement );
i++;
}
// Merge at each unwrap.
let offsetChange = 0;
for ( const position of unwrapPositions ) {
position.offset -= offsetChange;
// Do not merge with elements outside selected children.
if ( position.offset == startOffset || position.offset == endOffset ) {
continue;
}
const newPosition = this.mergeAttributes( position );
// If nodes were merged - other merge offsets will change.
if ( !newPosition.isEqual( position ) ) {
offsetChange++;
endOffset--;
}
}
return _range__WEBPACK_IMPORTED_MODULE_1__["default"]._createFromParentsAndOffsets( parent, startOffset, parent, endOffset );
}
/**
* Helper function for `view.writer.wrap`. Wraps range with provided attribute element.
* This method will also merge newly added attribute element with its siblings whenever possible.
*
* Throws {@link module:utils/ckeditorerror~CKEditorError} `view-writer-wrap-invalid-attribute` when passed attribute element is not
* an instance of {@link module:engine/view/attributeelement~AttributeElement AttributeElement}.
*
* @private
* @param {module:engine/view/range~Range} range
* @param {module:engine/view/attributeelement~AttributeElement} attribute
* @returns {module:engine/view/range~Range} New range after wrapping, spanning over wrapping attribute element.
*/
_wrapRange( range, attribute ) {
// Break attributes at range start and end.
const { start: breakStart, end: breakEnd } = this._breakAttributesRange( range, true );
const parentContainer = breakStart.parent;
// Wrap all children with attribute.
const newRange = this._wrapChildren( parentContainer, breakStart.offset, breakEnd.offset, attribute );
// Merge attributes at the both ends and return a new range.
const start = this.mergeAttributes( newRange.start );
// If start position was merged - move end position back.
if ( !start.isEqual( newRange.start ) ) {
newRange.end.offset--;
}
const end = this.mergeAttributes( newRange.end );
return new _range__WEBPACK_IMPORTED_MODULE_1__["default"]( start, end );
}
/**
* Helper function for {@link #wrap}. Wraps position with provided attribute element.
* This method will also merge newly added attribute element with its siblings whenever possible.
*
* Throws {@link module:utils/ckeditorerror~CKEditorError} `view-writer-wrap-invalid-attribute` when passed attribute element is not
* an instance of {@link module:engine/view/attributeelement~AttributeElement AttributeElement}.
*
* @private
* @param {module:engine/view/position~Position} position
* @param {module:engine/view/attributeelement~AttributeElement} attribute
* @returns {module:engine/view/position~Position} New position after wrapping.
*/
_wrapPosition( position, attribute ) {
// Return same position when trying to wrap with attribute similar to position parent.
if ( attribute.isSimilar( position.parent ) ) {
return movePositionToTextNode( position.clone() );
}
// When position is inside text node - break it and place new position between two text nodes.
if ( position.parent.is( '$text' ) ) {
position = breakTextNode( position );
}
// Create fake element that will represent position, and will not be merged with other attributes.
const fakePosition = this.createAttributeElement();
fakePosition._priority = Number.POSITIVE_INFINITY;
fakePosition.isSimilar = () => false;
// Insert fake element in position location.
position.parent._insertChild( position.offset, fakePosition );
// Range around inserted fake attribute element.
const wrapRange = new _range__WEBPACK_IMPORTED_MODULE_1__["default"]( position, position.getShiftedBy( 1 ) );
// Wrap fake element with attribute (it will also merge if possible).
this.wrap( wrapRange, attribute );
// Remove fake element and place new position there.
const newPosition = new _position__WEBPACK_IMPORTED_MODULE_0__["default"]( fakePosition.parent, fakePosition.index );
fakePosition._remove();
// If position is placed between text nodes - merge them and return position inside.
const nodeBefore = newPosition.nodeBefore;
const nodeAfter = newPosition.nodeAfter;
if ( nodeBefore instanceof _text__WEBPACK_IMPORTED_MODULE_11__["default"] && nodeAfter instanceof _text__WEBPACK_IMPORTED_MODULE_11__["default"] ) {
return mergeTextNodes( nodeBefore, nodeAfter );
}
// If position is next to text node - move position inside.
return movePositionToTextNode( newPosition );
}
/**
* Wraps one {@link module:engine/view/attributeelement~AttributeElement AttributeElement} into another by
* merging them if possible. When merging is possible - all attributes, styles and classes are moved from wrapper
* element to element being wrapped.
*
* @private
* @param {module:engine/view/attributeelement~AttributeElement} wrapper Wrapper AttributeElement.
* @param {module:engine/view/attributeelement~AttributeElement} toWrap AttributeElement to wrap using wrapper element.
* @returns {Boolean} Returns `true` if elements are merged.
*/
_wrapAttributeElement( wrapper, toWrap ) {
if ( !canBeJoined( wrapper, toWrap ) ) {
return false;
}
// Can't merge if name or priority differs.
if ( wrapper.name !== toWrap.name || wrapper.priority !== toWrap.priority ) {
return false;
}
// Check if attributes can be merged.
for ( const key of wrapper.getAttributeKeys() ) {
// Classes and styles should be checked separately.
if ( key === 'class' || key === 'style' ) {
continue;
}
// If some attributes are different we cannot wrap.
if ( toWrap.hasAttribute( key ) && toWrap.getAttribute( key ) !== wrapper.getAttribute( key ) ) {
return false;
}
}
// Check if styles can be merged.
for ( const key of wrapper.getStyleNames() ) {
if ( toWrap.hasStyle( key ) && toWrap.getStyle( key ) !== wrapper.getStyle( key ) ) {
return false;
}
}
// Move all attributes/classes/styles from wrapper to wrapped AttributeElement.
for ( const key of wrapper.getAttributeKeys() ) {
// Classes and styles should be checked separately.
if ( key === 'class' || key === 'style' ) {
continue;
}
// Move only these attributes that are not present - other are similar.
if ( !toWrap.hasAttribute( key ) ) {
this.setAttribute( key, wrapper.getAttribute( key ), toWrap );
}
}
for ( const key of wrapper.getStyleNames() ) {
if ( !toWrap.hasStyle( key ) ) {
this.setStyle( key, wrapper.getStyle( key ), toWrap );
}
}
for ( const key of wrapper.getClassNames() ) {
if ( !toWrap.hasClass( key ) ) {
this.addClass( key, toWrap );
}
}
return true;
}
/**
* Unwraps {@link module:engine/view/attributeelement~AttributeElement AttributeElement} from another by removing
* corresponding attributes, classes and styles. All attributes, classes and styles from wrapper should be present
* inside element being unwrapped.
*
* @private
* @param {module:engine/view/attributeelement~AttributeElement} wrapper Wrapper AttributeElement.
* @param {module:engine/view/attributeelement~AttributeElement} toUnwrap AttributeElement to unwrap using wrapper element.
* @returns {Boolean} Returns `true` if elements are unwrapped.
**/
_unwrapAttributeElement( wrapper, toUnwrap ) {
if ( !canBeJoined( wrapper, toUnwrap ) ) {
return false;
}
// Can't unwrap if name or priority differs.
if ( wrapper.name !== toUnwrap.name || wrapper.priority !== toUnwrap.priority ) {
return false;
}
// Check if AttributeElement has all wrapper attributes.
for ( const key of wrapper.getAttributeKeys() ) {
// Classes and styles should be checked separately.
if ( key === 'class' || key === 'style' ) {
continue;
}
// If some attributes are missing or different we cannot unwrap.
if ( !toUnwrap.hasAttribute( key ) || toUnwrap.getAttribute( key ) !== wrapper.getAttribute( key ) ) {
return false;
}
}
// Check if AttributeElement has all wrapper classes.
if ( !toUnwrap.hasClass( ...wrapper.getClassNames() ) ) {
return false;
}
// Check if AttributeElement has all wrapper styles.
for ( const key of wrapper.getStyleNames() ) {
// If some styles are missing or different we cannot unwrap.
if ( !toUnwrap.hasStyle( key ) || toUnwrap.getStyle( key ) !== wrapper.getStyle( key ) ) {
return false;
}
}
// Remove all wrapper's attributes from unwrapped element.
for ( const key of wrapper.getAttributeKeys() ) {
// Classes and styles should be checked separately.
if ( key === 'class' || key === 'style' ) {
continue;
}
this.removeAttribute( key, toUnwrap );
}
// Remove all wrapper's classes from unwrapped element.
this.removeClass( Array.from( wrapper.getClassNames() ), toUnwrap );
// Remove all wrapper's styles from unwrapped element.
this.removeStyle( Array.from( wrapper.getStyleNames() ), toUnwrap );
return true;
}
/**
* Helper function used by other `DowncastWriter` methods. Breaks attribute elements at the boundaries of given range.
*
* @private
* @param {module:engine/view/range~Range} range Range which `start` and `end` positions will be used to break attributes.
* @param {Boolean} [forceSplitText=false] If set to `true`, will break text nodes even if they are directly in container element.
* This behavior will result in incorrect view state, but is needed by other view writing methods which then fixes view state.
* @returns {module:engine/view/range~Range} New range with located at break positions.
*/
_breakAttributesRange( range, forceSplitText = false ) {
const rangeStart = range.start;
const rangeEnd = range.end;
validateRangeContainer( range, this.document );
// Break at the collapsed position. Return new collapsed range.
if ( range.isCollapsed ) {
const position = this._breakAttributes( range.start, forceSplitText );
return new _range__WEBPACK_IMPORTED_MODULE_1__["default"]( position, position );
}
const breakEnd = this._breakAttributes( rangeEnd, forceSplitText );
const count = breakEnd.parent.childCount;
const breakStart = this._breakAttributes( rangeStart, forceSplitText );
// Calculate new break end offset.
breakEnd.offset += breakEnd.parent.childCount - count;
return new _range__WEBPACK_IMPORTED_MODULE_1__["default"]( breakStart, breakEnd );
}
/**
* Helper function used by other `DowncastWriter` methods. Breaks attribute elements at given position.
*
* Throws {@link module:utils/ckeditorerror~CKEditorError CKEditorError} `view-writer-cannot-break-empty-element` when break position
* is placed inside {@link module:engine/view/emptyelement~EmptyElement EmptyElement}.
*
* Throws {@link module:utils/ckeditorerror~CKEditorError CKEditorError} `view-writer-cannot-break-ui-element` when break position
* is placed inside {@link module:engine/view/uielement~UIElement UIElement}.
*
* @private
* @param {module:engine/view/position~Position} position Position where to break attributes.
* @param {Boolean} [forceSplitText=false] If set to `true`, will break text nodes even if they are directly in container element.
* This behavior will result in incorrect view state, but is needed by other view writing methods which then fixes view state.
* @returns {module:engine/view/position~Position} New position after breaking the attributes.
*/
_breakAttributes( position, forceSplitText = false ) {
const positionOffset = position.offset;
const positionParent = position.parent;
// If position is placed inside EmptyElement - throw an exception as we cannot break inside.
if ( position.parent.is( 'emptyElement' ) ) {
/**
* Cannot break an `EmptyElement` instance.
*
* This error is thrown if
* {@link module:engine/view/downcastwriter~DowncastWriter#breakAttributes `DowncastWriter#breakAttributes()`}
* was executed in an incorrect position.
*
* @error view-writer-cannot-break-empty-element
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_8__["default"]( 'view-writer-cannot-break-empty-element', this.document );
}
// If position is placed inside UIElement - throw an exception as we cannot break inside.
if ( position.parent.is( 'uiElement' ) ) {
/**
* Cannot break a `UIElement` instance.
*
* This error is thrown if
* {@link module:engine/view/downcastwriter~DowncastWriter#breakAttributes `DowncastWriter#breakAttributes()`}
* was executed in an incorrect position.
*
* @error view-writer-cannot-break-ui-element
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_8__["default"]( 'view-writer-cannot-break-ui-element', this.document );
}
// If position is placed inside RawElement - throw an exception as we cannot break inside.
if ( position.parent.is( 'rawElement' ) ) {
/**
* Cannot break a `RawElement` instance.
*
* This error is thrown if
* {@link module:engine/view/downcastwriter~DowncastWriter#breakAttributes `DowncastWriter#breakAttributes()`}
* was executed in an incorrect position.
*
* @error view-writer-cannot-break-raw-element
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_8__["default"]( 'view-writer-cannot-break-raw-element', this.document );
}
// There are no attributes to break and text nodes breaking is not forced.
if ( !forceSplitText && positionParent.is( '$text' ) && isContainerOrFragment( positionParent.parent ) ) {
return position.clone();
}
// Position's parent is container, so no attributes to break.
if ( isContainerOrFragment( positionParent ) ) {
return position.clone();
}
// Break text and start again in new position.
if ( positionParent.is( '$text' ) ) {
return this._breakAttributes( breakTextNode( position ), forceSplitText );
}
const length = positionParent.childCount;
// <p>foo<b><u>bar{}</u></b></p>
// <p>foo<b><u>bar</u>[]</b></p>
// <p>foo<b><u>bar</u></b>[]</p>
if ( positionOffset == length ) {
const newPosition = new _position__WEBPACK_IMPORTED_MODULE_0__["default"]( positionParent.parent, positionParent.index + 1 );
return this._breakAttributes( newPosition, forceSplitText );
} else {
// <p>foo<b><u>{}bar</u></b></p>
// <p>foo<b>[]<u>bar</u></b></p>
// <p>foo{}<b><u>bar</u></b></p>
if ( positionOffset === 0 ) {
const newPosition = new _position__WEBPACK_IMPORTED_MODULE_0__["default"]( positionParent.parent, positionParent.index );
return this._breakAttributes( newPosition, forceSplitText );
}
// <p>foo<b><u>b{}ar</u></b></p>
// <p>foo<b><u>b[]ar</u></b></p>
// <p>foo<b><u>b</u>[]<u>ar</u></b></p>
// <p>foo<b><u>b</u></b>[]<b><u>ar</u></b></p>
else {
const offsetAfter = positionParent.index + 1;
// Break element.
const clonedNode = positionParent._clone();
// Insert cloned node to position's parent node.
positionParent.parent._insertChild( offsetAfter, clonedNode );
this._addToClonedElementsGroup( clonedNode );
// Get nodes to move.
const count = positionParent.childCount - positionOffset;
const nodesToMove = positionParent._removeChildren( positionOffset, count );
// Move nodes to cloned node.
clonedNode._appendChild( nodesToMove );
// Create new position to work on.
const newPosition = new _position__WEBPACK_IMPORTED_MODULE_0__["default"]( positionParent.parent, offsetAfter );
return this._breakAttributes( newPosition, forceSplitText );
}
}
}
/**
* Stores the information that an {@link module:engine/view/attributeelement~AttributeElement attribute element} was
* added to the tree. Saves the reference to the group in the given element and updates the group, so other elements
* from the group now keep a reference to the given attribute element.
*
* The clones group can be obtained using {@link module:engine/view/attributeelement~AttributeElement#getElementsWithSameId}.
*
* Does nothing if added element has no {@link module:engine/view/attributeelement~AttributeElement#id id}.
*
* @private
* @param {module:engine/view/attributeelement~AttributeElement} element Attribute element to save.
*/
_addToClonedElementsGroup( element ) {
// Add only if the element is in document tree.
if ( !element.root.is( 'rootElement' ) ) {
return;
}
// Traverse the element's children recursively to find other attribute elements that also might got inserted.
// The loop is at the beginning so we can make fast returns later in the code.
if ( element.is( 'element' ) ) {
for ( const child of element.getChildren() ) {
this._addToClonedElementsGroup( child );
}
}
const id = element.id;
if ( !id ) {
return;
}
let group = this._cloneGroups.get( id );
if ( !group ) {
group = new Set();
this._cloneGroups.set( id, group );
}
group.add( element );
element._clonesGroup = group;
}
/**
* Removes all the information about the given {@link module:engine/view/attributeelement~AttributeElement attribute element}
* from its clones group.
*
* Keep in mind, that the element will still keep a reference to the group (but the group will not keep a reference to it).
* This allows to reference the whole group even if the element was already removed from the tree.
*
* Does nothing if the element has no {@link module:engine/view/attributeelement~AttributeElement#id id}.
*
* @private
* @param {module:engine/view/attributeelement~AttributeElement} element Attribute element to remove.
*/
_removeFromClonedElementsGroup( element ) {
// Traverse the element's children recursively to find other attribute elements that also got removed.
// The loop is at the beginning so we can make fast returns later in the code.
if ( element.is( 'element' ) ) {
for ( const child of element.getChildren() ) {
this._removeFromClonedElementsGroup( child );
}
}
const id = element.id;
if ( !id ) {
return;
}
const group = this._cloneGroups.get( id );
if ( !group ) {
return;
}
group.delete( element );
// Not removing group from element on purpose!
// If other parts of code have reference to this element, they will be able to get references to other elements from the group.
}
}
// Helper function for `view.writer.wrap`. Checks if given element has any children that are not ui elements.
function _hasNonUiChildren( parent ) {
return Array.from( parent.getChildren() ).some( child => !child.is( 'uiElement' ) );
}
/**
* The `attribute` passed to {@link module:engine/view/downcastwriter~DowncastWriter#wrap `DowncastWriter#wrap()`}
* must be an instance of {@link module:engine/view/attributeelement~AttributeElement `AttributeElement`}.
*
* @error view-writer-wrap-invalid-attribute
*/
// Returns first parent container of specified {@link module:engine/view/position~Position Position}.
// Position's parent node is checked as first, then next parents are checked.
// Note that {@link module:engine/view/documentfragment~DocumentFragment DocumentFragment} is treated like a container.
//
// @param {module:engine/view/position~Position} position Position used as a start point to locate parent container.
// @returns {module:engine/view/containerelement~ContainerElement|module:engine/view/documentfragment~DocumentFragment|undefined}
// Parent container element or `undefined` if container is not found.
function getParentContainer( position ) {
let parent = position.parent;
while ( !isContainerOrFragment( parent ) ) {
if ( !parent ) {
return undefined;
}
parent = parent.parent;
}
return parent;
}
// Checks if first {@link module:engine/view/attributeelement~AttributeElement AttributeElement} provided to the function
// can be wrapped outside second element. It is done by comparing elements'
// {@link module:engine/view/attributeelement~AttributeElement#priority priorities}, if both have same priority
// {@link module:engine/view/element~Element#getIdentity identities} are compared.
//
// @param {module:engine/view/attributeelement~AttributeElement} a
// @param {module:engine/view/attributeelement~AttributeElement} b
// @returns {Boolean}
function shouldABeOutsideB( a, b ) {
if ( a.priority < b.priority ) {
return true;
} else if ( a.priority > b.priority ) {
return false;
}
// When priorities are equal and names are different - use identities.
return a.getIdentity() < b.getIdentity();
}
// Returns new position that is moved to near text node. Returns same position if there is no text node before of after
// specified position.
//
// <p>foo[]</p> -> <p>foo{}</p>
// <p>[]foo</p> -> <p>{}foo</p>
//
// @param {module:engine/view/position~Position} position
// @returns {module:engine/view/position~Position} Position located inside text node or same position if there is no text nodes
// before or after position location.
function movePositionToTextNode( position ) {
const nodeBefore = position.nodeBefore;
if ( nodeBefore && nodeBefore.is( '$text' ) ) {
return new _position__WEBPACK_IMPORTED_MODULE_0__["default"]( nodeBefore, nodeBefore.data.length );
}
const nodeAfter = position.nodeAfter;
if ( nodeAfter && nodeAfter.is( '$text' ) ) {
return new _position__WEBPACK_IMPORTED_MODULE_0__["default"]( nodeAfter, 0 );
}
return position;
}
// Breaks text node into two text nodes when possible.
//
// <p>foo{}bar</p> -> <p>foo[]bar</p>
// <p>{}foobar</p> -> <p>[]foobar</p>
// <p>foobar{}</p> -> <p>foobar[]</p>
//
// @param {module:engine/view/position~Position} position Position that need to be placed inside text node.
// @returns {module:engine/view/position~Position} New position after breaking text node.
function breakTextNode( position ) {
if ( position.offset == position.parent.data.length ) {
return new _position__WEBPACK_IMPORTED_MODULE_0__["default"]( position.parent.parent, position.parent.index + 1 );
}
if ( position.offset === 0 ) {
return new _position__WEBPACK_IMPORTED_MODULE_0__["default"]( position.parent.parent, position.parent.index );
}
// Get part of the text that need to be moved.
const textToMove = position.parent.data.slice( position.offset );
// Leave rest of the text in position's parent.
position.parent._data = position.parent.data.slice( 0, position.offset );
// Insert new text node after position's parent text node.
position.parent.parent._insertChild( position.parent.index + 1, new _text__WEBPACK_IMPORTED_MODULE_11__["default"]( position.root.document, textToMove ) );
// Return new position between two newly created text nodes.
return new _position__WEBPACK_IMPORTED_MODULE_0__["default"]( position.parent.parent, position.parent.index + 1 );
}
// Merges two text nodes into first node. Removes second node and returns merge position.
//
// @param {module:engine/view/text~Text} t1 First text node to merge. Data from second text node will be moved at the end of
// this text node.
// @param {module:engine/view/text~Text} t2 Second text node to merge. This node will be removed after merging.
// @returns {module:engine/view/position~Position} Position after merging text nodes.
function mergeTextNodes( t1, t2 ) {
// Merge text data into first text node and remove second one.
const nodeBeforeLength = t1.data.length;
t1._data += t2.data;
t2._remove();
return new _position__WEBPACK_IMPORTED_MODULE_0__["default"]( t1, nodeBeforeLength );
}
// Checks if provided nodes are valid to insert.
//
// Throws {@link module:utils/ckeditorerror~CKEditorError CKEditorError} `view-writer-insert-invalid-node` when nodes to insert
// contains instances that are not supported ones (see error description for valid ones.
//
// @param Iterable.<module:engine/view/text~Text|module:engine/view/element~Element> nodes
// @param {Object} errorContext
function validateNodesToInsert( nodes, errorContext ) {
for ( const node of nodes ) {
if ( !validNodesToInsert.some( ( validNode => node instanceof validNode ) ) ) { // eslint-disable-line no-use-before-define
/**
* One of the nodes to be inserted is of an invalid type.
*
* Nodes to be inserted with {@link module:engine/view/downcastwriter~DowncastWriter#insert `DowncastWriter#insert()`} should be
* of the following types:
*
* * {@link module:engine/view/attributeelement~AttributeElement AttributeElement},
* * {@link module:engine/view/containerelement~ContainerElement ContainerElement},
* * {@link module:engine/view/emptyelement~EmptyElement EmptyElement},
* * {@link module:engine/view/uielement~UIElement UIElement},
* * {@link module:engine/view/rawelement~RawElement RawElement},
* * {@link module:engine/view/text~Text Text}.
*
* @error view-writer-insert-invalid-node-type
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_8__["default"]( 'view-writer-insert-invalid-node-type', errorContext );
}
if ( !node.is( '$text' ) ) {
validateNodesToInsert( node.getChildren(), errorContext );
}
}
}
const validNodesToInsert = [ _text__WEBPACK_IMPORTED_MODULE_11__["default"], _attributeelement__WEBPACK_IMPORTED_MODULE_4__["default"], _containerelement__WEBPACK_IMPORTED_MODULE_3__["default"], _emptyelement__WEBPACK_IMPORTED_MODULE_5__["default"], _rawelement__WEBPACK_IMPORTED_MODULE_7__["default"], _uielement__WEBPACK_IMPORTED_MODULE_6__["default"] ];
// Checks if node is ContainerElement or DocumentFragment, because in most cases they should be treated the same way.
//
// @param {module:engine/view/node~Node} node
// @returns {Boolean} Returns `true` if node is instance of ContainerElement or DocumentFragment.
function isContainerOrFragment( node ) {
return node && ( node.is( 'containerElement' ) || node.is( 'documentFragment' ) );
}
// Checks if {@link module:engine/view/range~Range#start range start} and {@link module:engine/view/range~Range#end range end} are placed
// inside same {@link module:engine/view/containerelement~ContainerElement container element}.
// Throws {@link module:utils/ckeditorerror~CKEditorError CKEditorError} `view-writer-invalid-range-container` when validation fails.
//
// @param {module:engine/view/range~Range} range
// @param {Object} errorContext
function validateRangeContainer( range, errorContext ) {
const startContainer = getParentContainer( range.start );
const endContainer = getParentContainer( range.end );
if ( !startContainer || !endContainer || startContainer !== endContainer ) {
/**
* The container of the given range is invalid.
*
* This may happen if {@link module:engine/view/range~Range#start range start} and
* {@link module:engine/view/range~Range#end range end} positions are not placed inside the same container element or
* a parent container for these positions cannot be found.
*
* Methods like {@link module:engine/view/downcastwriter~DowncastWriter#wrap `DowncastWriter#remove()`},
* {@link module:engine/view/downcastwriter~DowncastWriter#wrap `DowncastWriter#clean()`},
* {@link module:engine/view/downcastwriter~DowncastWriter#wrap `DowncastWriter#wrap()`},
* {@link module:engine/view/downcastwriter~DowncastWriter#wrap `DowncastWriter#unwrap()`} need to be called
* on a range that has its start and end positions located in the same container element. Both positions can be
* nested within other elements (e.g. an attribute element) but the closest container ancestor must be the same.
*
* @error view-writer-invalid-range-container
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_8__["default"]( 'view-writer-invalid-range-container', errorContext );
}
}
// Checks if two attribute elements can be joined together. Elements can be joined together if, and only if
// they do not have ids specified.
//
// @private
// @param {module:engine/view/element~Element} a
// @param {module:engine/view/element~Element} b
// @returns {Boolean}
function canBeJoined( a, b ) {
return a.id === null && b.id === null;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/view/editableelement.js":
/*!*****************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/view/editableelement.js ***!
\*****************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ EditableElement)
/* harmony export */ });
/* harmony import */ var _containerelement__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./containerelement */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/containerelement.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/mix */ "./node_modules/@ckeditor/ckeditor5-utils/src/mix.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_observablemixin__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/observablemixin */ "./node_modules/@ckeditor/ckeditor5-utils/src/observablemixin.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/view/editableelement
*/
/**
* Editable element which can be a {@link module:engine/view/rooteditableelement~RootEditableElement root}
* or nested editable area in the editor.
*
* Editable is automatically read-only when its {@link module:engine/view/document~Document Document} is read-only.
*
* The constructor of this class shouldn't be used directly. To create new `EditableElement` use the
* {@link module:engine/view/downcastwriter~DowncastWriter#createEditableElement `downcastWriter#createEditableElement()`} method.
*
* @extends module:engine/view/containerelement~ContainerElement
* @mixes module:utils/observablemixin~ObservableMixin
*/
class EditableElement extends _containerelement__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* Creates an editable element.
*
* @see module:engine/view/downcastwriter~DowncastWriter#createEditableElement
* @protected
*/
constructor( document, name, attrs, children ) {
super( document, name, attrs, children );
/**
* Whether the editable is in read-write or read-only mode.
*
* @observable
* @member {Boolean} module:engine/view/editableelement~EditableElement#isReadOnly
*/
this.set( 'isReadOnly', false );
/**
* Whether the editable is focused.
*
* This property updates when {@link module:engine/view/document~Document#isFocused document.isFocused} or view
* selection is changed.
*
* @readonly
* @observable
* @member {Boolean} module:engine/view/editableelement~EditableElement#isFocused
*/
this.set( 'isFocused', false );
this.bind( 'isReadOnly' ).to( document );
this.bind( 'isFocused' ).to(
document,
'isFocused',
isFocused => isFocused && document.selection.editableElement == this
);
// Update focus state based on selection changes.
this.listenTo( document.selection, 'change', () => {
this.isFocused = document.isFocused && document.selection.editableElement == this;
} );
}
/**
* Checks whether this object is of the given.
*
* editableElement.is( 'editableElement' ); // -> true
* editableElement.is( 'element' ); // -> true
* editableElement.is( 'node' ); // -> true
* editableElement.is( 'view:editableElement' ); // -> true
* editableElement.is( 'view:element' ); // -> true
* editableElement.is( 'view:node' ); // -> true
*
* editableElement.is( 'model:element' ); // -> false
* editableElement.is( 'documentFragment' ); // -> false
*
* Assuming that the object being checked is an editbale element, you can also check its
* {@link module:engine/view/editableelement~EditableElement#name name}:
*
* editableElement.is( 'element', 'div' ); // -> true if this is a div element
* editableElement.is( 'editableElement', 'div' ); // -> same as above
* text.is( 'element', 'div' ); -> false
*
* {@link module:engine/view/node~Node#is Check the entire list of view objects} which implement the `is()` method.
*
* @param {String} type Type to check.
* @param {String} [name] Element name.
* @returns {Boolean}
*/
is( type, name = null ) {
if ( !name ) {
return type === 'editableElement' || type === 'view:editableElement' ||
// From super.is(). This is highly utilised method and cannot call super. See ckeditor/ckeditor5#6529.
type === 'containerElement' || type === 'view:containerElement' ||
type === 'element' || type === 'view:element' ||
type === 'node' || type === 'view:node';
} else {
return name === this.name && (
type === 'editableElement' || type === 'view:editableElement' ||
// From super.is(). This is highly utilised method and cannot call super. See ckeditor/ckeditor5#6529.
type === 'containerElement' || type === 'view:containerElement' ||
type === 'element' || type === 'view:element'
);
}
}
destroy() {
this.stopListening();
}
}
(0,_ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_1__["default"])( EditableElement, _ckeditor_ckeditor5_utils_src_observablemixin__WEBPACK_IMPORTED_MODULE_2__["default"] );
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/view/element.js":
/*!*********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/view/element.js ***!
\*********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ Element)
/* harmony export */ });
/* harmony import */ var _node__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./node */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/node.js");
/* harmony import */ var _text__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./text */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/text.js");
/* harmony import */ var _textproxy__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./textproxy */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/textproxy.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_tomap__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/tomap */ "./node_modules/@ckeditor/ckeditor5-utils/src/tomap.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_toarray__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/toarray */ "./node_modules/@ckeditor/ckeditor5-utils/src/toarray.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_isiterable__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/isiterable */ "./node_modules/@ckeditor/ckeditor5-utils/src/isiterable.js");
/* harmony import */ var _matcher__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./matcher */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/matcher.js");
/* harmony import */ var _stylesmap__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./stylesmap */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/stylesmap.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/view/element
*/
// @if CK_DEBUG_ENGINE // const { convertMapToTags } = require( '../dev-utils/utils' );
/**
* View element.
*
* The editing engine does not define a fixed semantics of its elements (it is "DTD-free").
* This is why the type of the {@link module:engine/view/element~Element} need to
* be defined by the feature developer. When creating an element you should use one of the following methods:
*
* * {@link module:engine/view/downcastwriter~DowncastWriter#createContainerElement `downcastWriter#createContainerElement()`}
* in order to create a {@link module:engine/view/containerelement~ContainerElement},
* * {@link module:engine/view/downcastwriter~DowncastWriter#createAttributeElement `downcastWriter#createAttributeElement()`}
* in order to create a {@link module:engine/view/attributeelement~AttributeElement},
* * {@link module:engine/view/downcastwriter~DowncastWriter#createEmptyElement `downcastWriter#createEmptyElement()`}
* in order to create a {@link module:engine/view/emptyelement~EmptyElement}.
* * {@link module:engine/view/downcastwriter~DowncastWriter#createUIElement `downcastWriter#createUIElement()`}
* in order to create a {@link module:engine/view/uielement~UIElement}.
* * {@link module:engine/view/downcastwriter~DowncastWriter#createEditableElement `downcastWriter#createEditableElement()`}
* in order to create a {@link module:engine/view/editableelement~EditableElement}.
*
* Note that for view elements which are not created from the model, like elements from mutations, paste or
* {@link module:engine/controller/datacontroller~DataController#set data.set} it is not possible to define the type of the element.
* In such cases the {@link module:engine/view/upcastwriter~UpcastWriter#createElement `UpcastWriter#createElement()`} method
* should be used to create generic view elements.
*
* @extends module:engine/view/node~Node
*/
class Element extends _node__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* Creates a view element.
*
* Attributes can be passed in various formats:
*
* new Element( viewDocument, 'div', { class: 'editor', contentEditable: 'true' } ); // object
* new Element( viewDocument, 'div', [ [ 'class', 'editor' ], [ 'contentEditable', 'true' ] ] ); // map-like iterator
* new Element( viewDocument, 'div', mapOfAttributes ); // map
*
* @protected
* @param {module:engine/view/document~Document} document The document instance to which this element belongs.
* @param {String} name Node name.
* @param {Object|Iterable} [attrs] Collection of attributes.
* @param {module:engine/view/node~Node|Iterable.<module:engine/view/node~Node>} [children]
* A list of nodes to be inserted into created element.
*/
constructor( document, name, attrs, children ) {
super( document );
/**
* Name of the element.
*
* @readonly
* @member {String}
*/
this.name = name;
/**
* Map of attributes, where attributes names are keys and attributes values are values.
*
* @protected
* @member {Map} #_attrs
*/
this._attrs = parseAttributes( attrs );
/**
* Array of child nodes.
*
* @protected
* @member {Array.<module:engine/view/node~Node>}
*/
this._children = [];
if ( children ) {
this._insertChild( 0, children );
}
/**
* Set of classes associated with element instance.
*
* @protected
* @member {Set}
*/
this._classes = new Set();
if ( this._attrs.has( 'class' ) ) {
// Remove class attribute and handle it by class set.
const classString = this._attrs.get( 'class' );
parseClasses( this._classes, classString );
this._attrs.delete( 'class' );
}
/**
* Normalized styles.
*
* @protected
* @member {module:engine/view/stylesmap~StylesMap} module:engine/view/element~Element#_styles
*/
this._styles = new _stylesmap__WEBPACK_IMPORTED_MODULE_7__["default"]( this.document.stylesProcessor );
if ( this._attrs.has( 'style' ) ) {
// Remove style attribute and handle it by styles map.
this._styles.setTo( this._attrs.get( 'style' ) );
this._attrs.delete( 'style' );
}
/**
* Map of custom properties.
* Custom properties can be added to element instance, will be cloned but not rendered into DOM.
*
* @protected
* @member {Map}
*/
this._customProperties = new Map();
/**
* Whether an element is allowed inside an AttributeElement and can be wrapped with
* {@link module:engine/view/attributeelement~AttributeElement} by {@link module:engine/view/downcastwriter~DowncastWriter}.
*
* @protected
* @member {Boolean}
*/
this._isAllowedInsideAttributeElement = false;
/**
* A list of attribute names that should be rendered in the editing pipeline even though filtering mechanisms
* implemented in the {@link module:engine/view/domconverter~DomConverter} (for instance,
* {@link module:engine/view/domconverter~DomConverter#shouldRenderAttribute}) would filter them out.
*
* These attributes can be specified as an option when the element is created by
* the {@link module:engine/view/downcastwriter~DowncastWriter}. To check whether an unsafe an attribute should
* be permitted, use the {@link #shouldRenderUnsafeAttribute} method.
*
* @private
* @readonly
* @member {Array.<String>}
*/
this._unsafeAttributesToRender = [];
}
/**
* Number of element's children.
*
* @readonly
* @type {Number}
*/
get childCount() {
return this._children.length;
}
/**
* Is `true` if there are no nodes inside this element, `false` otherwise.
*
* @readonly
* @type {Boolean}
*/
get isEmpty() {
return this._children.length === 0;
}
/**
* Whether the element is allowed inside an AttributeElement and can be wrapped with
* {@link module:engine/view/attributeelement~AttributeElement} by {@link module:engine/view/downcastwriter~DowncastWriter}.
*
* @readonly
* @type {Boolean}
*/
get isAllowedInsideAttributeElement() {
return this._isAllowedInsideAttributeElement;
}
/**
* Checks whether this object is of the given.
*
* element.is( 'element' ); // -> true
* element.is( 'node' ); // -> true
* element.is( 'view:element' ); // -> true
* element.is( 'view:node' ); // -> true
*
* element.is( 'model:element' ); // -> false
* element.is( 'documentSelection' ); // -> false
*
* Assuming that the object being checked is an element, you can also check its
* {@link module:engine/view/element~Element#name name}:
*
* element.is( 'element', 'img' ); // -> true if this is an <img> element
* text.is( 'element', 'img' ); -> false
*
* {@link module:engine/view/node~Node#is Check the entire list of view objects} which implement the `is()` method.
*
* @param {String} type Type to check.
* @param {String} [name] Element name.
* @returns {Boolean}
*/
is( type, name = null ) {
if ( !name ) {
return type === 'element' || type === 'view:element' ||
// From super.is(). This is highly utilised method and cannot call super. See ckeditor/ckeditor5#6529.
type === 'node' || type === 'view:node';
} else {
return name === this.name && ( type === 'element' || type === 'view:element' );
}
}
/**
* Gets child at the given index.
*
* @param {Number} index Index of child.
* @returns {module:engine/view/node~Node} Child node.
*/
getChild( index ) {
return this._children[ index ];
}
/**
* Gets index of the given child node. Returns `-1` if child node is not found.
*
* @param {module:engine/view/node~Node} node Child node.
* @returns {Number} Index of the child node.
*/
getChildIndex( node ) {
return this._children.indexOf( node );
}
/**
* Gets child nodes iterator.
*
* @returns {Iterable.<module:engine/view/node~Node>} Child nodes iterator.
*/
getChildren() {
return this._children[ Symbol.iterator ]();
}
/**
* Returns an iterator that contains the keys for attributes. Order of inserting attributes is not preserved.
*
* @returns {Iterable.<String>} Keys for attributes.
*/
* getAttributeKeys() {
if ( this._classes.size > 0 ) {
yield 'class';
}
if ( !this._styles.isEmpty ) {
yield 'style';
}
yield* this._attrs.keys();
}
/**
* Returns iterator that iterates over this element's attributes.
*
* Attributes are returned as arrays containing two items. First one is attribute key and second is attribute value.
* This format is accepted by native `Map` object and also can be passed in `Node` constructor.
*
* @returns {Iterable.<*>}
*/
* getAttributes() {
yield* this._attrs.entries();
if ( this._classes.size > 0 ) {
yield [ 'class', this.getAttribute( 'class' ) ];
}
if ( !this._styles.isEmpty ) {
yield [ 'style', this.getAttribute( 'style' ) ];
}
}
/**
* Gets attribute by key. If attribute is not present - returns undefined.
*
* @param {String} key Attribute key.
* @returns {String|undefined} Attribute value.
*/
getAttribute( key ) {
if ( key == 'class' ) {
if ( this._classes.size > 0 ) {
return [ ...this._classes ].join( ' ' );
}
return undefined;
}
if ( key == 'style' ) {
const inlineStyle = this._styles.toString();
return inlineStyle == '' ? undefined : inlineStyle;
}
return this._attrs.get( key );
}
/**
* Returns a boolean indicating whether an attribute with the specified key exists in the element.
*
* @param {String} key Attribute key.
* @returns {Boolean} `true` if attribute with the specified key exists in the element, false otherwise.
*/
hasAttribute( key ) {
if ( key == 'class' ) {
return this._classes.size > 0;
}
if ( key == 'style' ) {
return !this._styles.isEmpty;
}
return this._attrs.has( key );
}
/**
* Checks if this element is similar to other element.
* Both elements should have the same name and attributes to be considered as similar. Two similar elements
* can contain different set of children nodes.
*
* @param {module:engine/view/element~Element} otherElement
* @returns {Boolean}
*/
isSimilar( otherElement ) {
if ( !( otherElement instanceof Element ) ) {
return false;
}
// If exactly the same Element is provided - return true immediately.
if ( this === otherElement ) {
return true;
}
// Check element name.
if ( this.name != otherElement.name ) {
return false;
}
// Check isAllowedInsideAttributeElement property.
if ( this.isAllowedInsideAttributeElement != otherElement.isAllowedInsideAttributeElement ) {
return false;
}
// Check number of attributes, classes and styles.
if ( this._attrs.size !== otherElement._attrs.size || this._classes.size !== otherElement._classes.size ||
this._styles.size !== otherElement._styles.size ) {
return false;
}
// Check if attributes are the same.
for ( const [ key, value ] of this._attrs ) {
if ( !otherElement._attrs.has( key ) || otherElement._attrs.get( key ) !== value ) {
return false;
}
}
// Check if classes are the same.
for ( const className of this._classes ) {
if ( !otherElement._classes.has( className ) ) {
return false;
}
}
// Check if styles are the same.
for ( const property of this._styles.getStyleNames() ) {
if (
!otherElement._styles.has( property ) ||
otherElement._styles.getAsString( property ) !== this._styles.getAsString( property )
) {
return false;
}
}
return true;
}
/**
* Returns true if class is present.
* If more then one class is provided - returns true only when all classes are present.
*
* element.hasClass( 'foo' ); // Returns true if 'foo' class is present.
* element.hasClass( 'foo', 'bar' ); // Returns true if 'foo' and 'bar' classes are both present.
*
* @param {...String} className
*/
hasClass( ...className ) {
for ( const name of className ) {
if ( !this._classes.has( name ) ) {
return false;
}
}
return true;
}
/**
* Returns iterator that contains all class names.
*
* @returns {Iterable.<String>}
*/
getClassNames() {
return this._classes.keys();
}
/**
* Returns style value for the given property mae.
* If the style does not exist `undefined` is returned.
*
* **Note**: This method can work with normalized style names if
* {@link module:engine/controller/datacontroller~DataController#addStyleProcessorRules a particular style processor rule is enabled}.
* See {@link module:engine/view/stylesmap~StylesMap#getAsString `StylesMap#getAsString()`} for details.
*
* For an element with style set to `'margin:1px'`:
*
* // Enable 'margin' shorthand processing:
* editor.data.addStyleProcessorRules( addMarginRules );
*
* const element = view.change( writer => {
* const element = writer.createElement();
* writer.setStyle( 'margin', '1px' );
* writer.setStyle( 'margin-bottom', '3em' );
*
* return element;
* } );
*
* element.getStyle( 'margin' ); // -> 'margin: 1px 1px 3em;'
*
* @param {String} property
* @returns {String|undefined}
*/
getStyle( property ) {
return this._styles.getAsString( property );
}
/**
* Returns a normalized style object or single style value.
*
* For an element with style set to: margin:1px 2px 3em;
*
* element.getNormalizedStyle( 'margin' ) );
*
* will return:
*
* {
* top: '1px',
* right: '2px',
* bottom: '3em',
* left: '2px' // a normalized value from margin shorthand
* }
*
* and reading for single style value:
*
* styles.getNormalizedStyle( 'margin-left' );
*
* Will return a `2px` string.
*
* **Note**: This method will return normalized values only if
* {@link module:engine/controller/datacontroller~DataController#addStyleProcessorRules a particular style processor rule is enabled}.
* See {@link module:engine/view/stylesmap~StylesMap#getNormalized `StylesMap#getNormalized()`} for details.
*
*
* @param {String} property Name of CSS property
* @returns {Object|String|undefined}
*/
getNormalizedStyle( property ) {
return this._styles.getNormalized( property );
}
/**
* Returns iterator that contains all style names.
*
* @param {Boolean} [expand=false] Expand shorthand style properties and return all equivalent style representations.
* @returns {Iterable.<String>}
*/
getStyleNames( expand = false ) {
return this._styles.getStyleNames( expand );
}
/**
* Returns true if style keys are present.
* If more then one style property is provided - returns true only when all properties are present.
*
* element.hasStyle( 'color' ); // Returns true if 'border-top' style is present.
* element.hasStyle( 'color', 'border-top' ); // Returns true if 'color' and 'border-top' styles are both present.
*
* @param {...String} property
*/
hasStyle( ...property ) {
for ( const name of property ) {
if ( !this._styles.has( name ) ) {
return false;
}
}
return true;
}
/**
* Returns ancestor element that match specified pattern.
* Provided patterns should be compatible with {@link module:engine/view/matcher~Matcher Matcher} as it is used internally.
*
* @see module:engine/view/matcher~Matcher
* @param {Object|String|RegExp|Function} patterns Patterns used to match correct ancestor.
* See {@link module:engine/view/matcher~Matcher}.
* @returns {module:engine/view/element~Element|null} Found element or `null` if no matching ancestor was found.
*/
findAncestor( ...patterns ) {
const matcher = new _matcher__WEBPACK_IMPORTED_MODULE_6__["default"]( ...patterns );
let parent = this.parent;
while ( parent ) {
if ( matcher.match( parent ) ) {
return parent;
}
parent = parent.parent;
}
return null;
}
/**
* Returns the custom property value for the given key.
*
* @param {String|Symbol} key
* @returns {*}
*/
getCustomProperty( key ) {
return this._customProperties.get( key );
}
/**
* Returns an iterator which iterates over this element's custom properties.
* Iterator provides `[ key, value ]` pairs for each stored property.
*
* @returns {Iterable.<*>}
*/
* getCustomProperties() {
yield* this._customProperties.entries();
}
/**
* Returns identity string based on element's name, styles, classes and other attributes.
* Two elements that {@link #isSimilar are similar} will have same identity string.
* It has the following format:
*
* 'name class="class1,class2" style="style1:value1;style2:value2" attr1="val1" attr2="val2"'
*
* For example:
*
* const element = writer.createContainerElement( 'foo', {
* banana: '10',
* apple: '20',
* style: 'color: red; border-color: white;',
* class: 'baz'
* } );
*
* // returns 'foo class="baz" style="border-color:white;color:red" apple="20" banana="10"'
* element.getIdentity();
*
* **Note**: Classes, styles and other attributes are sorted alphabetically.
*
* @returns {String}
*/
getIdentity() {
const classes = Array.from( this._classes ).sort().join( ',' );
const styles = this._styles.toString();
const attributes = Array.from( this._attrs ).map( i => `${ i[ 0 ] }="${ i[ 1 ] }"` ).sort().join( ' ' );
return this.name +
( classes == '' ? '' : ` class="${ classes }"` ) +
( !styles ? '' : ` style="${ styles }"` ) +
( attributes == '' ? '' : ` ${ attributes }` );
}
/**
* Decides whether an unsafe attribute is whitelisted and should be rendered in the editing pipeline even though filtering mechanisms
* like {@link module:engine/view/domconverter~DomConverter#shouldRenderAttribute} say it should not.
*
* Unsafe attribute names can be specified when creating an element via {@link module:engine/view/downcastwriter~DowncastWriter}.
*
* @param {String} attributeName The name of the attribute to be checked.
* @returns {Boolean}
*/
shouldRenderUnsafeAttribute( attributeName ) {
return this._unsafeAttributesToRender.includes( attributeName );
}
/**
* Clones provided element.
*
* @protected
* @param {Boolean} [deep=false] If set to `true` clones element and all its children recursively. When set to `false`,
* element will be cloned without any children.
* @returns {module:engine/view/element~Element} Clone of this element.
*/
_clone( deep = false ) {
const childrenClone = [];
if ( deep ) {
for ( const child of this.getChildren() ) {
childrenClone.push( child._clone( deep ) );
}
}
// ContainerElement and AttributeElement should be also cloned properly.
const cloned = new this.constructor( this.document, this.name, this._attrs, childrenClone );
// Classes and styles are cloned separately - this solution is faster than adding them back to attributes and
// parse once again in constructor.
cloned._classes = new Set( this._classes );
cloned._styles.set( this._styles.getNormalized() );
// Clone custom properties.
cloned._customProperties = new Map( this._customProperties );
// Clone filler offset method.
// We can't define this method in a prototype because it's behavior which
// is changed by e.g. toWidget() function from ckeditor5-widget. Perhaps this should be one of custom props.
cloned.getFillerOffset = this.getFillerOffset;
cloned._isAllowedInsideAttributeElement = this.isAllowedInsideAttributeElement;
return cloned;
}
/**
* {@link module:engine/view/element~Element#_insertChild Insert} a child node or a list of child nodes at the end of this node
* and sets the parent of these nodes to this element.
*
* @see module:engine/view/downcastwriter~DowncastWriter#insert
* @protected
* @param {module:engine/view/item~Item|Iterable.<module:engine/view/item~Item>} items Items to be inserted.
* @fires module:engine/view/node~Node#change
* @returns {Number} Number of appended nodes.
*/
_appendChild( items ) {
return this._insertChild( this.childCount, items );
}
/**
* Inserts a child node or a list of child nodes on the given index and sets the parent of these nodes to
* this element.
*
* @see module:engine/view/downcastwriter~DowncastWriter#insert
* @protected
* @param {Number} index Position where nodes should be inserted.
* @param {module:engine/view/item~Item|Iterable.<module:engine/view/item~Item>} items Items to be inserted.
* @fires module:engine/view/node~Node#change
* @returns {Number} Number of inserted nodes.
*/
_insertChild( index, items ) {
this._fireChange( 'children', this );
let count = 0;
const nodes = normalize( this.document, items );
for ( const node of nodes ) {
// If node that is being added to this element is already inside another element, first remove it from the old parent.
if ( node.parent !== null ) {
node._remove();
}
node.parent = this;
node.document = this.document;
this._children.splice( index, 0, node );
index++;
count++;
}
return count;
}
/**
* Removes number of child nodes starting at the given index and set the parent of these nodes to `null`.
*
* @see module:engine/view/downcastwriter~DowncastWriter#remove
* @protected
* @param {Number} index Number of the first node to remove.
* @param {Number} [howMany=1] Number of nodes to remove.
* @fires module:engine/view/node~Node#change
* @returns {Array.<module:engine/view/node~Node>} The array of removed nodes.
*/
_removeChildren( index, howMany = 1 ) {
this._fireChange( 'children', this );
for ( let i = index; i < index + howMany; i++ ) {
this._children[ i ].parent = null;
}
return this._children.splice( index, howMany );
}
/**
* Adds or overwrite attribute with a specified key and value.
*
* @see module:engine/view/downcastwriter~DowncastWriter#setAttribute
* @protected
* @param {String} key Attribute key.
* @param {String} value Attribute value.
* @fires module:engine/view/node~Node#change
*/
_setAttribute( key, value ) {
value = String( value );
this._fireChange( 'attributes', this );
if ( key == 'class' ) {
parseClasses( this._classes, value );
} else if ( key == 'style' ) {
this._styles.setTo( value );
} else {
this._attrs.set( key, value );
}
}
/**
* Removes attribute from the element.
*
* @see module:engine/view/downcastwriter~DowncastWriter#removeAttribute
* @protected
* @param {String} key Attribute key.
* @returns {Boolean} Returns true if an attribute existed and has been removed.
* @fires module:engine/view/node~Node#change
*/
_removeAttribute( key ) {
this._fireChange( 'attributes', this );
// Remove class attribute.
if ( key == 'class' ) {
if ( this._classes.size > 0 ) {
this._classes.clear();
return true;
}
return false;
}
// Remove style attribute.
if ( key == 'style' ) {
if ( !this._styles.isEmpty ) {
this._styles.clear();
return true;
}
return false;
}
// Remove other attributes.
return this._attrs.delete( key );
}
/**
* Adds specified class.
*
* element._addClass( 'foo' ); // Adds 'foo' class.
* element._addClass( [ 'foo', 'bar' ] ); // Adds 'foo' and 'bar' classes.
*
* @see module:engine/view/downcastwriter~DowncastWriter#addClass
* @protected
* @param {Array.<String>|String} className
* @fires module:engine/view/node~Node#change
*/
_addClass( className ) {
this._fireChange( 'attributes', this );
for ( const name of (0,_ckeditor_ckeditor5_utils_src_toarray__WEBPACK_IMPORTED_MODULE_4__["default"])( className ) ) {
this._classes.add( name );
}
}
/**
* Removes specified class.
*
* element._removeClass( 'foo' ); // Removes 'foo' class.
* element._removeClass( [ 'foo', 'bar' ] ); // Removes both 'foo' and 'bar' classes.
*
* @see module:engine/view/downcastwriter~DowncastWriter#removeClass
* @protected
* @param {Array.<String>|String} className
* @fires module:engine/view/node~Node#change
*/
_removeClass( className ) {
this._fireChange( 'attributes', this );
for ( const name of (0,_ckeditor_ckeditor5_utils_src_toarray__WEBPACK_IMPORTED_MODULE_4__["default"])( className ) ) {
this._classes.delete( name );
}
}
/**
* Adds style to the element.
*
* element._setStyle( 'color', 'red' );
* element._setStyle( {
* color: 'red',
* position: 'fixed'
* } );
*
* **Note**: This method can work with normalized style names if
* {@link module:engine/controller/datacontroller~DataController#addStyleProcessorRules a particular style processor rule is enabled}.
* See {@link module:engine/view/stylesmap~StylesMap#set `StylesMap#set()`} for details.
*
* @see module:engine/view/downcastwriter~DowncastWriter#setStyle
* @protected
* @param {String|Object} property Property name or object with key - value pairs.
* @param {String} [value] Value to set. This parameter is ignored if object is provided as the first parameter.
* @fires module:engine/view/node~Node#change
*/
_setStyle( property, value ) {
this._fireChange( 'attributes', this );
this._styles.set( property, value );
}
/**
* Removes specified style.
*
* element._removeStyle( 'color' ); // Removes 'color' style.
* element._removeStyle( [ 'color', 'border-top' ] ); // Removes both 'color' and 'border-top' styles.
*
* **Note**: This method can work with normalized style names if
* {@link module:engine/controller/datacontroller~DataController#addStyleProcessorRules a particular style processor rule is enabled}.
* See {@link module:engine/view/stylesmap~StylesMap#remove `StylesMap#remove()`} for details.
*
* @see module:engine/view/downcastwriter~DowncastWriter#removeStyle
* @protected
* @param {Array.<String>|String} property
* @fires module:engine/view/node~Node#change
*/
_removeStyle( property ) {
this._fireChange( 'attributes', this );
for ( const name of (0,_ckeditor_ckeditor5_utils_src_toarray__WEBPACK_IMPORTED_MODULE_4__["default"])( property ) ) {
this._styles.remove( name );
}
}
/**
* Sets a custom property. Unlike attributes, custom properties are not rendered to the DOM,
* so they can be used to add special data to elements.
*
* @see module:engine/view/downcastwriter~DowncastWriter#setCustomProperty
* @protected
* @param {String|Symbol} key
* @param {*} value
*/
_setCustomProperty( key, value ) {
this._customProperties.set( key, value );
}
/**
* Removes the custom property stored under the given key.
*
* @see module:engine/view/downcastwriter~DowncastWriter#removeCustomProperty
* @protected
* @param {String|Symbol} key
* @returns {Boolean} Returns true if property was removed.
*/
_removeCustomProperty( key ) {
return this._customProperties.delete( key );
}
/**
* Returns block {@link module:engine/view/filler filler} offset or `null` if block filler is not needed.
*
* @abstract
* @method module:engine/view/element~Element#getFillerOffset
*/
// @if CK_DEBUG_ENGINE // printTree( level = 0) {
// @if CK_DEBUG_ENGINE // let string = '';
// @if CK_DEBUG_ENGINE // string += '\t'.repeat( level ) + `<${ this.name }${ convertMapToTags( this.getAttributes() ) }>`;
// @if CK_DEBUG_ENGINE // for ( const child of this.getChildren() ) {
// @if CK_DEBUG_ENGINE // if ( child.is( '$text' ) ) {
// @if CK_DEBUG_ENGINE // string += '\n' + '\t'.repeat( level + 1 ) + child.data;
// @if CK_DEBUG_ENGINE // } else {
// @if CK_DEBUG_ENGINE // string += '\n' + child.printTree( level + 1 );
// @if CK_DEBUG_ENGINE // }
// @if CK_DEBUG_ENGINE // }
// @if CK_DEBUG_ENGINE // if ( this.childCount ) {
// @if CK_DEBUG_ENGINE // string += '\n' + '\t'.repeat( level );
// @if CK_DEBUG_ENGINE // }
// @if CK_DEBUG_ENGINE // string += `</${ this.name }>`;
// @if CK_DEBUG_ENGINE // return string;
// @if CK_DEBUG_ENGINE // }
// @if CK_DEBUG_ENGINE // logTree() {
// @if CK_DEBUG_ENGINE // console.log( this.printTree() );
// @if CK_DEBUG_ENGINE // }
}
// Parses attributes provided to the element constructor before they are applied to an element. If attributes are passed
// as an object (instead of `Iterable`), the object is transformed to the map. Attributes with `null` value are removed.
// Attributes with non-`String` value are converted to `String`.
//
// @param {Object|Iterable} attrs Attributes to parse.
// @returns {Map} Parsed attributes.
function parseAttributes( attrs ) {
attrs = (0,_ckeditor_ckeditor5_utils_src_tomap__WEBPACK_IMPORTED_MODULE_3__["default"])( attrs );
for ( const [ key, value ] of attrs ) {
if ( value === null ) {
attrs.delete( key );
} else if ( typeof value != 'string' ) {
attrs.set( key, String( value ) );
}
}
return attrs;
}
// Parses class attribute and puts all classes into classes set.
// Classes set s cleared before insertion.
//
// @param {Set.<String>} classesSet Set to insert parsed classes.
// @param {String} classesString String with classes to parse.
function parseClasses( classesSet, classesString ) {
const classArray = classesString.split( /\s+/ );
classesSet.clear();
classArray.forEach( name => classesSet.add( name ) );
}
// Converts strings to Text and non-iterables to arrays.
//
// @param {String|module:engine/view/item~Item|Iterable.<String|module:engine/view/item~Item>}
// @returns {Iterable.<module:engine/view/node~Node>}
function normalize( document, nodes ) {
// Separate condition because string is iterable.
if ( typeof nodes == 'string' ) {
return [ new _text__WEBPACK_IMPORTED_MODULE_1__["default"]( document, nodes ) ];
}
if ( !(0,_ckeditor_ckeditor5_utils_src_isiterable__WEBPACK_IMPORTED_MODULE_5__["default"])( nodes ) ) {
nodes = [ nodes ];
}
// Array.from to enable .map() on non-arrays.
return Array.from( nodes )
.map( node => {
if ( typeof node == 'string' ) {
return new _text__WEBPACK_IMPORTED_MODULE_1__["default"]( document, node );
}
if ( node instanceof _textproxy__WEBPACK_IMPORTED_MODULE_2__["default"] ) {
return new _text__WEBPACK_IMPORTED_MODULE_1__["default"]( document, node.data );
}
return node;
} );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/view/emptyelement.js":
/*!**************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/view/emptyelement.js ***!
\**************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ EmptyElement)
/* harmony export */ });
/* harmony import */ var _element__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./element */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/element.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/* harmony import */ var _node__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./node */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/node.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/view/emptyelement
*/
/**
* Empty element class. It is used to represent elements that cannot contain any child nodes (for example `<img>` elements).
*
* To create a new empty element use the
* {@link module:engine/view/downcastwriter~DowncastWriter#createEmptyElement `downcastWriter#createEmptyElement()`} method.
*
* @extends module:engine/view/element~Element
*/
class EmptyElement extends _element__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* Creates new instance of EmptyElement.
*
* Throws {@link module:utils/ckeditorerror~CKEditorError CKEditorError} `view-emptyelement-cannot-add` when third parameter is passed,
* to inform that usage of EmptyElement is incorrect (adding child nodes to EmptyElement is forbidden).
*
* @see module:engine/view/downcastwriter~DowncastWriter#createEmptyElement
* @protected
* @param {module:engine/view/document~Document} document The document instance to which this element belongs.
* @param {String} name Node name.
* @param {Object|Iterable} [attrs] Collection of attributes.
* @param {module:engine/view/node~Node|Iterable.<module:engine/view/node~Node>} [children]
* A list of nodes to be inserted into created element.
*/
constructor( document, name, attrs, children ) {
super( document, name, attrs, children );
// Override the default of the base class.
this._isAllowedInsideAttributeElement = true;
/**
* Returns `null` because filler is not needed for EmptyElements.
*
* @method #getFillerOffset
* @returns {null} Always returns null.
*/
this.getFillerOffset = getFillerOffset;
}
/**
* Checks whether this object is of the given.
*
* emptyElement.is( 'emptyElement' ); // -> true
* emptyElement.is( 'element' ); // -> true
* emptyElement.is( 'node' ); // -> true
* emptyElement.is( 'view:emptyElement' ); // -> true
* emptyElement.is( 'view:element' ); // -> true
* emptyElement.is( 'view:node' ); // -> true
*
* emptyElement.is( 'model:element' ); // -> false
* emptyElement.is( 'documentFragment' ); // -> false
*
* Assuming that the object being checked is an empty element, you can also check its
* {@link module:engine/view/emptyelement~EmptyElement#name name}:
*
* emptyElement.is( 'element', 'img' ); // -> true if this is a img element
* emptyElement.is( 'emptyElement', 'img' ); // -> same as above
* text.is( 'element', 'img' ); -> false
*
* {@link module:engine/view/node~Node#is Check the entire list of view objects} which implement the `is()` method.
*
* @param {String} type Type to check.
* @param {String} [name] Element name.
* @returns {Boolean}
*/
is( type, name = null ) {
if ( !name ) {
return type === 'emptyElement' || type === 'view:emptyElement' ||
// From super.is(). This is highly utilised method and cannot call super. See ckeditor/ckeditor5#6529.
type === 'element' || type === 'view:element' ||
type === 'node' || type === 'view:node';
} else {
return name === this.name && (
type === 'emptyElement' || type === 'view:emptyElement' ||
type === 'element' || type === 'view:element'
);
}
}
/**
* Overrides {@link module:engine/view/element~Element#_insertChild} method.
* Throws {@link module:utils/ckeditorerror~CKEditorError CKEditorError} `view-emptyelement-cannot-add` to prevent
* adding any child nodes to EmptyElement.
*
* @protected
*/
_insertChild( index, nodes ) {
if ( nodes && ( nodes instanceof _node__WEBPACK_IMPORTED_MODULE_2__["default"] || Array.from( nodes ).length > 0 ) ) {
/**
* Cannot add children to {@link module:engine/view/emptyelement~EmptyElement}.
*
* @error view-emptyelement-cannot-add
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_1__["default"](
'view-emptyelement-cannot-add',
[ this, nodes ]
);
}
}
}
// Returns `null` because block filler is not needed for EmptyElements.
//
// @returns {null}
function getFillerOffset() {
return null;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/view/filler.js":
/*!********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/view/filler.js ***!
\********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "BR_FILLER": () => (/* binding */ BR_FILLER),
/* harmony export */ "INLINE_FILLER": () => (/* binding */ INLINE_FILLER),
/* harmony export */ "INLINE_FILLER_LENGTH": () => (/* binding */ INLINE_FILLER_LENGTH),
/* harmony export */ "MARKED_NBSP_FILLER": () => (/* binding */ MARKED_NBSP_FILLER),
/* harmony export */ "NBSP_FILLER": () => (/* binding */ NBSP_FILLER),
/* harmony export */ "getDataWithoutFiller": () => (/* binding */ getDataWithoutFiller),
/* harmony export */ "injectQuirksHandling": () => (/* binding */ injectQuirksHandling),
/* harmony export */ "isInlineFiller": () => (/* binding */ isInlineFiller),
/* harmony export */ "startsWithFiller": () => (/* binding */ startsWithFiller)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_utils_src_keyboard__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/keyboard */ "./node_modules/@ckeditor/ckeditor5-utils/src/keyboard.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_dom_istext__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/dom/istext */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/istext.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* Set of utilities related to handling block and inline fillers.
*
* Browsers do not allow to put caret in elements which does not have height. Because of it, we need to fill all
* empty elements which should be selectable with elements or characters called "fillers". Unfortunately there is no one
* universal filler, this is why two types are uses:
*
* * Block filler is an element which fill block elements, like `<p>`. CKEditor uses `<br>` as a block filler during the editing,
* as browsers do natively. So instead of an empty `<p>` there will be `<p><br></p>`. The advantage of block filler is that
* it is transparent for the selection, so when the caret is before the `<br>` and user presses right arrow he will be
* moved to the next paragraph, not after the `<br>`. The disadvantage is that it breaks a block, so it can not be used
* in the middle of a line of text. The {@link module:engine/view/filler~BR_FILLER `<br>` filler} can be replaced with any other
* character in the data output, for instance {@link module:engine/view/filler~NBSP_FILLER non-breaking space} or
* {@link module:engine/view/filler~MARKED_NBSP_FILLER marked non-breaking space}.
*
* * Inline filler is a filler which does not break a line of text, so it can be used inside the text, for instance in the empty
* `<b>` surrendered by text: `foo<b></b>bar`, if we want to put the caret there. CKEditor uses a sequence of the zero-width
* spaces as an {@link module:engine/view/filler~INLINE_FILLER inline filler} having the predetermined
* {@link module:engine/view/filler~INLINE_FILLER_LENGTH length}. A sequence is used, instead of a single character to
* avoid treating random zero-width spaces as the inline filler. Disadvantage of the inline filler is that it is not
* transparent for the selection. The arrow key moves the caret between zero-width spaces characters, so the additional
* code is needed to handle the caret.
*
* Both inline and block fillers are handled by the {@link module:engine/view/renderer~Renderer renderer} and are not present in the
* view.
*
* @module engine/view/filler
*/
/**
* Non-breaking space filler creator. This function creates the ` ` text node.
* It defines how the filler is created.
*
* @see module:engine/view/filler~MARKED_NBSP_FILLER
* @see module:engine/view/filler~BR_FILLER
* @function
*/
const NBSP_FILLER = domDocument => domDocument.createTextNode( '\u00A0' );
/**
* Marked non-breaking space filler creator. This function creates the `<span data-cke-filler="true"> </span>` element.
* It defines how the filler is created.
*
* @see module:engine/view/filler~NBSP_FILLER
* @see module:engine/view/filler~BR_FILLER
* @function
*/
const MARKED_NBSP_FILLER = domDocument => {
const span = domDocument.createElement( 'span' );
span.dataset.ckeFiller = true;
span.innerHTML = '\u00A0';
return span;
};
/**
* `<br>` filler creator. This function creates the `<br data-cke-filler="true">` element.
* It defines how the filler is created.
*
* @see module:engine/view/filler~NBSP_FILLER
* @see module:engine/view/filler~MARKED_NBSP_FILLER
* @function
*/
const BR_FILLER = domDocument => {
const fillerBr = domDocument.createElement( 'br' );
fillerBr.dataset.ckeFiller = true;
return fillerBr;
};
/**
* Length of the {@link module:engine/view/filler~INLINE_FILLER INLINE_FILLER}.
*/
const INLINE_FILLER_LENGTH = 7;
/**
* Inline filler which is a sequence of the word joiners.
*
* @type {String}
*/
const INLINE_FILLER = '\u2060'.repeat( INLINE_FILLER_LENGTH );
/**
* Checks if the node is a text node which starts with the {@link module:engine/view/filler~INLINE_FILLER inline filler}.
*
* startsWithFiller( document.createTextNode( INLINE_FILLER ) ); // true
* startsWithFiller( document.createTextNode( INLINE_FILLER + 'foo' ) ); // true
* startsWithFiller( document.createTextNode( 'foo' ) ); // false
* startsWithFiller( document.createElement( 'p' ) ); // false
*
* @param {Node} domNode DOM node.
* @returns {Boolean} True if the text node starts with the {@link module:engine/view/filler~INLINE_FILLER inline filler}.
*/
function startsWithFiller( domNode ) {
return (0,_ckeditor_ckeditor5_utils_src_dom_istext__WEBPACK_IMPORTED_MODULE_1__["default"])( domNode ) && ( domNode.data.substr( 0, INLINE_FILLER_LENGTH ) === INLINE_FILLER );
}
/**
* Checks if the text node contains only the {@link module:engine/view/filler~INLINE_FILLER inline filler}.
*
* isInlineFiller( document.createTextNode( INLINE_FILLER ) ); // true
* isInlineFiller( document.createTextNode( INLINE_FILLER + 'foo' ) ); // false
*
* @param {Text} domText DOM text node.
* @returns {Boolean} True if the text node contains only the {@link module:engine/view/filler~INLINE_FILLER inline filler}.
*/
function isInlineFiller( domText ) {
return domText.data.length == INLINE_FILLER_LENGTH && startsWithFiller( domText );
}
/**
* Get string data from the text node, removing an {@link module:engine/view/filler~INLINE_FILLER inline filler} from it,
* if text node contains it.
*
* getDataWithoutFiller( document.createTextNode( INLINE_FILLER + 'foo' ) ) == 'foo' // true
* getDataWithoutFiller( document.createTextNode( 'foo' ) ) == 'foo' // true
*
* @param {Text} domText DOM text node, possible with inline filler.
* @returns {String} Data without filler.
*/
function getDataWithoutFiller( domText ) {
if ( startsWithFiller( domText ) ) {
return domText.data.slice( INLINE_FILLER_LENGTH );
} else {
return domText.data;
}
}
/**
* Assign key observer which move cursor from the end of the inline filler to the beginning of it when
* the left arrow is pressed, so the filler does not break navigation.
*
* @param {module:engine/view/view~View} view View controller instance we should inject quirks handling on.
*/
function injectQuirksHandling( view ) {
view.document.on( 'arrowKey', jumpOverInlineFiller, { priority: 'low' } );
}
// Move cursor from the end of the inline filler to the beginning of it when, so the filler does not break navigation.
function jumpOverInlineFiller( evt, data ) {
if ( data.keyCode == _ckeditor_ckeditor5_utils_src_keyboard__WEBPACK_IMPORTED_MODULE_0__.keyCodes.arrowleft ) {
const domSelection = data.domTarget.ownerDocument.defaultView.getSelection();
if ( domSelection.rangeCount == 1 && domSelection.getRangeAt( 0 ).collapsed ) {
const domParent = domSelection.getRangeAt( 0 ).startContainer;
const domOffset = domSelection.getRangeAt( 0 ).startOffset;
if ( startsWithFiller( domParent ) && domOffset <= INLINE_FILLER_LENGTH ) {
domSelection.collapse( domParent, 0 );
}
}
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/view/matcher.js":
/*!*********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/view/matcher.js ***!
\*********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ Matcher)
/* harmony export */ });
/* harmony import */ var lodash_es__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! lodash-es */ "./node_modules/lodash-es/isPlainObject.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/view/matcher
*/
/**
* View matcher class.
* Instance of this class can be used to find {@link module:engine/view/element~Element elements} that match given pattern.
*/
class Matcher {
/**
* Creates new instance of Matcher.
*
* @param {String|RegExp|Object} [pattern] Match patterns. See {@link module:engine/view/matcher~Matcher#add add method} for
* more information.
*/
constructor( ...pattern ) {
/**
* @private
* @type {Array<String|RegExp|Object>}
*/
this._patterns = [];
this.add( ...pattern );
}
/**
* Adds pattern or patterns to matcher instance.
*
* // String.
* matcher.add( 'div' );
*
* // Regular expression.
* matcher.add( /^\w/ );
*
* // Single class.
* matcher.add( {
* classes: 'foobar'
* } );
*
* See {@link module:engine/view/matcher~MatcherPattern} for more examples.
*
* Multiple patterns can be added in one call:
*
* matcher.add( 'div', { classes: 'foobar' } );
*
* @param {Object|String|RegExp|Function} pattern Object describing pattern details. If string or regular expression
* is provided it will be used to match element's name. Pattern can be also provided in a form
* of a function - then this function will be called with each {@link module:engine/view/element~Element element} as a parameter.
* Function's return value will be stored under `match` key of the object returned from
* {@link module:engine/view/matcher~Matcher#match match} or {@link module:engine/view/matcher~Matcher#matchAll matchAll} methods.
* @param {String|RegExp} [pattern.name] Name or regular expression to match element's name.
* @param {Object} [pattern.attributes] Object with key-value pairs representing attributes to match. Each object key
* represents attribute name. Value under that key can be either:
* * `true` - then attribute is just required (can be empty),
* * a string - then attribute has to be equal, or
* * a regular expression - then attribute has to match the expression.
* @param {String|RegExp|Array} [pattern.classes] Class name or array of class names to match. Each name can be
* provided in a form of string or regular expression.
* @param {Object} [pattern.styles] Object with key-value pairs representing styles to match. Each object key
* represents style name. Value under that key can be either a string or a regular expression and it will be used
* to match style value.
*/
add( ...pattern ) {
for ( let item of pattern ) {
// String or RegExp pattern is used as element's name.
if ( typeof item == 'string' || item instanceof RegExp ) {
item = { name: item };
}
this._patterns.push( item );
}
}
/**
* Matches elements for currently stored patterns. Returns match information about first found
* {@link module:engine/view/element~Element element}, otherwise returns `null`.
*
* Example of returned object:
*
* {
* element: <instance of found element>,
* pattern: <pattern used to match found element>,
* match: {
* name: true,
* attributes: [ 'title', 'href' ],
* classes: [ 'foo' ],
* styles: [ 'color', 'position' ]
* }
* }
*
* @see module:engine/view/matcher~Matcher#add
* @see module:engine/view/matcher~Matcher#matchAll
* @param {...module:engine/view/element~Element} element View element to match against stored patterns.
* @returns {Object|null} result
* @returns {module:engine/view/element~Element} result.element Matched view element.
* @returns {Object|String|RegExp|Function} result.pattern Pattern that was used to find matched element.
* @returns {Object} result.match Object representing matched element parts.
* @returns {Boolean} [result.match.name] True if name of the element was matched.
* @returns {Array} [result.match.attributes] Array with matched attribute names.
* @returns {Array} [result.match.classes] Array with matched class names.
* @returns {Array} [result.match.styles] Array with matched style names.
*/
match( ...element ) {
for ( const singleElement of element ) {
for ( const pattern of this._patterns ) {
const match = isElementMatching( singleElement, pattern );
if ( match ) {
return {
element: singleElement,
pattern,
match
};
}
}
}
return null;
}
/**
* Matches elements for currently stored patterns. Returns array of match information with all found
* {@link module:engine/view/element~Element elements}. If no element is found - returns `null`.
*
* @see module:engine/view/matcher~Matcher#add
* @see module:engine/view/matcher~Matcher#match
* @param {...module:engine/view/element~Element} element View element to match against stored patterns.
* @returns {Array.<Object>|null} Array with match information about found elements or `null`. For more information
* see {@link module:engine/view/matcher~Matcher#match match method} description.
*/
matchAll( ...element ) {
const results = [];
for ( const singleElement of element ) {
for ( const pattern of this._patterns ) {
const match = isElementMatching( singleElement, pattern );
if ( match ) {
results.push( {
element: singleElement,
pattern,
match
} );
}
}
}
return results.length > 0 ? results : null;
}
/**
* Returns the name of the element to match if there is exactly one pattern added to the matcher instance
* and it matches element name defined by `string` (not `RegExp`). Otherwise, returns `null`.
*
* @returns {String|null} Element name trying to match.
*/
getElementName() {
if ( this._patterns.length !== 1 ) {
return null;
}
const pattern = this._patterns[ 0 ];
const name = pattern.name;
return ( typeof pattern != 'function' && name && !( name instanceof RegExp ) ) ? name : null;
}
}
// Returns match information if {@link module:engine/view/element~Element element} is matching provided pattern.
// If element cannot be matched to provided pattern - returns `null`.
//
// @param {module:engine/view/element~Element} element
// @param {Object|String|RegExp|Function} pattern
// @returns {Object|null} Returns object with match information or null if element is not matching.
function isElementMatching( element, pattern ) {
// If pattern is provided as function - return result of that function;
if ( typeof pattern == 'function' ) {
return pattern( element );
}
const match = {};
// Check element's name.
if ( pattern.name ) {
match.name = matchName( pattern.name, element.name );
if ( !match.name ) {
return null;
}
}
// Check element's attributes.
if ( pattern.attributes ) {
match.attributes = matchAttributes( pattern.attributes, element );
if ( !match.attributes ) {
return null;
}
}
// Check element's classes.
if ( pattern.classes ) {
match.classes = matchClasses( pattern.classes, element );
if ( !match.classes ) {
return false;
}
}
// Check element's styles.
if ( pattern.styles ) {
match.styles = matchStyles( pattern.styles, element );
if ( !match.styles ) {
return false;
}
}
return match;
}
// Checks if name can be matched by provided pattern.
//
// @param {String|RegExp} pattern
// @param {String} name
// @returns {Boolean} Returns `true` if name can be matched, `false` otherwise.
function matchName( pattern, name ) {
// If pattern is provided as RegExp - test against this regexp.
if ( pattern instanceof RegExp ) {
return !!name.match( pattern );
}
return pattern === name;
}
// Checks if an array of key/value pairs can be matched against provided patterns.
//
// Patterns can be provided in a following ways:
// - a boolean value matches any attribute with any value (or no value):
//
// pattern: true
//
// - a RegExp expression or object matches any attribute name:
//
// pattern: /h[1-6]/
//
// - an object matches any attribute that has the same name as the object item's key, where object item's value is:
// - equal to `true`, which matches any attribute value:
//
// pattern: {
// required: true
// }
//
// - a string that is equal to attribute value:
//
// pattern: {
// rel: 'nofollow'
// }
//
// - a regular expression that matches attribute value,
//
// pattern: {
// src: /https.*/
// }
//
// - an array with items, where the item is:
// - a string that is equal to attribute value:
//
// pattern: [ 'data-property-1', 'data-property-2' ],
//
// - an object with `key` and `value` property, where `key` is a regular expression matching attribute name and
// `value` is either regular expression matching attribute value or a string equal to attribute value:
//
// pattern: [
// { key: /data-property-.*/, value: true },
// // or:
// { key: /data-property-.*/, value: 'foobar' },
// // or:
// { key: /data-property-.*/, value: /foo.*/ }
// ]
//
// @param {Object} patterns Object with information about attributes to match.
// @param {Iterable.<String>} keys Attribute, style or class keys.
// @param {Function} valueGetter A function providing value for a given item key.
// @returns {Array|null} Returns array with matched attribute names or `null` if no attributes were matched.
function matchPatterns( patterns, keys, valueGetter ) {
const normalizedPatterns = normalizePatterns( patterns );
const normalizedItems = Array.from( keys );
const match = [];
normalizedPatterns.forEach( ( [ patternKey, patternValue ] ) => {
normalizedItems.forEach( itemKey => {
if (
isKeyMatched( patternKey, itemKey ) &&
isValueMatched( patternValue, itemKey, valueGetter )
) {
match.push( itemKey );
}
} );
} );
// Return matches only if there are at least as many of them as there are patterns.
// The RegExp pattern can match more than one item.
if ( !normalizedPatterns.length || match.length < normalizedPatterns.length ) {
return null;
}
return match;
}
// Bring all the possible pattern forms to an array of arrays where first item is a key and second is a value.
//
// Examples:
//
// Boolean pattern value:
//
// true
//
// to
//
// [ [ true, true ] ]
//
// Textual pattern value:
//
// 'attribute-name-or-class-or-style'
//
// to
//
// [ [ 'attribute-name-or-class-or-style', true ] ]
//
// Regular expression:
//
// /^data-.*$/
//
// to
//
// [ [ /^data-.*$/, true ] ]
//
// Objects (plain or with `key` and `value` specified explicitly):
//
// {
// src: /^https:.*$/
// }
//
// or
//
// [ {
// key: 'src',
// value: /^https:.*$/
// } ]
//
// to:
//
// [ [ 'src', /^https:.*$/ ] ]
//
// @param {Object|Array} patterns
// @returns {Array|null} Returns an array of objects or null if provided patterns were not in an expected form.
function normalizePatterns( patterns ) {
if ( Array.isArray( patterns ) ) {
return patterns.map( pattern => {
if ( (0,lodash_es__WEBPACK_IMPORTED_MODULE_1__["default"])( pattern ) ) {
if ( pattern.key === undefined || pattern.value === undefined ) {
// Documented at the end of matcher.js.
(0,_ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_0__.logWarning)( 'matcher-pattern-missing-key-or-value', pattern );
}
return [ pattern.key, pattern.value ];
}
// Assume the pattern is either String or RegExp.
return [ pattern, true ];
} );
}
if ( (0,lodash_es__WEBPACK_IMPORTED_MODULE_1__["default"])( patterns ) ) {
return Object.entries( patterns );
}
// Other cases (true, string or regexp).
return [ [ patterns, true ] ];
}
// @param {String|RegExp} patternKey A pattern representing a key we want to match.
// @param {String} itemKey An actual item key (e.g. `'src'`, `'background-color'`, `'ck-widget'`) we're testing against pattern.
// @returns {Boolean}
function isKeyMatched( patternKey, itemKey ) {
return patternKey === true ||
patternKey === itemKey ||
patternKey instanceof RegExp && itemKey.match( patternKey );
}
// @param {String|RegExp} patternValue A pattern representing a value we want to match.
// @param {String} itemKey An item key, e.g. `background`, `href`, 'rel', etc.
// @param {Function} valueGetter A function used to provide a value for a given `itemKey`.
// @returns {Boolean}
function isValueMatched( patternValue, itemKey, valueGetter ) {
if ( patternValue === true ) {
return true;
}
const itemValue = valueGetter( itemKey );
// For now, the reducers are not returning the full tree of properties.
// Casting to string preserves the old behavior until the root cause is fixed.
// More can be found in https://github.com/ckeditor/ckeditor5/issues/10399.
return patternValue === itemValue ||
patternValue instanceof RegExp && !!String( itemValue ).match( patternValue );
}
// Checks if attributes of provided element can be matched against provided patterns.
//
// @param {Object} patterns Object with information about attributes to match. Each key of the object will be
// used as attribute name. Value of each key can be a string or regular expression to match against attribute value.
// @param {module:engine/view/element~Element} element Element which attributes will be tested.
// @returns {Array|null} Returns array with matched attribute names or `null` if no attributes were matched.
function matchAttributes( patterns, element ) {
const attributeKeys = new Set( element.getAttributeKeys() );
// `style` and `class` attribute keys are deprecated. Only allow them in object pattern
// for backward compatibility.
if ( (0,lodash_es__WEBPACK_IMPORTED_MODULE_1__["default"])( patterns ) ) {
if ( patterns.style !== undefined ) {
// Documented at the end of matcher.js.
(0,_ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_0__.logWarning)( 'matcher-pattern-deprecated-attributes-style-key', patterns );
}
if ( patterns.class !== undefined ) {
// Documented at the end of matcher.js.
(0,_ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_0__.logWarning)( 'matcher-pattern-deprecated-attributes-class-key', patterns );
}
} else {
attributeKeys.delete( 'style' );
attributeKeys.delete( 'class' );
}
return matchPatterns( patterns, attributeKeys, key => element.getAttribute( key ) );
}
// Checks if classes of provided element can be matched against provided patterns.
//
// @param {Array.<String|RegExp>} patterns Array of strings or regular expressions to match against element's classes.
// @param {module:engine/view/element~Element} element Element which classes will be tested.
// @returns {Array|null} Returns array with matched class names or `null` if no classes were matched.
function matchClasses( patterns, element ) {
// We don't need `getter` here because patterns for classes are always normalized to `[ className, true ]`.
return matchPatterns( patterns, element.getClassNames() );
}
// Checks if styles of provided element can be matched against provided patterns.
//
// @param {Object} patterns Object with information about styles to match. Each key of the object will be
// used as style name. Value of each key can be a string or regular expression to match against style value.
// @param {module:engine/view/element~Element} element Element which styles will be tested.
// @returns {Array|null} Returns array with matched style names or `null` if no styles were matched.
function matchStyles( patterns, element ) {
return matchPatterns( patterns, element.getStyleNames( true ), key => element.getStyle( key ) );
}
/**
* An entity that is a valid pattern recognized by a matcher. `MatcherPattern` is used by {@link ~Matcher} to recognize
* if a view element fits in a group of view elements described by the pattern.
*
* `MatcherPattern` can be given as a `String`, a `RegExp`, an `Object` or a `Function`.
*
* If `MatcherPattern` is given as a `String` or `RegExp`, it will match any view element that has a matching name:
*
* // Match any element with name equal to 'div'.
* const pattern = 'div';
*
* // Match any element which name starts on 'p'.
* const pattern = /^p/;
*
* If `MatcherPattern` is given as an `Object`, all the object's properties will be matched with view element properties.
* If the view element does not meet all of the object's pattern properties, the match will not happen.
* Available `Object` matching properties:
*
* Matching view element:
*
* // Match view element's name using String:
* const pattern = { name: 'p' };
*
* // or by providing RegExp:
* const pattern = { name: /^(ul|ol)$/ };
*
* // The name can also be skipped to match any view element with matching attributes:
* const pattern = {
* attributes: {
* 'title': true
* }
* };
*
* Matching view element attributes:
*
* // Match view element with any attribute value.
* const pattern = {
* name: 'p',
* attributes: true
* };
*
* // Match view element which has matching attributes (String).
* const pattern = {
* name: 'figure',
* attributes: 'title' // Match title attribute (can be empty).
* };
*
* // Match view element which has matching attributes (RegExp).
* const pattern = {
* name: 'figure',
* attributes: /^data-.*$/ // Match attributes starting with `data-` e.g. `data-foo` with any value (can be empty).
* };
*
* // Match view element which has matching attributes (Object).
* const pattern = {
* name: 'figure',
* attributes: {
* title: 'foobar', // Match `title` attribute with 'foobar' value.
* alt: true, // Match `alt` attribute with any value (can be empty).
* 'data-type': /^(jpg|png)$/ // Match `data-type` attribute with `jpg` or `png` value.
* }
* };
*
* // Match view element which has matching attributes (Array).
* const pattern = {
* name: 'figure',
* attributes: [
* 'title', // Match `title` attribute (can be empty).
* /^data-*$/ // Match attributes starting with `data-` e.g. `data-foo` with any value (can be empty).
* ]
* };
*
* // Match view element which has matching attributes (key-value pairs).
* const pattern = {
* name: 'input',
* attributes: [
* {
* key: 'type', // Match `type` as an attribute key.
* value: /^(text|number|date)$/ // Match `text`, `number` or `date` values.
* },
* {
* key: /^data-.*$/, // Match attributes starting with `data-` e.g. `data-foo`.
* value: true // Match any value (can be empty).
* }
* ]
* };
*
* Matching view element styles:
*
* // Match view element with any style.
* const pattern = {
* name: 'p',
* styles: true
* };
*
* // Match view element which has matching styles (String).
* const pattern = {
* name: 'p',
* styles: 'color' // Match attributes with `color` style.
* };
*
* // Match view element which has matching styles (RegExp).
* const pattern = {
* name: 'p',
* styles: /^border.*$/ // Match view element with any border style.
* };
*
* // Match view element which has matching styles (Object).
* const pattern = {
* name: 'p',
* styles: {
* color: /rgb\((\d{1,3}), (\d{1,3}), (\d{1,3})\)/, // Match `color` in RGB format only.
* 'font-weight': 600, // Match `font-weight` only if it's `600`.
* 'text-decoration': true // Match any text decoration.
* }
* };
*
* // Match view element which has matching styles (Array).
* const pattern = {
* name: 'p',
* styles: [
* 'color', // Match `color` with any value.
* /^border.*$/ // Match all border properties.
* ]
* };
*
* // Match view element which has matching styles (key-value pairs).
* const pattern = {
* name: 'p',
* styles: [
* {
* key: 'color', // Match `color` as an property key.
* value: /rgb\((\d{1,3}), (\d{1,3}), (\d{1,3})\)/ // Match RGB format only.
* },
* {
* key: /^border.*$/, // Match any border style.
* value: true // Match any value.
* }
* ]
* };
*
* Matching view element classes:
*
* // Match view element with any class.
* const pattern = {
* name: 'p',
* classes: true
* };
*
* // Match view element which has matching class (String).
* const pattern = {
* name: 'p',
* classes: 'highlighted' // Match `highlighted` class.
* };
*
* // Match view element which has matching classes (RegExp).
* const pattern = {
* name: 'figure',
* classes: /^image-side-(left|right)$/ // Match `image-side-left` or `image-side-right` class.
* };
*
* // Match view element which has matching classes (Object).
* const pattern = {
* name: 'p',
* classes: {
* highlighted: true, // Match `highlighted` class.
* marker: true // Match `marker` class.
* }
* };
*
* // Match view element which has matching classes (Array).
* const pattern = {
* name: 'figure',
* classes: [
* 'image', // Match `image` class.
* /^image-side-(left|right)$/ // Match `image-side-left` or `image-side-right` class.
* ]
* };
*
* // Match view element which has matching classes (key-value pairs).
* const pattern = {
* name: 'figure',
* classes: [
* {
* key: 'image', // Match `image` class.
* value: true
* },
* {
* key: /^image-side-(left|right)$/, // Match `image-side-left` or `image-side-right` class.
* value: true
* }
* ]
* };
*
* Pattern can combine multiple properties allowing for more complex view element matching:
*
* const pattern = {
* name: 'span',
* attributes: [ 'title' ],
* styles: {
* 'font-weight': 'bold'
* },
* classes: 'highlighted'
* };
*
* If `MatcherPattern` is given as a `Function`, the function takes a view element as a first and only parameter and
* the function should decide whether that element matches. If so, it should return what part of the view element has been matched.
* Otherwise, the function should return `null`. The returned result will be included in `match` property of the object
* returned by {@link ~Matcher#match} call.
*
* // Match an empty <div> element.
* const pattern = element => {
* if ( element.name == 'div' && element.childCount > 0 ) {
* // Return which part of the element was matched.
* return { name: true };
* }
*
* return null;
* };
*
* // Match a <p> element with big font ("heading-like" element).
* const pattern = element => {
* if ( element.name == 'p' ) {
* const fontSize = element.getStyle( 'font-size' );
* const size = fontSize.match( /(\d+)/px );
*
* if ( size && Number( size[ 1 ] ) > 26 ) {
* return { name: true, attribute: [ 'font-size' ] };
* }
* }
*
* return null;
* };
*
* `MatcherPattern` is defined in a way that it is a superset of {@link module:engine/view/elementdefinition~ElementDefinition},
* that is, every `ElementDefinition` also can be used as a `MatcherPattern`.
*
* @typedef {String|RegExp|Object|Function} module:engine/view/matcher~MatcherPattern
*
* @property {String|RegExp} [name] View element name to match.
* @property {Boolean|String|RegExp|Object|Array.<String|RegExp|Object>} [classes] View element's classes to match.
* @property {Boolean|String|RegExp|Object|Array.<String|RegExp|Object>} [styles] View element's styles to match.
* @property {Boolean|String|RegExp|Object|Array.<String|RegExp|Object>} [attributes] View element's attributes to match.
*/
/**
* The key-value matcher pattern is missing key or value. Both must be present.
* Refer the documentation: {@link module:engine/view/matcher~MatcherPattern}.
*
* @param {Object} pattern Pattern with missing properties.
* @error matcher-pattern-missing-key-or-value
*/
/**
* The key-value matcher pattern for `attributes` option is using deprecated `style` key.
*
* Use `styles` matcher pattern option instead:
*
* // Instead of:
* const pattern = {
* attributes: {
* key1: 'value1',
* key2: 'value2',
* style: /^border.*$/
* }
* }
*
* // Use:
* const pattern = {
* attributes: {
* key1: 'value1',
* key2: 'value2'
* },
* styles: /^border.*$/
* }
*
* Refer to the {@glink builds/guides/migration/migration-to-29##migration-to-ckeditor-5-v2910 Migration to v29.1.0} guide
* and {@link module:engine/view/matcher~MatcherPattern} documentation.
*
* @param {Object} pattern Pattern with missing properties.
* @error matcher-pattern-deprecated-attributes-style-key
*/
/**
* The key-value matcher pattern for `attributes` option is using deprecated `class` key.
*
* Use `classes` matcher pattern option instead:
*
* // Instead of:
* const pattern = {
* attributes: {
* key1: 'value1',
* key2: 'value2',
* class: 'foobar'
* }
* }
*
* // Use:
* const pattern = {
* attributes: {
* key1: 'value1',
* key2: 'value2'
* },
* classes: 'foobar'
* }
*
* Refer to the {@glink builds/guides/migration/migration-to-29##migration-to-ckeditor-5-v2910 Migration to v29.1.0} guide
* and the {@link module:engine/view/matcher~MatcherPattern} documentation.
*
* @param {Object} pattern Pattern with missing properties.
* @error matcher-pattern-deprecated-attributes-class-key
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/view/node.js":
/*!******************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/view/node.js ***!
\******************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ Node)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_emittermixin__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/emittermixin */ "./node_modules/@ckeditor/ckeditor5-utils/src/emittermixin.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/mix */ "./node_modules/@ckeditor/ckeditor5-utils/src/mix.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_comparearrays__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/comparearrays */ "./node_modules/@ckeditor/ckeditor5-utils/src/comparearrays.js");
/* harmony import */ var lodash_es__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! lodash-es */ "./node_modules/lodash-es/clone.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_version__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/version */ "./node_modules/@ckeditor/ckeditor5-utils/src/version.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/view/node
*/
// To check if component is loaded more than once.
/**
* Abstract view node class.
*
* This is an abstract class. Its constructor should not be used directly.
* Use the {@link module:engine/view/downcastwriter~DowncastWriter} or {@link module:engine/view/upcastwriter~UpcastWriter}
* to create new instances of view nodes.
*
* @abstract
*/
class Node {
/**
* Creates a tree view node.
*
* @protected
* @param {module:engine/view/document~Document} document The document instance to which this node belongs.
*/
constructor( document ) {
/**
* The document instance to which this node belongs.
*
* @readonly
* @member {module:engine/view/document~Document}
*/
this.document = document;
/**
* Parent element. Null by default. Set by {@link module:engine/view/element~Element#_insertChild}.
*
* @readonly
* @member {module:engine/view/element~Element|module:engine/view/documentfragment~DocumentFragment|null}
*/
this.parent = null;
}
/**
* Index of the node in the parent element or null if the node has no parent.
*
* Accessing this property throws an error if this node's parent element does not contain it.
* This means that view tree got broken.
*
* @readonly
* @type {Number|null}
*/
get index() {
let pos;
if ( !this.parent ) {
return null;
}
// No parent or child doesn't exist in parent's children.
if ( ( pos = this.parent.getChildIndex( this ) ) == -1 ) {
/**
* The node's parent does not contain this node. It means that the document tree is corrupted.
*
* @error view-node-not-found-in-parent
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_0__["default"]( 'view-node-not-found-in-parent', this );
}
return pos;
}
/**
* Node's next sibling, or `null` if it is the last child.
*
* @readonly
* @type {module:engine/view/node~Node|null}
*/
get nextSibling() {
const index = this.index;
return ( index !== null && this.parent.getChild( index + 1 ) ) || null;
}
/**
* Node's previous sibling, or `null` if it is the first child.
*
* @readonly
* @type {module:engine/view/node~Node|null}
*/
get previousSibling() {
const index = this.index;
return ( index !== null && this.parent.getChild( index - 1 ) ) || null;
}
/**
* Top-most ancestor of the node. If the node has no parent it is the root itself.
*
* @readonly
* @type {module:engine/view/node~Node|module:engine/view/documentfragment~DocumentFragment}
*/
get root() {
let root = this; // eslint-disable-line consistent-this
while ( root.parent ) {
root = root.parent;
}
return root;
}
/**
* Returns true if the node is in a tree rooted in the document (is a descendant of one of its roots).
*
* @returns {Boolean}
*/
isAttached() {
return this.root.is( 'rootElement' );
}
/**
* Gets a path to the node. The path is an array containing indices of consecutive ancestors of this node,
* beginning from {@link module:engine/view/node~Node#root root}, down to this node's index.
*
* const abc = downcastWriter.createText( 'abc' );
* const foo = downcastWriter.createText( 'foo' );
* const h1 = downcastWriter.createElement( 'h1', null, downcastWriter.createText( 'header' ) );
* const p = downcastWriter.createElement( 'p', null, [ abc, foo ] );
* const div = downcastWriter.createElement( 'div', null, [ h1, p ] );
* foo.getPath(); // Returns [ 1, 3 ]. `foo` is in `p` which is in `div`. `p` starts at offset 1, while `foo` at 3.
* h1.getPath(); // Returns [ 0 ].
* div.getPath(); // Returns [].
*
* @returns {Array.<Number>} The path.
*/
getPath() {
const path = [];
let node = this; // eslint-disable-line consistent-this
while ( node.parent ) {
path.unshift( node.index );
node = node.parent;
}
return path;
}
/**
* Returns ancestors array of this node.
*
* @param {Object} options Options object.
* @param {Boolean} [options.includeSelf=false] When set to `true` this node will be also included in parent's array.
* @param {Boolean} [options.parentFirst=false] When set to `true`, array will be sorted from node's parent to root element,
* otherwise root element will be the first item in the array.
* @returns {Array} Array with ancestors.
*/
getAncestors( options = { includeSelf: false, parentFirst: false } ) {
const ancestors = [];
let parent = options.includeSelf ? this : this.parent;
while ( parent ) {
ancestors[ options.parentFirst ? 'push' : 'unshift' ]( parent );
parent = parent.parent;
}
return ancestors;
}
/**
* Returns a {@link module:engine/view/element~Element} or {@link module:engine/view/documentfragment~DocumentFragment}
* which is a common ancestor of both nodes.
*
* @param {module:engine/view/node~Node} node The second node.
* @param {Object} options Options object.
* @param {Boolean} [options.includeSelf=false] When set to `true` both nodes will be considered "ancestors" too.
* Which means that if e.g. node A is inside B, then their common ancestor will be B.
* @returns {module:engine/view/element~Element|module:engine/view/documentfragment~DocumentFragment|null}
*/
getCommonAncestor( node, options = {} ) {
const ancestorsA = this.getAncestors( options );
const ancestorsB = node.getAncestors( options );
let i = 0;
while ( ancestorsA[ i ] == ancestorsB[ i ] && ancestorsA[ i ] ) {
i++;
}
return i === 0 ? null : ancestorsA[ i - 1 ];
}
/**
* Returns whether this node is before given node. `false` is returned if nodes are in different trees (for example,
* in different {@link module:engine/view/documentfragment~DocumentFragment}s).
*
* @param {module:engine/view/node~Node} node Node to compare with.
* @returns {Boolean}
*/
isBefore( node ) {
// Given node is not before this node if they are same.
if ( this == node ) {
return false;
}
// Return `false` if it is impossible to compare nodes.
if ( this.root !== node.root ) {
return false;
}
const thisPath = this.getPath();
const nodePath = node.getPath();
const result = (0,_ckeditor_ckeditor5_utils_src_comparearrays__WEBPACK_IMPORTED_MODULE_3__["default"])( thisPath, nodePath );
switch ( result ) {
case 'prefix':
return true;
case 'extension':
return false;
default:
return thisPath[ result ] < nodePath[ result ];
}
}
/**
* Returns whether this node is after given node. `false` is returned if nodes are in different trees (for example,
* in different {@link module:engine/view/documentfragment~DocumentFragment}s).
*
* @param {module:engine/view/node~Node} node Node to compare with.
* @returns {Boolean}
*/
isAfter( node ) {
// Given node is not before this node if they are same.
if ( this == node ) {
return false;
}
// Return `false` if it is impossible to compare nodes.
if ( this.root !== node.root ) {
return false;
}
// In other cases, just check if the `node` is before, and return the opposite.
return !this.isBefore( node );
}
/**
* Removes node from parent.
*
* @protected
*/
_remove() {
this.parent._removeChildren( this.index );
}
/**
* @protected
* @param {module:engine/view/document~ChangeType} type Type of the change.
* @param {module:engine/view/node~Node} node Changed node.
* @fires change
*/
_fireChange( type, node ) {
this.fire( 'change:' + type, node );
if ( this.parent ) {
this.parent._fireChange( type, node );
}
}
/**
* Custom toJSON method to solve child-parent circular dependencies.
*
* @returns {Object} Clone of this object with the parent property removed.
*/
toJSON() {
const json = (0,lodash_es__WEBPACK_IMPORTED_MODULE_5__["default"])( this );
// Due to circular references we need to remove parent reference.
delete json.parent;
return json;
}
/**
* Checks whether this object is of the given type.
*
* This method is useful when processing view objects that are of unknown type. For example, a function
* may return a {@link module:engine/view/documentfragment~DocumentFragment} or a {@link module:engine/view/node~Node}
* that can be either a text node or an element. This method can be used to check what kind of object is returned.
*
* someObject.is( 'element' ); // -> true if this is an element
* someObject.is( 'node' ); // -> true if this is a node (a text node or an element)
* someObject.is( 'documentFragment' ); // -> true if this is a document fragment
*
* Since this method is also available on a range of model objects, you can prefix the type of the object with
* `model:` or `view:` to check, for example, if this is the model's or view's element:
*
* viewElement.is( 'view:element' ); // -> true
* viewElement.is( 'model:element' ); // -> false
*
* By using this method it is also possible to check a name of an element:
*
* imgElement.is( 'element', 'img' ); // -> true
* imgElement.is( 'view:element', 'img' ); // -> same as above, but more precise
*
* The list of view objects which implement the `is()` method:
*
* * {@link module:engine/view/attributeelement~AttributeElement#is `AttributeElement#is()`}
* * {@link module:engine/view/containerelement~ContainerElement#is `ContainerElement#is()`}
* * {@link module:engine/view/documentfragment~DocumentFragment#is `DocumentFragment#is()`}
* * {@link module:engine/view/documentselection~DocumentSelection#is `DocumentSelection#is()`}
* * {@link module:engine/view/editableelement~EditableElement#is `EditableElement#is()`}
* * {@link module:engine/view/element~Element#is `Element#is()`}
* * {@link module:engine/view/emptyelement~EmptyElement#is `EmptyElement#is()`}
* * {@link module:engine/view/node~Node#is `Node#is()`}
* * {@link module:engine/view/position~Position#is `Position#is()`}
* * {@link module:engine/view/range~Range#is `Range#is()`}
* * {@link module:engine/view/rooteditableelement~RootEditableElement#is `RootEditableElement#is()`}
* * {@link module:engine/view/selection~Selection#is `Selection#is()`}
* * {@link module:engine/view/text~Text#is `Text#is()`}
* * {@link module:engine/view/textproxy~TextProxy#is `TextProxy#is()`}
* * {@link module:engine/view/uielement~UIElement#is `UIElement#is()`}
*
* @method #is
* @param {String} type Type to check.
* @returns {Boolean}
*/
is( type ) {
return type === 'node' || type === 'view:node';
}
/**
* Clones this node.
*
* @protected
* @method #_clone
* @returns {module:engine/view/node~Node} Clone of this node.
*/
/**
* Checks if provided node is similar to this node.
*
* @method #isSimilar
* @returns {Boolean} True if nodes are similar.
*/
}
/**
* Fired when list of {@link module:engine/view/element~Element elements} children changes.
*
* Change event is bubbled – it is fired on all ancestors.
*
* @event change:children
* @param {module:engine/view/node~Node} changedNode
*/
/**
* Fired when list of {@link module:engine/view/element~Element elements} attributes changes.
*
* Change event is bubbled – it is fired on all ancestors.
*
* @event change:attributes
* @param {module:engine/view/node~Node} changedNode
*/
/**
* Fired when {@link module:engine/view/text~Text text nodes} data changes.
*
* Change event is bubbled – it is fired on all ancestors.
*
* @event change:text
* @param {module:engine/view/node~Node} changedNode
*/
/**
* @event change
*/
(0,_ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_2__["default"])( Node, _ckeditor_ckeditor5_utils_src_emittermixin__WEBPACK_IMPORTED_MODULE_1__["default"] );
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/arrowkeysobserver.js":
/*!****************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/arrowkeysobserver.js ***!
\****************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ArrowKeysObserver)
/* harmony export */ });
/* harmony import */ var _observer__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./observer */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/observer.js");
/* harmony import */ var _bubblingeventinfo__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./bubblingeventinfo */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/bubblingeventinfo.js");
/* harmony import */ var _ckeditor_ckeditor5_utils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils */ "./node_modules/@ckeditor/ckeditor5-utils/src/index.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/view/observer/arrowkeysobserver
*/
/**
* Arrow keys observer introduces the {@link module:engine/view/document~Document#event:arrowKey `Document#arrowKey`} event.
*
* Note that this observer is attached by the {@link module:engine/view/view~View} and is available by default.
*
* @extends module:engine/view/observer/observer~Observer
*/
class ArrowKeysObserver extends _observer__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* @inheritDoc
*/
constructor( view ) {
super( view );
this.document.on( 'keydown', ( event, data ) => {
if ( this.isEnabled && (0,_ckeditor_ckeditor5_utils__WEBPACK_IMPORTED_MODULE_2__.isArrowKeyCode)( data.keyCode ) ) {
const eventInfo = new _bubblingeventinfo__WEBPACK_IMPORTED_MODULE_1__["default"]( this.document, 'arrowKey', this.document.selection.getFirstRange() );
this.document.fire( eventInfo, data );
if ( eventInfo.stop.called ) {
event.stop();
}
}
} );
}
/**
* @inheritDoc
*/
observe() {}
}
/**
* Event fired when the user presses an arrow keys.
*
* Introduced by {@link module:engine/view/observer/arrowkeysobserver~ArrowKeysObserver}.
*
* Note that because {@link module:engine/view/observer/arrowkeysobserver~ArrowKeysObserver} is attached by the
* {@link module:engine/view/view~View} this event is available by default.
*
* @event module:engine/view/document~Document#event:arrowKey
* @param {module:engine/view/observer/domeventdata~DomEventData} data
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/bubblingemittermixin.js":
/*!*******************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/bubblingemittermixin.js ***!
\*******************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_utils_src_eventinfo__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/eventinfo */ "./node_modules/@ckeditor/ckeditor5-utils/src/eventinfo.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_emittermixin__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/emittermixin */ "./node_modules/@ckeditor/ckeditor5-utils/src/emittermixin.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_toarray__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/toarray */ "./node_modules/@ckeditor/ckeditor5-utils/src/toarray.js");
/* harmony import */ var _bubblingeventinfo__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./bubblingeventinfo */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/bubblingeventinfo.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/view/observer/bubblingemittermixin
*/
const contextsSymbol = Symbol( 'bubbling contexts' );
/**
* Bubbling emitter mixin for the view document as described in the
* {@link ~BubblingEmitter} interface.
*
* @mixin BubblingEmitterMixin
* @implements module:engine/view/observer/bubblingemittermixin~BubblingEmitter
*/
const BubblingEmitterMixin = {
/**
* @inheritDoc
*/
fire( eventOrInfo, ...eventArgs ) {
try {
const eventInfo = eventOrInfo instanceof _ckeditor_ckeditor5_utils_src_eventinfo__WEBPACK_IMPORTED_MODULE_0__["default"] ? eventOrInfo : new _ckeditor_ckeditor5_utils_src_eventinfo__WEBPACK_IMPORTED_MODULE_0__["default"]( this, eventOrInfo );
const eventContexts = getBubblingContexts( this );
if ( !eventContexts.size ) {
return;
}
updateEventInfo( eventInfo, 'capturing', this );
// The capture phase of the event.
if ( fireListenerFor( eventContexts, '$capture', eventInfo, ...eventArgs ) ) {
return eventInfo.return;
}
const startRange = eventInfo.startRange || this.selection.getFirstRange();
const selectedElement = startRange ? startRange.getContainedElement() : null;
const isCustomContext = selectedElement ? Boolean( getCustomContext( eventContexts, selectedElement ) ) : false;
let node = selectedElement || getDeeperRangeParent( startRange );
updateEventInfo( eventInfo, 'atTarget', node );
// For the not yet bubbling event trigger for $text node if selection can be there and it's not a custom context selected.
if ( !isCustomContext ) {
if ( fireListenerFor( eventContexts, '$text', eventInfo, ...eventArgs ) ) {
return eventInfo.return;
}
updateEventInfo( eventInfo, 'bubbling', node );
}
while ( node ) {
// Root node handling.
if ( node.is( 'rootElement' ) ) {
if ( fireListenerFor( eventContexts, '$root', eventInfo, ...eventArgs ) ) {
return eventInfo.return;
}
}
// Element node handling.
else if ( node.is( 'element' ) ) {
if ( fireListenerFor( eventContexts, node.name, eventInfo, ...eventArgs ) ) {
return eventInfo.return;
}
}
// Check custom contexts (i.e., a widget).
if ( fireListenerFor( eventContexts, node, eventInfo, ...eventArgs ) ) {
return eventInfo.return;
}
node = node.parent;
updateEventInfo( eventInfo, 'bubbling', node );
}
updateEventInfo( eventInfo, 'bubbling', this );
// Document context.
fireListenerFor( eventContexts, '$document', eventInfo, ...eventArgs );
return eventInfo.return;
} catch ( err ) {
// @if CK_DEBUG // throw err;
/* istanbul ignore next */
_ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_1__["default"].rethrowUnexpectedError( err, this );
}
},
/**
* @inheritDoc
*/
_addEventListener( event, callback, options ) {
const contexts = (0,_ckeditor_ckeditor5_utils_src_toarray__WEBPACK_IMPORTED_MODULE_3__["default"])( options.context || '$document' );
const eventContexts = getBubblingContexts( this );
for ( const context of contexts ) {
let emitter = eventContexts.get( context );
if ( !emitter ) {
emitter = Object.create( _ckeditor_ckeditor5_utils_src_emittermixin__WEBPACK_IMPORTED_MODULE_2__["default"] );
eventContexts.set( context, emitter );
}
this.listenTo( emitter, event, callback, options );
}
},
/**
* @inheritDoc
*/
_removeEventListener( event, callback ) {
const eventContexts = getBubblingContexts( this );
for ( const emitter of eventContexts.values() ) {
this.stopListening( emitter, event, callback );
}
}
};
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (BubblingEmitterMixin);
// Update the event info bubbling fields.
//
// @param {module:utils/eventinfo~EventInfo} eventInfo The event info object to update.
// @param {'none'|'capturing'|'atTarget'|'bubbling'} eventPhase The current event phase.
// @param {module:engine/view/document~Document|module:engine/view/node~Node} currentTarget The current bubbling target.
function updateEventInfo( eventInfo, eventPhase, currentTarget ) {
if ( eventInfo instanceof _bubblingeventinfo__WEBPACK_IMPORTED_MODULE_4__["default"] ) {
eventInfo._eventPhase = eventPhase;
eventInfo._currentTarget = currentTarget;
}
}
// Fires the listener for the specified context. Returns `true` if event was stopped.
//
// @private
// @param {Map.<String|Function, module:utils/emittermixin~Emitter>} eventContexts
// @param {String|module:engine/view/node~Node} context
// @param {module:utils/eventinfo~EventInfo} eventInfo The `EventInfo` object.
// @param {...*} [eventArgs] Additional arguments to be passed to the callbacks.
// @returns {Boolean} True if event stop was called.
function fireListenerFor( eventContexts, context, eventInfo, ...eventArgs ) {
const emitter = typeof context == 'string' ? eventContexts.get( context ) : getCustomContext( eventContexts, context );
if ( !emitter ) {
return false;
}
emitter.fire( eventInfo, ...eventArgs );
return eventInfo.stop.called;
}
// Returns an emitter for a specified view node.
//
// @private
// @param {Map.<String|Function, module:utils/emittermixin~Emitter>} eventContexts
// @param {module:engine/view/node~Node} node
// @returns {module:utils/emittermixin~Emitter|null}
function getCustomContext( eventContexts, node ) {
for ( const [ context, emitter ] of eventContexts ) {
if ( typeof context == 'function' && context( node ) ) {
return emitter;
}
}
return null;
}
// Returns bubbling contexts map for the source (emitter).
function getBubblingContexts( source ) {
if ( !source[ contextsSymbol ] ) {
source[ contextsSymbol ] = new Map();
}
return source[ contextsSymbol ];
}
// Returns the deeper parent element for the range.
function getDeeperRangeParent( range ) {
if ( !range ) {
return null;
}
const startParent = range.start.parent;
const endParent = range.end.parent;
const startPath = startParent.getPath();
const endPath = endParent.getPath();
return startPath.length > endPath.length ? startParent : endParent;
}
/**
* Bubbling emitter for the view document.
*
* Bubbling emitter is triggering events in the context of specified {@link module:engine/view/element~Element view element} name,
* predefined `'$text'`, `'$root'`, `'$document'` and `'$capture'` contexts, and context matchers provided as a function.
*
* Before bubbling starts, listeners for `'$capture'` context are triggered. Then the bubbling starts from the deeper selection
* position (by firing event on the `'$text'` context) and propagates the view document tree up to the `'$root'` and finally
* the listeners at `'$document'` context are fired (this is the default context).
*
* Examples:
*
* // Listeners registered in the context of the view element names:
* this.listenTo( viewDocument, 'enter', ( evt, data ) => {
* // ...
* }, { context: 'blockquote' } );
*
* this.listenTo( viewDocument, 'enter', ( evt, data ) => {
* // ...
* }, { context: 'li' } );
*
* // Listeners registered in the context of the '$text' and '$root' nodes.
* this.listenTo( view.document, 'arrowKey', ( evt, data ) => {
* // ...
* }, { context: '$text', priority: 'high' } );
*
* this.listenTo( view.document, 'arrowKey', ( evt, data ) => {
* // ...
* }, { context: '$root' } );
*
* // Listeners registered in the context of custom callback function.
* this.listenTo( view.document, 'arrowKey', ( evt, data ) => {
* // ...
* }, { context: isWidget } );
*
* this.listenTo( view.document, 'arrowKey', ( evt, data ) => {
* // ...
* }, { context: isWidget, priority: 'high' } );
*
* Example flow for selection in text:
*
* <blockquote><p>Foo[]bar</p></blockquote>
*
* Fired events on contexts:
* 1. `'$capture'`
* 2. `'$text'`
* 3. `'p'`
* 4. `'blockquote'`
* 5. `'$root'`
* 6. `'$document'`
*
* Example flow for selection on element (i.e., Widget):
*
* <blockquote><p>Foo[<widget/>]bar</p></blockquote>
*
* Fired events on contexts:
* 1. `'$capture'`
* 2. *widget* (custom matcher)
* 3. `'p'`
* 4. `'blockquote'`
* 5. `'$root'`
* 6. `'$document'`
*
* There could be multiple listeners registered for the same context and at different priority levels:
*
* <p>Foo[]bar</p>
*
* 1. `'$capture'` at priorities:
* 1. `'highest'`
* 2. `'high'`
* 3. `'normal'`
* 4. `'low'`
* 5. `'lowest'`
* 2. `'$text'` at priorities:
* 1. `'highest'`
* 2. `'high'`
* 3. `'normal'`
* 4. `'low'`
* 5. `'lowest'`
* 3. `'p'` at priorities:
* 1. `'highest'`
* 2. `'high'`
* 3. `'normal'`
* 4. `'low'`
* 5. `'lowest'`
* 4. `'$root'` at priorities:
* 1. `'highest'`
* 2. `'high'`
* 3. `'normal'`
* 4. `'low'`
* 5. `'lowest'`
* 5. `'$document'` at priorities:
* 1. `'highest'`
* 2. `'high'`
* 3. `'normal'`
* 4. `'low'`
* 5. `'lowest'`
*
* @interface BubblingEmitter
* @extends module:utils/emittermixin~Emitter
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/bubblingeventinfo.js":
/*!****************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/bubblingeventinfo.js ***!
\****************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ BubblingEventInfo)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_utils_src_eventinfo__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/eventinfo */ "./node_modules/@ckeditor/ckeditor5-utils/src/eventinfo.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/view/observer/bubblingeventinfo
*/
/**
* The event object passed to bubbling event callbacks. It is used to provide information about the event as well as a tool to
* manipulate it.
*
* @extends module:utils/eventinfo~EventInfo
*/
class BubblingEventInfo extends _ckeditor_ckeditor5_utils_src_eventinfo__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* @param {Object} source The emitter.
* @param {String} name The event name.
* @param {module:engine/view/range~Range} startRange The view range that the bubbling should start from.
*/
constructor( source, name, startRange ) {
super( source, name );
/**
* The view range that the bubbling should start from.
*
* @readonly
* @member {module:engine/view/range~Range}
*/
this.startRange = startRange;
/**
* The current event phase.
*
* @protected
* @member {'none'|'capturing'|'atTarget'|'bubbling'}
*/
this._eventPhase = 'none';
/**
* The current bubbling target.
*
* @protected
* @member {module:engine/view/document~Document|module:engine/view/node~Node|null}
*/
this._currentTarget = null;
}
/**
* The current event phase.
*
* @readonly
* @member {'none'|'capturing'|'atTarget'|'bubbling'}
*/
get eventPhase() {
return this._eventPhase;
}
/**
* The current bubbling target.
*
* @readonly
* @member {module:engine/view/document~Document|module:engine/view/node~Node|null}
*/
get currentTarget() {
return this._currentTarget;
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/clickobserver.js":
/*!************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/clickobserver.js ***!
\************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ClickObserver)
/* harmony export */ });
/* harmony import */ var _domeventobserver__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./domeventobserver */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/domeventobserver.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/view/observer/clickobserver
*/
/**
* {@link module:engine/view/document~Document#event:click Click} event observer.
*
* Note that this observer is not available by default. To make it available it needs to be added to
* {@link module:engine/view/view~View view controller}
* by a {@link module:engine/view/view~View#addObserver} method.
*
* @extends module:engine/view/observer/domeventobserver~DomEventObserver
*/
class ClickObserver extends _domeventobserver__WEBPACK_IMPORTED_MODULE_0__["default"] {
constructor( view ) {
super( view );
this.domEventType = 'click';
}
onDomEvent( domEvent ) {
this.fire( domEvent.type, domEvent );
}
}
/**
* Fired when one of the editables has been clicked.
*
* Introduced by {@link module:engine/view/observer/clickobserver~ClickObserver}.
*
* Note that this event is not available by default. To make it available
* {@link module:engine/view/observer/clickobserver~ClickObserver} needs to be added
* to {@link module:engine/view/view~View} by a {@link module:engine/view/view~View#addObserver} method.
*
* @see module:engine/view/observer/clickobserver~ClickObserver
* @event module:engine/view/document~Document#event:click
* @param {module:engine/view/observer/domeventdata~DomEventData} data Event data.
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/compositionobserver.js":
/*!******************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/compositionobserver.js ***!
\******************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ CompositionObserver)
/* harmony export */ });
/* harmony import */ var _domeventobserver__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./domeventobserver */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/domeventobserver.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/view/observer/compositionobserver
*/
/**
* {@link module:engine/view/document~Document#event:compositionstart Compositionstart},
* {@link module:engine/view/document~Document#event:compositionupdate compositionupdate} and
* {@link module:engine/view/document~Document#event:compositionend compositionend} events observer.
*
* Note that this observer is attached by the {@link module:engine/view/view~View} and is available by default.
*
* @extends module:engine/view/observer/domeventobserver~DomEventObserver
*/
class CompositionObserver extends _domeventobserver__WEBPACK_IMPORTED_MODULE_0__["default"] {
constructor( view ) {
super( view );
this.domEventType = [ 'compositionstart', 'compositionupdate', 'compositionend' ];
const document = this.document;
document.on( 'compositionstart', () => {
document.isComposing = true;
} );
document.on( 'compositionend', () => {
document.isComposing = false;
} );
}
onDomEvent( domEvent ) {
this.fire( domEvent.type, domEvent );
}
}
/**
* Fired when composition starts inside one of the editables.
*
* Introduced by {@link module:engine/view/observer/compositionobserver~CompositionObserver}.
*
* Note that because {@link module:engine/view/observer/compositionobserver~CompositionObserver} is attached by the
* {@link module:engine/view/view~View} this event is available by default.
*
* @see module:engine/view/observer/compositionobserver~CompositionObserver
* @event module:engine/view/document~Document#event:compositionstart
* @param {module:engine/view/observer/domeventdata~DomEventData} data Event data.
*/
/**
* Fired when composition is updated inside one of the editables.
*
* Introduced by {@link module:engine/view/observer/compositionobserver~CompositionObserver}.
*
* Note that because {@link module:engine/view/observer/compositionobserver~CompositionObserver} is attached by the
* {@link module:engine/view/view~View} this event is available by default.
*
* @see module:engine/view/observer/compositionobserver~CompositionObserver
* @event module:engine/view/document~Document#event:compositionupdate
* @param {module:engine/view/observer/domeventdata~DomEventData} data Event data.
*/
/**
* Fired when composition ends inside one of the editables.
*
* Introduced by {@link module:engine/view/observer/compositionobserver~CompositionObserver}.
*
* Note that because {@link module:engine/view/observer/compositionobserver~CompositionObserver} is attached by the
* {@link module:engine/view/view~View} this event is available by default.
*
* @see module:engine/view/observer/compositionobserver~CompositionObserver
* @event module:engine/view/document~Document#event:compositionend
* @param {module:engine/view/observer/domeventdata~DomEventData} data Event data.
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/domeventdata.js":
/*!***********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/domeventdata.js ***!
\***********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ DomEventData)
/* harmony export */ });
/* harmony import */ var lodash_es__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! lodash-es */ "./node_modules/lodash-es/assignIn.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/view/observer/domeventdata
*/
/**
* Information about a DOM event in context of the {@link module:engine/view/document~Document}.
* It wraps the native event, which usually should not be used as the wrapper contains
* additional data (like key code for keyboard events).
*/
class DomEventData {
/**
* @param {module:engine/view/view~View} view The instance of the view controller.
* @param {Event} domEvent The DOM event.
* @param {Object} [additionalData] Additional properties that the instance should contain.
*/
constructor( view, domEvent, additionalData ) {
/**
* Instance of the view controller.
*
* @readonly
* @member {module:engine/view/view~View} module:engine/view/observer/observer~Observer.DomEvent#view
*/
this.view = view;
/**
* The instance of the document.
*
* @readonly
* @member {module:engine/view/document~Document} module:engine/view/observer/observer~Observer.DomEvent#document
*/
this.document = view.document;
/**
* The DOM event.
*
* @readonly
* @member {Event} module:engine/view/observer/observer~Observer.DomEvent#domEvent
*/
this.domEvent = domEvent;
/**
* The DOM target.
*
* @readonly
* @member {HTMLElement} module:engine/view/observer/observer~Observer.DomEvent#target
*/
this.domTarget = domEvent.target;
(0,lodash_es__WEBPACK_IMPORTED_MODULE_0__["default"])( this, additionalData );
}
/**
* The tree view element representing the target.
*
* @readonly
* @type module:engine/view/element~Element
*/
get target() {
return this.view.domConverter.mapDomToView( this.domTarget );
}
/**
* Prevents the native's event default action.
*/
preventDefault() {
this.domEvent.preventDefault();
}
/**
* Stops native event propagation.
*/
stopPropagation() {
this.domEvent.stopPropagation();
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/domeventobserver.js":
/*!***************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/domeventobserver.js ***!
\***************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ DomEventObserver)
/* harmony export */ });
/* harmony import */ var _observer__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./observer */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/observer.js");
/* harmony import */ var _domeventdata__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./domeventdata */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/domeventdata.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/view/observer/domeventobserver
*/
/**
* Base class for DOM event observers. This class handles
* {@link module:engine/view/observer/observer~Observer#observe adding} listeners to DOM elements,
* {@link module:engine/view/observer/observer~Observer#disable disabling} and
* {@link module:engine/view/observer/observer~Observer#enable re-enabling} events.
* Child class needs to define
* {@link module:engine/view/observer/domeventobserver~DomEventObserver#domEventType DOM event type} and
* {@link module:engine/view/observer/domeventobserver~DomEventObserver#onDomEvent callback}.
*
* For instance:
*
* class ClickObserver extends DomEventObserver {
* // It can also be defined as a normal property in the constructor.
* get domEventType() {
* return 'click';
* }
*
* onDomEvent( domEvent ) {
* this.fire( 'click', domEvent );
* }
* }
*
* @extends module:engine/view/observer/observer~Observer
*/
class DomEventObserver extends _observer__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* Type of the DOM event the observer should listen to. Array of types can be defined
* if the observer should listen to multiple DOM events.
*
* @readonly
* @member {String|Array.<String>} #domEventType
*/
/**
* Callback which should be called when the DOM event occurred. Note that the callback will not be called if
* observer {@link #isEnabled is not enabled}.
*
* @see #domEventType
* @abstract
* @method #onDomEvent
*/
/**
* @inheritDoc
*/
constructor( view ) {
super( view );
/**
* If set to `true` DOM events will be listened on the capturing phase.
* Default value is `false`.
*
* @member {Boolean}
*/
this.useCapture = false;
}
/**
* @inheritDoc
*/
observe( domElement ) {
const types = typeof this.domEventType == 'string' ? [ this.domEventType ] : this.domEventType;
types.forEach( type => {
this.listenTo( domElement, type, ( eventInfo, domEvent ) => {
if ( this.isEnabled && !this.checkShouldIgnoreEventFromTarget( domEvent.target ) ) {
this.onDomEvent( domEvent );
}
}, { useCapture: this.useCapture } );
} );
}
/**
* Calls `Document#fire()` if observer {@link #isEnabled is enabled}.
*
* @see module:utils/emittermixin~EmitterMixin#fire
* @param {String} eventType The event type (name).
* @param {Event} domEvent The DOM event.
* @param {Object} [additionalData] The additional data which should extend the
* {@link module:engine/view/observer/domeventdata~DomEventData event data} object.
*/
fire( eventType, domEvent, additionalData ) {
if ( this.isEnabled ) {
this.document.fire( eventType, new _domeventdata__WEBPACK_IMPORTED_MODULE_1__["default"]( this.view, domEvent, additionalData ) );
}
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/fakeselectionobserver.js":
/*!********************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/fakeselectionobserver.js ***!
\********************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ FakeSelectionObserver)
/* harmony export */ });
/* harmony import */ var _observer__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./observer */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/observer.js");
/* harmony import */ var _selection__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../selection */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/selection.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_keyboard__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/keyboard */ "./node_modules/@ckeditor/ckeditor5-utils/src/keyboard.js");
/* harmony import */ var lodash_es__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! lodash-es */ "./node_modules/lodash-es/debounce.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/view/observer/fakeselectionobserver
*/
/**
* Fake selection observer class. If view selection is fake it is placed in dummy DOM container. This observer listens
* on {@link module:engine/view/document~Document#event:keydown keydown} events and handles moving fake view selection to the correct place
* if arrow keys are pressed.
* Fires {@link module:engine/view/document~Document#event:selectionChange selectionChange event} simulating natural behaviour of
* {@link module:engine/view/observer/selectionobserver~SelectionObserver SelectionObserver}.
*
* @extends module:engine/view/observer/observer~Observer
*/
class FakeSelectionObserver extends _observer__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* Creates new FakeSelectionObserver instance.
*
* @param {module:engine/view/view~View} view
*/
constructor( view ) {
super( view );
/**
* Fires debounced event `selectionChangeDone`. It uses `lodash#debounce` method to delay function call.
*
* @private
* @param {Object} data Selection change data.
* @method #_fireSelectionChangeDoneDebounced
*/
this._fireSelectionChangeDoneDebounced = (0,lodash_es__WEBPACK_IMPORTED_MODULE_3__["default"])( data => this.document.fire( 'selectionChangeDone', data ), 200 );
}
/**
* @inheritDoc
*/
observe() {
const document = this.document;
document.on( 'arrowKey', ( eventInfo, data ) => {
const selection = document.selection;
if ( selection.isFake && this.isEnabled ) {
// Prevents default key down handling - no selection change will occur.
data.preventDefault();
}
}, { context: '$capture' } );
document.on( 'arrowKey', ( eventInfo, data ) => {
const selection = document.selection;
if ( selection.isFake && this.isEnabled ) {
this._handleSelectionMove( data.keyCode );
}
}, { priority: 'lowest' } );
}
/**
* @inheritDoc
*/
destroy() {
super.destroy();
this._fireSelectionChangeDoneDebounced.cancel();
}
/**
* Handles collapsing view selection according to given key code. If left or up key is provided - new selection will be
* collapsed to left. If right or down key is pressed - new selection will be collapsed to right.
*
* This method fires {@link module:engine/view/document~Document#event:selectionChange} and
* {@link module:engine/view/document~Document#event:selectionChangeDone} events imitating behaviour of
* {@link module:engine/view/observer/selectionobserver~SelectionObserver}.
*
* @private
* @param {Number} keyCode
* @fires module:engine/view/document~Document#event:selectionChange
* @fires module:engine/view/document~Document#event:selectionChangeDone
*/
_handleSelectionMove( keyCode ) {
const selection = this.document.selection;
const newSelection = new _selection__WEBPACK_IMPORTED_MODULE_1__["default"]( selection.getRanges(), { backward: selection.isBackward, fake: false } );
// Left or up arrow pressed - move selection to start.
if ( keyCode == _ckeditor_ckeditor5_utils_src_keyboard__WEBPACK_IMPORTED_MODULE_2__.keyCodes.arrowleft || keyCode == _ckeditor_ckeditor5_utils_src_keyboard__WEBPACK_IMPORTED_MODULE_2__.keyCodes.arrowup ) {
newSelection.setTo( newSelection.getFirstPosition() );
}
// Right or down arrow pressed - move selection to end.
if ( keyCode == _ckeditor_ckeditor5_utils_src_keyboard__WEBPACK_IMPORTED_MODULE_2__.keyCodes.arrowright || keyCode == _ckeditor_ckeditor5_utils_src_keyboard__WEBPACK_IMPORTED_MODULE_2__.keyCodes.arrowdown ) {
newSelection.setTo( newSelection.getLastPosition() );
}
const data = {
oldSelection: selection,
newSelection,
domSelection: null
};
// Fire dummy selection change event.
this.document.fire( 'selectionChange', data );
// Call` #_fireSelectionChangeDoneDebounced` every time when `selectionChange` event is fired.
// This function is debounced what means that `selectionChangeDone` event will be fired only when
// defined int the function time will elapse since the last time the function was called.
// So `selectionChangeDone` will be fired when selection will stop changing.
this._fireSelectionChangeDoneDebounced( data );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/focusobserver.js":
/*!************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/focusobserver.js ***!
\************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ FocusObserver)
/* harmony export */ });
/* harmony import */ var _domeventobserver__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./domeventobserver */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/domeventobserver.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/view/observer/focusobserver
*/
/* globals setTimeout, clearTimeout */
/**
* {@link module:engine/view/document~Document#event:focus Focus}
* and {@link module:engine/view/document~Document#event:blur blur} events observer.
* Focus observer handle also {@link module:engine/view/rooteditableelement~RootEditableElement#isFocused isFocused} property of the
* {@link module:engine/view/rooteditableelement~RootEditableElement root elements}.
*
* Note that this observer is attached by the {@link module:engine/view/view~View} and is available by default.
*
* @extends module:engine/view/observer/domeventobserver~DomEventObserver
*/
class FocusObserver extends _domeventobserver__WEBPACK_IMPORTED_MODULE_0__["default"] {
constructor( view ) {
super( view );
this.domEventType = [ 'focus', 'blur' ];
this.useCapture = true;
const document = this.document;
document.on( 'focus', () => {
document.isFocused = true;
// Unfortunately native `selectionchange` event is fired asynchronously.
// We need to wait until `SelectionObserver` handle the event and then render. Otherwise rendering will
// overwrite new DOM selection with selection from the view.
// See https://github.com/ckeditor/ckeditor5-engine/issues/795 for more details.
// Long timeout is needed to solve #676 and https://github.com/ckeditor/ckeditor5-engine/issues/1157 issues.
//
// Using `view.change()` instead of `view.forceRender()` to prevent double rendering
// in a situation where `selectionchange` already caused selection change.
this._renderTimeoutId = setTimeout( () => view.change( () => {} ), 50 );
} );
document.on( 'blur', ( evt, data ) => {
const selectedEditable = document.selection.editableElement;
if ( selectedEditable === null || selectedEditable === data.target ) {
document.isFocused = false;
// Re-render the document to update view elements
// (changing document.isFocused already marked view as changed since last rendering).
view.change( () => {} );
}
} );
/**
* Identifier of the timeout currently used by focus listener to delay rendering execution.
*
* @private
* @member {Number} #_renderTimeoutId
*/
}
onDomEvent( domEvent ) {
this.fire( domEvent.type, domEvent );
}
/**
* @inheritDoc
*/
destroy() {
if ( this._renderTimeoutId ) {
clearTimeout( this._renderTimeoutId );
}
super.destroy();
}
}
/**
* Fired when one of the editables gets focus.
*
* Introduced by {@link module:engine/view/observer/focusobserver~FocusObserver}.
*
* Note that because {@link module:engine/view/observer/focusobserver~FocusObserver} is attached by the
* {@link module:engine/view/view~View} this event is available by default.
*
* @see module:engine/view/observer/focusobserver~FocusObserver
* @event module:engine/view/document~Document#event:focus
* @param {module:engine/view/observer/domeventdata~DomEventData} data Event data.
*/
/**
* Fired when one of the editables loses focus.
*
* Introduced by {@link module:engine/view/observer/focusobserver~FocusObserver}.
*
* Note that because {@link module:engine/view/observer/focusobserver~FocusObserver} is attached by the
* {@link module:engine/view/view~View} this event is available by default.
*
* @see module:engine/view/observer/focusobserver~FocusObserver
* @event module:engine/view/document~Document#event:blur
* @param {module:engine/view/observer/domeventdata~DomEventData} data Event data.
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/inputobserver.js":
/*!************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/inputobserver.js ***!
\************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ InputObserver)
/* harmony export */ });
/* harmony import */ var _domeventobserver__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./domeventobserver */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/domeventobserver.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md.
*/
/**
* @module engine/view/observer/inputobserver
*/
/**
* Observer for events connected with data input.
*
* Note that this observer is attached by the {@link module:engine/view/view~View} and is available by default.
*
* @extends module:engine/view/observer/domeventobserver~DomEventObserver
*/
class InputObserver extends _domeventobserver__WEBPACK_IMPORTED_MODULE_0__["default"] {
constructor( view ) {
super( view );
this.domEventType = [ 'beforeinput' ];
}
onDomEvent( domEvent ) {
this.fire( domEvent.type, domEvent );
}
}
/**
* Fired before browser inputs (or deletes) some data.
*
* This event is available only on browsers which support DOM `beforeinput` event.
*
* Introduced by {@link module:engine/view/observer/inputobserver~InputObserver}.
*
* Note that because {@link module:engine/view/observer/inputobserver~InputObserver} is attached by the
* {@link module:engine/view/view~View} this event is available by default.
*
* @see module:engine/view/observer/inputobserver~InputObserver
* @event module:engine/view/document~Document#event:beforeinput
* @param {module:engine/view/observer/domeventdata~DomEventData} data Event data.
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/keyobserver.js":
/*!**********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/keyobserver.js ***!
\**********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ KeyObserver)
/* harmony export */ });
/* harmony import */ var _domeventobserver__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./domeventobserver */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/domeventobserver.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_keyboard__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/keyboard */ "./node_modules/@ckeditor/ckeditor5-utils/src/keyboard.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/view/observer/keyobserver
*/
/**
* Observer for events connected with pressing keyboard keys.
*
* Note that this observer is attached by the {@link module:engine/view/view~View} and is available by default.
*
* @extends module:engine/view/observer/domeventobserver~DomEventObserver
*/
class KeyObserver extends _domeventobserver__WEBPACK_IMPORTED_MODULE_0__["default"] {
constructor( view ) {
super( view );
this.domEventType = [ 'keydown', 'keyup' ];
}
onDomEvent( domEvt ) {
this.fire( domEvt.type, domEvt, {
keyCode: domEvt.keyCode,
altKey: domEvt.altKey,
ctrlKey: domEvt.ctrlKey,
shiftKey: domEvt.shiftKey,
metaKey: domEvt.metaKey,
get keystroke() {
return (0,_ckeditor_ckeditor5_utils_src_keyboard__WEBPACK_IMPORTED_MODULE_1__.getCode)( this );
}
} );
}
}
/**
* Fired when a key has been pressed.
*
* Introduced by {@link module:engine/view/observer/keyobserver~KeyObserver}.
*
* Note that because {@link module:engine/view/observer/keyobserver~KeyObserver} is attached by the
* {@link module:engine/view/view~View} this event is available by default.
*
* @see module:engine/view/observer/keyobserver~KeyObserver
* @event module:engine/view/document~Document#event:keydown
* @param {module:engine/view/observer/keyobserver~KeyEventData} keyEventData
*/
/**
* Fired when a key has been released.
*
* Introduced by {@link module:engine/view/observer/keyobserver~KeyObserver}.
*
* Note that because {@link module:engine/view/observer/keyobserver~KeyObserver} is attached by the
* {@link module:engine/view/view~View} this event is available by default.
*
* @see module:engine/view/observer/keyobserver~KeyObserver
* @event module:engine/view/document~Document#event:keyup
* @param {module:engine/view/observer/keyobserver~KeyEventData} keyEventData
*/
/**
* The value of both events - {@link module:engine/view/document~Document#event:keydown} and
* {@link module:engine/view/document~Document#event:keyup}.
*
* @class module:engine/view/observer/keyobserver~KeyEventData
* @extends module:engine/view/observer/domeventdata~DomEventData
* @implements module:utils/keyboard~KeystrokeInfo
*/
/**
* Code of the whole keystroke. See {@link module:utils/keyboard~getCode}.
*
* @readonly
* @member {Number} module:engine/view/observer/keyobserver~KeyEventData#keystroke
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/mouseobserver.js":
/*!************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/mouseobserver.js ***!
\************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ MouseObserver)
/* harmony export */ });
/* harmony import */ var _domeventobserver__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./domeventobserver */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/domeventobserver.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/view/observer/mouseobserver
*/
/**
* Mouse events observer.
*
* Note that this observer is not available by default. To make it available it needs to be added to
* {@link module:engine/view/view~View} by {@link module:engine/view/view~View#addObserver} method.
*
* @extends module:engine/view/observer/domeventobserver~DomEventObserver
*/
class MouseObserver extends _domeventobserver__WEBPACK_IMPORTED_MODULE_0__["default"] {
constructor( view ) {
super( view );
this.domEventType = [ 'mousedown', 'mouseup', 'mouseover', 'mouseout' ];
}
onDomEvent( domEvent ) {
this.fire( domEvent.type, domEvent );
}
}
/**
* Fired when the mouse button is pressed down on one of the editing roots of the editor.
*
* Introduced by {@link module:engine/view/observer/mouseobserver~MouseObserver}.
*
* Note that this event is not available by default. To make it available, {@link module:engine/view/observer/mouseobserver~MouseObserver}
* needs to be added to {@link module:engine/view/view~View} by the {@link module:engine/view/view~View#addObserver} method.
*
* @see module:engine/view/observer/mouseobserver~MouseObserver
* @event module:engine/view/document~Document#event:mousedown
* @param {module:engine/view/observer/domeventdata~DomEventData} data The event data.
*/
/**
* Fired when the mouse button is released over one of the editing roots of the editor.
*
* Introduced by {@link module:engine/view/observer/mouseobserver~MouseObserver}.
*
* Note that this event is not available by default. To make it available, {@link module:engine/view/observer/mouseobserver~MouseObserver}
* needs to be added to {@link module:engine/view/view~View} by the {@link module:engine/view/view~View#addObserver} method.
*
* @see module:engine/view/observer/mouseobserver~MouseObserver
* @event module:engine/view/document~Document#event:mouseup
* @param {module:engine/view/observer/domeventdata~DomEventData} data The event data.
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/mutationobserver.js":
/*!***************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/mutationobserver.js ***!
\***************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ MutationObserver)
/* harmony export */ });
/* harmony import */ var _observer__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./observer */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/observer.js");
/* harmony import */ var _selection__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../selection */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/selection.js");
/* harmony import */ var _filler__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../filler */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/filler.js");
/* harmony import */ var lodash_es__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! lodash-es */ "./node_modules/lodash-es/isEqualWith.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/view/observer/mutationobserver
*/
/* globals window */
/**
* Mutation observer class observes changes in the DOM, fires {@link module:engine/view/document~Document#event:mutations} event, mark view
* elements as changed and call {@link module:engine/view/renderer~Renderer#render}.
* Because all mutated nodes are marked as "to be rendered" and the
* {@link module:engine/view/renderer~Renderer#render} is called, all changes will be reverted, unless the mutation will be handled by the
* {@link module:engine/view/document~Document#event:mutations} event listener. It means user will see only handled changes, and the editor
* will block all changes which are not handled.
*
* Mutation Observer also take care of reducing number of mutations which are fired. It removes duplicates and
* mutations on elements which do not have corresponding view elements. Also
* {@link module:engine/view/observer/mutationobserver~MutatedText text mutation} is fired only if parent element do not change child list.
*
* Note that this observer is attached by the {@link module:engine/view/view~View} and is available by default.
*
* @extends module:engine/view/observer/observer~Observer
*/
class MutationObserver extends _observer__WEBPACK_IMPORTED_MODULE_0__["default"] {
constructor( view ) {
super( view );
/**
* Native mutation observer config.
*
* @private
* @member {Object}
*/
this._config = {
childList: true,
characterData: true,
characterDataOldValue: true,
subtree: true
};
/**
* Reference to the {@link module:engine/view/view~View#domConverter}.
*
* @member {module:engine/view/domconverter~DomConverter}
*/
this.domConverter = view.domConverter;
/**
* Reference to the {@link module:engine/view/view~View#_renderer}.
*
* @member {module:engine/view/renderer~Renderer}
*/
this.renderer = view._renderer;
/**
* Observed DOM elements.
*
* @private
* @member {Array.<HTMLElement>}
*/
this._domElements = [];
/**
* Native mutation observer.
*
* @private
* @member {MutationObserver}
*/
this._mutationObserver = new window.MutationObserver( this._onMutations.bind( this ) );
}
/**
* Synchronously fires {@link module:engine/view/document~Document#event:mutations} event with all mutations in record queue.
* At the same time empties the queue so mutations will not be fired twice.
*/
flush() {
this._onMutations( this._mutationObserver.takeRecords() );
}
/**
* @inheritDoc
*/
observe( domElement ) {
this._domElements.push( domElement );
if ( this.isEnabled ) {
this._mutationObserver.observe( domElement, this._config );
}
}
/**
* @inheritDoc
*/
enable() {
super.enable();
for ( const domElement of this._domElements ) {
this._mutationObserver.observe( domElement, this._config );
}
}
/**
* @inheritDoc
*/
disable() {
super.disable();
this._mutationObserver.disconnect();
}
/**
* @inheritDoc
*/
destroy() {
super.destroy();
this._mutationObserver.disconnect();
}
/**
* Handles mutations. Deduplicates, mark view elements to sync, fire event and call render.
*
* @private
* @param {Array.<Object>} domMutations Array of native mutations.
*/
_onMutations( domMutations ) {
// As a result of this.flush() we can have an empty collection.
if ( domMutations.length === 0 ) {
return;
}
const domConverter = this.domConverter;
// Use map and set for deduplication.
const mutatedTexts = new Map();
const mutatedElements = new Set();
// Handle `childList` mutations first, so we will be able to check if the `characterData` mutation is in the
// element with changed structure anyway.
for ( const mutation of domMutations ) {
if ( mutation.type === 'childList' ) {
const element = domConverter.mapDomToView( mutation.target );
// Do not collect mutations from UIElements and RawElements.
if ( element && ( element.is( 'uiElement' ) || element.is( 'rawElement' ) ) ) {
continue;
}
if ( element && !this._isBogusBrMutation( mutation ) ) {
mutatedElements.add( element );
}
}
}
// Handle `characterData` mutations later, when we have the full list of nodes which changed structure.
for ( const mutation of domMutations ) {
const element = domConverter.mapDomToView( mutation.target );
// Do not collect mutations from UIElements and RawElements.
if ( element && ( element.is( 'uiElement' ) || element.is( 'rawElement' ) ) ) {
continue;
}
if ( mutation.type === 'characterData' ) {
const text = domConverter.findCorrespondingViewText( mutation.target );
if ( text && !mutatedElements.has( text.parent ) ) {
// Use text as a key, for deduplication. If there will be another mutation on the same text element
// we will have only one in the map.
mutatedTexts.set( text, {
type: 'text',
oldText: text.data,
newText: (0,_filler__WEBPACK_IMPORTED_MODULE_2__.getDataWithoutFiller)( mutation.target ),
node: text
} );
}
// When we added first letter to the text node which had only inline filler, for the DOM it is mutation
// on text, but for the view, where filler text node did not existed, new text node was created, so we
// need to fire 'children' mutation instead of 'text'.
else if ( !text && (0,_filler__WEBPACK_IMPORTED_MODULE_2__.startsWithFiller)( mutation.target ) ) {
mutatedElements.add( domConverter.mapDomToView( mutation.target.parentNode ) );
}
}
}
// Now we build the list of mutations to fire and mark elements. We did not do it earlier to avoid marking the
// same node multiple times in case of duplication.
// List of mutations we will fire.
const viewMutations = [];
for ( const mutatedText of mutatedTexts.values() ) {
this.renderer.markToSync( 'text', mutatedText.node );
viewMutations.push( mutatedText );
}
for ( const viewElement of mutatedElements ) {
const domElement = domConverter.mapViewToDom( viewElement );
const viewChildren = Array.from( viewElement.getChildren() );
const newViewChildren = Array.from( domConverter.domChildrenToView( domElement, { withChildren: false } ) );
// It may happen that as a result of many changes (sth was inserted and then removed),
// both elements haven't really changed. #1031
if ( !(0,lodash_es__WEBPACK_IMPORTED_MODULE_3__["default"])( viewChildren, newViewChildren, sameNodes ) ) {
this.renderer.markToSync( 'children', viewElement );
viewMutations.push( {
type: 'children',
oldChildren: viewChildren,
newChildren: newViewChildren,
node: viewElement
} );
}
}
// Retrieve `domSelection` using `ownerDocument` of one of mutated nodes.
// There should not be simultaneous mutation in multiple documents, so it's fine.
const domSelection = domMutations[ 0 ].target.ownerDocument.getSelection();
let viewSelection = null;
if ( domSelection && domSelection.anchorNode ) {
// If `domSelection` is inside a dom node that is already bound to a view node from view tree, get
// corresponding selection in the view and pass it together with `viewMutations`. The `viewSelection` may
// be used by features handling mutations.
// Only one range is supported.
const viewSelectionAnchor = domConverter.domPositionToView( domSelection.anchorNode, domSelection.anchorOffset );
const viewSelectionFocus = domConverter.domPositionToView( domSelection.focusNode, domSelection.focusOffset );
// Anchor and focus has to be properly mapped to view.
if ( viewSelectionAnchor && viewSelectionFocus ) {
viewSelection = new _selection__WEBPACK_IMPORTED_MODULE_1__["default"]( viewSelectionAnchor );
viewSelection.setFocus( viewSelectionFocus );
}
}
// In case only non-relevant mutations were recorded it skips the event and force render (#5600).
if ( viewMutations.length ) {
this.document.fire( 'mutations', viewMutations, viewSelection );
// If nothing changes on `mutations` event, at this point we have "dirty DOM" (changed) and de-synched
// view (which has not been changed). In order to "reset DOM" we render the view again.
this.view.forceRender();
}
function sameNodes( child1, child2 ) {
// First level of comparison (array of children vs array of children) – use the Lodash's default behavior.
if ( Array.isArray( child1 ) ) {
return;
}
// Elements.
if ( child1 === child2 ) {
return true;
}
// Texts.
else if ( child1.is( '$text' ) && child2.is( '$text' ) ) {
return child1.data === child2.data;
}
// Not matching types.
return false;
}
}
/**
* Checks if mutation was generated by the browser inserting bogus br on the end of the block element.
* Such mutations are generated while pressing space or performing native spellchecker correction
* on the end of the block element in Firefox browser.
*
* @private
* @param {Object} mutation Native mutation object.
* @returns {Boolean}
*/
_isBogusBrMutation( mutation ) {
let addedNode = null;
// Check if mutation added only one node on the end of its parent.
if ( mutation.nextSibling === null && mutation.removedNodes.length === 0 && mutation.addedNodes.length == 1 ) {
addedNode = this.domConverter.domToView( mutation.addedNodes[ 0 ], {
withChildren: false
} );
}
return addedNode && addedNode.is( 'element', 'br' );
}
}
/**
* Fired when mutation occurred. If tree view is not changed on this event, DOM will be reverted to the state before
* mutation, so all changes which should be applied, should be handled on this event.
*
* Introduced by {@link module:engine/view/observer/mutationobserver~MutationObserver}.
*
* Note that because {@link module:engine/view/observer/mutationobserver~MutationObserver} is attached by the
* {@link module:engine/view/view~View} this event is available by default.
*
* @see module:engine/view/observer/mutationobserver~MutationObserver
* @event module:engine/view/document~Document#event:mutations
* @param {Array.<module:engine/view/observer/mutationobserver~MutatedText|module:engine/view/observer/mutationobserver~MutatedChildren>}
* viewMutations Array of mutations.
* For mutated texts it will be {@link module:engine/view/observer/mutationobserver~MutatedText} and for mutated elements it will be
* {@link module:engine/view/observer/mutationobserver~MutatedChildren}. You can recognize the type based on the `type` property.
* @param {module:engine/view/selection~Selection|null} viewSelection View selection that is a result of converting DOM selection to view.
* Keep in
* mind that the DOM selection is already "updated", meaning that it already acknowledges changes done in mutation.
*/
/**
* Mutation item for text.
*
* @see module:engine/view/document~Document#event:mutations
* @see module:engine/view/observer/mutationobserver~MutatedChildren
*
* @typedef {Object} module:engine/view/observer/mutationobserver~MutatedText
*
* @property {String} type For text mutations it is always 'text'.
* @property {module:engine/view/text~Text} node Mutated text node.
* @property {String} oldText Old text.
* @property {String} newText New text.
*/
/**
* Mutation item for child nodes.
*
* @see module:engine/view/document~Document#event:mutations
* @see module:engine/view/observer/mutationobserver~MutatedText
*
* @typedef {Object} module:engine/view/observer/mutationobserver~MutatedChildren
*
* @property {String} type For child nodes mutations it is always 'children'.
* @property {module:engine/view/element~Element} node Parent of the mutated children.
* @property {Array.<module:engine/view/node~Node>} oldChildren Old child nodes.
* @property {Array.<module:engine/view/node~Node>} newChildren New child nodes.
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/observer.js":
/*!*******************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/observer.js ***!
\*******************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ Observer)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_utils_src_dom_emittermixin__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/dom/emittermixin */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/emittermixin.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/mix */ "./node_modules/@ckeditor/ckeditor5-utils/src/mix.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/view/observer/observer
*/
/**
* Abstract base observer class. Observers are classes which listen to DOM events, do the preliminary
* processing and fire events on the {@link module:engine/view/document~Document} objects.
* Observers can also add features to the view, for instance by updating its status or marking elements
* which need a refresh on DOM events.
*
* @abstract
*/
class Observer {
/**
* Creates an instance of the observer.
*
* @param {module:engine/view/view~View} view
*/
constructor( view ) {
/**
* An instance of the view controller.
*
* @readonly
* @member {module:engine/view/view~View}
*/
this.view = view;
/**
* A reference to the {@link module:engine/view/document~Document} object.
*
* @readonly
* @member {module:engine/view/document~Document}
*/
this.document = view.document;
/**
* The state of the observer. If it is disabled, no events will be fired.
*
* @readonly
* @member {Boolean}
*/
this.isEnabled = false;
}
/**
* Enables the observer. This method is called when the observer is registered to the
* {@link module:engine/view/view~View} and after {@link module:engine/view/view~View#forceRender rendering}
* (all observers are {@link #disable disabled} before rendering).
*
* A typical use case for disabling observers is that mutation observers need to be disabled for the rendering.
* However, a child class may not need to be disabled, so it can implement an empty method.
*
* @see module:engine/view/observer/observer~Observer#disable
*/
enable() {
this.isEnabled = true;
}
/**
* Disables the observer. This method is called before
* {@link module:engine/view/view~View#forceRender rendering} to prevent firing events during rendering.
*
* @see module:engine/view/observer/observer~Observer#enable
*/
disable() {
this.isEnabled = false;
}
/**
* Disables and destroys the observer, among others removes event listeners created by the observer.
*/
destroy() {
this.disable();
this.stopListening();
}
/**
* Checks whether a given DOM event should be ignored (should not be turned into a synthetic view document event).
*
* Currently, an event will be ignored only if its target or any of its ancestors has the `data-cke-ignore-events` attribute.
* This attribute can be used inside the structures generated by
* {@link module:engine/view/downcastwriter~DowncastWriter#createUIElement `DowncastWriter#createUIElement()`} to ignore events
* fired within a UI that should be excluded from CKEditor 5's realms.
*
* @param {Node} domTarget The DOM event target to check (usually an element, sometimes a text node and
* potentially sometimes a document, too).
* @returns {Boolean} Whether this event should be ignored by the observer.
*/
checkShouldIgnoreEventFromTarget( domTarget ) {
if ( domTarget && domTarget.nodeType === 3 ) {
domTarget = domTarget.parentNode;
}
if ( !domTarget || domTarget.nodeType !== 1 ) {
return false;
}
return domTarget.matches( '[data-cke-ignore-events], [data-cke-ignore-events] *' );
}
/**
* Starts observing the given root element.
*
* @method #observe
* @param {HTMLElement} domElement
* @param {String} name The name of the root element.
*/
}
(0,_ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_1__["default"])( Observer, _ckeditor_ckeditor5_utils_src_dom_emittermixin__WEBPACK_IMPORTED_MODULE_0__["default"] );
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/selectionobserver.js":
/*!****************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/selectionobserver.js ***!
\****************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ SelectionObserver)
/* harmony export */ });
/* harmony import */ var _observer__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./observer */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/observer.js");
/* harmony import */ var _mutationobserver__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./mutationobserver */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/mutationobserver.js");
/* harmony import */ var lodash_es__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! lodash-es */ "./node_modules/lodash-es/debounce.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/view/observer/selectionobserver
*/
/* global setInterval, clearInterval */
/**
* Selection observer class observes selection changes in the document. If a selection changes on the document this
* observer checks if there are any mutations and if the DOM selection is different from the
* {@link module:engine/view/document~Document#selection view selection}. The selection observer fires
* {@link module:engine/view/document~Document#event:selectionChange} event only if a selection change was the only change in the document
* and the DOM selection is different then the view selection.
*
* This observer also manages the {@link module:engine/view/document~Document#isSelecting} property of the view document.
*
* Note that this observer is attached by the {@link module:engine/view/view~View} and is available by default.
*
* @see module:engine/view/observer/mutationobserver~MutationObserver
* @extends module:engine/view/observer/observer~Observer
*/
class SelectionObserver extends _observer__WEBPACK_IMPORTED_MODULE_0__["default"] {
constructor( view ) {
super( view );
/**
* Instance of the mutation observer. Selection observer calls
* {@link module:engine/view/observer/mutationobserver~MutationObserver#flush} to ensure that the mutations will be handled
* before the {@link module:engine/view/document~Document#event:selectionChange} event is fired.
*
* @readonly
* @member {module:engine/view/observer/mutationobserver~MutationObserver}
* module:engine/view/observer/selectionobserver~SelectionObserver#mutationObserver
*/
this.mutationObserver = view.getObserver( _mutationobserver__WEBPACK_IMPORTED_MODULE_1__["default"] );
/**
* Reference to the view {@link module:engine/view/documentselection~DocumentSelection} object used to compare
* new selection with it.
*
* @readonly
* @member {module:engine/view/documentselection~DocumentSelection}
* module:engine/view/observer/selectionobserver~SelectionObserver#selection
*/
this.selection = this.document.selection;
/* eslint-disable max-len */
/**
* Reference to the {@link module:engine/view/view~View#domConverter}.
*
* @readonly
* @member {module:engine/view/domconverter~DomConverter} module:engine/view/observer/selectionobserver~SelectionObserver#domConverter
*/
/* eslint-enable max-len */
this.domConverter = view.domConverter;
/**
* A set of documents which have added `selectionchange` listener to avoid adding a listener twice to the same
* document.
*
* @private
* @member {WeakSet.<Document>} module:engine/view/observer/selectionobserver~SelectionObserver#_documents
*/
this._documents = new WeakSet();
/**
* Fires debounced event `selectionChangeDone`. It uses `lodash#debounce` method to delay function call.
*
* @private
* @param {Object} data Selection change data.
* @method #_fireSelectionChangeDoneDebounced
*/
this._fireSelectionChangeDoneDebounced = (0,lodash_es__WEBPACK_IMPORTED_MODULE_2__["default"])( data => this.document.fire( 'selectionChangeDone', data ), 200 );
/**
* When called, starts clearing the {@link #_loopbackCounter} counter in time intervals. When the number of selection
* changes exceeds a certain limit within the interval of time, the observer will not fire `selectionChange` but warn about
* possible infinite selection loop.
*
* @private
* @member {Number} #_clearInfiniteLoopInterval
*/
this._clearInfiniteLoopInterval = setInterval( () => this._clearInfiniteLoop(), 1000 );
/**
* Unlocks the `isSelecting` state of the view document in case the selection observer did not record this fact
* correctly (for whatever reason). It is a safeguard (paranoid check), that returns document to the normal state
* after a certain period of time (debounced, postponed by each selectionchange event).
*
* @private
* @method #_documentIsSelectingInactivityTimeoutDebounced
*/
this._documentIsSelectingInactivityTimeoutDebounced = (0,lodash_es__WEBPACK_IMPORTED_MODULE_2__["default"])( () => ( this.document.isSelecting = false ), 5000 );
/**
* Private property to check if the code does not enter infinite loop.
*
* @private
* @member {Number} module:engine/view/observer/selectionobserver~SelectionObserver#_loopbackCounter
*/
this._loopbackCounter = 0;
}
/**
* @inheritDoc
*/
observe( domElement ) {
const domDocument = domElement.ownerDocument;
const startDocumentIsSelecting = () => {
this.document.isSelecting = true;
// Let's activate the safety timeout each time the document enters the "is selecting" state.
this._documentIsSelectingInactivityTimeoutDebounced();
};
const endDocumentIsSelecting = () => {
this.document.isSelecting = false;
// The safety timeout can be canceled when the document leaves the "is selecting" state.
this._documentIsSelectingInactivityTimeoutDebounced.cancel();
};
// The document has the "is selecting" state while the user keeps making (extending) the selection
// (e.g. by holding the mouse button and moving the cursor). The state resets when they either released
// the mouse button or interrupted the process by pressing or releasing any key.
this.listenTo( domElement, 'selectstart', startDocumentIsSelecting, { priority: 'highest' } );
this.listenTo( domElement, 'keydown', endDocumentIsSelecting, { priority: 'highest' } );
this.listenTo( domElement, 'keyup', endDocumentIsSelecting, { priority: 'highest' } );
// Add document-wide listeners only once. This method could be called for multiple editing roots.
if ( this._documents.has( domDocument ) ) {
return;
}
this.listenTo( domDocument, 'mouseup', endDocumentIsSelecting, { priority: 'highest' } );
this.listenTo( domDocument, 'selectionchange', ( evt, domEvent ) => {
this._handleSelectionChange( domEvent, domDocument );
// Defer the safety timeout when the selection changes (e.g. the user keeps extending the selection
// using their mouse).
this._documentIsSelectingInactivityTimeoutDebounced();
} );
this._documents.add( domDocument );
}
/**
* @inheritDoc
*/
destroy() {
super.destroy();
clearInterval( this._clearInfiniteLoopInterval );
this._fireSelectionChangeDoneDebounced.cancel();
this._documentIsSelectingInactivityTimeoutDebounced.cancel();
}
/**
* Selection change listener. {@link module:engine/view/observer/mutationobserver~MutationObserver#flush Flush} mutations, check if
* a selection changes and fires {@link module:engine/view/document~Document#event:selectionChange} event on every change
* and {@link module:engine/view/document~Document#event:selectionChangeDone} when a selection stop changing.
*
* @private
* @param {Event} domEvent DOM event.
* @param {Document} domDocument DOM document.
*/
_handleSelectionChange( domEvent, domDocument ) {
if ( !this.isEnabled ) {
return;
}
const domSelection = domDocument.defaultView.getSelection();
if ( this.checkShouldIgnoreEventFromTarget( domSelection.anchorNode ) ) {
return;
}
// Ensure the mutation event will be before selection event on all browsers.
this.mutationObserver.flush();
// If there were mutations then the view will be re-rendered by the mutation observer and the selection
// will be updated, so the selections will equal and the event will not be fired, as expected.
const newViewSelection = this.domConverter.domSelectionToView( domSelection );
// Do not convert selection change if the new view selection has no ranges in it.
//
// It means that the DOM selection is in some way incorrect. Ranges that were in the DOM selection could not be
// converted to the view. This happens when the DOM selection was moved outside of the editable element.
if ( newViewSelection.rangeCount == 0 ) {
this.view.hasDomSelection = false;
return;
}
this.view.hasDomSelection = true;
if ( this.selection.isEqual( newViewSelection ) && this.domConverter.isDomSelectionCorrect( domSelection ) ) {
return;
}
// Ensure we are not in the infinite loop (#400).
// This counter is reset each second. 60 selection changes in 1 second is enough high number
// to be very difficult (impossible) to achieve using just keyboard keys (during normal editor use).
if ( ++this._loopbackCounter > 60 ) {
// Selection change observer detected an infinite rendering loop.
// Most probably you try to put the selection in the position which is not allowed
// by the browser and browser fixes it automatically what causes `selectionchange` event on
// which a loopback through a model tries to re-render the wrong selection and again.
//
// @if CK_DEBUG // console.warn( 'Selection change observer detected an infinite rendering loop.' );
return;
}
if ( this.selection.isSimilar( newViewSelection ) ) {
// If selection was equal and we are at this point of algorithm, it means that it was incorrect.
// Just re-render it, no need to fire any events, etc.
this.view.forceRender();
} else {
const data = {
oldSelection: this.selection,
newSelection: newViewSelection,
domSelection
};
// Prepare data for new selection and fire appropriate events.
this.document.fire( 'selectionChange', data );
// Call `#_fireSelectionChangeDoneDebounced` every time when `selectionChange` event is fired.
// This function is debounced what means that `selectionChangeDone` event will be fired only when
// defined int the function time will elapse since the last time the function was called.
// So `selectionChangeDone` will be fired when selection will stop changing.
this._fireSelectionChangeDoneDebounced( data );
}
}
/**
* Clears `SelectionObserver` internal properties connected with preventing infinite loop.
*
* @protected
*/
_clearInfiniteLoop() {
this._loopbackCounter = 0;
}
}
/**
* Fired when a selection has changed. This event is fired only when the selection change was the only change that happened
* in the document, and the old selection is different then the new selection.
*
* Introduced by {@link module:engine/view/observer/selectionobserver~SelectionObserver}.
*
* Note that because {@link module:engine/view/observer/selectionobserver~SelectionObserver} is attached by the
* {@link module:engine/view/view~View} this event is available by default.
*
* @see module:engine/view/observer/selectionobserver~SelectionObserver
* @event module:engine/view/document~Document#event:selectionChange
* @param {Object} data
* @param {module:engine/view/documentselection~DocumentSelection} data.oldSelection Old View selection which is
* {@link module:engine/view/document~Document#selection}.
* @param {module:engine/view/selection~Selection} data.newSelection New View selection which is converted DOM selection.
* @param {Selection} data.domSelection Native DOM selection.
*/
/**
* Fired when selection stops changing.
*
* Introduced by {@link module:engine/view/observer/selectionobserver~SelectionObserver}.
*
* Note that because {@link module:engine/view/observer/selectionobserver~SelectionObserver} is attached by the
* {@link module:engine/view/view~View} this event is available by default.
*
* @see module:engine/view/observer/selectionobserver~SelectionObserver
* @event module:engine/view/document~Document#event:selectionChangeDone
* @param {Object} data
* @param {module:engine/view/documentselection~DocumentSelection} data.oldSelection Old View selection which is
* {@link module:engine/view/document~Document#selection}.
* @param {module:engine/view/selection~Selection} data.newSelection New View selection which is converted DOM selection.
* @param {Selection} data.domSelection Native DOM selection.
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/view/placeholder.js":
/*!*************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/view/placeholder.js ***!
\*************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "disablePlaceholder": () => (/* binding */ disablePlaceholder),
/* harmony export */ "enablePlaceholder": () => (/* binding */ enablePlaceholder),
/* harmony export */ "hidePlaceholder": () => (/* binding */ hidePlaceholder),
/* harmony export */ "needsPlaceholder": () => (/* binding */ needsPlaceholder),
/* harmony export */ "showPlaceholder": () => (/* binding */ showPlaceholder)
/* harmony export */ });
/* harmony import */ var _theme_placeholder_css__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../../theme/placeholder.css */ "./node_modules/@ckeditor/ckeditor5-engine/theme/placeholder.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/view/placeholder
*/
// Each document stores information about its placeholder elements and check functions.
const documentPlaceholders = new WeakMap();
/**
* A helper that enables a placeholder on the provided view element (also updates its visibility).
* The placeholder is a CSS pseudo–element (with a text content) attached to the element.
*
* To change the placeholder text, simply call this method again with new options.
*
* To disable the placeholder, use {@link module:engine/view/placeholder~disablePlaceholder `disablePlaceholder()`} helper.
*
* @param {Object} [options] Configuration options of the placeholder.
* @param {module:engine/view/view~View} options.view Editing view instance.
* @param {module:engine/view/element~Element} options.element Element that will gain a placeholder.
* See `options.isDirectHost` to learn more.
* @param {String} options.text Placeholder text.
* @param {Boolean} [options.isDirectHost=true] If set `false`, the placeholder will not be enabled directly
* in the passed `element` but in one of its children (selected automatically, i.e. a first empty child element).
* Useful when attaching placeholders to elements that can host other elements (not just text), for instance,
* editable root elements.
* @param {Boolean} [options.keepOnFocus=false] If set `true`, the placeholder stay visible when the host element is focused.
*/
function enablePlaceholder( options ) {
const { view, element, text, isDirectHost = true, keepOnFocus = false } = options;
const doc = view.document;
// Use a single a single post fixer per—document to update all placeholders.
if ( !documentPlaceholders.has( doc ) ) {
documentPlaceholders.set( doc, new Map() );
// If a post-fixer callback makes a change, it should return `true` so other post–fixers
// can re–evaluate the document again.
doc.registerPostFixer( writer => updateDocumentPlaceholders( doc, writer ) );
}
// Store information about the element placeholder under its document.
documentPlaceholders.get( doc ).set( element, {
text,
isDirectHost,
keepOnFocus,
hostElement: isDirectHost ? element : null
} );
// Update the placeholders right away.
view.change( writer => updateDocumentPlaceholders( doc, writer ) );
}
/**
* Disables the placeholder functionality from a given element.
*
* See {@link module:engine/view/placeholder~enablePlaceholder `enablePlaceholder()`} to learn more.
*
* @param {module:engine/view/view~View} view
* @param {module:engine/view/element~Element} element
*/
function disablePlaceholder( view, element ) {
const doc = element.document;
view.change( writer => {
if ( !documentPlaceholders.has( doc ) ) {
return;
}
const placeholders = documentPlaceholders.get( doc );
const config = placeholders.get( element );
writer.removeAttribute( 'data-placeholder', config.hostElement );
hidePlaceholder( writer, config.hostElement );
placeholders.delete( element );
} );
}
/**
* Shows a placeholder in the provided element by changing related attributes and CSS classes.
*
* **Note**: This helper will not update the placeholder visibility nor manage the
* it in any way in the future. What it does is a one–time state change of an element. Use
* {@link module:engine/view/placeholder~enablePlaceholder `enablePlaceholder()`} and
* {@link module:engine/view/placeholder~disablePlaceholder `disablePlaceholder()`} for full
* placeholder functionality.
*
* **Note**: This helper will blindly show the placeholder directly in the root editable element if
* one is passed, which could result in a visual clash if the editable element has some children
* (for instance, an empty paragraph). Use {@link module:engine/view/placeholder~enablePlaceholder `enablePlaceholder()`}
* in that case or make sure the correct element is passed to the helper.
*
* @param {module:engine/view/downcastwriter~DowncastWriter} writer
* @param {module:engine/view/element~Element} element
* @returns {Boolean} `true`, if any changes were made to the `element`.
*/
function showPlaceholder( writer, element ) {
if ( !element.hasClass( 'ck-placeholder' ) ) {
writer.addClass( 'ck-placeholder', element );
return true;
}
return false;
}
/**
* Hides a placeholder in the element by changing related attributes and CSS classes.
*
* **Note**: This helper will not update the placeholder visibility nor manage the
* it in any way in the future. What it does is a one–time state change of an element. Use
* {@link module:engine/view/placeholder~enablePlaceholder `enablePlaceholder()`} and
* {@link module:engine/view/placeholder~disablePlaceholder `disablePlaceholder()`} for full
* placeholder functionality.
*
* @param {module:engine/view/downcastwriter~DowncastWriter} writer
* @param {module:engine/view/element~Element} element
* @returns {Boolean} `true`, if any changes were made to the `element`.
*/
function hidePlaceholder( writer, element ) {
if ( element.hasClass( 'ck-placeholder' ) ) {
writer.removeClass( 'ck-placeholder', element );
return true;
}
return false;
}
/**
* Checks if a placeholder should be displayed in the element.
*
* **Note**: This helper will blindly check the possibility of showing a placeholder directly in the
* root editable element if one is passed, which may not be the expected result. If an element can
* host other elements (not just text), most likely one of its children should be checked instead
* because it will be the final host for the placeholder. Use
* {@link module:engine/view/placeholder~enablePlaceholder `enablePlaceholder()`} in that case or make
* sure the correct element is passed to the helper.
*
* @param {module:engine/view/element~Element} element Element that holds the placeholder.
* @param {Boolean} keepOnFocus Focusing the element will keep the placeholder visible.
* @returns {Boolean}
*/
function needsPlaceholder( element, keepOnFocus ) {
if ( !element.isAttached() ) {
return false;
}
// Anything but uiElement(s) counts as content.
const hasContent = Array.from( element.getChildren() )
.some( element => !element.is( 'uiElement' ) );
if ( hasContent ) {
return false;
}
// Skip the focus check and make the placeholder visible already regardless of document focus state.
if ( keepOnFocus ) {
return true;
}
const doc = element.document;
// If the document is blurred.
if ( !doc.isFocused ) {
return true;
}
const viewSelection = doc.selection;
const selectionAnchor = viewSelection.anchor;
// If document is focused and the element is empty but the selection is not anchored inside it.
return selectionAnchor && selectionAnchor.parent !== element;
}
// Updates all placeholders associated with a document in a post–fixer callback.
//
// @private
// @param { module:engine/view/document~Document} doc
// @param {module:engine/view/downcastwriter~DowncastWriter} writer
// @returns {Boolean} True if any changes were made to the view document.
function updateDocumentPlaceholders( doc, writer ) {
const placeholders = documentPlaceholders.get( doc );
const directHostElements = [];
let wasViewModified = false;
// First set placeholders on the direct hosts.
for ( const [ element, config ] of placeholders ) {
if ( config.isDirectHost ) {
directHostElements.push( element );
if ( updatePlaceholder( writer, element, config ) ) {
wasViewModified = true;
}
}
}
// Then set placeholders on the indirect hosts but only on those that does not already have an direct host placeholder.
for ( const [ element, config ] of placeholders ) {
if ( config.isDirectHost ) {
continue;
}
const hostElement = getChildPlaceholderHostSubstitute( element );
// When not a direct host, it could happen that there is no child element
// capable of displaying a placeholder.
if ( !hostElement ) {
continue;
}
// Don't override placeholder if the host element already has some direct placeholder.
if ( directHostElements.includes( hostElement ) ) {
continue;
}
// Update the host element (used for setting and removing the placeholder).
config.hostElement = hostElement;
if ( updatePlaceholder( writer, element, config ) ) {
wasViewModified = true;
}
}
return wasViewModified;
}
// Updates a single placeholder in a post–fixer callback.
//
// @private
// @param {module:engine/view/downcastwriter~DowncastWriter} writer
// @param {module:engine/view/element~Element} element
// @param {Object} config Configuration of the placeholder
// @param {String} config.text
// @param {Boolean} config.isDirectHost
// @returns {Boolean} True if any changes were made to the view document.
function updatePlaceholder( writer, element, config ) {
const { text, isDirectHost, hostElement } = config;
let wasViewModified = false;
// This may be necessary when updating the placeholder text to something else.
if ( hostElement.getAttribute( 'data-placeholder' ) !== text ) {
writer.setAttribute( 'data-placeholder', text, hostElement );
wasViewModified = true;
}
// If the host element is not a direct host then placeholder is needed only when there is only one element.
const isOnlyChild = isDirectHost || element.childCount == 1;
if ( isOnlyChild && needsPlaceholder( hostElement, config.keepOnFocus ) ) {
if ( showPlaceholder( writer, hostElement ) ) {
wasViewModified = true;
}
} else if ( hidePlaceholder( writer, hostElement ) ) {
wasViewModified = true;
}
return wasViewModified;
}
// Gets a child element capable of displaying a placeholder if a parent element can host more
// than just text (for instance, when it is a root editable element). The child element
// can then be used in other placeholder helpers as a substitute of its parent.
//
// @private
// @param {module:engine/view/element~Element} parent
// @returns {module:engine/view/element~Element|null}
function getChildPlaceholderHostSubstitute( parent ) {
if ( parent.childCount ) {
const firstChild = parent.getChild( 0 );
if ( firstChild.is( 'element' ) && !firstChild.is( 'uiElement' ) ) {
return firstChild;
}
}
return null;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/view/position.js":
/*!**********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/view/position.js ***!
\**********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ Position)
/* harmony export */ });
/* harmony import */ var _treewalker__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./treewalker */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/treewalker.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_comparearrays__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/comparearrays */ "./node_modules/@ckeditor/ckeditor5-utils/src/comparearrays.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/* harmony import */ var _editableelement__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./editableelement */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/editableelement.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_version__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/version */ "./node_modules/@ckeditor/ckeditor5-utils/src/version.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/view/position
*/
// To check if component is loaded more than once.
/**
* Position in the view tree. Position is represented by its parent node and an offset in this parent.
*
* In order to create a new position instance use the `createPosition*()` factory methods available in:
*
* * {@link module:engine/view/view~View}
* * {@link module:engine/view/downcastwriter~DowncastWriter}
* * {@link module:engine/view/upcastwriter~UpcastWriter}
*/
class Position {
/**
* Creates a position.
*
* @param {module:engine/view/node~Node|module:engine/view/documentfragment~DocumentFragment} parent Position parent.
* @param {Number} offset Position offset.
*/
constructor( parent, offset ) {
/**
* Position parent.
*
* @readonly
* @member {module:engine/view/node~Node|module:engine/view/documentfragment~DocumentFragment}
* module:engine/view/position~Position#parent
*/
this.parent = parent;
/**
* Position offset.
*
* @readonly
* @member {Number} module:engine/view/position~Position#offset
*/
this.offset = offset;
}
/**
* Node directly after the position. Equals `null` when there is no node after position or position is located
* inside text node.
*
* @readonly
* @type {module:engine/view/node~Node|null}
*/
get nodeAfter() {
if ( this.parent.is( '$text' ) ) {
return null;
}
return this.parent.getChild( this.offset ) || null;
}
/**
* Node directly before the position. Equals `null` when there is no node before position or position is located
* inside text node.
*
* @readonly
* @type {module:engine/view/node~Node|null}
*/
get nodeBefore() {
if ( this.parent.is( '$text' ) ) {
return null;
}
return this.parent.getChild( this.offset - 1 ) || null;
}
/**
* Is `true` if position is at the beginning of its {@link module:engine/view/position~Position#parent parent}, `false` otherwise.
*
* @readonly
* @type {Boolean}
*/
get isAtStart() {
return this.offset === 0;
}
/**
* Is `true` if position is at the end of its {@link module:engine/view/position~Position#parent parent}, `false` otherwise.
*
* @readonly
* @type {Boolean}
*/
get isAtEnd() {
const endOffset = this.parent.is( '$text' ) ? this.parent.data.length : this.parent.childCount;
return this.offset === endOffset;
}
/**
* Position's root, that is the root of the position's parent element.
*
* @readonly
* @type {module:engine/view/node~Node|module:engine/view/documentfragment~DocumentFragment}
*/
get root() {
return this.parent.root;
}
/**
* {@link module:engine/view/editableelement~EditableElement EditableElement} instance that contains this position, or `null` if
* position is not inside an editable element.
*
* @type {module:engine/view/editableelement~EditableElement|null}
*/
get editableElement() {
let editable = this.parent;
while ( !( editable instanceof _editableelement__WEBPACK_IMPORTED_MODULE_3__["default"] ) ) {
if ( editable.parent ) {
editable = editable.parent;
} else {
return null;
}
}
return editable;
}
/**
* Returns a new instance of Position with offset incremented by `shift` value.
*
* @param {Number} shift How position offset should get changed. Accepts negative values.
* @returns {module:engine/view/position~Position} Shifted position.
*/
getShiftedBy( shift ) {
const shifted = Position._createAt( this );
const offset = shifted.offset + shift;
shifted.offset = offset < 0 ? 0 : offset;
return shifted;
}
/**
* Gets the farthest position which matches the callback using
* {@link module:engine/view/treewalker~TreeWalker TreeWalker}.
*
* For example:
*
* getLastMatchingPosition( value => value.type == 'text' ); // <p>{}foo</p> -> <p>foo[]</p>
* getLastMatchingPosition( value => value.type == 'text', { direction: 'backward' } ); // <p>foo[]</p> -> <p>{}foo</p>
* getLastMatchingPosition( value => false ); // Do not move the position.
*
* @param {Function} skip Callback function. Gets {@link module:engine/view/treewalker~TreeWalkerValue} and should
* return `true` if the value should be skipped or `false` if not.
* @param {Object} options Object with configuration options. See {@link module:engine/view/treewalker~TreeWalker}.
*
* @returns {module:engine/view/position~Position} The position after the last item which matches the `skip` callback test.
*/
getLastMatchingPosition( skip, options = {} ) {
options.startPosition = this;
const treeWalker = new _treewalker__WEBPACK_IMPORTED_MODULE_0__["default"]( options );
treeWalker.skip( skip );
return treeWalker.position;
}
/**
* Returns ancestors array of this position, that is this position's parent and it's ancestors.
*
* @returns {Array} Array with ancestors.
*/
getAncestors() {
if ( this.parent.is( 'documentFragment' ) ) {
return [ this.parent ];
} else {
return this.parent.getAncestors( { includeSelf: true } );
}
}
/**
* Returns a {@link module:engine/view/node~Node} or {@link module:engine/view/documentfragment~DocumentFragment}
* which is a common ancestor of both positions.
*
* @param {module:engine/view/position~Position} position
* @returns {module:engine/view/node~Node|module:engine/view/documentfragment~DocumentFragment|null}
*/
getCommonAncestor( position ) {
const ancestorsA = this.getAncestors();
const ancestorsB = position.getAncestors();
let i = 0;
while ( ancestorsA[ i ] == ancestorsB[ i ] && ancestorsA[ i ] ) {
i++;
}
return i === 0 ? null : ancestorsA[ i - 1 ];
}
/**
* Checks whether this object is of the given type.
*
* position.is( 'position' ); // -> true
* position.is( 'view:position' ); // -> true
*
* position.is( 'model:position' ); // -> false
* position.is( 'element' ); // -> false
* position.is( 'range' ); // -> false
*
* {@link module:engine/view/node~Node#is Check the entire list of view objects} which implement the `is()` method.
*
* @param {String} type
* @returns {Boolean}
*/
is( type ) {
return type === 'position' || type === 'view:position';
}
/**
* Checks whether this position equals given position.
*
* @param {module:engine/view/position~Position} otherPosition Position to compare with.
* @returns {Boolean} True if positions are same.
*/
isEqual( otherPosition ) {
return ( this.parent == otherPosition.parent && this.offset == otherPosition.offset );
}
/**
* Checks whether this position is located before given position. When method returns `false` it does not mean that
* this position is after give one. Two positions may be located inside separate roots and in that situation this
* method will still return `false`.
*
* @see module:engine/view/position~Position#isAfter
* @see module:engine/view/position~Position#compareWith
* @param {module:engine/view/position~Position} otherPosition Position to compare with.
* @returns {Boolean} Returns `true` if this position is before given position.
*/
isBefore( otherPosition ) {
return this.compareWith( otherPosition ) == 'before';
}
/**
* Checks whether this position is located after given position. When method returns `false` it does not mean that
* this position is before give one. Two positions may be located inside separate roots and in that situation this
* method will still return `false`.
*
* @see module:engine/view/position~Position#isBefore
* @see module:engine/view/position~Position#compareWith
* @param {module:engine/view/position~Position} otherPosition Position to compare with.
* @returns {Boolean} Returns `true` if this position is after given position.
*/
isAfter( otherPosition ) {
return this.compareWith( otherPosition ) == 'after';
}
/**
* Checks whether this position is before, after or in same position that other position. Two positions may be also
* different when they are located in separate roots.
*
* @param {module:engine/view/position~Position} otherPosition Position to compare with.
* @returns {module:engine/view/position~PositionRelation}
*/
compareWith( otherPosition ) {
if ( this.root !== otherPosition.root ) {
return 'different';
}
if ( this.isEqual( otherPosition ) ) {
return 'same';
}
// Get path from root to position's parent element.
const thisPath = this.parent.is( 'node' ) ? this.parent.getPath() : [];
const otherPath = otherPosition.parent.is( 'node' ) ? otherPosition.parent.getPath() : [];
// Add the positions' offsets to the parents offsets.
thisPath.push( this.offset );
otherPath.push( otherPosition.offset );
// Compare both path arrays to find common ancestor.
const result = (0,_ckeditor_ckeditor5_utils_src_comparearrays__WEBPACK_IMPORTED_MODULE_1__["default"])( thisPath, otherPath );
switch ( result ) {
case 'prefix':
return 'before';
case 'extension':
return 'after';
default:
return thisPath[ result ] < otherPath[ result ] ? 'before' : 'after';
}
}
/**
* Creates a {@link module:engine/view/treewalker~TreeWalker TreeWalker} instance with this positions as a start position.
*
* @param {Object} options Object with configuration options. See {@link module:engine/view/treewalker~TreeWalker}
* @param {module:engine/view/range~Range} [options.boundaries=null] Range to define boundaries of the iterator.
* @param {Boolean} [options.singleCharacters=false]
* @param {Boolean} [options.shallow=false]
* @param {Boolean} [options.ignoreElementEnd=false]
*/
getWalker( options = {} ) {
options.startPosition = this;
return new _treewalker__WEBPACK_IMPORTED_MODULE_0__["default"]( options );
}
clone() {
return new Position( this.parent, this.offset );
}
/**
* Creates position at the given location. The location can be specified as:
*
* * a {@link module:engine/view/position~Position position},
* * parent element and offset (offset defaults to `0`),
* * parent element and `'end'` (sets position at the end of that element),
* * {@link module:engine/view/item~Item view item} and `'before'` or `'after'` (sets position before or after given view item).
*
* This method is a shortcut to other constructors such as:
*
* * {@link module:engine/view/position~Position._createBefore},
* * {@link module:engine/view/position~Position._createAfter}.
*
* @protected
* @param {module:engine/view/item~Item|module:engine/view/position~Position} itemOrPosition
* @param {Number|'end'|'before'|'after'} [offset] Offset or one of the flags. Used only when
* first parameter is a {@link module:engine/view/item~Item view item}.
*/
static _createAt( itemOrPosition, offset ) {
if ( itemOrPosition instanceof Position ) {
return new this( itemOrPosition.parent, itemOrPosition.offset );
} else {
const node = itemOrPosition;
if ( offset == 'end' ) {
offset = node.is( '$text' ) ? node.data.length : node.childCount;
} else if ( offset == 'before' ) {
return this._createBefore( node );
} else if ( offset == 'after' ) {
return this._createAfter( node );
} else if ( offset !== 0 && !offset ) {
/**
* {@link module:engine/view/view~View#createPositionAt `View#createPositionAt()`}
* requires the offset to be specified when the first parameter is a view item.
*
* @error view-createpositionat-offset-required
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_2__["default"]( 'view-createpositionat-offset-required', node );
}
return new Position( node, offset );
}
}
/**
* Creates a new position after given view item.
*
* @protected
* @param {module:engine/view/item~Item} item View item after which the position should be located.
* @returns {module:engine/view/position~Position}
*/
static _createAfter( item ) {
// TextProxy is not a instance of Node so we need do handle it in specific way.
if ( item.is( '$textProxy' ) ) {
return new Position( item.textNode, item.offsetInText + item.data.length );
}
if ( !item.parent ) {
/**
* You can not make a position after a root.
*
* @error view-position-after-root
* @param {module:engine/view/node~Node} root
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_2__["default"]( 'view-position-after-root', item, { root: item } );
}
return new Position( item.parent, item.index + 1 );
}
/**
* Creates a new position before given view item.
*
* @protected
* @param {module:engine/view/item~Item} item View item before which the position should be located.
* @returns {module:engine/view/position~Position}
*/
static _createBefore( item ) {
// TextProxy is not a instance of Node so we need do handle it in specific way.
if ( item.is( '$textProxy' ) ) {
return new Position( item.textNode, item.offsetInText );
}
if ( !item.parent ) {
/**
* You cannot make a position before a root.
*
* @error view-position-before-root
* @param {module:engine/view/node~Node} root
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_2__["default"]( 'view-position-before-root', item, { root: item } );
}
return new Position( item.parent, item.index );
}
}
/**
* A flag indicating whether this position is `'before'` or `'after'` or `'same'` as given position.
* If positions are in different roots `'different'` flag is returned.
*
* @typedef {String} module:engine/view/position~PositionRelation
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/view/range.js":
/*!*******************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/view/range.js ***!
\*******************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ Range)
/* harmony export */ });
/* harmony import */ var _position__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./position */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/position.js");
/* harmony import */ var _treewalker__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./treewalker */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/treewalker.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/view/range
*/
/**
* Range in the view tree. A range is represented by its start and end {@link module:engine/view/position~Position positions}.
*
* In order to create a new position instance use the `createPosition*()` factory methods available in:
*
* * {@link module:engine/view/view~View}
* * {@link module:engine/view/downcastwriter~DowncastWriter}
* * {@link module:engine/view/upcastwriter~UpcastWriter}
*/
class Range {
/**
* Creates a range spanning from `start` position to `end` position.
*
* **Note:** Constructor creates it's own {@link module:engine/view/position~Position} instances basing on passed values.
*
* @param {module:engine/view/position~Position} start Start position.
* @param {module:engine/view/position~Position} [end] End position. If not set, range will be collapsed at the `start` position.
*/
constructor( start, end = null ) {
/**
* Start position.
*
* @readonly
* @member {module:engine/view/position~Position}
*/
this.start = start.clone();
/**
* End position.
*
* @readonly
* @member {module:engine/view/position~Position}
*/
this.end = end ? end.clone() : start.clone();
}
/**
* Iterable interface.
*
* Iterates over all {@link module:engine/view/item~Item view items} that are in this range and returns
* them together with additional information like length or {@link module:engine/view/position~Position positions},
* grouped as {@link module:engine/view/treewalker~TreeWalkerValue}.
*
* This iterator uses {@link module:engine/view/treewalker~TreeWalker TreeWalker} with `boundaries` set to this range and
* `ignoreElementEnd` option
* set to `true`.
*
* @returns {Iterable.<module:engine/view/treewalker~TreeWalkerValue>}
*/
* [ Symbol.iterator ]() {
yield* new _treewalker__WEBPACK_IMPORTED_MODULE_1__["default"]( { boundaries: this, ignoreElementEnd: true } );
}
/**
* Returns whether the range is collapsed, that is it start and end positions are equal.
*
* @type {Boolean}
*/
get isCollapsed() {
return this.start.isEqual( this.end );
}
/**
* Returns whether this range is flat, that is if {@link module:engine/view/range~Range#start start} position and
* {@link module:engine/view/range~Range#end end} position are in the same {@link module:engine/view/position~Position#parent parent}.
*
* @type {Boolean}
*/
get isFlat() {
return this.start.parent === this.end.parent;
}
/**
* Range root element.
*
* @type {module:engine/view/element~Element|module:engine/view/documentfragment~DocumentFragment}
*/
get root() {
return this.start.root;
}
/**
* Creates a maximal range that has the same content as this range but is expanded in both ways (at the beginning
* and at the end).
*
* For example:
*
* <p>Foo</p><p><b>{Bar}</b></p> -> <p>Foo</p>[<p><b>Bar</b>]</p>
* <p><b>foo</b>{bar}<span></span></p> -> <p><b>foo[</b>bar<span></span>]</p>
*
* Note that in the sample above:
*
* - `<p>` have type of {@link module:engine/view/containerelement~ContainerElement},
* - `<b>` have type of {@link module:engine/view/attributeelement~AttributeElement},
* - `<span>` have type of {@link module:engine/view/uielement~UIElement}.
*
* @returns {module:engine/view/range~Range} Enlarged range.
*/
getEnlarged() {
let start = this.start.getLastMatchingPosition( enlargeTrimSkip, { direction: 'backward' } );
let end = this.end.getLastMatchingPosition( enlargeTrimSkip );
// Fix positions, in case if they are in Text node.
if ( start.parent.is( '$text' ) && start.isAtStart ) {
start = _position__WEBPACK_IMPORTED_MODULE_0__["default"]._createBefore( start.parent );
}
if ( end.parent.is( '$text' ) && end.isAtEnd ) {
end = _position__WEBPACK_IMPORTED_MODULE_0__["default"]._createAfter( end.parent );
}
return new Range( start, end );
}
/**
* Creates a minimum range that has the same content as this range but is trimmed in both ways (at the beginning
* and at the end).
*
* For example:
*
* <p>Foo</p>[<p><b>Bar</b>]</p> -> <p>Foo</p><p><b>{Bar}</b></p>
* <p><b>foo[</b>bar<span></span>]</p> -> <p><b>foo</b>{bar}<span></span></p>
*
* Note that in the sample above:
*
* - `<p>` have type of {@link module:engine/view/containerelement~ContainerElement},
* - `<b>` have type of {@link module:engine/view/attributeelement~AttributeElement},
* - `<span>` have type of {@link module:engine/view/uielement~UIElement}.
*
* @returns {module:engine/view/range~Range} Shrink range.
*/
getTrimmed() {
let start = this.start.getLastMatchingPosition( enlargeTrimSkip );
if ( start.isAfter( this.end ) || start.isEqual( this.end ) ) {
return new Range( start, start );
}
let end = this.end.getLastMatchingPosition( enlargeTrimSkip, { direction: 'backward' } );
const nodeAfterStart = start.nodeAfter;
const nodeBeforeEnd = end.nodeBefore;
// Because TreeWalker prefers positions next to text node, we need to move them manually into these text nodes.
if ( nodeAfterStart && nodeAfterStart.is( '$text' ) ) {
start = new _position__WEBPACK_IMPORTED_MODULE_0__["default"]( nodeAfterStart, 0 );
}
if ( nodeBeforeEnd && nodeBeforeEnd.is( '$text' ) ) {
end = new _position__WEBPACK_IMPORTED_MODULE_0__["default"]( nodeBeforeEnd, nodeBeforeEnd.data.length );
}
return new Range( start, end );
}
/**
* Two ranges are equal if their start and end positions are equal.
*
* @param {module:engine/view/range~Range} otherRange Range to compare with.
* @returns {Boolean} `true` if ranges are equal, `false` otherwise
*/
isEqual( otherRange ) {
return this == otherRange || ( this.start.isEqual( otherRange.start ) && this.end.isEqual( otherRange.end ) );
}
/**
* Checks whether this range contains given {@link module:engine/view/position~Position position}.
*
* @param {module:engine/view/position~Position} position Position to check.
* @returns {Boolean} `true` if given {@link module:engine/view/position~Position position} is contained in this range,
* `false` otherwise.
*/
containsPosition( position ) {
return position.isAfter( this.start ) && position.isBefore( this.end );
}
/**
* Checks whether this range contains given {@link module:engine/view/range~Range range}.
*
* @param {module:engine/view/range~Range} otherRange Range to check.
* @param {Boolean} [loose=false] Whether the check is loose or strict. If the check is strict (`false`), compared range cannot
* start or end at the same position as this range boundaries. If the check is loose (`true`), compared range can start, end or
* even be equal to this range. Note that collapsed ranges are always compared in strict mode.
* @returns {Boolean} `true` if given {@link module:engine/view/range~Range range} boundaries are contained by this range, `false`
* otherwise.
*/
containsRange( otherRange, loose = false ) {
if ( otherRange.isCollapsed ) {
loose = false;
}
const containsStart = this.containsPosition( otherRange.start ) || ( loose && this.start.isEqual( otherRange.start ) );
const containsEnd = this.containsPosition( otherRange.end ) || ( loose && this.end.isEqual( otherRange.end ) );
return containsStart && containsEnd;
}
/**
* Computes which part(s) of this {@link module:engine/view/range~Range range} is not a part of given
* {@link module:engine/view/range~Range range}.
* Returned array contains zero, one or two {@link module:engine/view/range~Range ranges}.
*
* Examples:
*
* let foo = downcastWriter.createText( 'foo' );
* let img = downcastWriter.createContainerElement( 'img' );
* let bar = downcastWriter.createText( 'bar' );
* let p = downcastWriter.createContainerElement( 'p', null, [ foo, img, bar ] );
*
* let range = view.createRange( view.createPositionAt( foo, 2 ), view.createPositionAt( bar, 1 ); // "o", img, "b" are in range.
* let otherRange = view.createRange( // "oo", img, "ba" are in range.
* view.createPositionAt( foo, 1 ),
* view.createPositionAt( bar, 2 )
* );
* let transformed = range.getDifference( otherRange );
* // transformed array has no ranges because `otherRange` contains `range`
*
* otherRange = view.createRange( view.createPositionAt( foo, 1 ), view.createPositionAt( p, 2 ); // "oo", img are in range.
* transformed = range.getDifference( otherRange );
* // transformed array has one range: from ( p, 2 ) to ( bar, 1 )
*
* otherRange = view.createRange( view.createPositionAt( p, 1 ), view.createPositionAt( p, 2 ) ); // img is in range.
* transformed = range.getDifference( otherRange );
* // transformed array has two ranges: from ( foo, 1 ) to ( p, 1 ) and from ( p, 2 ) to ( bar, 1 )
*
* @param {module:engine/view/range~Range} otherRange Range to differentiate against.
* @returns {Array.<module:engine/view/range~Range>} The difference between ranges.
*/
getDifference( otherRange ) {
const ranges = [];
if ( this.isIntersecting( otherRange ) ) {
// Ranges intersect.
if ( this.containsPosition( otherRange.start ) ) {
// Given range start is inside this range. This means that we have to
// add shrunken range - from the start to the middle of this range.
ranges.push( new Range( this.start, otherRange.start ) );
}
if ( this.containsPosition( otherRange.end ) ) {
// Given range end is inside this range. This means that we have to
// add shrunken range - from the middle of this range to the end.
ranges.push( new Range( otherRange.end, this.end ) );
}
} else {
// Ranges do not intersect, return the original range.
ranges.push( this.clone() );
}
return ranges;
}
/**
* Returns an intersection of this {@link module:engine/view/range~Range range} and given {@link module:engine/view/range~Range range}.
* Intersection is a common part of both of those ranges. If ranges has no common part, returns `null`.
*
* Examples:
*
* let foo = downcastWriter.createText( 'foo' );
* let img = downcastWriter.createContainerElement( 'img' );
* let bar = downcastWriter.createText( 'bar' );
* let p = downcastWriter.createContainerElement( 'p', null, [ foo, img, bar ] );
*
* let range = view.createRange( view.createPositionAt( foo, 2 ), view.createPositionAt( bar, 1 ); // "o", img, "b" are in range.
* let otherRange = view.createRange( view.createPositionAt( foo, 1 ), view.createPositionAt( p, 2 ); // "oo", img are in range.
* let transformed = range.getIntersection( otherRange ); // range from ( foo, 1 ) to ( p, 2 ).
*
* otherRange = view.createRange( view.createPositionAt( bar, 1 ), view.createPositionAt( bar, 3 ); "ar" is in range.
* transformed = range.getIntersection( otherRange ); // null - no common part.
*
* @param {module:engine/view/range~Range} otherRange Range to check for intersection.
* @returns {module:engine/view/range~Range|null} A common part of given ranges or `null` if ranges have no common part.
*/
getIntersection( otherRange ) {
if ( this.isIntersecting( otherRange ) ) {
// Ranges intersect, so a common range will be returned.
// At most, it will be same as this range.
let commonRangeStart = this.start;
let commonRangeEnd = this.end;
if ( this.containsPosition( otherRange.start ) ) {
// Given range start is inside this range. This means thaNt we have to
// shrink common range to the given range start.
commonRangeStart = otherRange.start;
}
if ( this.containsPosition( otherRange.end ) ) {
// Given range end is inside this range. This means that we have to
// shrink common range to the given range end.
commonRangeEnd = otherRange.end;
}
return new Range( commonRangeStart, commonRangeEnd );
}
// Ranges do not intersect, so they do not have common part.
return null;
}
/**
* Creates a {@link module:engine/view/treewalker~TreeWalker TreeWalker} instance with this range as a boundary.
*
* @param {Object} options Object with configuration options. See {@link module:engine/view/treewalker~TreeWalker}.
* @param {module:engine/view/position~Position} [options.startPosition]
* @param {Boolean} [options.singleCharacters=false]
* @param {Boolean} [options.shallow=false]
* @param {Boolean} [options.ignoreElementEnd=false]
* @returns {module:engine/view/treewalker~TreeWalker}
*/
getWalker( options = {} ) {
options.boundaries = this;
return new _treewalker__WEBPACK_IMPORTED_MODULE_1__["default"]( options );
}
/**
* Returns a {@link module:engine/view/node~Node} or {@link module:engine/view/documentfragment~DocumentFragment}
* which is a common ancestor of range's both ends (in which the entire range is contained).
*
* @returns {module:engine/view/node~Node|module:engine/view/documentfragment~DocumentFragment|null}
*/
getCommonAncestor() {
return this.start.getCommonAncestor( this.end );
}
/**
* Returns an {@link module:engine/view/element~Element Element} contained by the range.
* The element will be returned when it is the **only** node within the range and **fully–contained**
* at the same time.
*
* @returns {module:engine/view/element~Element|null}
*/
getContainedElement() {
if ( this.isCollapsed ) {
return null;
}
let nodeAfterStart = this.start.nodeAfter;
let nodeBeforeEnd = this.end.nodeBefore;
// Handle the situation when the range position is at the beginning / at the end of a text node.
// In such situation `.nodeAfter` and `.nodeBefore` are `null` but the range still might be spanning
// over one element.
//
// <p>Foo{<span class="widget"></span>}bar</p> vs <p>Foo[<span class="widget"></span>]bar</p>
//
// These are basically the same range, only the difference is if the range position is at
// at the end/at the beginning of a text node or just before/just after the text node.
//
if ( this.start.parent.is( '$text' ) && this.start.isAtEnd && this.start.parent.nextSibling ) {
nodeAfterStart = this.start.parent.nextSibling;
}
if ( this.end.parent.is( '$text' ) && this.end.isAtStart && this.end.parent.previousSibling ) {
nodeBeforeEnd = this.end.parent.previousSibling;
}
if ( nodeAfterStart && nodeAfterStart.is( 'element' ) && nodeAfterStart === nodeBeforeEnd ) {
return nodeAfterStart;
}
return null;
}
/**
* Clones this range.
*
* @returns {module:engine/view/range~Range}
*/
clone() {
return new Range( this.start, this.end );
}
/**
* Returns an iterator that iterates over all {@link module:engine/view/item~Item view items} that are in this range and returns
* them.
*
* This method uses {@link module:engine/view/treewalker~TreeWalker} with `boundaries` set to this range and `ignoreElementEnd` option
* set to `true`. However it returns only {@link module:engine/view/item~Item items},
* not {@link module:engine/view/treewalker~TreeWalkerValue}.
*
* You may specify additional options for the tree walker. See {@link module:engine/view/treewalker~TreeWalker} for
* a full list of available options.
*
* @param {Object} options Object with configuration options. See {@link module:engine/view/treewalker~TreeWalker}.
* @returns {Iterable.<module:engine/view/item~Item>}
*/
* getItems( options = {} ) {
options.boundaries = this;
options.ignoreElementEnd = true;
const treeWalker = new _treewalker__WEBPACK_IMPORTED_MODULE_1__["default"]( options );
for ( const value of treeWalker ) {
yield value.item;
}
}
/**
* Returns an iterator that iterates over all {@link module:engine/view/position~Position positions} that are boundaries or
* contained in this range.
*
* This method uses {@link module:engine/view/treewalker~TreeWalker} with `boundaries` set to this range. However it returns only
* {@link module:engine/view/position~Position positions}, not {@link module:engine/view/treewalker~TreeWalkerValue}.
*
* You may specify additional options for the tree walker. See {@link module:engine/view/treewalker~TreeWalker} for
* a full list of available options.
*
* @param {Object} options Object with configuration options. See {@link module:engine/view/treewalker~TreeWalker}.
* @returns {Iterable.<module:engine/view/position~Position>}
*/
* getPositions( options = {} ) {
options.boundaries = this;
const treeWalker = new _treewalker__WEBPACK_IMPORTED_MODULE_1__["default"]( options );
yield treeWalker.position;
for ( const value of treeWalker ) {
yield value.nextPosition;
}
}
/**
* Checks whether this object is of the given type.
*
* range.is( 'range' ); // -> true
* range.is( 'view:range' ); // -> true
*
* range.is( 'model:range' ); // -> false
* range.is( 'element' ); // -> false
* range.is( 'selection' ); // -> false
*
* {@link module:engine/view/node~Node#is Check the entire list of view objects} which implement the `is()` method.
*
* @param {String} type
* @returns {Boolean}
*/
is( type ) {
return type === 'range' || type === 'view:range';
}
/**
* Checks and returns whether this range intersects with the given range.
*
* @param {module:engine/view/range~Range} otherRange Range to compare with.
* @returns {Boolean} True if ranges intersect.
*/
isIntersecting( otherRange ) {
return this.start.isBefore( otherRange.end ) && this.end.isAfter( otherRange.start );
}
/**
* Creates a range from the given parents and offsets.
*
* @protected
* @param {module:engine/view/node~Node|module:engine/view/documentfragment~DocumentFragment} startElement Start position
* parent element.
* @param {Number} startOffset Start position offset.
* @param {module:engine/view/node~Node|module:engine/view/documentfragment~DocumentFragment} endElement End position
* parent element.
* @param {Number} endOffset End position offset.
* @returns {module:engine/view/range~Range} Created range.
*/
static _createFromParentsAndOffsets( startElement, startOffset, endElement, endOffset ) {
return new this(
new _position__WEBPACK_IMPORTED_MODULE_0__["default"]( startElement, startOffset ),
new _position__WEBPACK_IMPORTED_MODULE_0__["default"]( endElement, endOffset )
);
}
/**
* Creates a new range, spreading from specified {@link module:engine/view/position~Position position} to a position moved by
* given `shift`. If `shift` is a negative value, shifted position is treated as the beginning of the range.
*
* @protected
* @param {module:engine/view/position~Position} position Beginning of the range.
* @param {Number} shift How long the range should be.
* @returns {module:engine/view/range~Range}
*/
static _createFromPositionAndShift( position, shift ) {
const start = position;
const end = position.getShiftedBy( shift );
return shift > 0 ? new this( start, end ) : new this( end, start );
}
/**
* Creates a range inside an {@link module:engine/view/element~Element element} which starts before the first child of
* that element and ends after the last child of that element.
*
* @protected
* @param {module:engine/view/element~Element} element Element which is a parent for the range.
* @returns {module:engine/view/range~Range}
*/
static _createIn( element ) {
return this._createFromParentsAndOffsets( element, 0, element, element.childCount );
}
/**
* Creates a range that starts before given {@link module:engine/view/item~Item view item} and ends after it.
*
* @protected
* @param {module:engine/view/item~Item} item
* @returns {module:engine/view/range~Range}
*/
static _createOn( item ) {
const size = item.is( '$textProxy' ) ? item.offsetSize : 1;
return this._createFromPositionAndShift( _position__WEBPACK_IMPORTED_MODULE_0__["default"]._createBefore( item ), size );
}
}
// Function used by getEnlarged and getTrimmed methods.
function enlargeTrimSkip( value ) {
if ( value.item.is( 'attributeElement' ) || value.item.is( 'uiElement' ) ) {
return true;
}
return false;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/view/rawelement.js":
/*!************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/view/rawelement.js ***!
\************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ RawElement)
/* harmony export */ });
/* harmony import */ var _element__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./element */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/element.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/* harmony import */ var _node__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./node */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/node.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/view/rawelement
*/
/**
* The raw element class.
*
* The raw elements work as data containers ("wrappers", "sandboxes") but their children are not managed or
* even recognized by the editor. This encapsulation allows integrations to maintain custom DOM structures
* in the editor content without, for instance, worrying about compatibility with other editor features.
* Raw elements are a perfect tool for integration with external frameworks and data sources.
*
* Unlike {@link module:engine/view/uielement~UIElement UI elements}, raw elements act like real editor
* content (similar to {@link module:engine/view/containerelement~ContainerElement} or
* {@link module:engine/view/emptyelement~EmptyElement}), they are considered by the editor selection and
* {@link module:widget/utils~toWidget they can work as widgets}.
*
* To create a new raw element, use the
* {@link module:engine/view/downcastwriter~DowncastWriter#createRawElement `downcastWriter#createRawElement()`} method.
*
* @extends module:engine/view/element~Element
*/
class RawElement extends _element__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* Creates a new instance of a raw element.
*
* Throws the `view-rawelement-cannot-add` {@link module:utils/ckeditorerror~CKEditorError CKEditorError} when the `children`
* parameter is passed to inform that the usage of `RawElement` is incorrect (adding child nodes to `RawElement` is forbidden).
*
* @see module:engine/view/downcastwriter~DowncastWriter#createRawElement
* @protected
* @param {module:engine/view/document~Document} document The document instance to which this element belongs.
* @param {String} name A node name.
* @param {Object|Iterable} [attrs] The collection of attributes.
* @param {module:engine/view/node~Node|Iterable.<module:engine/view/node~Node>} [children]
* A list of nodes to be inserted into the created element.
*/
constructor( document, name, attrs, children ) {
super( document, name, attrs, children );
// Override the default of the base class.
this._isAllowedInsideAttributeElement = true;
/**
* Returns `null` because filler is not needed for raw elements.
*
* @method #getFillerOffset
* @returns {null} Always returns null.
*/
this.getFillerOffset = getFillerOffset;
}
/**
* Checks whether this object is of the given type or name.
*
* rawElement.is( 'rawElement' ); // -> true
* rawElement.is( 'element' ); // -> true
* rawElement.is( 'node' ); // -> true
* rawElement.is( 'view:rawElement' ); // -> true
* rawElement.is( 'view:element' ); // -> true
* rawElement.is( 'view:node' ); // -> true
*
* rawElement.is( 'model:element' ); // -> false
* rawElement.is( 'documentFragment' ); // -> false
*
* Assuming that the object being checked is a raw element, you can also check its
* {@link module:engine/view/rawelement~RawElement#name name}:
*
* rawElement.is( 'img' ); // -> true if this is an img element
* rawElement.is( 'rawElement', 'img' ); // -> same as above
* text.is( 'img' ); -> false
*
* {@link module:engine/view/node~Node#is Check the entire list of view objects} which implement the `is()` method.
*
* @param {String} type The type to check when the `name` parameter is present.
* Otherwise, it acts like the `name` parameter.
* @param {String} [name] The element name.
* @returns {Boolean}
*/
is( type, name = null ) {
if ( !name ) {
return type === 'rawElement' || type === 'view:rawElement' ||
// From super.is(). This is highly utilised method and cannot call super. See ckeditor/ckeditor5#6529.
type === this.name || type === 'view:' + this.name ||
type === 'element' || type === 'view:element' ||
type === 'node' || type === 'view:node';
} else {
return name === this.name && (
type === 'rawElement' || type === 'view:rawElement' ||
type === 'element' || type === 'view:element'
);
}
}
/**
* Overrides the {@link module:engine/view/element~Element#_insertChild} method.
* Throws the `view-rawelement-cannot-add` {@link module:utils/ckeditorerror~CKEditorError CKEditorError} to prevent
* adding any child nodes to a raw element.
*
* @protected
*/
_insertChild( index, nodes ) {
if ( nodes && ( nodes instanceof _node__WEBPACK_IMPORTED_MODULE_2__["default"] || Array.from( nodes ).length > 0 ) ) {
/**
* Cannot add children to a {@link module:engine/view/rawelement~RawElement} instance.
*
* @error view-rawelement-cannot-add
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_1__["default"](
'view-rawelement-cannot-add',
[ this, nodes ]
);
}
}
/**
* This allows rendering the children of a {@link module:engine/view/rawelement~RawElement} on the DOM level.
* This method is called by the {@link module:engine/view/domconverter~DomConverter} with the raw DOM element
* passed as an argument, leaving the number and shape of the children up to the integrator.
*
* This method **must be defined** for the raw element to work:
*
* const myRawElement = downcastWriter.createRawElement( 'div' );
*
* myRawElement.render = function( domElement, domConverter ) {
* domConverter.setContentOf( domElement, '<b>This is the raw content of myRawElement.</b>' );
* };
*
* @method #render
* @param {HTMLElement} domElement The native DOM element representing the raw view element.
* @param {module:engine/view/domconverter~DomConverter} domConverter Instance of the DomConverter used to optimize the output.
*/
}
// Returns `null` because block filler is not needed for raw elements.
//
// @returns {null}
function getFillerOffset() {
return null;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/view/renderer.js":
/*!**********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/view/renderer.js ***!
\**********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ Renderer)
/* harmony export */ });
/* harmony import */ var _text__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./text */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/text.js");
/* harmony import */ var _position__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./position */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/position.js");
/* harmony import */ var _filler__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./filler */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/filler.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/mix */ "./node_modules/@ckeditor/ckeditor5-utils/src/mix.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_diff__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/diff */ "./node_modules/@ckeditor/ckeditor5-utils/src/diff.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_dom_insertat__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/dom/insertat */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/insertat.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_dom_remove__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/dom/remove */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/remove.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_observablemixin__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/observablemixin */ "./node_modules/@ckeditor/ckeditor5-utils/src/observablemixin.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_dom_istext__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/dom/istext */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/istext.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_dom_iscomment__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/dom/iscomment */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/iscomment.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_dom_isnode__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/dom/isnode */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/isnode.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_fastdiff__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/fastdiff */ "./node_modules/@ckeditor/ckeditor5-utils/src/fastdiff.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_env__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/env */ "./node_modules/@ckeditor/ckeditor5-utils/src/env.js");
/* harmony import */ var _theme_renderer_css__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(/*! ../../theme/renderer.css */ "./node_modules/@ckeditor/ckeditor5-engine/theme/renderer.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/* globals Node */
/**
* @module engine/view/renderer
*/
/**
* Renderer is responsible for updating the DOM structure and the DOM selection based on
* the {@link module:engine/view/renderer~Renderer#markToSync information about updated view nodes}.
* In other words, it renders the view to the DOM.
*
* Its main responsibility is to make only the necessary, minimal changes to the DOM. However, unlike in many
* virtual DOM implementations, the primary reason for doing minimal changes is not the performance but ensuring
* that native editing features such as text composition, autocompletion, spell checking, selection's x-index are
* affected as little as possible.
*
* Renderer uses {@link module:engine/view/domconverter~DomConverter} to transform view nodes and positions
* to and from the DOM.
*/
class Renderer {
/**
* Creates a renderer instance.
*
* @param {module:engine/view/domconverter~DomConverter} domConverter Converter instance.
* @param {module:engine/view/documentselection~DocumentSelection} selection View selection.
*/
constructor( domConverter, selection ) {
/**
* Set of DOM Documents instances.
*
* @readonly
* @member {Set.<Document>}
*/
this.domDocuments = new Set();
/**
* Converter instance.
*
* @readonly
* @member {module:engine/view/domconverter~DomConverter}
*/
this.domConverter = domConverter;
/**
* Set of nodes which attributes changed and may need to be rendered.
*
* @readonly
* @member {Set.<module:engine/view/node~Node>}
*/
this.markedAttributes = new Set();
/**
* Set of elements which child lists changed and may need to be rendered.
*
* @readonly
* @member {Set.<module:engine/view/node~Node>}
*/
this.markedChildren = new Set();
/**
* Set of text nodes which text data changed and may need to be rendered.
*
* @readonly
* @member {Set.<module:engine/view/node~Node>}
*/
this.markedTexts = new Set();
/**
* View selection. Renderer updates DOM selection based on the view selection.
*
* @readonly
* @member {module:engine/view/documentselection~DocumentSelection}
*/
this.selection = selection;
/**
* Indicates if the view document is focused and selection can be rendered. Selection will not be rendered if
* this is set to `false`.
*
* @member {Boolean}
* @observable
*/
this.set( 'isFocused', false );
/**
* Indicates whether the user is making a selection in the document (e.g. holding the mouse button and moving the cursor).
* When they stop selecting, the property goes back to `false`.
*
* Note: In some browsers, the renderer will stop rendering the selection and inline fillers while the user is making
* a selection to avoid glitches in DOM selection
* (https://github.com/ckeditor/ckeditor5/issues/10562, https://github.com/ckeditor/ckeditor5/issues/10723).
*
* @member {Boolean}
* @observable
*/
this.set( 'isSelecting', false );
// Rendering the selection and inline filler manipulation should be postponed in (non-Android) Blink until the user finishes
// creating the selection in DOM to avoid accidental selection collapsing
// (https://github.com/ckeditor/ckeditor5/issues/10562, https://github.com/ckeditor/ckeditor5/issues/10723).
// When the user stops selecting, all pending changes should be rendered ASAP, though.
if ( _ckeditor_ckeditor5_utils_src_env__WEBPACK_IMPORTED_MODULE_13__["default"].isBlink && !_ckeditor_ckeditor5_utils_src_env__WEBPACK_IMPORTED_MODULE_13__["default"].isAndroid ) {
this.on( 'change:isSelecting', () => {
if ( !this.isSelecting ) {
this.render();
}
} );
}
/**
* The text node in which the inline filler was rendered.
*
* @private
* @member {Text}
*/
this._inlineFiller = null;
/**
* DOM element containing fake selection.
*
* @private
* @type {null|HTMLElement}
*/
this._fakeSelectionContainer = null;
}
/**
* Marks a view node to be updated in the DOM by {@link #render `render()`}.
*
* Note that only view nodes whose parents have corresponding DOM elements need to be marked to be synchronized.
*
* @see #markedAttributes
* @see #markedChildren
* @see #markedTexts
*
* @param {module:engine/view/document~ChangeType} type Type of the change.
* @param {module:engine/view/node~Node} node Node to be marked.
*/
markToSync( type, node ) {
if ( type === 'text' ) {
if ( this.domConverter.mapViewToDom( node.parent ) ) {
this.markedTexts.add( node );
}
} else {
// If the node has no DOM element it is not rendered yet,
// its children/attributes do not need to be marked to be sync.
if ( !this.domConverter.mapViewToDom( node ) ) {
return;
}
if ( type === 'attributes' ) {
this.markedAttributes.add( node );
} else if ( type === 'children' ) {
this.markedChildren.add( node );
} else {
/**
* Unknown type passed to Renderer.markToSync.
*
* @error view-renderer-unknown-type
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_8__["default"]( 'view-renderer-unknown-type', this );
}
}
}
/**
* Renders all buffered changes ({@link #markedAttributes}, {@link #markedChildren} and {@link #markedTexts}) and
* the current view selection (if needed) to the DOM by applying a minimal set of changes to it.
*
* Renderer tries not to break the text composition (e.g. IME) and x-index of the selection,
* so it does as little as it is needed to update the DOM.
*
* Renderer also handles {@link module:engine/view/filler fillers}. Especially, it checks if the inline filler is needed
* at the selection position and adds or removes it. To prevent breaking text composition inline filler will not be
* removed as long as the selection is in the text node which needed it at first.
*/
render() {
let inlineFillerPosition;
const isInlineFillerRenderingPossible = _ckeditor_ckeditor5_utils_src_env__WEBPACK_IMPORTED_MODULE_13__["default"].isBlink && !_ckeditor_ckeditor5_utils_src_env__WEBPACK_IMPORTED_MODULE_13__["default"].isAndroid ? !this.isSelecting : true;
// Refresh mappings.
for ( const element of this.markedChildren ) {
this._updateChildrenMappings( element );
}
// Don't manipulate inline fillers while the selection is being made in (non-Android) Blink to prevent accidental
// DOM selection collapsing
// (https://github.com/ckeditor/ckeditor5/issues/10562, https://github.com/ckeditor/ckeditor5/issues/10723).
if ( isInlineFillerRenderingPossible ) {
// There was inline filler rendered in the DOM but it's not
// at the selection position any more, so we can remove it
// (cause even if it's needed, it must be placed in another location).
if ( this._inlineFiller && !this._isSelectionInInlineFiller() ) {
this._removeInlineFiller();
}
// If we've got the filler, let's try to guess its position in the view.
if ( this._inlineFiller ) {
inlineFillerPosition = this._getInlineFillerPosition();
}
// Otherwise, if it's needed, create it at the selection position.
else if ( this._needsInlineFillerAtSelection() ) {
inlineFillerPosition = this.selection.getFirstPosition();
// Do not use `markToSync` so it will be added even if the parent is already added.
this.markedChildren.add( inlineFillerPosition.parent );
}
}
// Paranoid check: we make sure the inline filler has any parent so it can be mapped to view position
// by DomConverter.
else if ( this._inlineFiller && this._inlineFiller.parentNode ) {
// While the user is making selection, preserve the inline filler at its original position.
inlineFillerPosition = this.domConverter.domPositionToView( this._inlineFiller );
}
for ( const element of this.markedAttributes ) {
this._updateAttrs( element );
}
for ( const element of this.markedChildren ) {
this._updateChildren( element, { inlineFillerPosition } );
}
for ( const node of this.markedTexts ) {
if ( !this.markedChildren.has( node.parent ) && this.domConverter.mapViewToDom( node.parent ) ) {
this._updateText( node, { inlineFillerPosition } );
}
}
// * Check whether the inline filler is required and where it really is in the DOM.
// At this point in most cases it will be in the DOM, but there are exceptions.
// For example, if the inline filler was deep in the created DOM structure, it will not be created.
// Similarly, if it was removed at the beginning of this function and then neither text nor children were updated,
// it will not be present. Fix those and similar scenarios.
// * Don't manipulate inline fillers while the selection is being made in (non-Android) Blink to prevent accidental
// DOM selection collapsing
// (https://github.com/ckeditor/ckeditor5/issues/10562, https://github.com/ckeditor/ckeditor5/issues/10723).
if ( isInlineFillerRenderingPossible ) {
if ( inlineFillerPosition ) {
const fillerDomPosition = this.domConverter.viewPositionToDom( inlineFillerPosition );
const domDocument = fillerDomPosition.parent.ownerDocument;
if ( !(0,_filler__WEBPACK_IMPORTED_MODULE_2__.startsWithFiller)( fillerDomPosition.parent ) ) {
// Filler has not been created at filler position. Create it now.
this._inlineFiller = addInlineFiller( domDocument, fillerDomPosition.parent, fillerDomPosition.offset );
} else {
// Filler has been found, save it.
this._inlineFiller = fillerDomPosition.parent;
}
} else {
// There is no filler needed.
this._inlineFiller = null;
}
}
// First focus the new editing host, then update the selection.
// Otherwise, FF may throw an error (https://github.com/ckeditor/ckeditor5/issues/721).
this._updateFocus();
this._updateSelection();
this.markedTexts.clear();
this.markedAttributes.clear();
this.markedChildren.clear();
}
/**
* Updates mappings of view element's children.
*
* Children that were replaced in the view structure by similar elements (same tag name) are treated as 'replaced'.
* This means that their mappings can be updated so the new view elements are mapped to the existing DOM elements.
* Thanks to that these elements do not need to be re-rendered completely.
*
* @private
* @param {module:engine/view/node~Node} viewElement The view element whose children mappings will be updated.
*/
_updateChildrenMappings( viewElement ) {
const domElement = this.domConverter.mapViewToDom( viewElement );
if ( !domElement ) {
// If there is no `domElement` it means that it was already removed from DOM and there is no need to process it.
return;
}
// Removing nodes from the DOM as we iterate can cause `actualDomChildren`
// (which is a live-updating `NodeList`) to get out of sync with the
// indices that we compute as we iterate over `actions`.
// This would produce incorrect element mappings.
//
// Converting live list to an array to make the list static.
const actualDomChildren = Array.from(
this.domConverter.mapViewToDom( viewElement ).childNodes
);
const expectedDomChildren = Array.from(
this.domConverter.viewChildrenToDom( viewElement, domElement.ownerDocument, { withChildren: false } )
);
const diff = this._diffNodeLists( actualDomChildren, expectedDomChildren );
const actions = this._findReplaceActions( diff, actualDomChildren, expectedDomChildren );
if ( actions.indexOf( 'replace' ) !== -1 ) {
const counter = { equal: 0, insert: 0, delete: 0 };
for ( const action of actions ) {
if ( action === 'replace' ) {
const insertIndex = counter.equal + counter.insert;
const deleteIndex = counter.equal + counter.delete;
const viewChild = viewElement.getChild( insertIndex );
// UIElement and RawElement are special cases. Their children are not stored in a view (#799)
// so we cannot use them with replacing flow (since they use view children during rendering
// which will always result in rendering empty elements).
if ( viewChild && !( viewChild.is( 'uiElement' ) || viewChild.is( 'rawElement' ) ) ) {
this._updateElementMappings( viewChild, actualDomChildren[ deleteIndex ] );
}
(0,_ckeditor_ckeditor5_utils_src_dom_remove__WEBPACK_IMPORTED_MODULE_6__["default"])( expectedDomChildren[ insertIndex ] );
counter.equal++;
} else {
counter[ action ]++;
}
}
}
}
/**
* Updates mappings of a given view element.
*
* @private
* @param {module:engine/view/node~Node} viewElement The view element whose mappings will be updated.
* @param {Node} domElement The DOM element representing the given view element.
*/
_updateElementMappings( viewElement, domElement ) {
// Remap 'DomConverter' bindings.
this.domConverter.unbindDomElement( domElement );
this.domConverter.bindElements( domElement, viewElement );
// View element may have children which needs to be updated, but are not marked, mark them to update.
this.markedChildren.add( viewElement );
// Because we replace new view element mapping with the existing one, the corresponding DOM element
// will not be rerendered. The new view element may have different attributes than the previous one.
// Since its corresponding DOM element will not be rerendered, new attributes will not be added
// to the DOM, so we need to mark it here to make sure its attributes gets updated. See #1427 for more
// detailed case study.
// Also there are cases where replaced element is removed from the view structure and then has
// its attributes changed or removed. In such cases the element will not be present in `markedAttributes`
// and also may be the same (`element.isSimilar()`) as the reused element not having its attributes updated.
// To prevent such situations we always mark reused element to have its attributes rerenderd (#1560).
this.markedAttributes.add( viewElement );
}
/**
* Gets the position of the inline filler based on the current selection.
* Here, we assume that we know that the filler is needed and
* {@link #_isSelectionInInlineFiller is at the selection position}, and, since it is needed,
* it is somewhere at the selection position.
*
* Note: The filler position cannot be restored based on the filler's DOM text node, because
* when this method is called (before rendering), the bindings will often be broken. View-to-DOM
* bindings are only dependable after rendering.
*
* @private
* @returns {module:engine/view/position~Position}
*/
_getInlineFillerPosition() {
const firstPos = this.selection.getFirstPosition();
if ( firstPos.parent.is( '$text' ) ) {
return _position__WEBPACK_IMPORTED_MODULE_1__["default"]._createBefore( this.selection.getFirstPosition().parent );
} else {
return firstPos;
}
}
/**
* Returns `true` if the selection has not left the inline filler's text node.
* If it is `true`, it means that the filler had been added for a reason and the selection did not
* leave the filler's text node. For example, the user can be in the middle of a composition so it should not be touched.
*
* @private
* @returns {Boolean} `true` if the inline filler and selection are in the same place.
*/
_isSelectionInInlineFiller() {
if ( this.selection.rangeCount != 1 || !this.selection.isCollapsed ) {
return false;
}
// Note, we can't check if selection's position equals position of the
// this._inlineFiller node, because of #663. We may not be able to calculate
// the filler's position in the view at this stage.
// Instead, we check it the other way – whether selection is anchored in
// that text node or next to it.
// Possible options are:
// "FILLER{}"
// "FILLERadded-text{}"
const selectionPosition = this.selection.getFirstPosition();
const position = this.domConverter.viewPositionToDom( selectionPosition );
if ( position && (0,_ckeditor_ckeditor5_utils_src_dom_istext__WEBPACK_IMPORTED_MODULE_9__["default"])( position.parent ) && (0,_filler__WEBPACK_IMPORTED_MODULE_2__.startsWithFiller)( position.parent ) ) {
return true;
}
return false;
}
/**
* Removes the inline filler.
*
* @private
*/
_removeInlineFiller() {
const domFillerNode = this._inlineFiller;
// Something weird happened and the stored node doesn't contain the filler's text.
if ( !(0,_filler__WEBPACK_IMPORTED_MODULE_2__.startsWithFiller)( domFillerNode ) ) {
/**
* The inline filler node was lost. Most likely, something overwrote the filler text node
* in the DOM.
*
* @error view-renderer-filler-was-lost
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_8__["default"]( 'view-renderer-filler-was-lost', this );
}
if ( (0,_filler__WEBPACK_IMPORTED_MODULE_2__.isInlineFiller)( domFillerNode ) ) {
domFillerNode.remove();
} else {
domFillerNode.data = domFillerNode.data.substr( _filler__WEBPACK_IMPORTED_MODULE_2__.INLINE_FILLER_LENGTH );
}
this._inlineFiller = null;
}
/**
* Checks if the inline {@link module:engine/view/filler filler} should be added.
*
* @private
* @returns {Boolean} `true` if the inline filler should be added.
*/
_needsInlineFillerAtSelection() {
if ( this.selection.rangeCount != 1 || !this.selection.isCollapsed ) {
return false;
}
const selectionPosition = this.selection.getFirstPosition();
const selectionParent = selectionPosition.parent;
const selectionOffset = selectionPosition.offset;
// If there is no DOM root we do not care about fillers.
if ( !this.domConverter.mapViewToDom( selectionParent.root ) ) {
return false;
}
if ( !( selectionParent.is( 'element' ) ) ) {
return false;
}
// Prevent adding inline filler inside elements with contenteditable=false.
// https://github.com/ckeditor/ckeditor5-engine/issues/1170
if ( !isEditable( selectionParent ) ) {
return false;
}
// We have block filler, we do not need inline one.
if ( selectionOffset === selectionParent.getFillerOffset() ) {
return false;
}
const nodeBefore = selectionPosition.nodeBefore;
const nodeAfter = selectionPosition.nodeAfter;
if ( nodeBefore instanceof _text__WEBPACK_IMPORTED_MODULE_0__["default"] || nodeAfter instanceof _text__WEBPACK_IMPORTED_MODULE_0__["default"] ) {
return false;
}
return true;
}
/**
* Checks if text needs to be updated and possibly updates it.
*
* @private
* @param {module:engine/view/text~Text} viewText View text to update.
* @param {Object} options
* @param {module:engine/view/position~Position} options.inlineFillerPosition The position where the inline
* filler should be rendered.
*/
_updateText( viewText, options ) {
const domText = this.domConverter.findCorrespondingDomText( viewText );
const newDomText = this.domConverter.viewToDom( viewText, domText.ownerDocument );
const actualText = domText.data;
let expectedText = newDomText.data;
const filler = options.inlineFillerPosition;
if ( filler && filler.parent == viewText.parent && filler.offset == viewText.index ) {
expectedText = _filler__WEBPACK_IMPORTED_MODULE_2__.INLINE_FILLER + expectedText;
}
if ( actualText != expectedText ) {
const actions = (0,_ckeditor_ckeditor5_utils_src_fastdiff__WEBPACK_IMPORTED_MODULE_12__["default"])( actualText, expectedText );
for ( const action of actions ) {
if ( action.type === 'insert' ) {
domText.insertData( action.index, action.values.join( '' ) );
} else { // 'delete'
domText.deleteData( action.index, action.howMany );
}
}
}
}
/**
* Checks if attribute list needs to be updated and possibly updates it.
*
* @private
* @param {module:engine/view/element~Element} viewElement The view element to update.
*/
_updateAttrs( viewElement ) {
const domElement = this.domConverter.mapViewToDom( viewElement );
if ( !domElement ) {
// If there is no `domElement` it means that 'viewElement' is outdated as its mapping was updated
// in 'this._updateChildrenMappings()'. There is no need to process it as new view element which
// replaced old 'viewElement' mapping was also added to 'this.markedAttributes'
// in 'this._updateChildrenMappings()' so it will be processed separately.
return;
}
const domAttrKeys = Array.from( domElement.attributes ).map( attr => attr.name );
const viewAttrKeys = viewElement.getAttributeKeys();
// Add or overwrite attributes.
for ( const key of viewAttrKeys ) {
this.domConverter.setDomElementAttribute( domElement, key, viewElement.getAttribute( key ), viewElement );
}
// Remove from DOM attributes which do not exists in the view.
for ( const key of domAttrKeys ) {
// All other attributes not present in the DOM should be removed.
if ( !viewElement.hasAttribute( key ) ) {
this.domConverter.removeDomElementAttribute( domElement, key );
}
}
}
/**
* Checks if elements child list needs to be updated and possibly updates it.
*
* @private
* @param {module:engine/view/element~Element} viewElement View element to update.
* @param {Object} options
* @param {module:engine/view/position~Position} options.inlineFillerPosition The position where the inline
* filler should be rendered.
*/
_updateChildren( viewElement, options ) {
const domElement = this.domConverter.mapViewToDom( viewElement );
if ( !domElement ) {
// If there is no `domElement` it means that it was already removed from DOM.
// There is no need to process it. It will be processed when re-inserted.
return;
}
const inlineFillerPosition = options.inlineFillerPosition;
const actualDomChildren = this.domConverter.mapViewToDom( viewElement ).childNodes;
const expectedDomChildren = Array.from(
this.domConverter.viewChildrenToDom( viewElement, domElement.ownerDocument, { bind: true } )
);
// Inline filler element has to be created as it is present in the DOM, but not in the view. It is required
// during diffing so text nodes could be compared correctly and also during rendering to maintain
// proper order and indexes while updating the DOM.
if ( inlineFillerPosition && inlineFillerPosition.parent === viewElement ) {
addInlineFiller( domElement.ownerDocument, expectedDomChildren, inlineFillerPosition.offset );
}
const diff = this._diffNodeLists( actualDomChildren, expectedDomChildren );
let i = 0;
const nodesToUnbind = new Set();
// Handle deletions first.
// This is to prevent a situation where an element that already exists in `actualDomChildren` is inserted at a different
// index in `actualDomChildren`. Since `actualDomChildren` is a `NodeList`, this works like move, not like an insert,
// and it disrupts the whole algorithm. See https://github.com/ckeditor/ckeditor5/issues/6367.
//
// It doesn't matter in what order we remove or add nodes, as long as we remove and add correct nodes at correct indexes.
for ( const action of diff ) {
if ( action === 'delete' ) {
nodesToUnbind.add( actualDomChildren[ i ] );
(0,_ckeditor_ckeditor5_utils_src_dom_remove__WEBPACK_IMPORTED_MODULE_6__["default"])( actualDomChildren[ i ] );
} else if ( action === 'equal' ) {
i++;
}
}
i = 0;
for ( const action of diff ) {
if ( action === 'insert' ) {
(0,_ckeditor_ckeditor5_utils_src_dom_insertat__WEBPACK_IMPORTED_MODULE_5__["default"])( domElement, i, expectedDomChildren[ i ] );
i++;
} else if ( action === 'equal' ) {
// Force updating text nodes inside elements which did not change and do not need to be re-rendered (#1125).
// Do it here (not in the loop above) because only after insertions the `i` index is correct.
this._markDescendantTextToSync( this.domConverter.domToView( expectedDomChildren[ i ] ) );
i++;
}
}
// Unbind removed nodes. When node does not have a parent it means that it was removed from DOM tree during
// comparison with the expected DOM. We don't need to check child nodes, because if child node was reinserted,
// it was moved to DOM tree out of the removed node.
for ( const node of nodesToUnbind ) {
if ( !node.parentNode ) {
this.domConverter.unbindDomElement( node );
}
}
}
/**
* Shorthand for diffing two arrays or node lists of DOM nodes.
*
* @private
* @param {Array.<Node>|NodeList} actualDomChildren Actual DOM children
* @param {Array.<Node>|NodeList} expectedDomChildren Expected DOM children.
* @returns {Array.<String>} The list of actions based on the {@link module:utils/diff~diff} function.
*/
_diffNodeLists( actualDomChildren, expectedDomChildren ) {
actualDomChildren = filterOutFakeSelectionContainer( actualDomChildren, this._fakeSelectionContainer );
return (0,_ckeditor_ckeditor5_utils_src_diff__WEBPACK_IMPORTED_MODULE_4__["default"])( actualDomChildren, expectedDomChildren, sameNodes.bind( null, this.domConverter ) );
}
/**
* Finds DOM nodes that were replaced with the similar nodes (same tag name) in the view. All nodes are compared
* within one `insert`/`delete` action group, for example:
*
* Actual DOM: <p><b>Foo</b>Bar<i>Baz</i><b>Bax</b></p>
* Expected DOM: <p>Bar<b>123</b><i>Baz</i><b>456</b></p>
* Input actions: [ insert, insert, delete, delete, equal, insert, delete ]
* Output actions: [ insert, replace, delete, equal, replace ]
*
* @private
* @param {Array.<String>} actions Actions array which is a result of the {@link module:utils/diff~diff} function.
* @param {Array.<Node>|NodeList} actualDom Actual DOM children
* @param {Array.<Node>} expectedDom Expected DOM children.
* @returns {Array.<String>} Actions array modified with the `replace` actions.
*/
_findReplaceActions( actions, actualDom, expectedDom ) {
// If there is no both 'insert' and 'delete' actions, no need to check for replaced elements.
if ( actions.indexOf( 'insert' ) === -1 || actions.indexOf( 'delete' ) === -1 ) {
return actions;
}
let newActions = [];
let actualSlice = [];
let expectedSlice = [];
const counter = { equal: 0, insert: 0, delete: 0 };
for ( const action of actions ) {
if ( action === 'insert' ) {
expectedSlice.push( expectedDom[ counter.equal + counter.insert ] );
} else if ( action === 'delete' ) {
actualSlice.push( actualDom[ counter.equal + counter.delete ] );
} else { // equal
newActions = newActions.concat( (0,_ckeditor_ckeditor5_utils_src_diff__WEBPACK_IMPORTED_MODULE_4__["default"])( actualSlice, expectedSlice, areSimilar ).map( x => x === 'equal' ? 'replace' : x ) );
newActions.push( 'equal' );
// Reset stored elements on 'equal'.
actualSlice = [];
expectedSlice = [];
}
counter[ action ]++;
}
return newActions.concat( (0,_ckeditor_ckeditor5_utils_src_diff__WEBPACK_IMPORTED_MODULE_4__["default"])( actualSlice, expectedSlice, areSimilar ).map( x => x === 'equal' ? 'replace' : x ) );
}
/**
* Marks text nodes to be synchronized.
*
* If a text node is passed, it will be marked. If an element is passed, all descendant text nodes inside it will be marked.
*
* @private
* @param {module:engine/view/node~Node} viewNode View node to sync.
*/
_markDescendantTextToSync( viewNode ) {
if ( !viewNode ) {
return;
}
if ( viewNode.is( '$text' ) ) {
this.markedTexts.add( viewNode );
} else if ( viewNode.is( 'element' ) ) {
for ( const child of viewNode.getChildren() ) {
this._markDescendantTextToSync( child );
}
}
}
/**
* Checks if the selection needs to be updated and possibly updates it.
*
* @private
*/
_updateSelection() {
// Block updating DOM selection in (non-Android) Blink while the user is selecting to prevent accidental selection collapsing.
// Note: Structural changes in DOM must trigger selection rendering, though. Nodes the selection was anchored
// to, may disappear in DOM which would break the selection (e.g. in real-time collaboration scenarios).
// https://github.com/ckeditor/ckeditor5/issues/10562, https://github.com/ckeditor/ckeditor5/issues/10723
if ( _ckeditor_ckeditor5_utils_src_env__WEBPACK_IMPORTED_MODULE_13__["default"].isBlink && !_ckeditor_ckeditor5_utils_src_env__WEBPACK_IMPORTED_MODULE_13__["default"].isAndroid && this.isSelecting && !this.markedChildren.size ) {
return;
}
// If there is no selection - remove DOM and fake selections.
if ( this.selection.rangeCount === 0 ) {
this._removeDomSelection();
this._removeFakeSelection();
return;
}
const domRoot = this.domConverter.mapViewToDom( this.selection.editableElement );
// Do nothing if there is no focus, or there is no DOM element corresponding to selection's editable element.
if ( !this.isFocused || !domRoot ) {
return;
}
// Render selection.
if ( this.selection.isFake ) {
this._updateFakeSelection( domRoot );
} else {
this._removeFakeSelection();
this._updateDomSelection( domRoot );
}
}
/**
* Updates the fake selection.
*
* @private
* @param {HTMLElement} domRoot A valid DOM root where the fake selection container should be added.
*/
_updateFakeSelection( domRoot ) {
const domDocument = domRoot.ownerDocument;
if ( !this._fakeSelectionContainer ) {
this._fakeSelectionContainer = createFakeSelectionContainer( domDocument );
}
const container = this._fakeSelectionContainer;
// Bind fake selection container with the current selection *position*.
this.domConverter.bindFakeSelection( container, this.selection );
if ( !this._fakeSelectionNeedsUpdate( domRoot ) ) {
return;
}
if ( !container.parentElement || container.parentElement != domRoot ) {
domRoot.appendChild( container );
}
container.textContent = this.selection.fakeSelectionLabel || '\u00A0';
const domSelection = domDocument.getSelection();
const domRange = domDocument.createRange();
domSelection.removeAllRanges();
domRange.selectNodeContents( container );
domSelection.addRange( domRange );
}
/**
* Updates the DOM selection.
*
* @private
* @param {HTMLElement} domRoot A valid DOM root where the DOM selection should be rendered.
*/
_updateDomSelection( domRoot ) {
const domSelection = domRoot.ownerDocument.defaultView.getSelection();
// Let's check whether DOM selection needs updating at all.
if ( !this._domSelectionNeedsUpdate( domSelection ) ) {
return;
}
// Multi-range selection is not available in most browsers, and, at least in Chrome, trying to
// set such selection, that is not continuous, throws an error. Because of that, we will just use anchor
// and focus of view selection.
// Since we are not supporting multi-range selection, we also do not need to check if proper editable is
// selected. If there is any editable selected, it is okay (editable is taken from selection anchor).
const anchor = this.domConverter.viewPositionToDom( this.selection.anchor );
const focus = this.domConverter.viewPositionToDom( this.selection.focus );
domSelection.collapse( anchor.parent, anchor.offset );
domSelection.extend( focus.parent, focus.offset );
// Firefox–specific hack (https://github.com/ckeditor/ckeditor5-engine/issues/1439).
if ( _ckeditor_ckeditor5_utils_src_env__WEBPACK_IMPORTED_MODULE_13__["default"].isGecko ) {
fixGeckoSelectionAfterBr( focus, domSelection );
}
}
/**
* Checks whether a given DOM selection needs to be updated.
*
* @private
* @param {Selection} domSelection The DOM selection to check.
* @returns {Boolean}
*/
_domSelectionNeedsUpdate( domSelection ) {
if ( !this.domConverter.isDomSelectionCorrect( domSelection ) ) {
// Current DOM selection is in incorrect position. We need to update it.
return true;
}
const oldViewSelection = domSelection && this.domConverter.domSelectionToView( domSelection );
if ( oldViewSelection && this.selection.isEqual( oldViewSelection ) ) {
return false;
}
// If selection is not collapsed, it does not need to be updated if it is similar.
if ( !this.selection.isCollapsed && this.selection.isSimilar( oldViewSelection ) ) {
// Selection did not changed and is correct, do not update.
return false;
}
// Selections are not similar.
return true;
}
/**
* Checks whether the fake selection needs to be updated.
*
* @private
* @param {HTMLElement} domRoot A valid DOM root where a new fake selection container should be added.
* @returns {Boolean}
*/
_fakeSelectionNeedsUpdate( domRoot ) {
const container = this._fakeSelectionContainer;
const domSelection = domRoot.ownerDocument.getSelection();
// Fake selection needs to be updated if there's no fake selection container, or the container currently sits
// in a different root.
if ( !container || container.parentElement !== domRoot ) {
return true;
}
// Make sure that the selection actually is within the fake selection.
if ( domSelection.anchorNode !== container && !container.contains( domSelection.anchorNode ) ) {
return true;
}
return container.textContent !== this.selection.fakeSelectionLabel;
}
/**
* Removes the DOM selection.
*
* @private
*/
_removeDomSelection() {
for ( const doc of this.domDocuments ) {
const domSelection = doc.getSelection();
if ( domSelection.rangeCount ) {
const activeDomElement = doc.activeElement;
const viewElement = this.domConverter.mapDomToView( activeDomElement );
if ( activeDomElement && viewElement ) {
doc.getSelection().removeAllRanges();
}
}
}
}
/**
* Removes the fake selection.
*
* @private
*/
_removeFakeSelection() {
const container = this._fakeSelectionContainer;
if ( container ) {
container.remove();
}
}
/**
* Checks if focus needs to be updated and possibly updates it.
*
* @private
*/
_updateFocus() {
if ( this.isFocused ) {
const editable = this.selection.editableElement;
if ( editable ) {
this.domConverter.focus( editable );
}
}
}
}
(0,_ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_3__["default"])( Renderer, _ckeditor_ckeditor5_utils_src_observablemixin__WEBPACK_IMPORTED_MODULE_7__["default"] );
// Checks if provided element is editable.
//
// @private
// @param {module:engine/view/element~Element} element
// @returns {Boolean}
function isEditable( element ) {
if ( element.getAttribute( 'contenteditable' ) == 'false' ) {
return false;
}
const parent = element.findAncestor( element => element.hasAttribute( 'contenteditable' ) );
return !parent || parent.getAttribute( 'contenteditable' ) == 'true';
}
// Adds inline filler at a given position.
//
// The position can be given as an array of DOM nodes and an offset in that array,
// or a DOM parent element and an offset in that element.
//
// @private
// @param {Document} domDocument
// @param {Element|Array.<Node>} domParentOrArray
// @param {Number} offset
// @returns {Text} The DOM text node that contains an inline filler.
function addInlineFiller( domDocument, domParentOrArray, offset ) {
const childNodes = domParentOrArray instanceof Array ? domParentOrArray : domParentOrArray.childNodes;
const nodeAfterFiller = childNodes[ offset ];
if ( (0,_ckeditor_ckeditor5_utils_src_dom_istext__WEBPACK_IMPORTED_MODULE_9__["default"])( nodeAfterFiller ) ) {
nodeAfterFiller.data = _filler__WEBPACK_IMPORTED_MODULE_2__.INLINE_FILLER + nodeAfterFiller.data;
return nodeAfterFiller;
} else {
const fillerNode = domDocument.createTextNode( _filler__WEBPACK_IMPORTED_MODULE_2__.INLINE_FILLER );
if ( Array.isArray( domParentOrArray ) ) {
childNodes.splice( offset, 0, fillerNode );
} else {
(0,_ckeditor_ckeditor5_utils_src_dom_insertat__WEBPACK_IMPORTED_MODULE_5__["default"])( domParentOrArray, offset, fillerNode );
}
return fillerNode;
}
}
// Whether two DOM nodes should be considered as similar.
// Nodes are considered similar if they have the same tag name.
//
// @private
// @param {Node} node1
// @param {Node} node2
// @returns {Boolean}
function areSimilar( node1, node2 ) {
return (0,_ckeditor_ckeditor5_utils_src_dom_isnode__WEBPACK_IMPORTED_MODULE_11__["default"])( node1 ) && (0,_ckeditor_ckeditor5_utils_src_dom_isnode__WEBPACK_IMPORTED_MODULE_11__["default"])( node2 ) &&
!(0,_ckeditor_ckeditor5_utils_src_dom_istext__WEBPACK_IMPORTED_MODULE_9__["default"])( node1 ) && !(0,_ckeditor_ckeditor5_utils_src_dom_istext__WEBPACK_IMPORTED_MODULE_9__["default"])( node2 ) &&
!(0,_ckeditor_ckeditor5_utils_src_dom_iscomment__WEBPACK_IMPORTED_MODULE_10__["default"])( node1 ) && !(0,_ckeditor_ckeditor5_utils_src_dom_iscomment__WEBPACK_IMPORTED_MODULE_10__["default"])( node2 ) &&
node1.tagName.toLowerCase() === node2.tagName.toLowerCase();
}
// Whether two dom nodes should be considered as the same.
// Two nodes which are considered the same are:
//
// * Text nodes with the same text.
// * Element nodes represented by the same object.
// * Two block filler elements.
//
// @private
// @param {String} blockFillerMode Block filler mode, see {@link module:engine/view/domconverter~DomConverter#blockFillerMode}.
// @param {Node} node1
// @param {Node} node2
// @returns {Boolean}
function sameNodes( domConverter, actualDomChild, expectedDomChild ) {
// Elements.
if ( actualDomChild === expectedDomChild ) {
return true;
}
// Texts.
else if ( (0,_ckeditor_ckeditor5_utils_src_dom_istext__WEBPACK_IMPORTED_MODULE_9__["default"])( actualDomChild ) && (0,_ckeditor_ckeditor5_utils_src_dom_istext__WEBPACK_IMPORTED_MODULE_9__["default"])( expectedDomChild ) ) {
return actualDomChild.data === expectedDomChild.data;
}
// Block fillers.
else if ( domConverter.isBlockFiller( actualDomChild ) &&
domConverter.isBlockFiller( expectedDomChild ) ) {
return true;
}
// Not matching types.
return false;
}
// The following is a Firefox–specific hack (https://github.com/ckeditor/ckeditor5-engine/issues/1439).
// When the native DOM selection is at the end of the block and preceded by <br /> e.g.
//
// <p>foo<br/>[]</p>
//
// which happens a lot when using the soft line break, the browser fails to (visually) move the
// caret to the new line. A quick fix is as simple as force–refreshing the selection with the same range.
function fixGeckoSelectionAfterBr( focus, domSelection ) {
const parent = focus.parent;
// This fix works only when the focus point is at the very end of an element.
// There is no point in running it in cases unrelated to the browser bug.
if ( parent.nodeType != Node.ELEMENT_NODE || focus.offset != parent.childNodes.length - 1 ) {
return;
}
const childAtOffset = parent.childNodes[ focus.offset ];
// To stay on the safe side, the fix being as specific as possible, it targets only the
// selection which is at the very end of the element and preceded by <br />.
if ( childAtOffset && childAtOffset.tagName == 'BR' ) {
domSelection.addRange( domSelection.getRangeAt( 0 ) );
}
}
function filterOutFakeSelectionContainer( domChildList, fakeSelectionContainer ) {
const childList = Array.from( domChildList );
if ( childList.length == 0 || !fakeSelectionContainer ) {
return childList;
}
const last = childList[ childList.length - 1 ];
if ( last == fakeSelectionContainer ) {
childList.pop();
}
return childList;
}
// Creates a fake selection container for a given document.
//
// @private
// @param {Document} domDocument
// @returns {HTMLElement}
function createFakeSelectionContainer( domDocument ) {
const container = domDocument.createElement( 'div' );
container.className = 'ck-fake-selection-container';
Object.assign( container.style, {
position: 'fixed',
top: 0,
left: '-9999px',
// See https://github.com/ckeditor/ckeditor5/issues/752.
width: '42px'
} );
// Fill it with a text node so we can update it later.
container.textContent = '\u00A0';
return container;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/view/rooteditableelement.js":
/*!*********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/view/rooteditableelement.js ***!
\*********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ RootEditableElement)
/* harmony export */ });
/* harmony import */ var _editableelement__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./editableelement */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/editableelement.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/view/rooteditableelement
*/
const rootNameSymbol = Symbol( 'rootName' );
/**
* Class representing a single root in the data view. A root can be either {@link ~RootEditableElement#isReadOnly editable or read-only},
* but in both cases it is called "an editable". Roots can contain other {@link module:engine/view/editableelement~EditableElement
* editable elements} making them "nested editables".
*
* @extends module:engine/view/editableelement~EditableElement
*/
class RootEditableElement extends _editableelement__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* Creates root editable element.
*
* @param {module:engine/view/document~Document} document The document instance to which this element belongs.
* @param {String} name Node name.
*/
constructor( document, name ) {
super( document, name );
/**
* Name of this root inside {@link module:engine/view/document~Document} that is an owner of this root. If no
* other name is set, `main` name is used.
*
* @readonly
* @member {String}
*/
this.rootName = 'main';
}
/**
* Checks whether this object is of the given.
*
* rootEditableElement.is( 'rootElement' ); // -> true
* rootEditableElement.is( 'editableElement' ); // -> true
* rootEditableElement.is( 'element' ); // -> true
* rootEditableElement.is( 'node' ); // -> true
* rootEditableElement.is( 'view:editableElement' ); // -> true
* rootEditableElement.is( 'view:element' ); // -> true
* rootEditableElement.is( 'view:node' ); // -> true
*
* rootEditableElement.is( 'model:element' ); // -> false
* rootEditableElement.is( 'documentFragment' ); // -> false
*
* Assuming that the object being checked is a root editable element, you can also check its
* {@link module:engine/view/rooteditableelement~RootEditableElement#name name}:
*
* rootEditableElement.is( 'element', 'div' ); // -> true if this is a div root editable element
* rootEditableElement.is( 'rootElement', 'div' ); // -> same as above
* text.is( 'element', 'div' ); -> false
*
* {@link module:engine/view/node~Node#is Check the entire list of view objects} which implement the `is()` method.
*
* @param {String} type Type to check.
* @param {String} [name] Element name.
* @returns {Boolean}
*/
is( type, name = null ) {
if ( !name ) {
return type === 'rootElement' || type === 'view:rootElement' ||
// From super.is(). This is highly utilised method and cannot call super. See ckeditor/ckeditor5#6529.
type === 'editableElement' || type === 'view:editableElement' ||
type === 'containerElement' || type === 'view:containerElement' ||
type === 'element' || type === 'view:element' ||
type === 'node' || type === 'view:node';
} else {
return name === this.name && (
type === 'rootElement' || type === 'view:rootElement' ||
// From super.is(). This is highly utilised method and cannot call super. See ckeditor/ckeditor5#6529.
type === 'editableElement' || type === 'view:editableElement' ||
type === 'containerElement' || type === 'view:containerElement' ||
type === 'element' || type === 'view:element'
);
}
}
get rootName() {
return this.getCustomProperty( rootNameSymbol );
}
set rootName( rootName ) {
this._setCustomProperty( rootNameSymbol, rootName );
}
/**
* Overrides old element name and sets new one.
* This is needed because view roots are created before they are attached to the DOM.
* The name of the root element is temporary at this stage. It has to be changed when the
* view root element is attached to the DOM element.
*
* @protected
* @param {String} name The new name of element.
*/
set _name( name ) {
this.name = name;
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/view/selection.js":
/*!***********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/view/selection.js ***!
\***********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ Selection)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/* harmony import */ var _range__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./range */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/range.js");
/* harmony import */ var _position__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./position */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/position.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/mix */ "./node_modules/@ckeditor/ckeditor5-utils/src/mix.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_emittermixin__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/emittermixin */ "./node_modules/@ckeditor/ckeditor5-utils/src/emittermixin.js");
/* harmony import */ var _node__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./node */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/node.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_count__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/count */ "./node_modules/@ckeditor/ckeditor5-utils/src/count.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_isiterable__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/isiterable */ "./node_modules/@ckeditor/ckeditor5-utils/src/isiterable.js");
/* harmony import */ var _documentselection__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./documentselection */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/documentselection.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/view/selection
*/
/**
* Class representing an arbirtary selection in the view.
* See also {@link module:engine/view/documentselection~DocumentSelection}.
*
* New selection instances can be created via the constructor or one these methods:
*
* * {@link module:engine/view/view~View#createSelection `View#createSelection()`},
* * {@link module:engine/view/upcastwriter~UpcastWriter#createSelection `UpcastWriter#createSelection()`}.
*
* A selection can consist of {@link module:engine/view/range~Range ranges} that can be set by using
* the {@link module:engine/view/selection~Selection#setTo `Selection#setTo()`} method.
*/
class Selection {
/**
* Creates new selection instance.
*
* **Note**: The selection constructor is available as a factory method:
*
* * {@link module:engine/view/view~View#createSelection `View#createSelection()`},
* * {@link module:engine/view/upcastwriter~UpcastWriter#createSelection `UpcastWriter#createSelection()`}.
*
* // Creates empty selection without ranges.
* const selection = writer.createSelection();
*
* // Creates selection at the given range.
* const range = writer.createRange( start, end );
* const selection = writer.createSelection( range );
*
* // Creates selection at the given ranges
* const ranges = [ writer.createRange( start1, end2 ), writer.createRange( star2, end2 ) ];
* const selection = writer.createSelection( ranges );
*
* // Creates selection from the other selection.
* const otherSelection = writer.createSelection();
* const selection = writer.createSelection( otherSelection );
*
* // Creates selection from the document selection.
* const selection = writer.createSelection( editor.editing.view.document.selection );
*
* // Creates selection at the given position.
* const position = writer.createPositionFromPath( root, path );
* const selection = writer.createSelection( position );
*
* // Creates collapsed selection at the position of given item and offset.
* const paragraph = writer.createContainerElement( 'paragraph' );
* const selection = writer.createSelection( paragraph, offset );
*
* // Creates a range inside an {@link module:engine/view/element~Element element} which starts before the
* // first child of that element and ends after the last child of that element.
* const selection = writer.createSelection( paragraph, 'in' );
*
* // Creates a range on an {@link module:engine/view/item~Item item} which starts before the item and ends
* // just after the item.
* const selection = writer.createSelection( paragraph, 'on' );
*
* `Selection`'s constructor allow passing additional options (`backward`, `fake` and `label`) as the last argument.
*
* // Creates backward selection.
* const selection = writer.createSelection( range, { backward: true } );
*
* Fake selection does not render as browser native selection over selected elements and is hidden to the user.
* This way, no native selection UI artifacts are displayed to the user and selection over elements can be
* represented in other way, for example by applying proper CSS class.
*
* Additionally fake's selection label can be provided. It will be used to describe fake selection in DOM
* (and be properly handled by screen readers).
*
* // Creates fake selection with label.
* const selection = writer.createSelection( range, { fake: true, label: 'foo' } );
*
* @param {module:engine/view/selection~Selectable} [selectable=null]
* @param {Number|'before'|'end'|'after'|'on'|'in'} [placeOrOffset] Offset or place when selectable is an `Item`.
* @param {Object} [options]
* @param {Boolean} [options.backward] Sets this selection instance to be backward.
* @param {Boolean} [options.fake] Sets this selection instance to be marked as `fake`.
* @param {String} [options.label] Label for the fake selection.
*/
constructor( selectable = null, placeOrOffset, options ) {
/**
* Stores all ranges that are selected.
*
* @protected
* @member {Array.<module:engine/view/range~Range>}
*/
this._ranges = [];
/**
* Specifies whether the last added range was added as a backward or forward range.
*
* @protected
* @member {Boolean}
*/
this._lastRangeBackward = false;
/**
* Specifies whether selection instance is fake.
*
* @private
* @member {Boolean}
*/
this._isFake = false;
/**
* Fake selection's label.
*
* @private
* @member {String}
*/
this._fakeSelectionLabel = '';
this.setTo( selectable, placeOrOffset, options );
}
/**
* Returns true if selection instance is marked as `fake`.
*
* @see #setTo
* @type {Boolean}
*/
get isFake() {
return this._isFake;
}
/**
* Returns fake selection label.
*
* @see #setTo
* @type {String}
*/
get fakeSelectionLabel() {
return this._fakeSelectionLabel;
}
/**
* Selection anchor. Anchor may be described as a position where the selection starts. Together with
* {@link #focus focus} they define the direction of selection, which is important
* when expanding/shrinking selection. Anchor is always the start or end of the most recent added range.
* It may be a bit unintuitive when there are multiple ranges in selection.
*
* @see #focus
* @type {module:engine/view/position~Position}
*/
get anchor() {
if ( !this._ranges.length ) {
return null;
}
const range = this._ranges[ this._ranges.length - 1 ];
const anchor = this._lastRangeBackward ? range.end : range.start;
return anchor.clone();
}
/**
* Selection focus. Focus is a position where the selection ends.
*
* @see #anchor
* @type {module:engine/view/position~Position}
*/
get focus() {
if ( !this._ranges.length ) {
return null;
}
const range = this._ranges[ this._ranges.length - 1 ];
const focus = this._lastRangeBackward ? range.start : range.end;
return focus.clone();
}
/**
* Returns whether the selection is collapsed. Selection is collapsed when there is exactly one range which is
* collapsed.
*
* @type {Boolean}
*/
get isCollapsed() {
return this.rangeCount === 1 && this._ranges[ 0 ].isCollapsed;
}
/**
* Returns number of ranges in selection.
*
* @type {Number}
*/
get rangeCount() {
return this._ranges.length;
}
/**
* Specifies whether the {@link #focus} precedes {@link #anchor}.
*
* @type {Boolean}
*/
get isBackward() {
return !this.isCollapsed && this._lastRangeBackward;
}
/**
* {@link module:engine/view/editableelement~EditableElement EditableElement} instance that contains this selection, or `null`
* if the selection is not inside an editable element.
*
* @type {module:engine/view/editableelement~EditableElement|null}
*/
get editableElement() {
if ( this.anchor ) {
return this.anchor.editableElement;
}
return null;
}
/**
* Returns an iterable that contains copies of all ranges added to the selection.
*
* @returns {Iterable.<module:engine/view/range~Range>}
*/
* getRanges() {
for ( const range of this._ranges ) {
yield range.clone();
}
}
/**
* Returns copy of the first range in the selection. First range is the one which
* {@link module:engine/view/range~Range#start start} position {@link module:engine/view/position~Position#isBefore is before} start
* position of all other ranges (not to confuse with the first range added to the selection).
* Returns `null` if no ranges are added to selection.
*
* @returns {module:engine/view/range~Range|null}
*/
getFirstRange() {
let first = null;
for ( const range of this._ranges ) {
if ( !first || range.start.isBefore( first.start ) ) {
first = range;
}
}
return first ? first.clone() : null;
}
/**
* Returns copy of the last range in the selection. Last range is the one which {@link module:engine/view/range~Range#end end}
* position {@link module:engine/view/position~Position#isAfter is after} end position of all other ranges (not to confuse
* with the last range added to the selection). Returns `null` if no ranges are added to selection.
*
* @returns {module:engine/view/range~Range|null}
*/
getLastRange() {
let last = null;
for ( const range of this._ranges ) {
if ( !last || range.end.isAfter( last.end ) ) {
last = range;
}
}
return last ? last.clone() : null;
}
/**
* Returns copy of the first position in the selection. First position is the position that
* {@link module:engine/view/position~Position#isBefore is before} any other position in the selection ranges.
* Returns `null` if no ranges are added to selection.
*
* @returns {module:engine/view/position~Position|null}
*/
getFirstPosition() {
const firstRange = this.getFirstRange();
return firstRange ? firstRange.start.clone() : null;
}
/**
* Returns copy of the last position in the selection. Last position is the position that
* {@link module:engine/view/position~Position#isAfter is after} any other position in the selection ranges.
* Returns `null` if no ranges are added to selection.
*
* @returns {module:engine/view/position~Position|null}
*/
getLastPosition() {
const lastRange = this.getLastRange();
return lastRange ? lastRange.end.clone() : null;
}
/**
* Checks whether, this selection is equal to given selection. Selections are equal if they have same directions,
* same number of ranges and all ranges from one selection equal to a range from other selection.
*
* @param {module:engine/view/selection~Selection|module:engine/view/documentselection~DocumentSelection} otherSelection
* Selection to compare with.
* @returns {Boolean} `true` if selections are equal, `false` otherwise.
*/
isEqual( otherSelection ) {
if ( this.isFake != otherSelection.isFake ) {
return false;
}
if ( this.isFake && this.fakeSelectionLabel != otherSelection.fakeSelectionLabel ) {
return false;
}
if ( this.rangeCount != otherSelection.rangeCount ) {
return false;
} else if ( this.rangeCount === 0 ) {
return true;
}
if ( !this.anchor.isEqual( otherSelection.anchor ) || !this.focus.isEqual( otherSelection.focus ) ) {
return false;
}
for ( const thisRange of this._ranges ) {
let found = false;
for ( const otherRange of otherSelection._ranges ) {
if ( thisRange.isEqual( otherRange ) ) {
found = true;
break;
}
}
if ( !found ) {
return false;
}
}
return true;
}
/**
* Checks whether this selection is similar to given selection. Selections are similar if they have same directions, same
* number of ranges, and all {@link module:engine/view/range~Range#getTrimmed trimmed} ranges from one selection are
* equal to any trimmed range from other selection.
*
* @param {module:engine/view/selection~Selection|module:engine/view/documentselection~DocumentSelection} otherSelection
* Selection to compare with.
* @returns {Boolean} `true` if selections are similar, `false` otherwise.
*/
isSimilar( otherSelection ) {
if ( this.isBackward != otherSelection.isBackward ) {
return false;
}
const numOfRangesA = (0,_ckeditor_ckeditor5_utils_src_count__WEBPACK_IMPORTED_MODULE_6__["default"])( this.getRanges() );
const numOfRangesB = (0,_ckeditor_ckeditor5_utils_src_count__WEBPACK_IMPORTED_MODULE_6__["default"])( otherSelection.getRanges() );
// If selections have different number of ranges, they cannot be similar.
if ( numOfRangesA != numOfRangesB ) {
return false;
}
// If both selections have no ranges, they are similar.
if ( numOfRangesA == 0 ) {
return true;
}
// Check if each range in one selection has a similar range in other selection.
for ( let rangeA of this.getRanges() ) {
rangeA = rangeA.getTrimmed();
let found = false;
for ( let rangeB of otherSelection.getRanges() ) {
rangeB = rangeB.getTrimmed();
if ( rangeA.start.isEqual( rangeB.start ) && rangeA.end.isEqual( rangeB.end ) ) {
found = true;
break;
}
}
// For `rangeA`, neither range in `otherSelection` was similar. So selections are not similar.
if ( !found ) {
return false;
}
}
// There were no ranges that weren't matched. Selections are similar.
return true;
}
/**
* Returns the selected element. {@link module:engine/view/element~Element Element} is considered as selected if there is only
* one range in the selection, and that range contains exactly one element.
* Returns `null` if there is no selected element.
*
* @returns {module:engine/view/element~Element|null}
*/
getSelectedElement() {
if ( this.rangeCount !== 1 ) {
return null;
}
return this.getFirstRange().getContainedElement();
}
/**
* Sets this selection's ranges and direction to the specified location based on the given
* {@link module:engine/view/selection~Selectable selectable}.
*
* // Sets selection to the given range.
* const range = writer.createRange( start, end );
* selection.setTo( range );
*
* // Sets selection to given ranges.
* const ranges = [ writer.createRange( start1, end2 ), writer.createRange( star2, end2 ) ];
* selection.setTo( range );
*
* // Sets selection to the other selection.
* const otherSelection = writer.createSelection();
* selection.setTo( otherSelection );
*
* // Sets selection to contents of DocumentSelection.
* selection.setTo( editor.editing.view.document.selection );
*
* // Sets collapsed selection at the given position.
* const position = writer.createPositionAt( root, path );
* selection.setTo( position );
*
* // Sets collapsed selection at the position of given item and offset.
* selection.setTo( paragraph, offset );
*
* Creates a range inside an {@link module:engine/view/element~Element element} which starts before the first child of
* that element and ends after the last child of that element.
*
* selection.setTo( paragraph, 'in' );
*
* Creates a range on an {@link module:engine/view/item~Item item} which starts before the item and ends just after the item.
*
* selection.setTo( paragraph, 'on' );
*
* // Clears selection. Removes all ranges.
* selection.setTo( null );
*
* `Selection#setTo()` method allow passing additional options (`backward`, `fake` and `label`) as the last argument.
*
* // Sets selection as backward.
* selection.setTo( range, { backward: true } );
*
* Fake selection does not render as browser native selection over selected elements and is hidden to the user.
* This way, no native selection UI artifacts are displayed to the user and selection over elements can be
* represented in other way, for example by applying proper CSS class.
*
* Additionally fake's selection label can be provided. It will be used to describe fake selection in DOM
* (and be properly handled by screen readers).
*
* // Creates fake selection with label.
* selection.setTo( range, { fake: true, label: 'foo' } );
*
* @fires change
* @param {module:engine/view/selection~Selectable} selectable
* @param {Number|'before'|'end'|'after'|'on'|'in'} [placeOrOffset] Sets place or offset of the selection.
* @param {Object} [options]
* @param {Boolean} [options.backward] Sets this selection instance to be backward.
* @param {Boolean} [options.fake] Sets this selection instance to be marked as `fake`.
* @param {String} [options.label] Label for the fake selection.
*/
setTo( selectable, placeOrOffset, options ) {
if ( selectable === null ) {
this._setRanges( [] );
this._setFakeOptions( placeOrOffset );
} else if ( selectable instanceof Selection || selectable instanceof _documentselection__WEBPACK_IMPORTED_MODULE_8__["default"] ) {
this._setRanges( selectable.getRanges(), selectable.isBackward );
this._setFakeOptions( { fake: selectable.isFake, label: selectable.fakeSelectionLabel } );
} else if ( selectable instanceof _range__WEBPACK_IMPORTED_MODULE_1__["default"] ) {
this._setRanges( [ selectable ], placeOrOffset && placeOrOffset.backward );
this._setFakeOptions( placeOrOffset );
} else if ( selectable instanceof _position__WEBPACK_IMPORTED_MODULE_2__["default"] ) {
this._setRanges( [ new _range__WEBPACK_IMPORTED_MODULE_1__["default"]( selectable ) ] );
this._setFakeOptions( placeOrOffset );
} else if ( selectable instanceof _node__WEBPACK_IMPORTED_MODULE_5__["default"] ) {
const backward = !!options && !!options.backward;
let range;
if ( placeOrOffset === undefined ) {
/**
* selection.setTo requires the second parameter when the first parameter is a node.
*
* @error view-selection-setto-required-second-parameter
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_0__["default"]( 'view-selection-setto-required-second-parameter', this );
} else if ( placeOrOffset == 'in' ) {
range = _range__WEBPACK_IMPORTED_MODULE_1__["default"]._createIn( selectable );
} else if ( placeOrOffset == 'on' ) {
range = _range__WEBPACK_IMPORTED_MODULE_1__["default"]._createOn( selectable );
} else {
range = new _range__WEBPACK_IMPORTED_MODULE_1__["default"]( _position__WEBPACK_IMPORTED_MODULE_2__["default"]._createAt( selectable, placeOrOffset ) );
}
this._setRanges( [ range ], backward );
this._setFakeOptions( options );
} else if ( (0,_ckeditor_ckeditor5_utils_src_isiterable__WEBPACK_IMPORTED_MODULE_7__["default"])( selectable ) ) {
// We assume that the selectable is an iterable of ranges.
// Array.from() is used to prevent setting ranges to the old iterable
this._setRanges( selectable, placeOrOffset && placeOrOffset.backward );
this._setFakeOptions( placeOrOffset );
} else {
/**
* Cannot set selection to given place.
*
* @error view-selection-setto-not-selectable
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_0__["default"]( 'view-selection-setto-not-selectable', this );
}
this.fire( 'change' );
}
/**
* Moves {@link #focus} to the specified location.
*
* The location can be specified in the same form as {@link module:engine/view/view~View#createPositionAt view.createPositionAt()}
* parameters.
*
* @fires change
* @param {module:engine/view/item~Item|module:engine/view/position~Position} itemOrPosition
* @param {Number|'end'|'before'|'after'} [offset] Offset or one of the flags. Used only when
* first parameter is a {@link module:engine/view/item~Item view item}.
*/
setFocus( itemOrPosition, offset ) {
if ( this.anchor === null ) {
/**
* Cannot set selection focus if there are no ranges in selection.
*
* @error view-selection-setfocus-no-ranges
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_0__["default"]( 'view-selection-setfocus-no-ranges', this );
}
const newFocus = _position__WEBPACK_IMPORTED_MODULE_2__["default"]._createAt( itemOrPosition, offset );
if ( newFocus.compareWith( this.focus ) == 'same' ) {
return;
}
const anchor = this.anchor;
this._ranges.pop();
if ( newFocus.compareWith( anchor ) == 'before' ) {
this._addRange( new _range__WEBPACK_IMPORTED_MODULE_1__["default"]( newFocus, anchor ), true );
} else {
this._addRange( new _range__WEBPACK_IMPORTED_MODULE_1__["default"]( anchor, newFocus ) );
}
this.fire( 'change' );
}
/**
* Checks whether this object is of the given type.
*
* selection.is( 'selection' ); // -> true
* selection.is( 'view:selection' ); // -> true
*
* selection.is( 'model:selection' ); // -> false
* selection.is( 'element' ); // -> false
* selection.is( 'range' ); // -> false
*
* {@link module:engine/view/node~Node#is Check the entire list of view objects} which implement the `is()` method.
*
* @param {String} type
* @returns {Boolean}
*/
is( type ) {
return type === 'selection' || type === 'view:selection';
}
/**
* Replaces all ranges that were added to the selection with given array of ranges. Last range of the array
* is treated like the last added range and is used to set {@link #anchor anchor} and {@link #focus focus}.
* Accepts a flag describing in which way the selection is made.
*
* @private
* @param {Iterable.<module:engine/view/range~Range>} newRanges Iterable object of ranges to set.
* @param {Boolean} [isLastBackward=false] Flag describing if last added range was selected forward - from start to end
* (`false`) or backward - from end to start (`true`). Defaults to `false`.
*/
_setRanges( newRanges, isLastBackward = false ) {
// New ranges should be copied to prevent removing them by setting them to `[]` first.
// Only applies to situations when selection is set to the same selection or same selection's ranges.
newRanges = Array.from( newRanges );
this._ranges = [];
for ( const range of newRanges ) {
this._addRange( range );
}
this._lastRangeBackward = !!isLastBackward;
}
/**
* Sets this selection instance to be marked as `fake`. A fake selection does not render as browser native selection
* over selected elements and is hidden to the user. This way, no native selection UI artifacts are displayed to
* the user and selection over elements can be represented in other way, for example by applying proper CSS class.
*
* Additionally fake's selection label can be provided. It will be used to describe fake selection in DOM (and be
* properly handled by screen readers).
*
* @private
* @param {Object} [options] Options.
* @param {Boolean} [options.fake] If set to true selection will be marked as `fake`.
* @param {String} [options.label=''] Fake selection label.
*/
_setFakeOptions( options = {} ) {
this._isFake = !!options.fake;
this._fakeSelectionLabel = options.fake ? options.label || '' : '';
}
/**
* Adds a range to the selection. Added range is copied. This means that passed range is not saved in the
* selection instance and you can safely operate on it.
*
* Accepts a flag describing in which way the selection is made - passed range might be selected from
* {@link module:engine/view/range~Range#start start} to {@link module:engine/view/range~Range#end end}
* or from {@link module:engine/view/range~Range#end end} to {@link module:engine/view/range~Range#start start}.
* The flag is used to set {@link #anchor anchor} and {@link #focus focus} properties.
*
* Throws {@link module:utils/ckeditorerror~CKEditorError CKEditorError} `view-selection-range-intersects` if added range intersects
* with ranges already stored in Selection instance.
*
* @private
* @fires change
* @param {module:engine/view/range~Range} range
* @param {Boolean} [isBackward]
*/
_addRange( range, isBackward = false ) {
if ( !( range instanceof _range__WEBPACK_IMPORTED_MODULE_1__["default"] ) ) {
/**
* Selection range set to an object that is not an instance of {@link module:engine/view/range~Range}.
*
* @error view-selection-add-range-not-range
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_0__["default"](
'view-selection-add-range-not-range',
this
);
}
this._pushRange( range );
this._lastRangeBackward = !!isBackward;
}
/**
* Adds range to selection - creates copy of given range so it can be safely used and modified.
*
* Throws {@link module:utils/ckeditorerror~CKEditorError CKEditorError} `view-selection-range-intersects` if added range intersects
* with ranges already stored in selection instance.
*
* @private
* @param {module:engine/view/range~Range} range
*/
_pushRange( range ) {
for ( const storedRange of this._ranges ) {
if ( range.isIntersecting( storedRange ) ) {
/**
* Trying to add a range that intersects with another range from selection.
*
* @error view-selection-range-intersects
* @param {module:engine/view/range~Range} addedRange Range that was added to the selection.
* @param {module:engine/view/range~Range} intersectingRange Range from selection that intersects with `addedRange`.
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_0__["default"](
'view-selection-range-intersects',
this,
{ addedRange: range, intersectingRange: storedRange }
);
}
}
this._ranges.push( new _range__WEBPACK_IMPORTED_MODULE_1__["default"]( range.start, range.end ) );
}
/**
* Fired whenever selection ranges are changed through {@link ~Selection Selection API}.
*
* @event change
*/
}
(0,_ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_3__["default"])( Selection, _ckeditor_ckeditor5_utils_src_emittermixin__WEBPACK_IMPORTED_MODULE_4__["default"] );
/**
* An entity that is used to set selection.
*
* See also {@link module:engine/view/selection~Selection#setTo}
*
* @typedef {
* module:engine/view/selection~Selection|
* module:engine/view/documentselection~DocumentSelection|
* module:engine/view/position~Position|
* Iterable.<module:engine/view/range~Range>|
* module:engine/view/range~Range|
* module:engine/view/item~Item|
* null
* } module:engine/view/selection~Selectable
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/view/styles/background.js":
/*!*******************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/view/styles/background.js ***!
\*******************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "addBackgroundRules": () => (/* binding */ addBackgroundRules)
/* harmony export */ });
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./utils */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/styles/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/view/styles/background
*/
/**
* Adds a background CSS styles processing rules.
*
* editor.data.addStyleProcessorRules( addBackgroundRules );
*
* The normalized value is stored as:
*
* const styles = {
* background: {
* color,
* repeat,
* position,
* attachment,
* image
* }
* };
*
* **Note**: Currently only `'background-color'` longhand value is parsed besides `'background'` shorthand. The reducer also supports only
* `'background-color'` value.
*
* @param {module:engine/view/stylesmap~StylesProcessor} stylesProcessor
*/
function addBackgroundRules( stylesProcessor ) {
stylesProcessor.setNormalizer( 'background', normalizeBackground );
stylesProcessor.setNormalizer( 'background-color', value => ( { path: 'background.color', value } ) );
stylesProcessor.setReducer( 'background', value => {
const ret = [];
ret.push( [ 'background-color', value.color ] );
return ret;
} );
stylesProcessor.setStyleRelation( 'background', [ 'background-color' ] );
}
function normalizeBackground( value ) {
const background = {};
const parts = (0,_utils__WEBPACK_IMPORTED_MODULE_0__.getShorthandValues)( value );
for ( const part of parts ) {
if ( (0,_utils__WEBPACK_IMPORTED_MODULE_0__.isRepeat)( part ) ) {
background.repeat = background.repeat || [];
background.repeat.push( part );
} else if ( (0,_utils__WEBPACK_IMPORTED_MODULE_0__.isPosition)( part ) ) {
background.position = background.position || [];
background.position.push( part );
} else if ( (0,_utils__WEBPACK_IMPORTED_MODULE_0__.isAttachment)( part ) ) {
background.attachment = part;
} else if ( (0,_utils__WEBPACK_IMPORTED_MODULE_0__.isColor)( part ) ) {
background.color = part;
} else if ( (0,_utils__WEBPACK_IMPORTED_MODULE_0__.isURL)( part ) ) {
background.image = part;
}
}
return {
path: 'background',
value: background
};
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/view/styles/border.js":
/*!***************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/view/styles/border.js ***!
\***************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "addBorderRules": () => (/* binding */ addBorderRules)
/* harmony export */ });
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./utils */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/styles/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/view/styles/border
*/
/**
* Adds a border CSS styles processing rules.
*
* editor.data.addStyleProcessorRules( addBorderRules );
*
* This rules merges all [border](https://developer.mozilla.org/en-US/docs/Web/CSS/border) styles notation shorthands:
*
* - border
* - border-top
* - border-right
* - border-bottom
* - border-left
* - border-color
* - border-style
* - border-width
*
* and all corresponding longhand forms (like `border-top-color`, `border-top-style`, etc).
*
* It does not handle other shorthands (like `border-radius` or `border-image`).
*
* The normalized model stores border values as:
*
* const styles = {
* border: {
* color: { top, right, bottom, left },
* style: { top, right, bottom, left },
* width: { top, right, bottom, left },
* }
* };
*
* @param {module:engine/view/stylesmap~StylesProcessor} stylesProcessor
*/
function addBorderRules( stylesProcessor ) {
stylesProcessor.setNormalizer( 'border', borderNormalizer );
// Border-position shorthands.
stylesProcessor.setNormalizer( 'border-top', getBorderPositionNormalizer( 'top' ) );
stylesProcessor.setNormalizer( 'border-right', getBorderPositionNormalizer( 'right' ) );
stylesProcessor.setNormalizer( 'border-bottom', getBorderPositionNormalizer( 'bottom' ) );
stylesProcessor.setNormalizer( 'border-left', getBorderPositionNormalizer( 'left' ) );
// Border-property shorthands.
stylesProcessor.setNormalizer( 'border-color', getBorderPropertyNormalizer( 'color' ) );
stylesProcessor.setNormalizer( 'border-width', getBorderPropertyNormalizer( 'width' ) );
stylesProcessor.setNormalizer( 'border-style', getBorderPropertyNormalizer( 'style' ) );
// Border longhands.
stylesProcessor.setNormalizer( 'border-top-color', getBorderPropertyPositionNormalizer( 'color', 'top' ) );
stylesProcessor.setNormalizer( 'border-top-style', getBorderPropertyPositionNormalizer( 'style', 'top' ) );
stylesProcessor.setNormalizer( 'border-top-width', getBorderPropertyPositionNormalizer( 'width', 'top' ) );
stylesProcessor.setNormalizer( 'border-right-color', getBorderPropertyPositionNormalizer( 'color', 'right' ) );
stylesProcessor.setNormalizer( 'border-right-style', getBorderPropertyPositionNormalizer( 'style', 'right' ) );
stylesProcessor.setNormalizer( 'border-right-width', getBorderPropertyPositionNormalizer( 'width', 'right' ) );
stylesProcessor.setNormalizer( 'border-bottom-color', getBorderPropertyPositionNormalizer( 'color', 'bottom' ) );
stylesProcessor.setNormalizer( 'border-bottom-style', getBorderPropertyPositionNormalizer( 'style', 'bottom' ) );
stylesProcessor.setNormalizer( 'border-bottom-width', getBorderPropertyPositionNormalizer( 'width', 'bottom' ) );
stylesProcessor.setNormalizer( 'border-left-color', getBorderPropertyPositionNormalizer( 'color', 'left' ) );
stylesProcessor.setNormalizer( 'border-left-style', getBorderPropertyPositionNormalizer( 'style', 'left' ) );
stylesProcessor.setNormalizer( 'border-left-width', getBorderPropertyPositionNormalizer( 'width', 'left' ) );
stylesProcessor.setExtractor( 'border-top', getBorderPositionExtractor( 'top' ) );
stylesProcessor.setExtractor( 'border-right', getBorderPositionExtractor( 'right' ) );
stylesProcessor.setExtractor( 'border-bottom', getBorderPositionExtractor( 'bottom' ) );
stylesProcessor.setExtractor( 'border-left', getBorderPositionExtractor( 'left' ) );
stylesProcessor.setExtractor( 'border-top-color', 'border.color.top' );
stylesProcessor.setExtractor( 'border-right-color', 'border.color.right' );
stylesProcessor.setExtractor( 'border-bottom-color', 'border.color.bottom' );
stylesProcessor.setExtractor( 'border-left-color', 'border.color.left' );
stylesProcessor.setExtractor( 'border-top-width', 'border.width.top' );
stylesProcessor.setExtractor( 'border-right-width', 'border.width.right' );
stylesProcessor.setExtractor( 'border-bottom-width', 'border.width.bottom' );
stylesProcessor.setExtractor( 'border-left-width', 'border.width.left' );
stylesProcessor.setExtractor( 'border-top-style', 'border.style.top' );
stylesProcessor.setExtractor( 'border-right-style', 'border.style.right' );
stylesProcessor.setExtractor( 'border-bottom-style', 'border.style.bottom' );
stylesProcessor.setExtractor( 'border-left-style', 'border.style.left' );
stylesProcessor.setReducer( 'border-color', (0,_utils__WEBPACK_IMPORTED_MODULE_0__.getBoxSidesValueReducer)( 'border-color' ) );
stylesProcessor.setReducer( 'border-style', (0,_utils__WEBPACK_IMPORTED_MODULE_0__.getBoxSidesValueReducer)( 'border-style' ) );
stylesProcessor.setReducer( 'border-width', (0,_utils__WEBPACK_IMPORTED_MODULE_0__.getBoxSidesValueReducer)( 'border-width' ) );
stylesProcessor.setReducer( 'border-top', getBorderPositionReducer( 'top' ) );
stylesProcessor.setReducer( 'border-right', getBorderPositionReducer( 'right' ) );
stylesProcessor.setReducer( 'border-bottom', getBorderPositionReducer( 'bottom' ) );
stylesProcessor.setReducer( 'border-left', getBorderPositionReducer( 'left' ) );
stylesProcessor.setReducer( 'border', getBorderReducer() );
stylesProcessor.setStyleRelation( 'border', [
'border-color', 'border-style', 'border-width',
'border-top', 'border-right', 'border-bottom', 'border-left',
'border-top-color', 'border-right-color', 'border-bottom-color', 'border-left-color',
'border-top-style', 'border-right-style', 'border-bottom-style', 'border-left-style',
'border-top-width', 'border-right-width', 'border-bottom-width', 'border-left-width'
] );
stylesProcessor.setStyleRelation( 'border-color', [
'border-top-color', 'border-right-color', 'border-bottom-color', 'border-left-color'
] );
stylesProcessor.setStyleRelation( 'border-style', [
'border-top-style', 'border-right-style', 'border-bottom-style', 'border-left-style'
] );
stylesProcessor.setStyleRelation( 'border-width', [
'border-top-width', 'border-right-width', 'border-bottom-width', 'border-left-width'
] );
stylesProcessor.setStyleRelation( 'border-top', [ 'border-top-color', 'border-top-style', 'border-top-width' ] );
stylesProcessor.setStyleRelation( 'border-right', [ 'border-right-color', 'border-right-style', 'border-right-width' ] );
stylesProcessor.setStyleRelation( 'border-bottom', [ 'border-bottom-color', 'border-bottom-style', 'border-bottom-width' ] );
stylesProcessor.setStyleRelation( 'border-left', [ 'border-left-color', 'border-left-style', 'border-left-width' ] );
}
function borderNormalizer( value ) {
const { color, style, width } = normalizeBorderShorthand( value );
return {
path: 'border',
value: {
color: (0,_utils__WEBPACK_IMPORTED_MODULE_0__.getBoxSidesValues)( color ),
style: (0,_utils__WEBPACK_IMPORTED_MODULE_0__.getBoxSidesValues)( style ),
width: (0,_utils__WEBPACK_IMPORTED_MODULE_0__.getBoxSidesValues)( width )
}
};
}
function getBorderPositionNormalizer( side ) {
return value => {
const { color, style, width } = normalizeBorderShorthand( value );
const border = {};
if ( color !== undefined ) {
border.color = { [ side ]: color };
}
if ( style !== undefined ) {
border.style = { [ side ]: style };
}
if ( width !== undefined ) {
border.width = { [ side ]: width };
}
return {
path: 'border',
value: border
};
};
}
function getBorderPropertyNormalizer( propertyName ) {
return value => {
return {
path: 'border',
value: toBorderPropertyShorthand( value, propertyName )
};
};
}
function toBorderPropertyShorthand( value, property ) {
return {
[ property ]: (0,_utils__WEBPACK_IMPORTED_MODULE_0__.getBoxSidesValues)( value )
};
}
function getBorderPropertyPositionNormalizer( property, side ) {
return value => {
return {
path: 'border',
value: {
[ property ]: {
[ side ]: value
}
}
};
};
}
function getBorderPositionExtractor( which ) {
return ( name, styles ) => {
if ( styles.border ) {
return extractBorderPosition( styles.border, which );
}
};
}
function extractBorderPosition( border, which ) {
const value = {};
if ( border.width && border.width[ which ] ) {
value.width = border.width[ which ];
}
if ( border.style && border.style[ which ] ) {
value.style = border.style[ which ];
}
if ( border.color && border.color[ which ] ) {
value.color = border.color[ which ];
}
return value;
}
function normalizeBorderShorthand( string ) {
const result = {};
const parts = (0,_utils__WEBPACK_IMPORTED_MODULE_0__.getShorthandValues)( string );
for ( const part of parts ) {
if ( (0,_utils__WEBPACK_IMPORTED_MODULE_0__.isLength)( part ) || /thin|medium|thick/.test( part ) ) {
result.width = part;
} else if ( (0,_utils__WEBPACK_IMPORTED_MODULE_0__.isLineStyle)( part ) ) {
result.style = part;
} else {
result.color = part;
}
}
return result;
}
// The border reducer factory.
//
// It tries to produce the most optimal output for the specified styles.
//
// For a border style:
//
// style: {top: "solid", bottom: "solid", right: "solid", left: "solid"}
//
// It will produce: `border-style: solid`.
// For a border style and color:
//
// color: {top: "#ff0", bottom: "#ff0", right: "#ff0", left: "#ff0"}
// style: {top: "solid", bottom: "solid", right: "solid", left: "solid"}
//
// It will produce: `border-color: #ff0; border-style: solid`.
// If all border parameters are specified:
//
// color: {top: "#ff0", bottom: "#ff0", right: "#ff0", left: "#ff0"}
// style: {top: "solid", bottom: "solid", right: "solid", left: "solid"}
// width: {top: "2px", bottom: "2px", right: "2px", left: "2px"}
//
// It will combine everything into a single property: `border: 2px solid #ff0`.
//
// The definitions are merged only if all border selectors have the same values.
//
// @returns {Function}
function getBorderReducer() {
return value => {
const topStyles = extractBorderPosition( value, 'top' );
const rightStyles = extractBorderPosition( value, 'right' );
const bottomStyles = extractBorderPosition( value, 'bottom' );
const leftStyles = extractBorderPosition( value, 'left' );
const borderStyles = [ topStyles, rightStyles, bottomStyles, leftStyles ];
const borderStylesByType = {
width: getReducedStyleValueForType( borderStyles, 'width' ),
style: getReducedStyleValueForType( borderStyles, 'style' ),
color: getReducedStyleValueForType( borderStyles, 'color' )
};
// Try reducing to a single `border:` property.
const reducedBorderStyle = reduceBorderPosition( borderStylesByType, 'all' );
if ( reducedBorderStyle.length ) {
return reducedBorderStyle;
}
// Try reducing to `border-style:`, `border-width:`, `border-color:` properties.
const reducedStyleTypes = Object.entries( borderStylesByType ).reduce( ( reducedStyleTypes, [ type, value ] ) => {
if ( value ) {
reducedStyleTypes.push( [ `border-${ type }`, value ] );
// Remove it from the full set to not include it in the most specific properties later.
borderStyles.forEach( style => ( style[ type ] = null ) );
}
return reducedStyleTypes;
}, [] );
// The reduced properties (by type) and all that remains that could not be reduced.
return [
...reducedStyleTypes,
...reduceBorderPosition( topStyles, 'top' ),
...reduceBorderPosition( rightStyles, 'right' ),
...reduceBorderPosition( bottomStyles, 'bottom' ),
...reduceBorderPosition( leftStyles, 'left' )
];
};
// @param {Array.<Object>} styles The array of objects with `style`, `color`, `width` properties.
// @param {'width'|'style'|'color'} type
function getReducedStyleValueForType( styles, type ) {
return styles
.map( style => style[ type ] )
.reduce( ( result, style ) => result == style ? result : null );
}
}
function getBorderPositionReducer( which ) {
return value => reduceBorderPosition( value, which );
}
// Returns an array with reduced border styles depending on the specified values.
//
// If all border properties (width, style, color) are specified, the returned selector will be
// merged into a group: `border-*: [width] [style] [color]`.
//
// Otherwise, the specific definitions will be returned: `border-(width|style|color)-*: [value]`.
//
// @param {Object|null} value Styles if defined.
// @param {'top'|'right'|'bottom'|'left'|'all'} which The border position.
// @returns {Array}
function reduceBorderPosition( value, which ) {
const borderTypes = [];
if ( value && value.width ) {
borderTypes.push( 'width' );
}
if ( value && value.style ) {
borderTypes.push( 'style' );
}
if ( value && value.color ) {
borderTypes.push( 'color' );
}
if ( borderTypes.length == 3 ) {
const borderValue = borderTypes.map( item => value[ item ] ).join( ' ' );
return [
which == 'all' ? [ 'border', borderValue ] : [ `border-${ which }`, borderValue ]
];
}
// We are unable to reduce to a single `border:` property.
if ( which == 'all' ) {
return [];
}
return borderTypes.map( type => {
return [ `border-${ which }-${ type }`, value[ type ] ];
} );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/view/styles/margin.js":
/*!***************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/view/styles/margin.js ***!
\***************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "addMarginRules": () => (/* binding */ addMarginRules)
/* harmony export */ });
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./utils */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/styles/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/view/styles/margin
*/
/**
* Adds a margin CSS styles processing rules.
*
* editor.data.addStyleProcessorRules( addMarginRules );
*
* The normalized value is stored as:
*
* const styles = {
* margin: {
* top,
* right,
* bottom,
* left
* }
* };
*
* @param {module:engine/view/stylesmap~StylesProcessor} stylesProcessor
*/
function addMarginRules( stylesProcessor ) {
stylesProcessor.setNormalizer( 'margin', (0,_utils__WEBPACK_IMPORTED_MODULE_0__.getPositionShorthandNormalizer)( 'margin' ) );
stylesProcessor.setNormalizer( 'margin-top', value => ( { path: 'margin.top', value } ) );
stylesProcessor.setNormalizer( 'margin-right', value => ( { path: 'margin.right', value } ) );
stylesProcessor.setNormalizer( 'margin-bottom', value => ( { path: 'margin.bottom', value } ) );
stylesProcessor.setNormalizer( 'margin-left', value => ( { path: 'margin.left', value } ) );
stylesProcessor.setReducer( 'margin', (0,_utils__WEBPACK_IMPORTED_MODULE_0__.getBoxSidesValueReducer)( 'margin' ) );
stylesProcessor.setStyleRelation( 'margin', [ 'margin-top', 'margin-right', 'margin-bottom', 'margin-left' ] );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/view/styles/padding.js":
/*!****************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/view/styles/padding.js ***!
\****************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "addPaddingRules": () => (/* binding */ addPaddingRules)
/* harmony export */ });
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./utils */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/styles/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/view/styles/padding
*/
/**
* Adds a margin CSS styles processing rules.
*
* editor.data.addStyleProcessorRules( addPaddingRules );
*
* The normalized value is stored as:
*
* const styles = {
* padding: {
* top,
* right,
* bottom,
* left
* }
* };
*
* @param {module:engine/view/stylesmap~StylesProcessor} stylesProcessor
*/
function addPaddingRules( stylesProcessor ) {
stylesProcessor.setNormalizer( 'padding', (0,_utils__WEBPACK_IMPORTED_MODULE_0__.getPositionShorthandNormalizer)( 'padding' ) );
stylesProcessor.setNormalizer( 'padding-top', value => ( { path: 'padding.top', value } ) );
stylesProcessor.setNormalizer( 'padding-right', value => ( { path: 'padding.right', value } ) );
stylesProcessor.setNormalizer( 'padding-bottom', value => ( { path: 'padding.bottom', value } ) );
stylesProcessor.setNormalizer( 'padding-left', value => ( { path: 'padding.left', value } ) );
stylesProcessor.setReducer( 'padding', (0,_utils__WEBPACK_IMPORTED_MODULE_0__.getBoxSidesValueReducer)( 'padding' ) );
stylesProcessor.setStyleRelation( 'padding', [ 'padding-top', 'padding-right', 'padding-bottom', 'padding-left' ] );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/view/styles/utils.js":
/*!**************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/view/styles/utils.js ***!
\**************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "getBoxSidesShorthandValue": () => (/* binding */ getBoxSidesShorthandValue),
/* harmony export */ "getBoxSidesValueReducer": () => (/* binding */ getBoxSidesValueReducer),
/* harmony export */ "getBoxSidesValues": () => (/* binding */ getBoxSidesValues),
/* harmony export */ "getPositionShorthandNormalizer": () => (/* binding */ getPositionShorthandNormalizer),
/* harmony export */ "getShorthandValues": () => (/* binding */ getShorthandValues),
/* harmony export */ "isAttachment": () => (/* binding */ isAttachment),
/* harmony export */ "isColor": () => (/* binding */ isColor),
/* harmony export */ "isLength": () => (/* binding */ isLength),
/* harmony export */ "isLineStyle": () => (/* binding */ isLineStyle),
/* harmony export */ "isPercentage": () => (/* binding */ isPercentage),
/* harmony export */ "isPosition": () => (/* binding */ isPosition),
/* harmony export */ "isRepeat": () => (/* binding */ isRepeat),
/* harmony export */ "isURL": () => (/* binding */ isURL)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/view/styles/utils
*/
const HEX_COLOR_REGEXP = /^#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i;
const RGB_COLOR_REGEXP = /^rgb\([ ]?([0-9]{1,3}[ %]?,[ ]?){2,3}[0-9]{1,3}[ %]?\)$/i;
const RGBA_COLOR_REGEXP = /^rgba\([ ]?([0-9]{1,3}[ %]?,[ ]?){3}(1|[0-9]+%|[0]?\.?[0-9]+)\)$/i;
const HSL_COLOR_REGEXP = /^hsl\([ ]?([0-9]{1,3}[ %]?[,]?[ ]*){3}(1|[0-9]+%|[0]?\.?[0-9]+)?\)$/i;
const HSLA_COLOR_REGEXP = /^hsla\([ ]?([0-9]{1,3}[ %]?,[ ]?){2,3}(1|[0-9]+%|[0]?\.?[0-9]+)\)$/i;
const COLOR_NAMES = new Set( [
// CSS Level 1
'black', 'silver', 'gray', 'white', 'maroon', 'red', 'purple', 'fuchsia',
'green', 'lime', 'olive', 'yellow', 'navy', 'blue', 'teal', 'aqua',
// CSS Level 2 (Revision 1)
'orange',
// CSS Color Module Level 3
'aliceblue', 'antiquewhite', 'aquamarine', 'azure', 'beige', 'bisque', 'blanchedalmond', 'blueviolet', 'brown',
'burlywood', 'cadetblue', 'chartreuse', 'chocolate', 'coral', 'cornflowerblue', 'cornsilk', 'crimson', 'cyan',
'darkblue', 'darkcyan', 'darkgoldenrod', 'darkgray', 'darkgreen', 'darkgrey', 'darkkhaki', 'darkmagenta',
'darkolivegreen', 'darkorange', 'darkorchid', 'darkred', 'darksalmon', 'darkseagreen', 'darkslateblue',
'darkslategray', 'darkslategrey', 'darkturquoise', 'darkviolet', 'deeppink', 'deepskyblue', 'dimgray', 'dimgrey',
'dodgerblue', 'firebrick', 'floralwhite', 'forestgreen', 'gainsboro', 'ghostwhite', 'gold', 'goldenrod',
'greenyellow', 'grey', 'honeydew', 'hotpink', 'indianred', 'indigo', 'ivory', 'khaki', 'lavender', 'lavenderblush',
'lawngreen', 'lemonchiffon', 'lightblue', 'lightcoral', 'lightcyan', 'lightgoldenrodyellow', 'lightgray',
'lightgreen', 'lightgrey', 'lightpink', 'lightsalmon', 'lightseagreen', 'lightskyblue', 'lightslategray',
'lightslategrey', 'lightsteelblue', 'lightyellow', 'limegreen', 'linen', 'magenta', 'mediumaquamarine',
'mediumblue', 'mediumorchid', 'mediumpurple', 'mediumseagreen', 'mediumslateblue', 'mediumspringgreen',
'mediumturquoise', 'mediumvioletred', 'midnightblue', 'mintcream', 'mistyrose', 'moccasin', 'navajowhite',
'oldlace', 'olivedrab', 'orangered', 'orchid', 'palegoldenrod', 'palegreen', 'paleturquoise', 'palevioletred',
'papayawhip', 'peachpuff', 'peru', 'pink', 'plum', 'powderblue', 'rosybrown', 'royalblue', 'saddlebrown', 'salmon',
'sandybrown', 'seagreen', 'seashell', 'sienna', 'skyblue', 'slateblue', 'slategray', 'slategrey', 'snow',
'springgreen', 'steelblue', 'tan', 'thistle', 'tomato', 'turquoise', 'violet', 'wheat', 'whitesmoke', 'yellowgreen',
// CSS Color Module Level 3 (System Colors)
'activeborder', 'activecaption', 'appworkspace', 'background', 'buttonface', 'buttonhighlight', 'buttonshadow',
'buttontext', 'captiontext', 'graytext', 'highlight', 'highlighttext', 'inactiveborder', 'inactivecaption',
'inactivecaptiontext', 'infobackground', 'infotext', 'menu', 'menutext', 'scrollbar', 'threeddarkshadow',
'threedface', 'threedhighlight', 'threedlightshadow', 'threedshadow', 'window', 'windowframe', 'windowtext',
// CSS Color Module Level 4
'rebeccapurple',
// Keywords
'currentcolor', 'transparent'
] );
/**
* Checks if string contains [color](https://developer.mozilla.org/en-US/docs/Web/CSS/color) CSS value.
*
* isColor( '#f00' ); // true
* isColor( '#AA00BB33' ); // true
* isColor( 'rgb(0, 0, 250)' ); // true
* isColor( 'hsla(240, 100%, 50%, .7)' ); // true
* isColor( 'deepskyblue' ); // true
*
* **Note**: It does not support CSS Level 4 whitespace syntax, system colors and radius values for HSL colors.
*
* @param {String} string
* @returns {Boolean}
*/
function isColor( string ) {
// As far as I was able to test checking some pre-conditions is faster than joining each test with ||.
if ( string.startsWith( '#' ) ) {
return HEX_COLOR_REGEXP.test( string );
}
if ( string.startsWith( 'rgb' ) ) {
return RGB_COLOR_REGEXP.test( string ) || RGBA_COLOR_REGEXP.test( string );
}
if ( string.startsWith( 'hsl' ) ) {
return HSL_COLOR_REGEXP.test( string ) || HSLA_COLOR_REGEXP.test( string );
}
// Array check > RegExp test.
return COLOR_NAMES.has( string.toLowerCase() );
}
const lineStyleValues = [ 'none', 'hidden', 'dotted', 'dashed', 'solid', 'double', 'groove', 'ridge', 'inset', 'outset' ];
/**
* Checks if string contains [line style](https://developer.mozilla.org/en-US/docs/Web/CSS/border-style) CSS value.
*
* @param {String} string
* @returns {Boolean}
*/
function isLineStyle( string ) {
return lineStyleValues.includes( string );
}
const lengthRegExp = /^([+-]?[0-9]*([.][0-9]+)?(px|cm|mm|in|pc|pt|ch|em|ex|rem|vh|vw|vmin|vmax)|0)$/;
/**
* Checks if string contains [length](https://developer.mozilla.org/en-US/docs/Web/CSS/length) CSS value.
*
* @param {String} string
* @returns {Boolean}
*/
function isLength( string ) {
return lengthRegExp.test( string );
}
const PERCENTAGE_VALUE_REGEXP = /^[+-]?[0-9]*([.][0-9]+)?%$/;
/**
* Checks if string contains [percentage](https://developer.mozilla.org/en-US/docs/Web/CSS/percentage) CSS value.
*
* @param {String} string
* @returns {Boolean}
*/
function isPercentage( string ) {
return PERCENTAGE_VALUE_REGEXP.test( string );
}
const repeatValues = [ 'repeat-x', 'repeat-y', 'repeat', 'space', 'round', 'no-repeat' ];
/**
* Checks if string contains [background repeat](https://developer.mozilla.org/en-US/docs/Web/CSS/background-repeat) CSS value.
*
* @param {String} string
* @returns {Boolean}
*/
function isRepeat( string ) {
return repeatValues.includes( string );
}
const positionValues = [ 'center', 'top', 'bottom', 'left', 'right' ];
/**
* Checks if string contains [background position](https://developer.mozilla.org/en-US/docs/Web/CSS/background-position) CSS value.
*
* @param {String} string
* @returns {Boolean}
*/
function isPosition( string ) {
return positionValues.includes( string );
}
const attachmentValues = [ 'fixed', 'scroll', 'local' ];
/**
* Checks if string contains [background attachment](https://developer.mozilla.org/en-US/docs/Web/CSS/background-attachment) CSS value.
*
* @param {String} string
* @returns {Boolean}
*/
function isAttachment( string ) {
return attachmentValues.includes( string );
}
const urlRegExp = /^url\(/;
/**
* Checks if string contains [URL](https://developer.mozilla.org/en-US/docs/Web/CSS/url) CSS value.
*
* @param {String} string
* @returns {Boolean}
*/
function isURL( string ) {
return urlRegExp.test( string );
}
function getBoxSidesValues( value = '' ) {
if ( value === '' ) {
return { top: undefined, right: undefined, bottom: undefined, left: undefined };
}
const values = getShorthandValues( value );
const top = values[ 0 ];
const bottom = values[ 2 ] || top;
const right = values[ 1 ] || top;
const left = values[ 3 ] || right;
return { top, bottom, right, left };
}
/**
* Default reducer for CSS properties that concerns edges of a box
* [shorthand](https://developer.mozilla.org/en-US/docs/Web/CSS/Shorthand_properties) notations:
*
* stylesProcessor.setReducer( 'padding', getBoxSidesValueReducer( 'padding' ) );
*
* @param {String} styleShorthand
* @returns {Function}
*/
function getBoxSidesValueReducer( styleShorthand ) {
return value => {
const { top, right, bottom, left } = value;
const reduced = [];
if ( ![ top, right, left, bottom ].every( value => !!value ) ) {
if ( top ) {
reduced.push( [ styleShorthand + '-top', top ] );
}
if ( right ) {
reduced.push( [ styleShorthand + '-right', right ] );
}
if ( bottom ) {
reduced.push( [ styleShorthand + '-bottom', bottom ] );
}
if ( left ) {
reduced.push( [ styleShorthand + '-left', left ] );
}
} else {
reduced.push( [ styleShorthand, getBoxSidesShorthandValue( value ) ] );
}
return reduced;
};
}
/**
* Returns a [shorthand](https://developer.mozilla.org/en-US/docs/Web/CSS/Shorthand_properties) notation
* of a CSS property value.
*
* getBoxSidesShorthandValue( { top: '1px', right: '1px', bottom: '2px', left: '1px' } );
* // will return '1px 1px 2px'
*
* @param {module:engine/view/stylesmap~BoxSides} styleShorthand
* @returns {String}
*/
function getBoxSidesShorthandValue( { top, right, bottom, left } ) {
const out = [];
if ( left !== right ) {
out.push( top, right, bottom, left );
} else if ( bottom !== top ) {
out.push( top, right, bottom );
} else if ( right !== top ) {
out.push( top, right );
} else {
out.push( top );
}
return out.join( ' ' );
}
/**
* Creates a normalizer for a [shorthand](https://developer.mozilla.org/en-US/docs/Web/CSS/Shorthand_properties) 1-to-4 value.
*
* stylesProcessor.setNormalizer( 'margin', getPositionShorthandNormalizer( 'margin' ) );
*
* @param {String} shorthand
* @returns {Function}
*/
function getPositionShorthandNormalizer( shorthand ) {
return value => {
return {
path: shorthand,
value: getBoxSidesValues( value )
};
};
}
/**
* Parses parts of a 1-to-4 value notation - handles some CSS values with spaces (like RGB()).
*
* getShorthandValues( 'red blue RGB(0, 0, 0)');
* // will return [ 'red', 'blue', 'RGB(0, 0, 0)' ]
*
* @param {String} string
* @returns {Array.<String>}
*/
function getShorthandValues( string ) {
return string
.replace( /, /g, ',' ) // Exclude comma from spaces evaluation as values are separated by spaces.
.split( ' ' )
.map( string => string.replace( /,/g, ', ' ) ); // Restore original notation.
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/view/stylesmap.js":
/*!***********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/view/stylesmap.js ***!
\***********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "StylesProcessor": () => (/* binding */ StylesProcessor),
/* harmony export */ "default": () => (/* binding */ StylesMap)
/* harmony export */ });
/* harmony import */ var lodash_es__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! lodash-es */ "./node_modules/lodash-es/isObject.js");
/* harmony import */ var lodash_es__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! lodash-es */ "./node_modules/lodash-es/unset.js");
/* harmony import */ var lodash_es__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! lodash-es */ "./node_modules/lodash-es/get.js");
/* harmony import */ var lodash_es__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! lodash-es */ "./node_modules/lodash-es/merge.js");
/* harmony import */ var lodash_es__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! lodash-es */ "./node_modules/lodash-es/set.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/view/stylesmap
*/
/**
* Styles map. Allows handling (adding, removing, retrieving) a set of style rules (usually, of an element).
*
* The styles map is capable of normalizing style names so e.g. the following operations are possible:
*/
class StylesMap {
/**
* Creates Styles instance.
*
* @param {module:engine/view/stylesmap~StylesProcessor} styleProcessor
*/
constructor( styleProcessor ) {
/**
* Keeps an internal representation of styles map. Normalized styles are kept as object tree to allow unified modification and
* value access model using lodash's get, set, unset, etc methods.
*
* When no style processor rules are defined it acts as simple key-value storage.
*
* @private
* @type {Object}
*/
this._styles = {};
/**
* An instance of the {@link module:engine/view/stylesmap~StylesProcessor}.
*
* @private
* @member {module:engine/view/stylesmap~StylesProcessor}
*/
this._styleProcessor = styleProcessor;
}
/**
* Returns true if style map has no styles set.
*
* @type {Boolean}
*/
get isEmpty() {
const entries = Object.entries( this._styles );
const from = Array.from( entries );
return !from.length;
}
/**
* Number of styles defined.
*
* @type {Number}
*/
get size() {
if ( this.isEmpty ) {
return 0;
}
return this.getStyleNames().length;
}
/**
* Set styles map to a new value.
*
* styles.setTo( 'border:1px solid blue;margin-top:1px;' );
*
* @param {String} inlineStyle
*/
setTo( inlineStyle ) {
this.clear();
const parsedStyles = Array.from( parseInlineStyles( inlineStyle ).entries() );
for ( const [ key, value ] of parsedStyles ) {
this._styleProcessor.toNormalizedForm( key, value, this._styles );
}
}
/**
* Checks if a given style is set.
*
* styles.setTo( 'margin-left:1px;' );
*
* styles.has( 'margin-left' ); // -> true
* styles.has( 'padding' ); // -> false
*
* **Note**: This check supports normalized style names.
*
* // Enable 'margin' shorthand processing:
* editor.data.addStyleProcessorRules( addMarginRules );
*
* styles.setTo( 'margin:2px;' );
*
* styles.has( 'margin' ); // -> true
* styles.has( 'margin-top' ); // -> true
* styles.has( 'margin-left' ); // -> true
*
* styles.remove( 'margin-top' );
*
* styles.has( 'margin' ); // -> false
* styles.has( 'margin-top' ); // -> false
* styles.has( 'margin-left' ); // -> true
*
* @param {String} name Style name.
* @returns {Boolean}
*/
has( name ) {
if ( this.isEmpty ) {
return false;
}
const styles = this._styleProcessor.getReducedForm( name, this._styles );
const propertyDescriptor = styles.find( ( [ property ] ) => property === name );
// Only return a value if it is set;
return Array.isArray( propertyDescriptor );
}
/**
* Sets a given style.
*
* Can insert one by one:
*
* styles.set( 'color', 'blue' );
* styles.set( 'margin-right', '1em' );
*
* or many styles at once:
*
* styles.set( {
* color: 'blue',
* 'margin-right': '1em'
* } );
*
* ***Note**:* This method uses {@link module:engine/controller/datacontroller~DataController#addStyleProcessorRules
* enabled style processor rules} to normalize passed values.
*
* // Enable 'margin' shorthand processing:
* editor.data.addStyleProcessorRules( addMarginRules );
*
* styles.set( 'margin', '2px' );
*
* The above code will set margin to:
*
* styles.getNormalized( 'margin' );
* // -> { top: '2px', right: '2px', bottom: '2px', left: '2px' }
*
* Which makes it possible to retrieve a "sub-value":
*
* styles.get( 'margin-left' ); // -> '2px'
*
* Or modify it:
*
* styles.remove( 'margin-left' );
*
* styles.getNormalized( 'margin' ); // -> { top: '1px', bottom: '1px', right: '1px' }
* styles.toString(); // -> 'margin-bottom:1px;margin-right:1px;margin-top:1px;'
*
* This method also allows to set normalized values directly (if a particular styles processor rule was enabled):
*
* styles.set( 'border-color', { top: 'blue' } );
* styles.set( 'margin', { right: '2em' } );
*
* styles.toString(); // -> 'border-color-top:blue;margin-right:2em;'
*
* @param {String|Object} nameOrObject Style property name or object with multiple properties.
* @param {String|Object} valueOrObject Value to set.
*/
set( nameOrObject, valueOrObject ) {
if ( (0,lodash_es__WEBPACK_IMPORTED_MODULE_0__["default"])( nameOrObject ) ) {
for ( const [ key, value ] of Object.entries( nameOrObject ) ) {
this._styleProcessor.toNormalizedForm( key, value, this._styles );
}
} else {
this._styleProcessor.toNormalizedForm( nameOrObject, valueOrObject, this._styles );
}
}
/**
* Removes given style.
*
* styles.setTo( 'background:#f00;margin-right:2px;' );
*
* styles.remove( 'background' );
*
* styles.toString(); // -> 'margin-right:2px;'
*
* ***Note**:* This method uses {@link module:engine/controller/datacontroller~DataController#addStyleProcessorRules
* enabled style processor rules} to normalize passed values.
*
* // Enable 'margin' shorthand processing:
* editor.data.addStyleProcessorRules( addMarginRules );
*
* styles.setTo( 'margin:1px' );
*
* styles.remove( 'margin-top' );
* styles.remove( 'margin-right' );
*
* styles.toString(); // -> 'margin-bottom:1px;margin-left:1px;'
*
* @param {String} name Style name.
*/
remove( name ) {
const path = toPath( name );
(0,lodash_es__WEBPACK_IMPORTED_MODULE_1__["default"])( this._styles, path );
delete this._styles[ name ];
this._cleanEmptyObjectsOnPath( path );
}
/**
* Returns a normalized style object or a single value.
*
* // Enable 'margin' shorthand processing:
* editor.data.addStyleProcessorRules( addMarginRules );
*
* const styles = new Styles();
* styles.setTo( 'margin:1px 2px 3em;' );
*
* styles.getNormalized( 'margin' );
* // will log:
* // {
* // top: '1px',
* // right: '2px',
* // bottom: '3em',
* // left: '2px' // normalized value from margin shorthand
* // }
*
* styles.getNormalized( 'margin-left' ); // -> '2px'
*
* **Note**: This method will only return normalized styles if a style processor was defined.
*
* @param {String} name Style name.
* @returns {Object|String|undefined}
*/
getNormalized( name ) {
return this._styleProcessor.getNormalized( name, this._styles );
}
/**
* Returns a normalized style string. Styles are sorted by name.
*
* styles.set( 'margin' , '1px' );
* styles.set( 'background', '#f00' );
*
* styles.toString(); // -> 'background:#f00;margin:1px;'
*
* **Note**: This method supports normalized styles if defined.
*
* // Enable 'margin' shorthand processing:
* editor.data.addStyleProcessorRules( addMarginRules );
*
* styles.set( 'margin' , '1px' );
* styles.set( 'background', '#f00' );
* styles.remove( 'margin-top' );
* styles.remove( 'margin-right' );
*
* styles.toString(); // -> 'background:#f00;margin-bottom:1px;margin-left:1px;'
*
* @returns {String}
*/
toString() {
if ( this.isEmpty ) {
return '';
}
return this._getStylesEntries()
.map( arr => arr.join( ':' ) )
.sort()
.join( ';' ) + ';';
}
/**
* Returns property as a value string or undefined if property is not set.
*
* // Enable 'margin' shorthand processing:
* editor.data.addStyleProcessorRules( addMarginRules );
*
* const styles = new Styles();
* styles.setTo( 'margin:1px;' );
* styles.set( 'margin-bottom', '3em' );
*
* styles.getAsString( 'margin' ); // -> 'margin: 1px 1px 3em;'
*
* Note, however, that all sub-values must be set for the longhand property name to return a value:
*
* const styles = new Styles();
* styles.setTo( 'margin:1px;' );
* styles.remove( 'margin-bottom' );
*
* styles.getAsString( 'margin' ); // -> undefined
*
* In the above scenario, it is not possible to return a `margin` value, so `undefined` is returned.
* Instead, you should use:
*
* const styles = new Styles();
* styles.setTo( 'margin:1px;' );
* styles.remove( 'margin-bottom' );
*
* for ( const styleName of styles.getStyleNames() ) {
* console.log( styleName, styles.getAsString( styleName ) );
* }
* // 'margin-top', '1px'
* // 'margin-right', '1px'
* // 'margin-left', '1px'
*
* In general, it is recommend to iterate over style names like in the example above. This way, you will always get all
* the currently set style values. So, if all the 4 margin values would be set
* the for-of loop above would yield only `'margin'`, `'1px'`:
*
* const styles = new Styles();
* styles.setTo( 'margin:1px;' );
*
* for ( const styleName of styles.getStyleNames() ) {
* console.log( styleName, styles.getAsString( styleName ) );
* }
* // 'margin', '1px'
*
* **Note**: To get a normalized version of a longhand property use the {@link #getNormalized `#getNormalized()`} method.
*
* @param {String} propertyName
* @returns {String|undefined}
*/
getAsString( propertyName ) {
if ( this.isEmpty ) {
return;
}
if ( this._styles[ propertyName ] && !(0,lodash_es__WEBPACK_IMPORTED_MODULE_0__["default"])( this._styles[ propertyName ] ) ) {
// Try return styles set directly - values that are not parsed.
return this._styles[ propertyName ];
}
const styles = this._styleProcessor.getReducedForm( propertyName, this._styles );
const propertyDescriptor = styles.find( ( [ property ] ) => property === propertyName );
// Only return a value if it is set;
if ( Array.isArray( propertyDescriptor ) ) {
return propertyDescriptor[ 1 ];
}
}
/**
* Returns all style properties names as they would appear when using {@link #toString `#toString()`}.
*
* When `expand` is set to true and there's a shorthand style property set, it will also return all equivalent styles:
*
* stylesMap.setTo( 'margin: 1em' )
*
* will be expanded to:
*
* [ 'margin', 'margin-top', 'margin-right', 'margin-bottom', 'margin-left' ]
*
* @param {Boolean} [expand=false] Expand shorthand style properties and all return equivalent style representations.
* @returns {Array.<String>}
*/
getStyleNames( expand = false ) {
if ( this.isEmpty ) {
return [];
}
if ( expand ) {
return this._styleProcessor.getStyleNames( this._styles );
}
const entries = this._getStylesEntries();
return entries.map( ( [ key ] ) => key );
}
/**
* Removes all styles.
*/
clear() {
this._styles = {};
}
/**
* Returns normalized styles entries for further processing.
*
* @private
* @returns {Array.<module:engine/view/stylesmap~PropertyDescriptor>}
*/
_getStylesEntries() {
const parsed = [];
const keys = Object.keys( this._styles );
for ( const key of keys ) {
parsed.push( ...this._styleProcessor.getReducedForm( key, this._styles ) );
}
return parsed;
}
/**
* Removes empty objects upon removing an entry from internal object.
*
* @param {String} path
* @private
*/
_cleanEmptyObjectsOnPath( path ) {
const pathParts = path.split( '.' );
const isChildPath = pathParts.length > 1;
if ( !isChildPath ) {
return;
}
const parentPath = pathParts.splice( 0, pathParts.length - 1 ).join( '.' );
const parentObject = (0,lodash_es__WEBPACK_IMPORTED_MODULE_2__["default"])( this._styles, parentPath );
if ( !parentObject ) {
return;
}
const isParentEmpty = !Array.from( Object.keys( parentObject ) ).length;
if ( isParentEmpty ) {
this.remove( parentPath );
}
}
}
/**
* Style processor is responsible for writing and reading a normalized styles object.
*/
class StylesProcessor {
/**
* Creates StylesProcessor instance.
*
* @private
*/
constructor() {
this._normalizers = new Map();
this._extractors = new Map();
this._reducers = new Map();
this._consumables = new Map();
}
/**
* Parse style string value to a normalized object and appends it to styles object.
*
* const styles = {};
*
* stylesProcessor.toNormalizedForm( 'margin', '1px', styles );
*
* // styles will consist: { margin: { top: '1px', right: '1px', bottom: '1px', left: '1px; } }
*
* **Note**: To define normalizer callbacks use {@link #setNormalizer}.
*
* @param {String} name Name of style property.
* @param {String} propertyValue Value of style property.
* @param {Object} styles Object holding normalized styles.
*/
toNormalizedForm( name, propertyValue, styles ) {
if ( (0,lodash_es__WEBPACK_IMPORTED_MODULE_0__["default"])( propertyValue ) ) {
appendStyleValue( styles, toPath( name ), propertyValue );
return;
}
if ( this._normalizers.has( name ) ) {
const normalizer = this._normalizers.get( name );
const { path, value } = normalizer( propertyValue );
appendStyleValue( styles, path, value );
} else {
appendStyleValue( styles, name, propertyValue );
}
}
/**
* Returns a normalized version of a style property.
* const styles = {
* margin: { top: '1px', right: '1px', bottom: '1px', left: '1px; },
* background: { color: '#f00' }
* };
*
* stylesProcessor.getNormalized( 'background' );
* // will return: { color: '#f00' }
*
* stylesProcessor.getNormalized( 'margin-top' );
* // will return: '1px'
*
* **Note**: In some cases extracting single value requires defining an extractor callback {@link #setExtractor}.
*
* @param {String} name Name of style property.
* @param {Object} styles Object holding normalized styles.
* @returns {*}
*/
getNormalized( name, styles ) {
if ( !name ) {
return (0,lodash_es__WEBPACK_IMPORTED_MODULE_3__["default"])( {}, styles );
}
// Might be empty string.
if ( styles[ name ] !== undefined ) {
return styles[ name ];
}
if ( this._extractors.has( name ) ) {
const extractor = this._extractors.get( name );
if ( typeof extractor === 'string' ) {
return (0,lodash_es__WEBPACK_IMPORTED_MODULE_2__["default"])( styles, extractor );
}
const value = extractor( name, styles );
if ( value ) {
return value;
}
}
return (0,lodash_es__WEBPACK_IMPORTED_MODULE_2__["default"])( styles, toPath( name ) );
}
/**
* Returns a reduced form of style property form normalized object.
*
* For default margin reducer, the below code:
*
* stylesProcessor.getReducedForm( 'margin', {
* margin: { top: '1px', right: '1px', bottom: '2px', left: '1px; }
* } );
*
* will return:
*
* [
* [ 'margin', '1px 1px 2px' ]
* ]
*
* because it might be represented as a shorthand 'margin' value. However if one of margin long hand values is missing it should return:
*
* [
* [ 'margin-top', '1px' ],
* [ 'margin-right', '1px' ],
* [ 'margin-bottom', '2px' ]
* // the 'left' value is missing - cannot use 'margin' shorthand.
* ]
*
* **Note**: To define reducer callbacks use {@link #setReducer}.
*
* @param {String} name Name of style property.
* @param {Object} styles Object holding normalized styles.
* @returns {Array.<module:engine/view/stylesmap~PropertyDescriptor>}
*/
getReducedForm( name, styles ) {
const normalizedValue = this.getNormalized( name, styles );
// Might be empty string.
if ( normalizedValue === undefined ) {
return [];
}
if ( this._reducers.has( name ) ) {
const reducer = this._reducers.get( name );
return reducer( normalizedValue );
}
return [ [ name, normalizedValue ] ];
}
/**
* Return all style properties. Also expand shorthand properties (e.g. `margin`, `background`) if respective extractor is available.
*
* @param {Object} styles Object holding normalized styles.
* @returns {Array.<String>}
*/
getStyleNames( styles ) {
// Find all extractable styles that have a value.
const expandedStyleNames = Array.from( this._consumables.keys() ).filter( name => {
const style = this.getNormalized( name, styles );
if ( style && typeof style == 'object' ) {
return Object.keys( style ).length;
}
return style;
} );
// For simple styles (for example `color`) we don't have a map of those styles
// but they are 1 to 1 with normalized object keys.
const styleNamesKeysSet = new Set( [
...expandedStyleNames,
...Object.keys( styles )
] );
return Array.from( styleNamesKeysSet.values() );
}
/**
* Returns related style names.
*
* stylesProcessor.getRelatedStyles( 'margin' );
* // will return: [ 'margin-top', 'margin-right', 'margin-bottom', 'margin-left' ];
*
* stylesProcessor.getRelatedStyles( 'margin-top' );
* // will return: [ 'margin' ];
*
* **Note**: To define new style relations load an existing style processor or use
* {@link module:engine/view/stylesmap~StylesProcessor#setStyleRelation `StylesProcessor.setStyleRelation()`}.
*
* @param {String} name
* @returns {Array.<String>}
*/
getRelatedStyles( name ) {
return this._consumables.get( name ) || [];
}
/**
* Adds a normalizer method for a style property.
*
* A normalizer returns describing how the value should be normalized.
*
* For instance 'margin' style is a shorthand for four margin values:
*
* - 'margin-top'
* - 'margin-right'
* - 'margin-bottom'
* - 'margin-left'
*
* and can be written in various ways if some values are equal to others. For instance `'margin: 1px 2em;'` is a shorthand for
* `'margin-top: 1px;margin-right: 2em;margin-bottom: 1px;margin-left: 2em'`.
*
* A normalizer should parse various margin notations as a single object:
*
* const styles = {
* margin: {
* top: '1px',
* right: '2em',
* bottom: '1px',
* left: '2em'
* }
* };
*
* Thus a normalizer for 'margin' style should return an object defining style path and value to store:
*
* const returnValue = {
* path: 'margin',
* value: {
* top: '1px',
* right: '2em',
* bottom: '1px',
* left: '2em'
* }
* };
*
* Additionally to fully support all margin notations there should be also defined 4 normalizers for longhand margin notations. Below
* is an example for 'margin-top' style property normalizer:
*
* stylesProcessor.setNormalizer( 'margin-top', valueString => {
* return {
* path: 'margin.top',
* value: valueString
* }
* } );
*
* @param {String} name
* @param {Function} callback
*/
setNormalizer( name, callback ) {
this._normalizers.set( name, callback );
}
/**
* Adds a extractor callback for a style property.
*
* Most normalized style values are stored as one level objects. It is assumed that `'margin-top'` style will be stored as:
*
* const styles = {
* margin: {
* top: 'value'
* }
* }
*
* However, some styles can have conflicting notations and thus it might be harder to extract a style value from shorthand. For instance
* the 'border-top-style' can be defined using `'border-top:solid'`, `'border-style:solid none none none'` or by `'border:solid'`
* shorthands. The default border styles processors stores styles as:
*
* const styles = {
* border: {
* style: {
* top: 'solid'
* }
* }
* }
*
* as it is better to modify border style independently from other values. On the other part the output of the border might be
* desired as `border-top`, `border-left`, etc notation.
*
* In the above example a reducer should return a side border value that combines style, color and width:
*
* styleProcessor.setExtractor( 'border-top', styles => {
* return {
* color: styles.border.color.top,
* style: styles.border.style.top,
* width: styles.border.width.top
* }
* } );
*
* @param {String} name
* @param {Function|String} callbackOrPath Callback that return a requested value or path string for single values.
*/
setExtractor( name, callbackOrPath ) {
this._extractors.set( name, callbackOrPath );
}
/**
* Adds a reducer callback for a style property.
*
* Reducer returns a minimal notation for given style name. For longhand properties it is not required to write a reducer as
* by default the direct value from style path is taken.
*
* For shorthand styles a reducer should return minimal style notation either by returning single name-value tuple or multiple tuples
* if a shorthand cannot be used. For instance for a margin shorthand a reducer might return:
*
* const marginShortHandTuple = [
* [ 'margin', '1px 1px 2px' ]
* ];
*
* or a longhand tuples for defined values:
*
* // Considering margin.bottom and margin.left are undefined.
* const marginLonghandsTuples = [
* [ 'margin-top', '1px' ],
* [ 'margin-right', '1px' ]
* ];
*
* A reducer obtains a normalized style value:
*
* // Simplified reducer that always outputs 4 values which are always present:
* stylesProcessor.setReducer( 'margin', margin => {
* return [
* [ 'margin', `${ margin.top } ${ margin.right } ${ margin.bottom } ${ margin.left }` ]
* ]
* } );
*
* @param {String} name
* @param {Function} callback
*/
setReducer( name, callback ) {
this._reducers.set( name, callback );
}
/**
* Defines a style shorthand relation to other style notations.
*
* stylesProcessor.setStyleRelation( 'margin', [
* 'margin-top',
* 'margin-right',
* 'margin-bottom',
* 'margin-left'
* ] );
*
* This enables expanding of style names for shorthands. For instance, if defined,
* {@link module:engine/conversion/viewconsumable~ViewConsumable view consumable} items are automatically created
* for long-hand margin style notation alongside the `'margin'` item.
*
* This means that when an element being converted has a style `margin`, a converter for `margin-left` will work just
* fine since the view consumable will contain a consumable `margin-left` item (thanks to the relation) and
* `element.getStyle( 'margin-left' )` will work as well assuming that the style processor was correctly configured.
* However, once `margin-left` is consumed, `margin` will not be consumable anymore.
*
* @param {String} shorthandName
* @param {Array.<String>} styleNames
*/
setStyleRelation( shorthandName, styleNames ) {
this._mapStyleNames( shorthandName, styleNames );
for ( const alsoName of styleNames ) {
this._mapStyleNames( alsoName, [ shorthandName ] );
}
}
/**
* Set two-way binding of style names.
*
* @param {String} name
* @param {Array.<String>} styleNames
* @private
*/
_mapStyleNames( name, styleNames ) {
if ( !this._consumables.has( name ) ) {
this._consumables.set( name, [] );
}
this._consumables.get( name ).push( ...styleNames );
}
}
// Parses inline styles and puts property - value pairs into styles map.
//
// @param {String} stylesString Styles to parse.
// @returns {Map.<String, String>} stylesMap Map of parsed properties and values.
function parseInlineStyles( stylesString ) {
// `null` if no quote was found in input string or last found quote was a closing quote. See below.
let quoteType = null;
let propertyNameStart = 0;
let propertyValueStart = 0;
let propertyName = null;
const stylesMap = new Map();
// Do not set anything if input string is empty.
if ( stylesString === '' ) {
return stylesMap;
}
// Fix inline styles that do not end with `;` so they are compatible with algorithm below.
if ( stylesString.charAt( stylesString.length - 1 ) != ';' ) {
stylesString = stylesString + ';';
}
// Seek the whole string for "special characters".
for ( let i = 0; i < stylesString.length; i++ ) {
const char = stylesString.charAt( i );
if ( quoteType === null ) {
// No quote found yet or last found quote was a closing quote.
switch ( char ) {
case ':':
// Most of time colon means that property name just ended.
// Sometimes however `:` is found inside property value (for example in background image url).
if ( !propertyName ) {
// Treat this as end of property only if property name is not already saved.
// Save property name.
propertyName = stylesString.substr( propertyNameStart, i - propertyNameStart );
// Save this point as the start of property value.
propertyValueStart = i + 1;
}
break;
case '"':
case '\'':
// Opening quote found (this is an opening quote, because `quoteType` is `null`).
quoteType = char;
break;
case ';': {
// Property value just ended.
// Use previously stored property value start to obtain property value.
const propertyValue = stylesString.substr( propertyValueStart, i - propertyValueStart );
if ( propertyName ) {
// Save parsed part.
stylesMap.set( propertyName.trim(), propertyValue.trim() );
}
propertyName = null;
// Save this point as property name start. Property name starts immediately after previous property value ends.
propertyNameStart = i + 1;
break;
}
}
} else if ( char === quoteType ) {
// If a quote char is found and it is a closing quote, mark this fact by `null`-ing `quoteType`.
quoteType = null;
}
}
return stylesMap;
}
// Return lodash compatible path from style name.
function toPath( name ) {
return name.replace( '-', '.' );
}
// Appends style definition to the styles object.
//
// @param {String} nameOrPath
// @param {String|Object} valueOrObject
// @private
function appendStyleValue( stylesObject, nameOrPath, valueOrObject ) {
let valueToSet = valueOrObject;
if ( (0,lodash_es__WEBPACK_IMPORTED_MODULE_0__["default"])( valueOrObject ) ) {
valueToSet = (0,lodash_es__WEBPACK_IMPORTED_MODULE_3__["default"])( {}, (0,lodash_es__WEBPACK_IMPORTED_MODULE_2__["default"])( stylesObject, nameOrPath ), valueOrObject );
}
(0,lodash_es__WEBPACK_IMPORTED_MODULE_4__["default"])( stylesObject, nameOrPath, valueToSet );
}
/**
* A CSS style property descriptor that contains tuplet of two strings:
*
* - first string describes property name
* - second string describes property value
*
* const marginDescriptor = [ 'margin', '2px 3em' ];
* const marginTopDescriptor = [ 'margin-top', '2px' ];
*
* @typedef {Array.<String, String>} module:engine/view/stylesmap~PropertyDescriptor
*/
/**
* An object describing values associated with the sides of a box, for instance margins, paddings,
* border widths, border colors, etc.
*
* const margin = {
* top: '1px',
* right: '3px',
* bottom: '3px',
* left: '7px'
* };
*
* const borderColor = {
* top: 'red',
* right: 'blue',
* bottom: 'blue',
* left: 'red'
* };
*
* @typedef {Object} module:engine/view/stylesmap~BoxSides
*
* @property {String} top Top side value.
* @property {String} right Right side value.
* @property {String} bottom Bottom side value.
* @property {String} left Left side value.
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/view/text.js":
/*!******************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/view/text.js ***!
\******************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ Text)
/* harmony export */ });
/* harmony import */ var _node__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./node */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/node.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/view/text
*/
/**
* Tree view text node.
*
* The constructor of this class should not be used directly. To create a new text node instance
* use the {@link module:engine/view/downcastwriter~DowncastWriter#createText `DowncastWriter#createText()`}
* method when working on data downcasted from the model or the
* {@link module:engine/view/upcastwriter~UpcastWriter#createText `UpcastWriter#createText()`}
* method when working on non-semantic views.
*
* @extends module:engine/view/node~Node
*/
class Text extends _node__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* Creates a tree view text node.
*
* @protected
* @param {module:engine/view/document~Document} document The document instance to which this text node belongs.
* @param {String} data The text's data.
*/
constructor( document, data ) {
super( document );
/**
* The text content.
*
* Setting the data fires the {@link module:engine/view/node~Node#event:change:text change event}.
*
* @protected
* @member {String} module:engine/view/text~Text#_textData
*/
this._textData = data;
}
/**
* Checks whether this object is of the given type.
*
* text.is( '$text' ); // -> true
* text.is( 'node' ); // -> true
* text.is( 'view:$text' ); // -> true
* text.is( 'view:node' ); // -> true
*
* text.is( 'model:$text' ); // -> false
* text.is( 'element' ); // -> false
* text.is( 'range' ); // -> false
*
* {@link module:engine/view/node~Node#is Check the entire list of view objects} which implement the `is()` method.
*
* **Note:** Until version 20.0.0 this method wasn't accepting `'$text'` type. The legacy `'text'` type is still
* accepted for backward compatibility.
*
* @param {String} type Type to check.
* @returns {Boolean}
*/
is( type ) {
return type === '$text' || type === 'view:$text' ||
// This are legacy values kept for backward compatibility.
type === 'text' || type === 'view:text' ||
// From super.is(). This is highly utilised method and cannot call super. See ckeditor/ckeditor5#6529.
type === 'node' || type === 'view:node';
}
/**
* The text content.
*
* @readonly
* @type {String}
*/
get data() {
return this._textData;
}
/**
* The `_data` property is controlled by a getter and a setter.
*
* The getter is required when using the addition assignment operator on protected property:
*
* const foo = downcastWriter.createText( 'foo' );
* const bar = downcastWriter.createText( 'bar' );
*
* foo._data += bar.data; // executes: `foo._data = foo._data + bar.data`
* console.log( foo.data ); // prints: 'foobar'
*
* If the protected getter didn't exist, `foo._data` will return `undefined` and result of the merge will be invalid.
*
* The setter sets data and fires the {@link module:engine/view/node~Node#event:change:text change event}.
*
* @protected
* @type {String}
*/
get _data() {
return this.data;
}
set _data( data ) {
this._fireChange( 'text', this );
this._textData = data;
}
/**
* Checks if this text node is similar to other text node.
* Both nodes should have the same data to be considered as similar.
*
* @param {module:engine/view/text~Text} otherNode Node to check if it is same as this node.
* @returns {Boolean}
*/
isSimilar( otherNode ) {
if ( !( otherNode instanceof Text ) ) {
return false;
}
return this === otherNode || this.data === otherNode.data;
}
/**
* Clones this node.
*
* @protected
* @returns {module:engine/view/text~Text} Text node that is a clone of this node.
*/
_clone() {
return new Text( this.document, this.data );
}
// @if CK_DEBUG_ENGINE // toString() {
// @if CK_DEBUG_ENGINE // return `#${ this.data }`;
// @if CK_DEBUG_ENGINE // }
// @if CK_DEBUG_ENGINE // log() {
// @if CK_DEBUG_ENGINE // console.log( 'ViewText: ' + this );
// @if CK_DEBUG_ENGINE // }
// @if CK_DEBUG_ENGINE // logExtended() {
// @if CK_DEBUG_ENGINE // console.log( 'ViewText: ' + this );
// @if CK_DEBUG_ENGINE // }
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/view/textproxy.js":
/*!***********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/view/textproxy.js ***!
\***********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ TextProxy)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/view/textproxy
*/
/**
* TextProxy is a wrapper for substring of {@link module:engine/view/text~Text}. Instance of this class is created by
* {@link module:engine/view/treewalker~TreeWalker} when only a part of {@link module:engine/view/text~Text} needs to be returned.
*
* `TextProxy` has an API similar to {@link module:engine/view/text~Text Text} and allows to do most of the common tasks performed
* on view nodes.
*
* **Note:** Some `TextProxy` instances may represent whole text node, not just a part of it.
* See {@link module:engine/view/textproxy~TextProxy#isPartial}.
*
* **Note:** `TextProxy` is a readonly interface.
*
* **Note:** `TextProxy` instances are created on the fly basing on the current state of parent {@link module:engine/view/text~Text}.
* Because of this it is highly unrecommended to store references to `TextProxy instances because they might get
* invalidated due to operations on Document. Also TextProxy is not a {@link module:engine/view/node~Node} so it can not be
* inserted as a child of {@link module:engine/view/element~Element}.
*
* `TextProxy` instances are created by {@link module:engine/view/treewalker~TreeWalker view tree walker}. You should not need to create
* an instance of this class by your own.
*/
class TextProxy {
/**
* Creates a text proxy.
*
* @protected
* @param {module:engine/view/text~Text} textNode Text node which part is represented by this text proxy.
* @param {Number} offsetInText Offset in {@link module:engine/view/textproxy~TextProxy#textNode text node}
* from which the text proxy starts.
* @param {Number} length Text proxy length, that is how many text node's characters, starting from `offsetInText` it represents.
* @constructor
*/
constructor( textNode, offsetInText, length ) {
/**
* Reference to the {@link module:engine/view/text~Text} element which TextProxy is a substring.
*
* @readonly
* @member {module:engine/view/text~Text} module:engine/view/textproxy~TextProxy#textNode
*/
this.textNode = textNode;
if ( offsetInText < 0 || offsetInText > textNode.data.length ) {
/**
* Given offsetInText value is incorrect.
*
* @error view-textproxy-wrong-offsetintext
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_0__["default"]( 'view-textproxy-wrong-offsetintext', this );
}
if ( length < 0 || offsetInText + length > textNode.data.length ) {
/**
* Given length value is incorrect.
*
* @error view-textproxy-wrong-length
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_0__["default"]( 'view-textproxy-wrong-length', this );
}
/**
* Text data represented by this text proxy.
*
* @readonly
* @member {String} module:engine/view/textproxy~TextProxy#data
*/
this.data = textNode.data.substring( offsetInText, offsetInText + length );
/**
* Offset in the `textNode` where this `TextProxy` instance starts.
*
* @readonly
* @member {Number} module:engine/view/textproxy~TextProxy#offsetInText
*/
this.offsetInText = offsetInText;
}
/**
* Offset size of this node.
*
* @readonly
* @type {Number}
*/
get offsetSize() {
return this.data.length;
}
/**
* Flag indicating whether `TextProxy` instance covers only part of the original {@link module:engine/view/text~Text text node}
* (`true`) or the whole text node (`false`).
*
* This is `false` when text proxy starts at the very beginning of {@link module:engine/view/textproxy~TextProxy#textNode textNode}
* ({@link module:engine/view/textproxy~TextProxy#offsetInText offsetInText} equals `0`) and text proxy sizes is equal to
* text node size.
*
* @readonly
* @type {Boolean}
*/
get isPartial() {
return this.data.length !== this.textNode.data.length;
}
/**
* Parent of this text proxy, which is same as parent of text node represented by this text proxy.
*
* @readonly
* @type {module:engine/view/element~Element|module:engine/view/documentfragment~DocumentFragment|null}
*/
get parent() {
return this.textNode.parent;
}
/**
* Root of this text proxy, which is same as root of text node represented by this text proxy.
*
* @readonly
* @type {module:engine/view/node~Node|module:engine/view/documentfragment~DocumentFragment}
*/
get root() {
return this.textNode.root;
}
/**
* {@link module:engine/view/document~Document View document} that owns this text proxy, or `null` if the text proxy is inside
* {@link module:engine/view/documentfragment~DocumentFragment document fragment}.
*
* @readonly
* @type {module:engine/view/document~Document|null}
*/
get document() {
return this.textNode.document;
}
/**
* Checks whether this object is of the given type.
*
* textProxy.is( '$textProxy' ); // -> true
* textProxy.is( 'view:$textProxy' ); // -> true
*
* textProxy.is( 'model:$textProxy' ); // -> false
* textProxy.is( 'element' ); // -> false
* textProxy.is( 'range' ); // -> false
*
* {@link module:engine/view/node~Node#is Check the entire list of view objects} which implement the `is()` method.
*
* **Note:** Until version 20.0.0 this method wasn't accepting `'$textProxy'` type. The legacy `'textProxy'` type is still
* accepted for backward compatibility.
*
* @param {String} type Type to check.
* @returns {Boolean}
*/
is( type ) {
return type === '$textProxy' || type === 'view:$textProxy' ||
// This are legacy values kept for backward compatibility.
type === 'textProxy' || type === 'view:textProxy';
}
/**
* Returns ancestors array of this text proxy.
*
* @param {Object} options Options object.
* @param {Boolean} [options.includeSelf=false] When set to `true` {#textNode} will be also included in parent's array.
* @param {Boolean} [options.parentFirst=false] When set to `true`, array will be sorted from text proxy parent to
* root element, otherwise root element will be the first item in the array.
* @returns {Array} Array with ancestors.
*/
getAncestors( options = { includeSelf: false, parentFirst: false } ) {
const ancestors = [];
let parent = options.includeSelf ? this.textNode : this.parent;
while ( parent !== null ) {
ancestors[ options.parentFirst ? 'push' : 'unshift' ]( parent );
parent = parent.parent;
}
return ancestors;
}
// @if CK_DEBUG_ENGINE // toString() {
// @if CK_DEBUG_ENGINE // return `#${ this.data }`;
// @if CK_DEBUG_ENGINE // }
// @if CK_DEBUG_ENGINE // log() {
// @if CK_DEBUG_ENGINE // console.log( 'ViewTextProxy: ' + this );
// @if CK_DEBUG_ENGINE // }
// @if CK_DEBUG_ENGINE // logExtended() {
// @if CK_DEBUG_ENGINE // console.log( 'ViewTextProxy: ' + this );
// @if CK_DEBUG_ENGINE // }
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/view/treewalker.js":
/*!************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/view/treewalker.js ***!
\************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ TreeWalker)
/* harmony export */ });
/* harmony import */ var _element__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./element */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/element.js");
/* harmony import */ var _text__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./text */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/text.js");
/* harmony import */ var _textproxy__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./textproxy */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/textproxy.js");
/* harmony import */ var _position__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./position */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/position.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/view/treewalker
*/
/**
* Position iterator class. It allows to iterate forward and backward over the document.
*/
class TreeWalker {
/**
* Creates a range iterator. All parameters are optional, but you have to specify either `boundaries` or `startPosition`.
*
* @constructor
* @param {Object} options Object with configuration.
* @param {module:engine/view/range~Range} [options.boundaries=null] Range to define boundaries of the iterator.
* @param {module:engine/view/position~Position} [options.startPosition] Starting position.
* @param {'forward'|'backward'} [options.direction='forward'] Walking direction.
* @param {Boolean} [options.singleCharacters=false] Flag indicating whether all characters from
* {@link module:engine/view/text~Text} should be returned as one {@link module:engine/view/text~Text} (`false`) ore one by one as
* {@link module:engine/view/textproxy~TextProxy} (`true`).
* @param {Boolean} [options.shallow=false] Flag indicating whether iterator should enter elements or not. If the
* iterator is shallow child nodes of any iterated node will not be returned along with `elementEnd` tag.
* @param {Boolean} [options.ignoreElementEnd=false] Flag indicating whether iterator should ignore `elementEnd`
* tags. If the option is true walker will not return a parent node of start position. If this option is `true`
* each {@link module:engine/view/element~Element} will be returned once, while if the option is `false` they might be returned
* twice: for `'elementStart'` and `'elementEnd'`.
*/
constructor( options = {} ) {
if ( !options.boundaries && !options.startPosition ) {
/**
* Neither boundaries nor starting position have been defined.
*
* @error view-tree-walker-no-start-position
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_4__["default"](
'view-tree-walker-no-start-position',
null
);
}
if ( options.direction && options.direction != 'forward' && options.direction != 'backward' ) {
/**
* Only `backward` and `forward` direction allowed.
*
* @error view-tree-walker-unknown-direction
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_4__["default"]( 'view-tree-walker-unknown-direction', options.startPosition, { direction: options.direction } );
}
/**
* Iterator boundaries.
*
* When the iterator is walking `'forward'` on the end of boundary or is walking `'backward'`
* on the start of boundary, then `{ done: true }` is returned.
*
* If boundaries are not defined they are set before first and after last child of the root node.
*
* @readonly
* @member {module:engine/view/range~Range} module:engine/view/treewalker~TreeWalker#boundaries
*/
this.boundaries = options.boundaries || null;
/**
* Iterator position. If start position is not defined then position depends on {@link #direction}. If direction is
* `'forward'` position starts form the beginning, when direction is `'backward'` position starts from the end.
*
* @readonly
* @member {module:engine/view/position~Position} module:engine/view/treewalker~TreeWalker#position
*/
if ( options.startPosition ) {
this.position = _position__WEBPACK_IMPORTED_MODULE_3__["default"]._createAt( options.startPosition );
} else {
this.position = _position__WEBPACK_IMPORTED_MODULE_3__["default"]._createAt( options.boundaries[ options.direction == 'backward' ? 'end' : 'start' ] );
}
/**
* Walking direction. Defaults `'forward'`.
*
* @readonly
* @member {'backward'|'forward'} module:engine/view/treewalker~TreeWalker#direction
*/
this.direction = options.direction || 'forward';
/**
* Flag indicating whether all characters from {@link module:engine/view/text~Text} should be returned as one
* {@link module:engine/view/text~Text} or one by one as {@link module:engine/view/textproxy~TextProxy}.
*
* @readonly
* @member {Boolean} module:engine/view/treewalker~TreeWalker#singleCharacters
*/
this.singleCharacters = !!options.singleCharacters;
/**
* Flag indicating whether iterator should enter elements or not. If the iterator is shallow child nodes of any
* iterated node will not be returned along with `elementEnd` tag.
*
* @readonly
* @member {Boolean} module:engine/view/treewalker~TreeWalker#shallow
*/
this.shallow = !!options.shallow;
/**
* Flag indicating whether iterator should ignore `elementEnd` tags. If set to `true`, walker will not
* return a parent node of the start position. Each {@link module:engine/view/element~Element} will be returned once.
* When set to `false` each element might be returned twice: for `'elementStart'` and `'elementEnd'`.
*
* @readonly
* @member {Boolean} module:engine/view/treewalker~TreeWalker#ignoreElementEnd
*/
this.ignoreElementEnd = !!options.ignoreElementEnd;
/**
* Start boundary parent.
*
* @private
* @member {module:engine/view/node~Node} module:engine/view/treewalker~TreeWalker#_boundaryStartParent
*/
this._boundaryStartParent = this.boundaries ? this.boundaries.start.parent : null;
/**
* End boundary parent.
*
* @private
* @member {module:engine/view/node~Node} module:engine/view/treewalker~TreeWalker#_boundaryEndParent
*/
this._boundaryEndParent = this.boundaries ? this.boundaries.end.parent : null;
}
/**
* Iterable interface.
*
* @returns {Iterable.<module:engine/view/treewalker~TreeWalkerValue>}
*/
[ Symbol.iterator ]() {
return this;
}
/**
* Moves {@link #position} in the {@link #direction} skipping values as long as the callback function returns `true`.
*
* For example:
*
* walker.skip( value => value.type == 'text' ); // <p>{}foo</p> -> <p>foo[]</p>
* walker.skip( value => true ); // Move the position to the end: <p>{}foo</p> -> <p>foo</p>[]
* walker.skip( value => false ); // Do not move the position.
*
* @param {Function} skip Callback function. Gets {@link module:engine/view/treewalker~TreeWalkerValue} and should
* return `true` if the value should be skipped or `false` if not.
*/
skip( skip ) {
let done, value, prevPosition;
do {
prevPosition = this.position;
( { done, value } = this.next() );
} while ( !done && skip( value ) );
if ( !done ) {
this.position = prevPosition;
}
}
/**
* Gets the next tree walker's value.
*
* @returns {module:engine/view/treewalker~TreeWalkerValue} Object implementing iterator interface, returning
* information about taken step.
*/
next() {
if ( this.direction == 'forward' ) {
return this._next();
} else {
return this._previous();
}
}
/**
* Makes a step forward in view. Moves the {@link #position} to the next position and returns the encountered value.
*
* @private
* @returns {Object}
* @returns {Boolean} return.done `true` if iterator is done, `false` otherwise.
* @returns {module:engine/view/treewalker~TreeWalkerValue} return.value Information about taken step.
*/
_next() {
let position = this.position.clone();
const previousPosition = this.position;
const parent = position.parent;
// We are at the end of the root.
if ( parent.parent === null && position.offset === parent.childCount ) {
return { done: true };
}
// We reached the walker boundary.
if ( parent === this._boundaryEndParent && position.offset == this.boundaries.end.offset ) {
return { done: true };
}
// Get node just after current position.
let node;
// Text is a specific parent because it contains string instead of child nodes.
if ( parent instanceof _text__WEBPACK_IMPORTED_MODULE_1__["default"] ) {
if ( position.isAtEnd ) {
// Prevent returning "elementEnd" for Text node. Skip that value and return the next walker step.
this.position = _position__WEBPACK_IMPORTED_MODULE_3__["default"]._createAfter( parent );
return this._next();
}
node = parent.data[ position.offset ];
} else {
node = parent.getChild( position.offset );
}
if ( node instanceof _element__WEBPACK_IMPORTED_MODULE_0__["default"] ) {
if ( !this.shallow ) {
position = new _position__WEBPACK_IMPORTED_MODULE_3__["default"]( node, 0 );
} else {
position.offset++;
}
this.position = position;
return this._formatReturnValue( 'elementStart', node, previousPosition, position, 1 );
} else if ( node instanceof _text__WEBPACK_IMPORTED_MODULE_1__["default"] ) {
if ( this.singleCharacters ) {
position = new _position__WEBPACK_IMPORTED_MODULE_3__["default"]( node, 0 );
this.position = position;
return this._next();
} else {
let charactersCount = node.data.length;
let item;
// If text stick out of walker range, we need to cut it and wrap in TextProxy.
if ( node == this._boundaryEndParent ) {
charactersCount = this.boundaries.end.offset;
item = new _textproxy__WEBPACK_IMPORTED_MODULE_2__["default"]( node, 0, charactersCount );
position = _position__WEBPACK_IMPORTED_MODULE_3__["default"]._createAfter( item );
} else {
item = new _textproxy__WEBPACK_IMPORTED_MODULE_2__["default"]( node, 0, node.data.length );
// If not just keep moving forward.
position.offset++;
}
this.position = position;
return this._formatReturnValue( 'text', item, previousPosition, position, charactersCount );
}
} else if ( typeof node == 'string' ) {
let textLength;
if ( this.singleCharacters ) {
textLength = 1;
} else {
// Check if text stick out of walker range.
const endOffset = parent === this._boundaryEndParent ? this.boundaries.end.offset : parent.data.length;
textLength = endOffset - position.offset;
}
const textProxy = new _textproxy__WEBPACK_IMPORTED_MODULE_2__["default"]( parent, position.offset, textLength );
position.offset += textLength;
this.position = position;
return this._formatReturnValue( 'text', textProxy, previousPosition, position, textLength );
} else {
// `node` is not set, we reached the end of current `parent`.
position = _position__WEBPACK_IMPORTED_MODULE_3__["default"]._createAfter( parent );
this.position = position;
if ( this.ignoreElementEnd ) {
return this._next();
} else {
return this._formatReturnValue( 'elementEnd', parent, previousPosition, position );
}
}
}
/**
* Makes a step backward in view. Moves the {@link #position} to the previous position and returns the encountered value.
*
* @private
* @returns {Object}
* @returns {Boolean} return.done True if iterator is done.
* @returns {module:engine/view/treewalker~TreeWalkerValue} return.value Information about taken step.
*/
_previous() {
let position = this.position.clone();
const previousPosition = this.position;
const parent = position.parent;
// We are at the beginning of the root.
if ( parent.parent === null && position.offset === 0 ) {
return { done: true };
}
// We reached the walker boundary.
if ( parent == this._boundaryStartParent && position.offset == this.boundaries.start.offset ) {
return { done: true };
}
// Get node just before current position.
let node;
// Text {@link module:engine/view/text~Text} element is a specific parent because contains string instead of child nodes.
if ( parent instanceof _text__WEBPACK_IMPORTED_MODULE_1__["default"] ) {
if ( position.isAtStart ) {
// Prevent returning "elementStart" for Text node. Skip that value and return the next walker step.
this.position = _position__WEBPACK_IMPORTED_MODULE_3__["default"]._createBefore( parent );
return this._previous();
}
node = parent.data[ position.offset - 1 ];
} else {
node = parent.getChild( position.offset - 1 );
}
if ( node instanceof _element__WEBPACK_IMPORTED_MODULE_0__["default"] ) {
if ( !this.shallow ) {
position = new _position__WEBPACK_IMPORTED_MODULE_3__["default"]( node, node.childCount );
this.position = position;
if ( this.ignoreElementEnd ) {
return this._previous();
} else {
return this._formatReturnValue( 'elementEnd', node, previousPosition, position );
}
} else {
position.offset--;
this.position = position;
return this._formatReturnValue( 'elementStart', node, previousPosition, position, 1 );
}
} else if ( node instanceof _text__WEBPACK_IMPORTED_MODULE_1__["default"] ) {
if ( this.singleCharacters ) {
position = new _position__WEBPACK_IMPORTED_MODULE_3__["default"]( node, node.data.length );
this.position = position;
return this._previous();
} else {
let charactersCount = node.data.length;
let item;
// If text stick out of walker range, we need to cut it and wrap in TextProxy.
if ( node == this._boundaryStartParent ) {
const offset = this.boundaries.start.offset;
item = new _textproxy__WEBPACK_IMPORTED_MODULE_2__["default"]( node, offset, node.data.length - offset );
charactersCount = item.data.length;
position = _position__WEBPACK_IMPORTED_MODULE_3__["default"]._createBefore( item );
} else {
item = new _textproxy__WEBPACK_IMPORTED_MODULE_2__["default"]( node, 0, node.data.length );
// If not just keep moving backward.
position.offset--;
}
this.position = position;
return this._formatReturnValue( 'text', item, previousPosition, position, charactersCount );
}
} else if ( typeof node == 'string' ) {
let textLength;
if ( !this.singleCharacters ) {
// Check if text stick out of walker range.
const startOffset = parent === this._boundaryStartParent ? this.boundaries.start.offset : 0;
textLength = position.offset - startOffset;
} else {
textLength = 1;
}
position.offset -= textLength;
const textProxy = new _textproxy__WEBPACK_IMPORTED_MODULE_2__["default"]( parent, position.offset, textLength );
this.position = position;
return this._formatReturnValue( 'text', textProxy, previousPosition, position, textLength );
} else {
// `node` is not set, we reached the beginning of current `parent`.
position = _position__WEBPACK_IMPORTED_MODULE_3__["default"]._createBefore( parent );
this.position = position;
return this._formatReturnValue( 'elementStart', parent, previousPosition, position, 1 );
}
}
/**
* Format returned data and adjust `previousPosition` and `nextPosition` if reach the bound of the {@link module:engine/view/text~Text}.
*
* @private
* @param {module:engine/view/treewalker~TreeWalkerValueType} type Type of step.
* @param {module:engine/view/item~Item} item Item between old and new position.
* @param {module:engine/view/position~Position} previousPosition Previous position of iterator.
* @param {module:engine/view/position~Position} nextPosition Next position of iterator.
* @param {Number} [length] Length of the item.
* @returns {module:engine/view/treewalker~TreeWalkerValue}
*/
_formatReturnValue( type, item, previousPosition, nextPosition, length ) {
// Text is a specific parent, because contains string instead of children.
// Walker doesn't enter to the Text except situations when walker is iterating over every single character,
// or the bound starts/ends inside the Text. So when the position is at the beginning or at the end of the Text
// we move it just before or just after Text.
if ( item instanceof _textproxy__WEBPACK_IMPORTED_MODULE_2__["default"] ) {
// Position is at the end of Text.
if ( item.offsetInText + item.data.length == item.textNode.data.length ) {
if ( this.direction == 'forward' && !( this.boundaries && this.boundaries.end.isEqual( this.position ) ) ) {
nextPosition = _position__WEBPACK_IMPORTED_MODULE_3__["default"]._createAfter( item.textNode );
// When we change nextPosition of returned value we need also update walker current position.
this.position = nextPosition;
} else {
previousPosition = _position__WEBPACK_IMPORTED_MODULE_3__["default"]._createAfter( item.textNode );
}
}
// Position is at the begining ot the text.
if ( item.offsetInText === 0 ) {
if ( this.direction == 'backward' && !( this.boundaries && this.boundaries.start.isEqual( this.position ) ) ) {
nextPosition = _position__WEBPACK_IMPORTED_MODULE_3__["default"]._createBefore( item.textNode );
// When we change nextPosition of returned value we need also update walker current position.
this.position = nextPosition;
} else {
previousPosition = _position__WEBPACK_IMPORTED_MODULE_3__["default"]._createBefore( item.textNode );
}
}
}
return {
done: false,
value: {
type,
item,
previousPosition,
nextPosition,
length
}
};
}
}
/**
* Type of the step made by {@link module:engine/view/treewalker~TreeWalker}.
* Possible values: `'elementStart'` if walker is at the beginning of a node, `'elementEnd'` if walker is at the end
* of node, or `'text'` if walker traversed over single and multiple characters.
* For {@link module:engine/view/text~Text} `elementStart` and `elementEnd` is not returned.
*
* @typedef {String} module:engine/view/treewalker~TreeWalkerValueType
*/
/**
* Object returned by {@link module:engine/view/treewalker~TreeWalker} when traversing tree view.
*
* @typedef {Object} module:engine/view/treewalker~TreeWalkerValue
* @property {module:engine/view/treewalker~TreeWalkerValueType} type
* @property {module:engine/view/item~Item} item Item between the old and the new positions
* of the tree walker.
* @property {module:engine/view/position~Position} previousPosition Previous position of the iterator.
* * Forward iteration: For `'elementEnd'` it is the last position inside the element. For all other types it is the
* position before the item.
* * Backward iteration: For `'elementStart'` it is the first position inside the element. For all other types it is
* the position after item.
* * If the position is at the beginning or at the end of the {@link module:engine/view/text~Text} it is always moved from the
* inside of the text to its parent just before or just after that text.
* @property {module:engine/view/position~Position} nextPosition Next position of the iterator.
* * Forward iteration: For `'elementStart'` it is the first position inside the element. For all other types it is
* the position after the item.
* * Backward iteration: For `'elementEnd'` it is last position inside element. For all other types it is the position
* before the item.
* * If the position is at the beginning or at the end of the {@link module:engine/view/text~Text} it is always moved from the
* inside of the text to its parent just before or just after that text.
* @property {Number} [length] Length of the item. For `'elementStart'` it is `1`. For `'text'` it is
* the length of that text. For `'elementEnd'` it is `undefined`.
*/
/**
* Tree walking directions.
*
* @typedef {'forward'|'backward'} module:engine/view/treewalker~TreeWalkerDirection
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/view/uielement.js":
/*!***********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/view/uielement.js ***!
\***********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ UIElement),
/* harmony export */ "injectUiElementHandling": () => (/* binding */ injectUiElementHandling)
/* harmony export */ });
/* harmony import */ var _element__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./element */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/element.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/* harmony import */ var _node__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./node */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/node.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_keyboard__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/keyboard */ "./node_modules/@ckeditor/ckeditor5-utils/src/keyboard.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/view/uielement
*/
/**
* UI element class. It should be used to represent editing UI which needs to be injected into the editing view
* If possible, you should keep your UI outside the editing view. However, if that is not possible,
* UI elements can be used.
*
* How a UI element is rendered is in your control (you pass a callback to
* {@link module:engine/view/downcastwriter~DowncastWriter#createUIElement `downcastWriter#createUIElement()`}).
* The editor will ignore your UI element – the selection cannot be placed in it, it is skipped (invisible) when
* the user modifies the selection by using arrow keys and the editor does not listen to any mutations which
* happen inside your UI elements.
*
* The limitation is that you cannot convert a model element to a UI element. UI elements need to be
* created for {@link module:engine/model/markercollection~Marker markers} or as additinal elements
* inside normal {@link module:engine/view/containerelement~ContainerElement container elements}.
*
* To create a new UI element use the
* {@link module:engine/view/downcastwriter~DowncastWriter#createUIElement `downcastWriter#createUIElement()`} method.
*
* @extends module:engine/view/element~Element
*/
class UIElement extends _element__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* Creates new instance of UIElement.
*
* Throws {@link module:utils/ckeditorerror~CKEditorError CKEditorError} `view-uielement-cannot-add` when third parameter is passed,
* to inform that usage of UIElement is incorrect (adding child nodes to UIElement is forbidden).
*
* @see module:engine/view/downcastwriter~DowncastWriter#createUIElement
* @protected
* @param {module:engine/view/document~Document} document The document instance to which this element belongs.
* @param {String} name Node name.
* @param {Object|Iterable} [attributes] Collection of attributes.
* @param {module:engine/view/node~Node|Iterable.<module:engine/view/node~Node>} [children]
* A list of nodes to be inserted into created element.
*/
constructor( document, name, attributes, children ) {
super( document, name, attributes, children );
// Override the default of the base class.
this._isAllowedInsideAttributeElement = true;
/**
* Returns `null` because filler is not needed for UIElements.
*
* @method #getFillerOffset
* @returns {null} Always returns null.
*/
this.getFillerOffset = getFillerOffset;
}
/**
* Checks whether this object is of the given.
*
* uiElement.is( 'uiElement' ); // -> true
* uiElement.is( 'element' ); // -> true
* uiElement.is( 'node' ); // -> true
* uiElement.is( 'view:uiElement' ); // -> true
* uiElement.is( 'view:element' ); // -> true
* uiElement.is( 'view:node' ); // -> true
*
* uiElement.is( 'model:element' ); // -> false
* uiElement.is( 'documentFragment' ); // -> false
*
* Assuming that the object being checked is an ui element, you can also check its
* {@link module:engine/view/uielement~UIElement#name name}:
*
* uiElement.is( 'element', 'span' ); // -> true if this is a span ui element
* uiElement.is( 'uiElement', 'span' ); // -> same as above
* text.is( 'element', 'span' ); -> false
*
* {@link module:engine/view/node~Node#is Check the entire list of view objects} which implement the `is()` method.
*
* @param {String} type Type to check.
* @param {String} [name] Element name.
* @returns {Boolean}
*/
is( type, name = null ) {
if ( !name ) {
return type === 'uiElement' || type === 'view:uiElement' ||
// From super.is(). This is highly utilised method and cannot call super. See ckeditor/ckeditor5#6529.
type === 'element' || type === 'view:element' ||
type === 'node' || type === 'view:node';
} else {
return name === this.name && (
type === 'uiElement' || type === 'view:uiElement' ||
type === 'element' || type === 'view:element'
);
}
}
/**
* Overrides {@link module:engine/view/element~Element#_insertChild} method.
* Throws {@link module:utils/ckeditorerror~CKEditorError CKEditorError} `view-uielement-cannot-add` to prevent adding any child nodes
* to UIElement.
*
* @protected
*/
_insertChild( index, nodes ) {
if ( nodes && ( nodes instanceof _node__WEBPACK_IMPORTED_MODULE_2__["default"] || Array.from( nodes ).length > 0 ) ) {
/**
* Cannot add children to {@link module:engine/view/uielement~UIElement}.
*
* @error view-uielement-cannot-add
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_1__["default"]( 'view-uielement-cannot-add', this );
}
}
/**
* Renders this {@link module:engine/view/uielement~UIElement} to DOM. This method is called by
* {@link module:engine/view/domconverter~DomConverter}.
* Do not use inheritance to create custom rendering method, replace `render()` method instead:
*
* const myUIElement = downcastWriter.createUIElement( 'span' );
* myUIElement.render = function( domDocument, domConverter ) {
* const domElement = this.toDomElement( domDocument );
*
* domConverter.setContentOf( domElement, '<b>this is ui element</b>' );
*
* return domElement;
* };
*
* If changes in your UI element should trigger some editor UI update you should call
* the {@link module:core/editor/editorui~EditorUI#update `editor.ui.update()`} method
* after rendering your UI element.
*
* @param {Document} domDocument
* @param {module:engine/view/domconverter~DomConverter} domConverter Instance of the DomConverter used to optimize the output.
* @returns {HTMLElement}
*/
render( domDocument ) {
// Provide basic, default output.
return this.toDomElement( domDocument );
}
/**
* Creates DOM element based on this view UIElement.
* Note that each time this method is called new DOM element is created.
*
* @param {Document} domDocument
* @returns {HTMLElement}
*/
toDomElement( domDocument ) {
const domElement = domDocument.createElement( this.name );
for ( const key of this.getAttributeKeys() ) {
domElement.setAttribute( key, this.getAttribute( key ) );
}
return domElement;
}
}
/**
* This function injects UI element handling to the given {@link module:engine/view/document~Document document}.
*
* A callback is added to {@link module:engine/view/document~Document#event:keydown document keydown event}.
* The callback handles the situation when right arrow key is pressed and selection is collapsed before a UI element.
* Without this handler, it would be impossible to "jump over" UI element using right arrow key.
*
* @param {module:engine/view/view~View} view View controller to which the quirks handling will be injected.
*/
function injectUiElementHandling( view ) {
view.document.on( 'arrowKey', ( evt, data ) => jumpOverUiElement( evt, data, view.domConverter ), { priority: 'low' } );
}
// Returns `null` because block filler is not needed for UIElements.
//
// @returns {null}
function getFillerOffset() {
return null;
}
// Selection cannot be placed in a `UIElement`. Whenever it is placed there, it is moved before it. This
// causes a situation when it is impossible to jump over `UIElement` using right arrow key, because the selection
// ends up in ui element (in DOM) and is moved back to the left. This handler fixes this situation.
function jumpOverUiElement( evt, data, domConverter ) {
if ( data.keyCode == _ckeditor_ckeditor5_utils_src_keyboard__WEBPACK_IMPORTED_MODULE_3__.keyCodes.arrowright ) {
const domSelection = data.domTarget.ownerDocument.defaultView.getSelection();
const domSelectionCollapsed = domSelection.rangeCount == 1 && domSelection.getRangeAt( 0 ).collapsed;
// Jump over UI element if selection is collapsed or shift key is pressed. These are the cases when selection would extend.
if ( domSelectionCollapsed || data.shiftKey ) {
const domParent = domSelection.focusNode;
const domOffset = domSelection.focusOffset;
const viewPosition = domConverter.domPositionToView( domParent, domOffset );
// In case if dom element is not converted to view or is not mapped or something. Happens for example in some tests.
if ( viewPosition === null ) {
return;
}
// Skip all following ui elements.
let jumpedOverAnyUiElement = false;
const nextViewPosition = viewPosition.getLastMatchingPosition( value => {
if ( value.item.is( 'uiElement' ) ) {
// Remember that there was at least one ui element.
jumpedOverAnyUiElement = true;
}
// Jump over ui elements, jump over empty attribute elements, move up from inside of attribute element.
if ( value.item.is( 'uiElement' ) || value.item.is( 'attributeElement' ) ) {
return true;
}
// Don't jump over text or don't get out of container element.
return false;
} );
// If anything has been skipped, fix position.
// This `if` could be possibly omitted but maybe it is better not to mess with DOM selection if not needed.
if ( jumpedOverAnyUiElement ) {
const newDomPosition = domConverter.viewPositionToDom( nextViewPosition );
if ( domSelectionCollapsed ) {
// Selection was collapsed, so collapse it at further position.
domSelection.collapse( newDomPosition.parent, newDomPosition.offset );
} else {
// Selection was not collapse, so extend it instead of collapsing.
domSelection.extend( newDomPosition.parent, newDomPosition.offset );
}
}
}
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/view/upcastwriter.js":
/*!**************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/view/upcastwriter.js ***!
\**************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ UpcastWriter)
/* harmony export */ });
/* harmony import */ var _documentfragment__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./documentfragment */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/documentfragment.js");
/* harmony import */ var _element__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./element */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/element.js");
/* harmony import */ var _text__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./text */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/text.js");
/* harmony import */ var lodash_es__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! lodash-es */ "./node_modules/lodash-es/isPlainObject.js");
/* harmony import */ var _position__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./position */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/position.js");
/* harmony import */ var _range__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./range */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/range.js");
/* harmony import */ var _selection__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./selection */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/selection.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module module:engine/view/upcastwriter
*/
/**
* View upcast writer. It provides a set of methods used to manipulate non-semantic view trees.
*
* It should be used only while working on a non-semantic view
* (e.g. a view created from HTML string on paste).
* To manipulate a view which was or is being downcasted from the the model use the
* {@link module:engine/view/downcastwriter~DowncastWriter downcast writer}.
*
* Read more about changing the view in the {@glink framework/guides/architecture/editing-engine#changing-the-view Changing the view}
* section of the {@glink framework/guides/architecture/editing-engine Editing engine architecture} guide.
*
* Unlike `DowncastWriter`, which is available in the {@link module:engine/view/view~View#change `View#change()`} block,
* `UpcastWriter` can be created wherever you need it:
*
* const writer = new UpcastWriter( viewDocument );
* const text = writer.createText( 'foo!' );
*
* writer.appendChild( text, someViewElement );
*/
class UpcastWriter {
/**
* @param {module:engine/view/document~Document} document The view document instance in which this upcast writer operates.
*/
constructor( document ) {
/**
* The view document instance in which this upcast writer operates.
*
* @readonly
* @type {module:engine/view/document~Document}
*/
this.document = document;
}
/**
* Creates a new {@link module:engine/view/documentfragment~DocumentFragment} instance.
*
* @param {module:engine/view/node~Node|Iterable.<module:engine/view/node~Node>} [children]
* A list of nodes to be inserted into the created document fragment.
* @returns {module:engine/view/documentfragment~DocumentFragment} The created document fragment.
*/
createDocumentFragment( children ) {
return new _documentfragment__WEBPACK_IMPORTED_MODULE_0__["default"]( this.document, children );
}
/**
* Creates a new {@link module:engine/view/element~Element} instance.
*
* Attributes can be passed in various formats:
*
* upcastWriter.createElement( 'div', { class: 'editor', contentEditable: 'true' } ); // object
* upcastWriter.createElement( 'div', [ [ 'class', 'editor' ], [ 'contentEditable', 'true' ] ] ); // map-like iterator
* upcastWriter.createElement( 'div', mapOfAttributes ); // map
*
* @param {String} name Node name.
* @param {Object|Iterable} [attrs] Collection of attributes.
* @param {module:engine/view/node~Node|Iterable.<module:engine/view/node~Node>} [children]
* A list of nodes to be inserted into created element.
* @returns {module:engine/view/element~Element} Created element.
*/
createElement( name, attrs, children ) {
return new _element__WEBPACK_IMPORTED_MODULE_1__["default"]( this.document, name, attrs, children );
}
/**
* Creates a new {@link module:engine/view/text~Text} instance.
*
* @param {String} data The text's data.
* @returns {module:engine/view/text~Text} The created text node.
*/
createText( data ) {
return new _text__WEBPACK_IMPORTED_MODULE_2__["default"]( this.document, data );
}
/**
* Clones the provided element.
*
* @see module:engine/view/element~Element#_clone
* @param {module:engine/view/element~Element} element Element to be cloned.
* @param {Boolean} [deep=false] If set to `true` clones element and all its children recursively. When set to `false`,
* element will be cloned without any children.
* @returns {module:engine/view/element~Element} Clone of this element.
*/
clone( element, deep = false ) {
return element._clone( deep );
}
/**
* Appends a child node or a list of child nodes at the end of this node
* and sets the parent of these nodes to this element.
*
* @see module:engine/view/element~Element#_appendChild
* @param {module:engine/view/item~Item|Iterable.<module:engine/view/item~Item>} items Items to be inserted.
* @param {module:engine/view/element~Element|module:engine/view/documentfragment~DocumentFragment} element Element
* to which items will be appended.
* @fires module:engine/view/node~Node#event:change
* @returns {Number} Number of appended nodes.
*/
appendChild( items, element ) {
return element._appendChild( items );
}
/**
* Inserts a child node or a list of child nodes on the given index and sets the parent of these nodes to
* this element.
*
* @see module:engine/view/element~Element#_insertChild
* @param {Number} index Offset at which nodes should be inserted.
* @param {module:engine/view/item~Item|Iterable.<module:engine/view/item~Item>} items Items to be inserted.
* @param {module:engine/view/element~Element|module:engine/view/documentfragment~DocumentFragment} element Element
* to which items will be inserted.
* @fires module:engine/view/node~Node#event:change
* @returns {Number} Number of inserted nodes.
*/
insertChild( index, items, element ) {
return element._insertChild( index, items );
}
/**
* Removes the given number of child nodes starting at the given index and set the parent of these nodes to `null`.
*
* @see module:engine/view/element~Element#_removeChildren
* @param {Number} index Offset from which nodes will be removed.
* @param {Number} howMany Number of nodes to remove.
* @param {module:engine/view/element~Element|module:engine/view/documentfragment~DocumentFragment} element Element
* which children will be removed.
* @fires module:engine/view/node~Node#event:change
* @returns {Array.<module:engine/view/node~Node>} The array containing removed nodes.
*/
removeChildren( index, howMany, element ) {
return element._removeChildren( index, howMany );
}
/**
* Removes given element from the view structure. Will not have effect on detached elements.
*
* @param {module:engine/view/element~Element} element Element which will be removed.
* @returns {Array.<module:engine/view/node~Node>} The array containing removed nodes.
*/
remove( element ) {
const parent = element.parent;
if ( parent ) {
return this.removeChildren( parent.getChildIndex( element ), 1, parent );
}
return [];
}
/**
* Replaces given element with the new one in the view structure. Will not have effect on detached elements.
*
* @param {module:engine/view/element~Element} oldElement Element which will be replaced.
* @param {module:engine/view/element~Element} newElement Element which will be inserted in the place of the old element.
* @returns {Boolean} Whether old element was successfully replaced.
*/
replace( oldElement, newElement ) {
const parent = oldElement.parent;
if ( parent ) {
const index = parent.getChildIndex( oldElement );
this.removeChildren( index, 1, parent );
this.insertChild( index, newElement, parent );
return true;
}
return false;
}
/**
* Removes given element from view structure and places its children in its position.
* It does nothing if element has no parent.
*
* @param {module:engine/view/element~Element} element Element to unwrap.
*/
unwrapElement( element ) {
const parent = element.parent;
if ( parent ) {
const index = parent.getChildIndex( element );
this.remove( element );
this.insertChild( index, element.getChildren(), parent );
}
}
/**
* Renames element by creating a copy of a given element but with its name changed and then moving contents of the
* old element to the new one.
*
* Since this function creates a new element and removes the given one, the new element is returned to keep reference.
*
* @param {String} newName New element name.
* @param {module:engine/view/element~Element} element Element to be renamed.
* @returns {module:engine/view/element~Element|null} New element or null if the old element
* was not replaced (happens for detached elements).
*/
rename( newName, element ) {
const newElement = new _element__WEBPACK_IMPORTED_MODULE_1__["default"]( this.document, newName, element.getAttributes(), element.getChildren() );
return this.replace( element, newElement ) ? newElement : null;
}
/**
* Adds or overwrites element's attribute with a specified key and value.
*
* writer.setAttribute( 'href', 'http://ckeditor.com', linkElement );
*
* @see module:engine/view/element~Element#_setAttribute
* @param {String} key Attribute key.
* @param {String} value Attribute value.
* @param {module:engine/view/element~Element} element Element for which attribute will be set.
*/
setAttribute( key, value, element ) {
element._setAttribute( key, value );
}
/**
* Removes attribute from the element.
*
* writer.removeAttribute( 'href', linkElement );
*
* @see module:engine/view/element~Element#_removeAttribute
* @param {String} key Attribute key.
* @param {module:engine/view/element~Element} element Element from which attribute will be removed.
*/
removeAttribute( key, element ) {
element._removeAttribute( key );
}
/**
* Adds specified class to the element.
*
* writer.addClass( 'foo', linkElement );
* writer.addClass( [ 'foo', 'bar' ], linkElement );
*
* @see module:engine/view/element~Element#_addClass
* @param {Array.<String>|String} className Single class name or array of class names which will be added.
* @param {module:engine/view/element~Element} element Element for which class will be added.
*/
addClass( className, element ) {
element._addClass( className );
}
/**
* Removes specified class from the element.
*
* writer.removeClass( 'foo', linkElement );
* writer.removeClass( [ 'foo', 'bar' ], linkElement );
*
* @see module:engine/view/element~Element#_removeClass
* @param {Array.<String>|String} className Single class name or array of class names which will be removed.
* @param {module:engine/view/element~Element} element Element from which class will be removed.
*/
removeClass( className, element ) {
element._removeClass( className );
}
/**
* Adds style to the element.
*
* writer.setStyle( 'color', 'red', element );
* writer.setStyle( {
* color: 'red',
* position: 'fixed'
* }, element );
*
* **Note**: This method can work with normalized style names if
* {@link module:engine/controller/datacontroller~DataController#addStyleProcessorRules a particular style processor rule is enabled}.
* See {@link module:engine/view/stylesmap~StylesMap#set `StylesMap#set()`} for details.
*
* @see module:engine/view/element~Element#_setStyle
* @param {String|Object} property Property name or object with key - value pairs.
* @param {String} [value] Value to set. This parameter is ignored if object is provided as the first parameter.
* @param {module:engine/view/element~Element} element Element for which style will be added.
*/
setStyle( property, value, element ) {
if ( (0,lodash_es__WEBPACK_IMPORTED_MODULE_6__["default"])( property ) && element === undefined ) {
element = value;
}
element._setStyle( property, value );
}
/**
* Removes specified style from the element.
*
* writer.removeStyle( 'color', element ); // Removes 'color' style.
* writer.removeStyle( [ 'color', 'border-top' ], element ); // Removes both 'color' and 'border-top' styles.
*
* **Note**: This method can work with normalized style names if
* {@link module:engine/controller/datacontroller~DataController#addStyleProcessorRules a particular style processor rule is enabled}.
* See {@link module:engine/view/stylesmap~StylesMap#remove `StylesMap#remove()`} for details.
*
* @see module:engine/view/element~Element#_removeStyle
* @param {Array.<String>|String} property Style property name or names to be removed.
* @param {module:engine/view/element~Element} element Element from which style will be removed.
*/
removeStyle( property, element ) {
element._removeStyle( property );
}
/**
* Sets a custom property on element. Unlike attributes, custom properties are not rendered to the DOM,
* so they can be used to add special data to elements.
*
* @see module:engine/view/element~Element#_setCustomProperty
* @param {String|Symbol} key Custom property name/key.
* @param {*} value Custom property value to be stored.
* @param {module:engine/view/element~Element} element Element for which custom property will be set.
*/
setCustomProperty( key, value, element ) {
element._setCustomProperty( key, value );
}
/**
* Removes a custom property stored under the given key.
*
* @see module:engine/view/element~Element#_removeCustomProperty
* @param {String|Symbol} key Name/key of the custom property to be removed.
* @param {module:engine/view/element~Element} element Element from which the custom property will be removed.
* @returns {Boolean} Returns true if property was removed.
*/
removeCustomProperty( key, element ) {
return element._removeCustomProperty( key );
}
/**
* Creates position at the given location. The location can be specified as:
*
* * a {@link module:engine/view/position~Position position},
* * parent element and offset (offset defaults to `0`),
* * parent element and `'end'` (sets position at the end of that element),
* * {@link module:engine/view/item~Item view item} and `'before'` or `'after'` (sets position before or after given view item).
*
* This method is a shortcut to other constructors such as:
*
* * {@link #createPositionBefore},
* * {@link #createPositionAfter},
*
* @param {module:engine/view/item~Item|module:engine/model/position~Position} itemOrPosition
* @param {Number|'end'|'before'|'after'} [offset] Offset or one of the flags. Used only when
* first parameter is a {@link module:engine/view/item~Item view item}.
* @returns {module:engine/view/position~Position}
*/
createPositionAt( itemOrPosition, offset ) {
return _position__WEBPACK_IMPORTED_MODULE_3__["default"]._createAt( itemOrPosition, offset );
}
/**
* Creates a new position after given view item.
*
* @param {module:engine/view/item~Item} item View item after which the position should be located.
* @returns {module:engine/view/position~Position}
*/
createPositionAfter( item ) {
return _position__WEBPACK_IMPORTED_MODULE_3__["default"]._createAfter( item );
}
/**
* Creates a new position before given view item.
*
* @param {module:engine/view/item~Item} item View item before which the position should be located.
* @returns {module:engine/view/position~Position}
*/
createPositionBefore( item ) {
return _position__WEBPACK_IMPORTED_MODULE_3__["default"]._createBefore( item );
}
/**
* Creates a range spanning from `start` position to `end` position.
*
* **Note:** This factory method creates it's own {@link module:engine/view/position~Position} instances basing on passed values.
*
* @param {module:engine/view/position~Position} start Start position.
* @param {module:engine/view/position~Position} [end] End position. If not set, range will be collapsed at `start` position.
* @returns {module:engine/view/range~Range}
*/
createRange( start, end ) {
return new _range__WEBPACK_IMPORTED_MODULE_4__["default"]( start, end );
}
/**
* Creates a range that starts before given {@link module:engine/view/item~Item view item} and ends after it.
*
* @param {module:engine/view/item~Item} item
* @returns {module:engine/view/range~Range}
*/
createRangeOn( item ) {
return _range__WEBPACK_IMPORTED_MODULE_4__["default"]._createOn( item );
}
/**
* Creates a range inside an {@link module:engine/view/element~Element element} which starts before the first child of
* that element and ends after the last child of that element.
*
* @param {module:engine/view/element~Element} element Element which is a parent for the range.
* @returns {module:engine/view/range~Range}
*/
createRangeIn( element ) {
return _range__WEBPACK_IMPORTED_MODULE_4__["default"]._createIn( element );
}
/**
* Creates a new {@link module:engine/view/selection~Selection} instance.
*
* // Creates empty selection without ranges.
* const selection = writer.createSelection();
*
* // Creates selection at the given range.
* const range = writer.createRange( start, end );
* const selection = writer.createSelection( range );
*
* // Creates selection at the given ranges
* const ranges = [ writer.createRange( start1, end2 ), writer.createRange( star2, end2 ) ];
* const selection = writer.createSelection( ranges );
*
* // Creates selection from the other selection.
* const otherSelection = writer.createSelection();
* const selection = writer.createSelection( otherSelection );
*
* // Creates selection from the document selection.
* const selection = writer.createSelection( editor.editing.view.document.selection );
*
* // Creates selection at the given position.
* const position = writer.createPositionFromPath( root, path );
* const selection = writer.createSelection( position );
*
* // Creates collapsed selection at the position of given item and offset.
* const paragraph = writer.createContainerElement( 'paragraph' );
* const selection = writer.createSelection( paragraph, offset );
*
* // Creates a range inside an {@link module:engine/view/element~Element element} which starts before the
* // first child of that element and ends after the last child of that element.
* const selection = writer.createSelection( paragraph, 'in' );
*
* // Creates a range on an {@link module:engine/view/item~Item item} which starts before the item and ends
* // just after the item.
* const selection = writer.createSelection( paragraph, 'on' );
*
* `Selection`'s constructor allow passing additional options (`backward`, `fake` and `label`) as the last argument.
*
* // Creates backward selection.
* const selection = writer.createSelection( range, { backward: true } );
*
* Fake selection does not render as browser native selection over selected elements and is hidden to the user.
* This way, no native selection UI artifacts are displayed to the user and selection over elements can be
* represented in other way, for example by applying proper CSS class.
*
* Additionally fake's selection label can be provided. It will be used to describe fake selection in DOM
* (and be properly handled by screen readers).
*
* // Creates fake selection with label.
* const selection = writer.createSelection( range, { fake: true, label: 'foo' } );
*
* @param {module:engine/view/selection~Selectable} [selectable=null]
* @param {Number|'before'|'end'|'after'|'on'|'in'} [placeOrOffset] Offset or place when selectable is an `Item`.
* @param {Object} [options]
* @param {Boolean} [options.backward] Sets this selection instance to be backward.
* @param {Boolean} [options.fake] Sets this selection instance to be marked as `fake`.
* @param {String} [options.label] Label for the fake selection.
* @returns {module:engine/view/selection~Selection}
*/
createSelection( selectable, placeOrOffset, options ) {
return new _selection__WEBPACK_IMPORTED_MODULE_5__["default"]( selectable, placeOrOffset, options );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/src/view/view.js":
/*!******************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/src/view/view.js ***!
\******************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ View)
/* harmony export */ });
/* harmony import */ var _document__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./document */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/document.js");
/* harmony import */ var _downcastwriter__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./downcastwriter */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/downcastwriter.js");
/* harmony import */ var _renderer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./renderer */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/renderer.js");
/* harmony import */ var _domconverter__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./domconverter */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/domconverter.js");
/* harmony import */ var _position__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./position */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/position.js");
/* harmony import */ var _range__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./range */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/range.js");
/* harmony import */ var _selection__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./selection */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/selection.js");
/* harmony import */ var _observer_mutationobserver__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./observer/mutationobserver */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/mutationobserver.js");
/* harmony import */ var _observer_keyobserver__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./observer/keyobserver */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/keyobserver.js");
/* harmony import */ var _observer_fakeselectionobserver__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./observer/fakeselectionobserver */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/fakeselectionobserver.js");
/* harmony import */ var _observer_selectionobserver__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./observer/selectionobserver */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/selectionobserver.js");
/* harmony import */ var _observer_focusobserver__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ./observer/focusobserver */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/focusobserver.js");
/* harmony import */ var _observer_compositionobserver__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! ./observer/compositionobserver */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/compositionobserver.js");
/* harmony import */ var _observer_inputobserver__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(/*! ./observer/inputobserver */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/inputobserver.js");
/* harmony import */ var _observer_arrowkeysobserver__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(/*! ./observer/arrowkeysobserver */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/arrowkeysobserver.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_observablemixin__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/observablemixin */ "./node_modules/@ckeditor/ckeditor5-utils/src/observablemixin.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/mix */ "./node_modules/@ckeditor/ckeditor5-utils/src/mix.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_dom_scroll__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/dom/scroll */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/scroll.js");
/* harmony import */ var _uielement__WEBPACK_IMPORTED_MODULE_18__ = __webpack_require__(/*! ./uielement */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/uielement.js");
/* harmony import */ var _filler__WEBPACK_IMPORTED_MODULE_19__ = __webpack_require__(/*! ./filler */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/filler.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_20__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_env__WEBPACK_IMPORTED_MODULE_21__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/env */ "./node_modules/@ckeditor/ckeditor5-utils/src/env.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module engine/view/view
*/
/**
* Editor's view controller class. Its main responsibility is DOM - View management for editing purposes, to provide
* abstraction over the DOM structure and events and hide all browsers quirks.
*
* View controller renders view document to DOM whenever view structure changes. To determine when view can be rendered,
* all changes need to be done using the {@link module:engine/view/view~View#change} method, using
* {@link module:engine/view/downcastwriter~DowncastWriter}:
*
* view.change( writer => {
* writer.insert( position, writer.createText( 'foo' ) );
* } );
*
* View controller also register {@link module:engine/view/observer/observer~Observer observers} which observes changes
* on DOM and fire events on the {@link module:engine/view/document~Document Document}.
* Note that the following observers are added by the class constructor and are always available:
*
* * {@link module:engine/view/observer/mutationobserver~MutationObserver},
* * {@link module:engine/view/observer/selectionobserver~SelectionObserver},
* * {@link module:engine/view/observer/focusobserver~FocusObserver},
* * {@link module:engine/view/observer/keyobserver~KeyObserver},
* * {@link module:engine/view/observer/fakeselectionobserver~FakeSelectionObserver}.
* * {@link module:engine/view/observer/compositionobserver~CompositionObserver}.
*
* This class also {@link module:engine/view/view~View#attachDomRoot binds the DOM and the view elements}.
*
* If you do not need full a DOM - view management, and only want to transform a tree of view elements to a tree of DOM
* elements you do not need this controller. You can use the {@link module:engine/view/domconverter~DomConverter DomConverter} instead.
*
* @mixes module:utils/observablemixin~ObservableMixin
*/
class View {
/**
* @param {module:engine/view/stylesmap~StylesProcessor} stylesProcessor The styles processor instance.
*/
constructor( stylesProcessor ) {
/**
* Instance of the {@link module:engine/view/document~Document} associated with this view controller.
*
* @readonly
* @type {module:engine/view/document~Document}
*/
this.document = new _document__WEBPACK_IMPORTED_MODULE_0__["default"]( stylesProcessor );
/**
* Instance of the {@link module:engine/view/domconverter~DomConverter domConverter} used by
* {@link module:engine/view/view~View#_renderer renderer}
* and {@link module:engine/view/observer/observer~Observer observers}.
*
* @readonly
* @type {module:engine/view/domconverter~DomConverter}
*/
this.domConverter = new _domconverter__WEBPACK_IMPORTED_MODULE_3__["default"]( this.document );
/**
* Roots of the DOM tree. Map on the `HTMLElement`s with roots names as keys.
*
* @readonly
* @type {Map.<String, HTMLElement>}
*/
this.domRoots = new Map();
/**
* Used to prevent calling {@link #forceRender} and {@link #change} during rendering view to the DOM.
*
* @readonly
* @member {Boolean} #isRenderingInProgress
*/
this.set( 'isRenderingInProgress', false );
/**
* Informs whether the DOM selection is inside any of the DOM roots managed by the view.
*
* @readonly
* @member {Boolean} #hasDomSelection
*/
this.set( 'hasDomSelection', false );
/**
* Instance of the {@link module:engine/view/renderer~Renderer renderer}.
*
* @protected
* @type {module:engine/view/renderer~Renderer}
*/
this._renderer = new _renderer__WEBPACK_IMPORTED_MODULE_2__["default"]( this.domConverter, this.document.selection );
this._renderer.bind( 'isFocused', 'isSelecting' ).to( this.document );
/**
* A DOM root attributes cache. It saves the initial values of DOM root attributes before the DOM element
* is {@link module:engine/view/view~View#attachDomRoot attached} to the view so later on, when
* the view is destroyed ({@link module:engine/view/view~View#detachDomRoot}), they can be easily restored.
* This way, the DOM element can go back to the (clean) state as if the editing view never used it.
*
* @private
* @member {WeakMap.<HTMLElement,Object>}
*/
this._initialDomRootAttributes = new WeakMap();
/**
* Map of registered {@link module:engine/view/observer/observer~Observer observers}.
*
* @private
* @type {Map.<Function, module:engine/view/observer/observer~Observer>}
*/
this._observers = new Map();
/**
* Is set to `true` when {@link #change view changes} are currently in progress.
*
* @private
* @type {Boolean}
*/
this._ongoingChange = false;
/**
* Used to prevent calling {@link #forceRender} and {@link #change} during rendering view to the DOM.
*
* @private
* @type {Boolean}
*/
this._postFixersInProgress = false;
/**
* Internal flag to temporary disable rendering. See the usage in the {@link #_disableRendering}.
*
* @private
* @type {Boolean}
*/
this._renderingDisabled = false;
/**
* Internal flag that disables rendering when there are no changes since the last rendering.
* It stores information about changed selection and changed elements from attached document roots.
*
* @private
* @type {Boolean}
*/
this._hasChangedSinceTheLastRendering = false;
/**
* DowncastWriter instance used in {@link #change change method} callbacks.
*
* @private
* @type {module:engine/view/downcastwriter~DowncastWriter}
*/
this._writer = new _downcastwriter__WEBPACK_IMPORTED_MODULE_1__["default"]( this.document );
// Add default observers.
this.addObserver( _observer_mutationobserver__WEBPACK_IMPORTED_MODULE_7__["default"] );
this.addObserver( _observer_selectionobserver__WEBPACK_IMPORTED_MODULE_10__["default"] );
this.addObserver( _observer_focusobserver__WEBPACK_IMPORTED_MODULE_11__["default"] );
this.addObserver( _observer_keyobserver__WEBPACK_IMPORTED_MODULE_8__["default"] );
this.addObserver( _observer_fakeselectionobserver__WEBPACK_IMPORTED_MODULE_9__["default"] );
this.addObserver( _observer_compositionobserver__WEBPACK_IMPORTED_MODULE_12__["default"] );
this.addObserver( _observer_arrowkeysobserver__WEBPACK_IMPORTED_MODULE_14__["default"] );
if ( _ckeditor_ckeditor5_utils_src_env__WEBPACK_IMPORTED_MODULE_21__["default"].isAndroid ) {
this.addObserver( _observer_inputobserver__WEBPACK_IMPORTED_MODULE_13__["default"] );
}
// Inject quirks handlers.
(0,_filler__WEBPACK_IMPORTED_MODULE_19__.injectQuirksHandling)( this );
(0,_uielement__WEBPACK_IMPORTED_MODULE_18__.injectUiElementHandling)( this );
// Use 'normal' priority so that rendering is performed as first when using that priority.
this.on( 'render', () => {
this._render();
// Informs that layout has changed after render.
this.document.fire( 'layoutChanged' );
// Reset the `_hasChangedSinceTheLastRendering` flag after rendering.
this._hasChangedSinceTheLastRendering = false;
} );
// Listen to the document selection changes directly.
this.listenTo( this.document.selection, 'change', () => {
this._hasChangedSinceTheLastRendering = true;
} );
// Trigger re-render if only the focus changed.
this.listenTo( this.document, 'change:isFocused', () => {
this._hasChangedSinceTheLastRendering = true;
} );
}
/**
* Attaches a DOM root element to the view element and enable all observers on that element.
* Also {@link module:engine/view/renderer~Renderer#markToSync mark element} to be synchronized
* with the view what means that all child nodes will be removed and replaced with content of the view root.
*
* This method also will change view element name as the same as tag name of given dom root.
* Name is always transformed to lower case.
*
* **Note:** Use {@link #detachDomRoot `detachDomRoot()`} to revert this action.
*
* @param {Element} domRoot DOM root element.
* @param {String} [name='main'] Name of the root.
*/
attachDomRoot( domRoot, name = 'main' ) {
const viewRoot = this.document.getRoot( name );
// Set view root name the same as DOM root tag name.
viewRoot._name = domRoot.tagName.toLowerCase();
const initialDomRootAttributes = {};
// 1. Copy and cache the attributes to remember the state of the element before attaching.
// The cached attributes will be restored in detachDomRoot() so the element goes to the
// clean state as if the editing view never used it.
// 2. Apply the attributes using the view writer, so they all go under the control of the engine.
// The editing view takes over the attribute management completely because various
// features (e.g. addPlaceholder()) require dynamic changes of those attributes and they
// cannot be managed by the engine and the UI library at the same time.
for ( const { name, value } of Array.from( domRoot.attributes ) ) {
initialDomRootAttributes[ name ] = value;
// Do not use writer.setAttribute() for the class attribute. The EditableUIView class
// and its descendants could have already set some using the writer.addClass() on the view
// document root. They haven't been rendered yet so they are not present in the DOM root.
// Using writer.setAttribute( 'class', ... ) would override them completely.
if ( name === 'class' ) {
this._writer.addClass( value.split( ' ' ), viewRoot );
} else {
this._writer.setAttribute( name, value, viewRoot );
}
}
this._initialDomRootAttributes.set( domRoot, initialDomRootAttributes );
const updateContenteditableAttribute = () => {
this._writer.setAttribute( 'contenteditable', !viewRoot.isReadOnly, viewRoot );
if ( viewRoot.isReadOnly ) {
this._writer.addClass( 'ck-read-only', viewRoot );
} else {
this._writer.removeClass( 'ck-read-only', viewRoot );
}
};
// Set initial value.
updateContenteditableAttribute();
this.domRoots.set( name, domRoot );
this.domConverter.bindElements( domRoot, viewRoot );
this._renderer.markToSync( 'children', viewRoot );
this._renderer.markToSync( 'attributes', viewRoot );
this._renderer.domDocuments.add( domRoot.ownerDocument );
viewRoot.on( 'change:children', ( evt, node ) => this._renderer.markToSync( 'children', node ) );
viewRoot.on( 'change:attributes', ( evt, node ) => this._renderer.markToSync( 'attributes', node ) );
viewRoot.on( 'change:text', ( evt, node ) => this._renderer.markToSync( 'text', node ) );
viewRoot.on( 'change:isReadOnly', () => this.change( updateContenteditableAttribute ) );
viewRoot.on( 'change', () => {
this._hasChangedSinceTheLastRendering = true;
} );
for ( const observer of this._observers.values() ) {
observer.observe( domRoot, name );
}
}
/**
* Detaches a DOM root element from the view element and restores its attributes to the state before
* {@link #attachDomRoot `attachDomRoot()`}.
*
* @param {String} name Name of the root to detach.
*/
detachDomRoot( name ) {
const domRoot = this.domRoots.get( name );
// Remove all root attributes so the DOM element is "bare".
Array.from( domRoot.attributes ).forEach( ( { name } ) => domRoot.removeAttribute( name ) );
const initialDomRootAttributes = this._initialDomRootAttributes.get( domRoot );
// Revert all view root attributes back to the state before attachDomRoot was called.
for ( const attribute in initialDomRootAttributes ) {
domRoot.setAttribute( attribute, initialDomRootAttributes[ attribute ] );
}
this.domRoots.delete( name );
this.domConverter.unbindDomElement( domRoot );
}
/**
* Gets DOM root element.
*
* @param {String} [name='main'] Name of the root.
* @returns {Element} DOM root element instance.
*/
getDomRoot( name = 'main' ) {
return this.domRoots.get( name );
}
/**
* Creates observer of the given type if not yet created, {@link module:engine/view/observer/observer~Observer#enable enables} it
* and {@link module:engine/view/observer/observer~Observer#observe attaches} to all existing and future
* {@link #domRoots DOM roots}.
*
* Note: Observers are recognized by their constructor (classes). A single observer will be instantiated and used only
* when registered for the first time. This means that features and other components can register a single observer
* multiple times without caring whether it has been already added or not.
*
* @param {Function} Observer The constructor of an observer to add.
* Should create an instance inheriting from {@link module:engine/view/observer/observer~Observer}.
* @returns {module:engine/view/observer/observer~Observer} Added observer instance.
*/
addObserver( Observer ) {
let observer = this._observers.get( Observer );
if ( observer ) {
return observer;
}
observer = new Observer( this );
this._observers.set( Observer, observer );
for ( const [ name, domElement ] of this.domRoots ) {
observer.observe( domElement, name );
}
observer.enable();
return observer;
}
/**
* Returns observer of the given type or `undefined` if such observer has not been added yet.
*
* @param {Function} Observer The constructor of an observer to get.
* @returns {module:engine/view/observer/observer~Observer|undefined} Observer instance or undefined.
*/
getObserver( Observer ) {
return this._observers.get( Observer );
}
/**
* Disables all added observers.
*/
disableObservers() {
for ( const observer of this._observers.values() ) {
observer.disable();
}
}
/**
* Enables all added observers.
*/
enableObservers() {
for ( const observer of this._observers.values() ) {
observer.enable();
}
}
/**
* Scrolls the page viewport and {@link #domRoots} with their ancestors to reveal the
* caret, if not already visible to the user.
*/
scrollToTheSelection() {
const range = this.document.selection.getFirstRange();
if ( range ) {
(0,_ckeditor_ckeditor5_utils_src_dom_scroll__WEBPACK_IMPORTED_MODULE_17__.scrollViewportToShowTarget)( {
target: this.domConverter.viewRangeToDom( range ),
viewportOffset: 20
} );
}
}
/**
* It will focus DOM element representing {@link module:engine/view/editableelement~EditableElement EditableElement}
* that is currently having selection inside.
*/
focus() {
if ( !this.document.isFocused ) {
const editable = this.document.selection.editableElement;
if ( editable ) {
this.domConverter.focus( editable );
this.forceRender();
} else {
// Before focusing view document, selection should be placed inside one of the view's editables.
// Normally its selection will be converted from model document (which have default selection), but
// when using view document on its own, we need to manually place selection before focusing it.
//
// @if CK_DEBUG // console.warn( 'There is no selection in any editable to focus.' );
}
}
}
/**
* The `change()` method is the primary way of changing the view. You should use it to modify any node in the view tree.
* It makes sure that after all changes are made the view is rendered to the DOM (assuming that the view will be changed
* inside the callback). It prevents situations when the DOM is updated when the view state is not yet correct. It allows
* to nest calls one inside another and still performs a single rendering after all those changes are made.
* It also returns the return value of its callback.
*
* const text = view.change( writer => {
* const newText = writer.createText( 'foo' );
* writer.insert( position1, newText );
*
* view.change( writer => {
* writer.insert( position2, writer.createText( 'bar' ) );
* } );
*
* writer.remove( range );
*
* return newText;
* } );
*
* When the outermost change block is done and rendering to the DOM is over the
* {@link module:engine/view/view~View#event:render `View#render`} event is fired.
*
* This method throws a `applying-view-changes-on-rendering` error when
* the change block is used after rendering to the DOM has started.
*
* @param {Function} callback Callback function which may modify the view.
* @returns {*} Value returned by the callback.
*/
change( callback ) {
if ( this.isRenderingInProgress || this._postFixersInProgress ) {
/**
* Thrown when there is an attempt to make changes to the view tree when it is in incorrect state. This may
* cause some unexpected behaviour and inconsistency between the DOM and the view.
* This may be caused by:
*
* * calling {@link #change} or {@link #forceRender} during rendering process,
* * calling {@link #change} or {@link #forceRender} inside of
* {@link module:engine/view/document~Document#registerPostFixer post-fixer function}.
*
* @error cannot-change-view-tree
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_20__["default"](
'cannot-change-view-tree',
this
);
}
try {
// Recursive call to view.change() method - execute listener immediately.
if ( this._ongoingChange ) {
return callback( this._writer );
}
// This lock will assure that all recursive calls to view.change() will end up in same block - one "render"
// event for all nested calls.
this._ongoingChange = true;
const callbackResult = callback( this._writer );
this._ongoingChange = false;
// This lock is used by editing controller to render changes from outer most model.change() once. As plugins might call
// view.change() inside model.change() block - this will ensures that postfixers and rendering are called once after all
// changes. Also, we don't need to render anything if there're no changes since last rendering.
if ( !this._renderingDisabled && this._hasChangedSinceTheLastRendering ) {
this._postFixersInProgress = true;
this.document._callPostFixers( this._writer );
this._postFixersInProgress = false;
this.fire( 'render' );
}
return callbackResult;
} catch ( err ) {
// @if CK_DEBUG // throw err;
/* istanbul ignore next */
_ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_20__["default"].rethrowUnexpectedError( err, this );
}
}
/**
* Forces rendering {@link module:engine/view/document~Document view document} to DOM. If any view changes are
* currently in progress, rendering will start after all {@link #change change blocks} are processed.
*
* Note that this method is dedicated for special cases. All view changes should be wrapped in the {@link #change}
* block and the view will automatically check whether it needs to render DOM or not.
*
* Throws {@link module:utils/ckeditorerror~CKEditorError CKEditorError} `applying-view-changes-on-rendering` when
* trying to re-render when rendering to DOM has already started.
*/
forceRender() {
this._hasChangedSinceTheLastRendering = true;
this.change( () => {} );
}
/**
* Destroys this instance. Makes sure that all observers are destroyed and listeners removed.
*/
destroy() {
for ( const observer of this._observers.values() ) {
observer.destroy();
}
this.document.destroy();
this.stopListening();
}
/**
* Creates position at the given location. The location can be specified as:
*
* * a {@link module:engine/view/position~Position position},
* * parent element and offset (offset defaults to `0`),
* * parent element and `'end'` (sets position at the end of that element),
* * {@link module:engine/view/item~Item view item} and `'before'` or `'after'` (sets position before or after given view item).
*
* This method is a shortcut to other constructors such as:
*
* * {@link #createPositionBefore},
* * {@link #createPositionAfter},
*
* @param {module:engine/view/item~Item|module:engine/model/position~Position} itemOrPosition
* @param {Number|'end'|'before'|'after'} [offset] Offset or one of the flags. Used only when
* first parameter is a {@link module:engine/view/item~Item view item}.
*/
createPositionAt( itemOrPosition, offset ) {
return _position__WEBPACK_IMPORTED_MODULE_4__["default"]._createAt( itemOrPosition, offset );
}
/**
* Creates a new position after given view item.
*
* @param {module:engine/view/item~Item} item View item after which the position should be located.
* @returns {module:engine/view/position~Position}
*/
createPositionAfter( item ) {
return _position__WEBPACK_IMPORTED_MODULE_4__["default"]._createAfter( item );
}
/**
* Creates a new position before given view item.
*
* @param {module:engine/view/item~Item} item View item before which the position should be located.
* @returns {module:engine/view/position~Position}
*/
createPositionBefore( item ) {
return _position__WEBPACK_IMPORTED_MODULE_4__["default"]._createBefore( item );
}
/**
* Creates a range spanning from `start` position to `end` position.
*
* **Note:** This factory method creates it's own {@link module:engine/view/position~Position} instances basing on passed values.
*
* @param {module:engine/view/position~Position} start Start position.
* @param {module:engine/view/position~Position} [end] End position. If not set, range will be collapsed at `start` position.
* @returns {module:engine/view/range~Range}
*/
createRange( start, end ) {
return new _range__WEBPACK_IMPORTED_MODULE_5__["default"]( start, end );
}
/**
* Creates a range that starts before given {@link module:engine/view/item~Item view item} and ends after it.
*
* @param {module:engine/view/item~Item} item
* @returns {module:engine/view/range~Range}
*/
createRangeOn( item ) {
return _range__WEBPACK_IMPORTED_MODULE_5__["default"]._createOn( item );
}
/**
* Creates a range inside an {@link module:engine/view/element~Element element} which starts before the first child of
* that element and ends after the last child of that element.
*
* @param {module:engine/view/element~Element} element Element which is a parent for the range.
* @returns {module:engine/view/range~Range}
*/
createRangeIn( element ) {
return _range__WEBPACK_IMPORTED_MODULE_5__["default"]._createIn( element );
}
/**
Creates new {@link module:engine/view/selection~Selection} instance.
*
* // Creates empty selection without ranges.
* const selection = view.createSelection();
*
* // Creates selection at the given range.
* const range = view.createRange( start, end );
* const selection = view.createSelection( range );
*
* // Creates selection at the given ranges
* const ranges = [ view.createRange( start1, end2 ), view.createRange( star2, end2 ) ];
* const selection = view.createSelection( ranges );
*
* // Creates selection from the other selection.
* const otherSelection = view.createSelection();
* const selection = view.createSelection( otherSelection );
*
* // Creates selection from the document selection.
* const selection = view.createSelection( editor.editing.view.document.selection );
*
* // Creates selection at the given position.
* const position = view.createPositionFromPath( root, path );
* const selection = view.createSelection( position );
*
* // Creates collapsed selection at the position of given item and offset.
* const paragraph = view.createContainerElement( 'paragraph' );
* const selection = view.createSelection( paragraph, offset );
*
* // Creates a range inside an {@link module:engine/view/element~Element element} which starts before the
* // first child of that element and ends after the last child of that element.
* const selection = view.createSelection( paragraph, 'in' );
*
* // Creates a range on an {@link module:engine/view/item~Item item} which starts before the item and ends
* // just after the item.
* const selection = view.createSelection( paragraph, 'on' );
*
* `Selection`'s factory method allow passing additional options (`backward`, `fake` and `label`) as the last argument.
*
* // Creates backward selection.
* const selection = view.createSelection( range, { backward: true } );
*
* Fake selection does not render as browser native selection over selected elements and is hidden to the user.
* This way, no native selection UI artifacts are displayed to the user and selection over elements can be
* represented in other way, for example by applying proper CSS class.
*
* Additionally fake's selection label can be provided. It will be used to describe fake selection in DOM
* (and be properly handled by screen readers).
*
* // Creates fake selection with label.
* const selection = view.createSelection( range, { fake: true, label: 'foo' } );
*
* @param {module:engine/view/selection~Selectable} [selectable=null]
* @param {Number|'before'|'end'|'after'|'on'|'in'} [placeOrOffset] Offset or place when selectable is an `Item`.
* @param {Object} [options]
* @param {Boolean} [options.backward] Sets this selection instance to be backward.
* @param {Boolean} [options.fake] Sets this selection instance to be marked as `fake`.
* @param {String} [options.label] Label for the fake selection.
* @returns {module:engine/view/selection~Selection}
*/
createSelection( selectable, placeOrOffset, options ) {
return new _selection__WEBPACK_IMPORTED_MODULE_6__["default"]( selectable, placeOrOffset, options );
}
/**
* Disables or enables rendering. If the flag is set to `true` then the rendering will be disabled.
* If the flag is set to `false` and if there was some change in the meantime, then the rendering action will be performed.
*
* @protected
* @param {Boolean} flag A flag indicates whether the rendering should be disabled.
*/
_disableRendering( flag ) {
this._renderingDisabled = flag;
if ( flag == false ) {
// Render when you stop blocking rendering.
this.change( () => {} );
}
}
/**
* Renders all changes. In order to avoid triggering the observers (e.g. mutations) all observers are disabled
* before rendering and re-enabled after that.
*
* @private
*/
_render() {
this.isRenderingInProgress = true;
this.disableObservers();
this._renderer.render();
this.enableObservers();
this.isRenderingInProgress = false;
}
/**
* Fired after a topmost {@link module:engine/view/view~View#change change block} and all
* {@link module:engine/view/document~Document#registerPostFixer post-fixers} are executed.
*
* Actual rendering is performed as a first listener on 'normal' priority.
*
* view.on( 'render', () => {
* // Rendering to the DOM is complete.
* } );
*
* This event is useful when you want to update interface elements after the rendering, e.g. position of the
* balloon panel. If you wants to change view structure use
* {@link module:engine/view/document~Document#registerPostFixer post-fixers}.
*
* @event module:engine/view/view~View#event:render
*/
}
(0,_ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_16__["default"])( View, _ckeditor_ckeditor5_utils_src_observablemixin__WEBPACK_IMPORTED_MODULE_15__["default"] );
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-enter/src/enter.js":
/*!*************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-enter/src/enter.js ***!
\*************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ Enter)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_core_src_plugin__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-core/src/plugin */ "./node_modules/@ckeditor/ckeditor5-core/src/plugin.js");
/* harmony import */ var _entercommand__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./entercommand */ "./node_modules/@ckeditor/ckeditor5-enter/src/entercommand.js");
/* harmony import */ var _enterobserver__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./enterobserver */ "./node_modules/@ckeditor/ckeditor5-enter/src/enterobserver.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module enter/enter
*/
/**
* This plugin handles the <kbd>Enter</kbd> key (hard line break) in the editor.
*
* See also the {@link module:enter/shiftenter~ShiftEnter} plugin.
*
* For more information about this feature see the {@glink api/enter package page}.
*
* @extends module:core/plugin~Plugin
*/
class Enter extends _ckeditor_ckeditor5_core_src_plugin__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* @inheritDoc
*/
static get pluginName() {
return 'Enter';
}
init() {
const editor = this.editor;
const view = editor.editing.view;
const viewDocument = view.document;
view.addObserver( _enterobserver__WEBPACK_IMPORTED_MODULE_2__["default"] );
editor.commands.add( 'enter', new _entercommand__WEBPACK_IMPORTED_MODULE_1__["default"]( editor ) );
this.listenTo( viewDocument, 'enter', ( evt, data ) => {
data.preventDefault();
// The soft enter key is handled by the ShiftEnter plugin.
if ( data.isSoft ) {
return;
}
editor.execute( 'enter' );
view.scrollToTheSelection();
}, { priority: 'low' } );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-enter/src/entercommand.js":
/*!********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-enter/src/entercommand.js ***!
\********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ EnterCommand)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_core_src_command__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-core/src/command */ "./node_modules/@ckeditor/ckeditor5-core/src/command.js");
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./utils */ "./node_modules/@ckeditor/ckeditor5-enter/src/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module enter/entercommand
*/
/**
* Enter command. It is used by the {@link module:enter/enter~Enter Enter feature} to handle the <kbd>Enter</kbd> key.
*
* @extends module:core/command~Command
*/
class EnterCommand extends _ckeditor_ckeditor5_core_src_command__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* @inheritDoc
*/
execute() {
const model = this.editor.model;
const doc = model.document;
model.change( writer => {
enterBlock( this.editor.model, writer, doc.selection, model.schema );
this.fire( 'afterExecute', { writer } );
} );
}
}
// Creates a new block in the way that the <kbd>Enter</kbd> key is expected to work.
//
// @param {module:engine/model~Model} model
// @param {module:engine/model/writer~Writer} writer
// @param {module:engine/model/selection~Selection|module:engine/model/documentselection~DocumentSelection} selection
// Selection on which the action should be performed.
// @param {module:engine/model/schema~Schema} schema
function enterBlock( model, writer, selection, schema ) {
const isSelectionEmpty = selection.isCollapsed;
const range = selection.getFirstRange();
const startElement = range.start.parent;
const endElement = range.end.parent;
// Don't touch the roots and other limit elements.
if ( schema.isLimit( startElement ) || schema.isLimit( endElement ) ) {
// Delete the selected content but only if inside a single limit element.
// Abort, when crossing limit elements boundary (e.g. <limit1>x[x</limit1>donttouchme<limit2>y]y</limit2>).
// This is an edge case and it's hard to tell what should actually happen because such a selection
// is not entirely valid.
if ( !isSelectionEmpty && startElement == endElement ) {
model.deleteContent( selection );
}
return;
}
if ( isSelectionEmpty ) {
const attributesToCopy = (0,_utils__WEBPACK_IMPORTED_MODULE_1__.getCopyOnEnterAttributes)( writer.model.schema, selection.getAttributes() );
splitBlock( writer, range.start );
writer.setSelectionAttribute( attributesToCopy );
} else {
const leaveUnmerged = !( range.start.isAtStart && range.end.isAtEnd );
const isContainedWithinOneElement = ( startElement == endElement );
model.deleteContent( selection, { leaveUnmerged } );
if ( leaveUnmerged ) {
// Partially selected elements.
//
// <h>x[xx]x</h> -> <h>x^x</h> -> <h>x</h><h>^x</h>
if ( isContainedWithinOneElement ) {
splitBlock( writer, selection.focus );
}
// Selection over multiple elements.
//
// <h>x[x</h><p>y]y<p> -> <h>x^</h><p>y</p> -> <h>x</h><p>^y</p>
else {
writer.setSelection( endElement, 0 );
}
}
}
}
function splitBlock( writer, splitPos ) {
writer.split( splitPos );
writer.setSelection( splitPos.parent.nextSibling, 0 );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-enter/src/enterobserver.js":
/*!*********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-enter/src/enterobserver.js ***!
\*********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ EnterObserver)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_engine_src_view_observer_observer__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-engine/src/view/observer/observer */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/observer.js");
/* harmony import */ var _ckeditor_ckeditor5_engine_src_view_observer_domeventdata__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-engine/src/view/observer/domeventdata */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/domeventdata.js");
/* harmony import */ var _ckeditor_ckeditor5_engine_src_view_observer_bubblingeventinfo__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @ckeditor/ckeditor5-engine/src/view/observer/bubblingeventinfo */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/bubblingeventinfo.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_keyboard__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/keyboard */ "./node_modules/@ckeditor/ckeditor5-utils/src/keyboard.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module enter/enterobserver
*/
/**
* Enter observer introduces the {@link module:engine/view/document~Document#event:enter} event.
*
* @extends module:engine/view/observer/observer~Observer
*/
class EnterObserver extends _ckeditor_ckeditor5_engine_src_view_observer_observer__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* @inheritDoc
*/
constructor( view ) {
super( view );
const doc = this.document;
doc.on( 'keydown', ( evt, data ) => {
if ( this.isEnabled && data.keyCode == _ckeditor_ckeditor5_utils_src_keyboard__WEBPACK_IMPORTED_MODULE_3__.keyCodes.enter ) {
const event = new _ckeditor_ckeditor5_engine_src_view_observer_bubblingeventinfo__WEBPACK_IMPORTED_MODULE_2__["default"]( doc, 'enter', doc.selection.getFirstRange() );
doc.fire( event, new _ckeditor_ckeditor5_engine_src_view_observer_domeventdata__WEBPACK_IMPORTED_MODULE_1__["default"]( doc, data.domEvent, {
isSoft: data.shiftKey
} ) );
// Stop `keydown` event if `enter` event was stopped.
// https://github.com/ckeditor/ckeditor5/issues/753
if ( event.stop.called ) {
evt.stop();
}
}
} );
}
/**
* @inheritDoc
*/
observe() {}
}
/**
* Event fired when the user presses the <kbd>Enter</kbd> key.
*
* Note: This event is fired by the {@link module:enter/enterobserver~EnterObserver observer}
* (usually registered by the {@link module:enter/enter~Enter Enter feature} and
* {@link module:enter/shiftenter~ShiftEnter ShiftEnter feature}).
*
* @event module:engine/view/document~Document#event:enter
* @param {module:engine/view/observer/domeventdata~DomEventData} data
* @param {Boolean} data.isSoft Whether it's a soft enter (<kbd>Shift</kbd>+<kbd>Enter</kbd>) or hard enter (<kbd>Enter</kbd>).
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-enter/src/index.js":
/*!*************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-enter/src/index.js ***!
\*************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "Enter": () => (/* reexport safe */ _enter__WEBPACK_IMPORTED_MODULE_0__["default"]),
/* harmony export */ "ShiftEnter": () => (/* reexport safe */ _shiftenter__WEBPACK_IMPORTED_MODULE_1__["default"])
/* harmony export */ });
/* harmony import */ var _enter__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./enter */ "./node_modules/@ckeditor/ckeditor5-enter/src/enter.js");
/* harmony import */ var _shiftenter__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./shiftenter */ "./node_modules/@ckeditor/ckeditor5-enter/src/shiftenter.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module enter
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-enter/src/shiftenter.js":
/*!******************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-enter/src/shiftenter.js ***!
\******************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ShiftEnter)
/* harmony export */ });
/* harmony import */ var _shiftentercommand__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./shiftentercommand */ "./node_modules/@ckeditor/ckeditor5-enter/src/shiftentercommand.js");
/* harmony import */ var _enterobserver__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./enterobserver */ "./node_modules/@ckeditor/ckeditor5-enter/src/enterobserver.js");
/* harmony import */ var _ckeditor_ckeditor5_core_src_plugin__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @ckeditor/ckeditor5-core/src/plugin */ "./node_modules/@ckeditor/ckeditor5-core/src/plugin.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module enter/shiftenter
*/
/**
* This plugin handles the <kbd>Shift</kbd>+<kbd>Enter</kbd> keystroke (soft line break) in the editor.
*
* See also the {@link module:enter/enter~Enter} plugin.
*
* For more information about this feature see the {@glink api/enter package page}.
*
* @extends module:core/plugin~Plugin
*/
class ShiftEnter extends _ckeditor_ckeditor5_core_src_plugin__WEBPACK_IMPORTED_MODULE_2__["default"] {
/**
* @inheritDoc
*/
static get pluginName() {
return 'ShiftEnter';
}
init() {
const editor = this.editor;
const schema = editor.model.schema;
const conversion = editor.conversion;
const view = editor.editing.view;
const viewDocument = view.document;
// Configure the schema.
schema.register( 'softBreak', {
allowWhere: '$text',
isInline: true
} );
// Configure converters.
conversion.for( 'upcast' )
.elementToElement( {
model: 'softBreak',
view: 'br'
} );
conversion.for( 'downcast' )
.elementToElement( {
model: 'softBreak',
view: ( modelElement, { writer } ) => writer.createEmptyElement( 'br' )
} );
view.addObserver( _enterobserver__WEBPACK_IMPORTED_MODULE_1__["default"] );
editor.commands.add( 'shiftEnter', new _shiftentercommand__WEBPACK_IMPORTED_MODULE_0__["default"]( editor ) );
this.listenTo( viewDocument, 'enter', ( evt, data ) => {
data.preventDefault();
// The hard enter key is handled by the Enter plugin.
if ( !data.isSoft ) {
return;
}
editor.execute( 'shiftEnter' );
view.scrollToTheSelection();
}, { priority: 'low' } );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-enter/src/shiftentercommand.js":
/*!*************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-enter/src/shiftentercommand.js ***!
\*************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ShiftEnterCommand)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_core_src_command__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-core/src/command */ "./node_modules/@ckeditor/ckeditor5-core/src/command.js");
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./utils */ "./node_modules/@ckeditor/ckeditor5-enter/src/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module enter/shiftentercommand
*/
/**
* ShiftEnter command. It is used by the {@link module:enter/shiftenter~ShiftEnter ShiftEnter feature} to handle
* the <kbd>Shift</kbd>+<kbd>Enter</kbd> keystroke.
*
* @extends module:core/command~Command
*/
class ShiftEnterCommand extends _ckeditor_ckeditor5_core_src_command__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* @inheritDoc
*/
execute() {
const model = this.editor.model;
const doc = model.document;
model.change( writer => {
softBreakAction( model, writer, doc.selection );
this.fire( 'afterExecute', { writer } );
} );
}
refresh() {
const model = this.editor.model;
const doc = model.document;
this.isEnabled = isEnabled( model.schema, doc.selection );
}
}
// Checks whether the ShiftEnter command should be enabled in the specified selection.
//
// @param {module:engine/model/schema~Schema} schema
// @param {module:engine/model/selection~Selection|module:engine/model/documentselection~DocumentSelection} selection
function isEnabled( schema, selection ) {
// At this moment it is okay to support single range selections only.
// But in the future we may need to change that.
if ( selection.rangeCount > 1 ) {
return false;
}
const anchorPos = selection.anchor;
// Check whether the break element can be inserted in the current selection anchor.
if ( !anchorPos || !schema.checkChild( anchorPos, 'softBreak' ) ) {
return false;
}
const range = selection.getFirstRange();
const startElement = range.start.parent;
const endElement = range.end.parent;
// Do not modify the content if selection is cross-limit elements.
if ( ( isInsideLimitElement( startElement, schema ) || isInsideLimitElement( endElement, schema ) ) && startElement !== endElement ) {
return false;
}
return true;
}
// Creates a break in the way that the <kbd>Shift</kbd>+<kbd>Enter</kbd> keystroke is expected to work.
//
// @param {module:engine/model~Model} model
// @param {module:engine/model/writer~Writer} writer
// @param {module:engine/model/selection~Selection|module:engine/model/documentselection~DocumentSelection} selection
// Selection on which the action should be performed.
function softBreakAction( model, writer, selection ) {
const isSelectionEmpty = selection.isCollapsed;
const range = selection.getFirstRange();
const startElement = range.start.parent;
const endElement = range.end.parent;
const isContainedWithinOneElement = ( startElement == endElement );
if ( isSelectionEmpty ) {
const attributesToCopy = (0,_utils__WEBPACK_IMPORTED_MODULE_1__.getCopyOnEnterAttributes)( model.schema, selection.getAttributes() );
insertBreak( model, writer, range.end );
writer.removeSelectionAttribute( selection.getAttributeKeys() );
writer.setSelectionAttribute( attributesToCopy );
} else {
const leaveUnmerged = !( range.start.isAtStart && range.end.isAtEnd );
model.deleteContent( selection, { leaveUnmerged } );
// Selection within one element:
//
// <h>x[xx]x</h> -> <h>x^x</h> -> <h>x<br>^x</h>
if ( isContainedWithinOneElement ) {
insertBreak( model, writer, selection.focus );
}
// Selection over multiple elements.
//
// <h>x[x</h><p>y]y<p> -> <h>x^</h><p>y</p> -> <h>x</h><p>^y</p>
//
// We chose not to insert a line break in this case because:
//
// * it's not a very common scenario,
// * it actually surprised me when I saw the "expected behavior" in real life.
//
// It's ok if the user will need to be more specific where they want the <br> to be inserted.
else {
// Move the selection to the 2nd element (last step of the example above).
if ( leaveUnmerged ) {
writer.setSelection( endElement, 0 );
}
}
}
}
function insertBreak( model, writer, position ) {
const breakLineElement = writer.createElement( 'softBreak' );
model.insertContent( breakLineElement, position );
writer.setSelection( breakLineElement, 'after' );
}
// Checks whether the specified `element` is a child of the limit element.
//
// Checking whether the `<p>` element is inside a limit element:
// - <$root><p>Text.</p></$root> => false
// - <$root><limitElement><p>Text</p></limitElement></$root> => true
//
// @param {module:engine/model/element~Element} element
// @param {module:engine/schema~Schema} schema
// @returns {Boolean}
function isInsideLimitElement( element, schema ) {
// `$root` is a limit element but in this case is an invalid element.
if ( element.is( 'rootElement' ) ) {
return false;
}
return schema.isLimit( element ) || isInsideLimitElement( element.parent, schema );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-enter/src/utils.js":
/*!*************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-enter/src/utils.js ***!
\*************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "getCopyOnEnterAttributes": () => (/* binding */ getCopyOnEnterAttributes)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module enter/utils
*/
/**
* Returns attributes that should be preserved on the enter key.
*
* Filtering is realized based on `copyOnEnter` attribute property. Read more about attribute properties
* {@link module:engine/model/schema~Schema#setAttributeProperties here}.
*
* @param {module:engine/model/schema~Schema} schema
* @param {Iterable.<*>} allAttributes attributes to filter.
* @returns {Iterable.<*>}
*/
function* getCopyOnEnterAttributes( schema, allAttributes ) {
for ( const attribute of allAttributes ) {
if ( attribute && schema.getAttributeProperties( attribute[ 0 ] ).copyOnEnter ) {
yield attribute;
}
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-essentials/src/essentials.js":
/*!***********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-essentials/src/essentials.js ***!
\***********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ Essentials)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_clipboard__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/clipboard */ "./node_modules/ckeditor5/src/clipboard.js");
/* harmony import */ var ckeditor5_src_enter__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ckeditor5/src/enter */ "./node_modules/ckeditor5/src/enter.js");
/* harmony import */ var ckeditor5_src_select_all__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ckeditor5/src/select-all */ "./node_modules/ckeditor5/src/select-all.js");
/* harmony import */ var ckeditor5_src_typing__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ckeditor5/src/typing */ "./node_modules/ckeditor5/src/typing.js");
/* harmony import */ var ckeditor5_src_undo__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ckeditor5/src/undo */ "./node_modules/ckeditor5/src/undo.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module essentials/essentials
*/
/**
* A plugin including all essential editing features. It represents a set of features that enables similar functionalities
* to a `<textarea>` element.
*
* It includes:
*
* * {@link module:clipboard/clipboard~Clipboard},
* * {@link module:enter/enter~Enter},
* * {@link module:select-all/selectall~SelectAll},
* * {@link module:enter/shiftenter~ShiftEnter},
* * {@link module:typing/typing~Typing},
* * {@link module:undo/undo~Undo}.
*
* This plugin set does not define any block-level containers (such as {@link module:paragraph/paragraph~Paragraph}).
* If your editor is supposed to handle block content, make sure to include it.
*
* @extends module:core/plugin~Plugin
*/
class Essentials extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [ ckeditor5_src_clipboard__WEBPACK_IMPORTED_MODULE_1__.Clipboard, ckeditor5_src_enter__WEBPACK_IMPORTED_MODULE_2__.Enter, ckeditor5_src_select_all__WEBPACK_IMPORTED_MODULE_3__.SelectAll, ckeditor5_src_enter__WEBPACK_IMPORTED_MODULE_2__.ShiftEnter, ckeditor5_src_typing__WEBPACK_IMPORTED_MODULE_4__.Typing, ckeditor5_src_undo__WEBPACK_IMPORTED_MODULE_5__.Undo ];
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'Essentials';
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-find-and-replace/src/findandreplace.js":
/*!*********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-find-and-replace/src/findandreplace.js ***!
\*********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ FindAndReplace)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var _findandreplaceui__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./findandreplaceui */ "./node_modules/@ckeditor/ckeditor5-find-and-replace/src/findandreplaceui.js");
/* harmony import */ var _findandreplaceediting__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./findandreplaceediting */ "./node_modules/@ckeditor/ckeditor5-find-and-replace/src/findandreplaceediting.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module find-and-replace/findandreplace
*/
/**
* The find and replace plugin.
*
* For a detailed overview, check the {@glink features/find-and-replace Find and replace feature documentation}.
*
* This is a "glue" plugin which loads the following plugins:
*
* * The {@link module:find-and-replace/findandreplaceediting~FindAndReplaceEditing find and replace editing feature},
* * The {@link module:find-and-replace/findandreplaceui~FindAndReplaceUI find and replace UI feature}
*
* @extends module:core/plugin~Plugin
*/
class FindAndReplace extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [ _findandreplaceediting__WEBPACK_IMPORTED_MODULE_2__["default"], _findandreplaceui__WEBPACK_IMPORTED_MODULE_1__["default"] ];
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'FindAndReplace';
}
/**
* @inheritDoc
*/
init() {
const ui = this.editor.plugins.get( 'FindAndReplaceUI' );
const findAndReplaceEditing = this.editor.plugins.get( 'FindAndReplaceEditing' );
const state = findAndReplaceEditing.state;
ui.on( 'findNext', ( event, data ) => {
// Data is contained only for the "find" button.
if ( data ) {
state.searchText = data.searchText;
this.editor.execute( 'find', data.searchText, data );
} else {
// Find next arrow button press.
this.editor.execute( 'findNext' );
}
} );
ui.on( 'findPrevious', ( event, data ) => {
if ( data && state.searchText !== data.searchText ) {
this.editor.execute( 'find', data.searchText );
} else {
// Subsequent calls.
this.editor.execute( 'findPrevious' );
}
} );
ui.on( 'replace', ( event, data ) => {
if ( state.searchText !== data.searchText ) {
this.editor.execute( 'find', data.searchText );
}
const highlightedResult = state.highlightedResult;
if ( highlightedResult ) {
this.editor.execute( 'replace', data.replaceText, highlightedResult );
}
} );
ui.on( 'replaceAll', ( event, data ) => {
// The state hadn't been yet built for this search text.
if ( state.searchText !== data.searchText ) {
this.editor.execute( 'find', data.searchText );
}
this.editor.execute( 'replaceAll', data.replaceText, state.results );
} );
// Reset the state when the user invalidated last search results, for instance,
// by starting typing another search query or changing options.
ui.on( 'searchReseted', () => {
state.clear( this.editor.model );
findAndReplaceEditing.stop();
} );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-find-and-replace/src/findandreplaceediting.js":
/*!****************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-find-and-replace/src/findandreplaceediting.js ***!
\****************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ FindAndReplaceEditing)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./utils */ "./node_modules/@ckeditor/ckeditor5-find-and-replace/src/utils.js");
/* harmony import */ var _findcommand__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./findcommand */ "./node_modules/@ckeditor/ckeditor5-find-and-replace/src/findcommand.js");
/* harmony import */ var _replacecommand__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./replacecommand */ "./node_modules/@ckeditor/ckeditor5-find-and-replace/src/replacecommand.js");
/* harmony import */ var _replaceallcommand__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./replaceallcommand */ "./node_modules/@ckeditor/ckeditor5-find-and-replace/src/replaceallcommand.js");
/* harmony import */ var _findnextcommand__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./findnextcommand */ "./node_modules/@ckeditor/ckeditor5-find-and-replace/src/findnextcommand.js");
/* harmony import */ var _findpreviouscommand__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./findpreviouscommand */ "./node_modules/@ckeditor/ckeditor5-find-and-replace/src/findpreviouscommand.js");
/* harmony import */ var _findandreplacestate__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./findandreplacestate */ "./node_modules/@ckeditor/ckeditor5-find-and-replace/src/findandreplacestate.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_dom_scroll__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/dom/scroll */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/scroll.js");
/* harmony import */ var lodash_es__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! lodash-es */ "./node_modules/lodash-es/debounce.js");
/* harmony import */ var _theme_findandreplace_css__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ../theme/findandreplace.css */ "./node_modules/@ckeditor/ckeditor5-find-and-replace/theme/findandreplace.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module find-and-replace/findandreplaceediting
*/
// eslint-disable-next-line ckeditor5-rules/ckeditor-imports
const HIGHLIGHT_CLASS = 'ck-find-result_selected';
// Reacts to document changes in order to update search list.
function onDocumentChange( results, model, searchCallback ) {
const changedNodes = new Set();
const removedMarkers = new Set();
const changes = model.document.differ.getChanges();
// Get nodes in which changes happened to re-run a search callback on them.
changes.forEach( change => {
if ( change.name === '$text' || model.schema.isInline( change.position.nodeAfter ) ) {
changedNodes.add( change.position.parent );
[ ...model.markers.getMarkersAtPosition( change.position ) ].forEach( markerAtChange => {
removedMarkers.add( markerAtChange.name );
} );
} else if ( change.type === 'insert' ) {
changedNodes.add( change.position.nodeAfter );
}
} );
// Get markers from removed nodes also.
model.document.differ.getChangedMarkers().forEach( ( { name, data: { newRange } } ) => {
if ( newRange && newRange.start.root.rootName === '$graveyard' ) {
removedMarkers.add( name );
}
} );
// Get markers from the updated nodes and remove all (search will be re-run on these nodes).
changedNodes.forEach( node => {
const markersInNode = [ ...model.markers.getMarkersIntersectingRange( model.createRangeIn( node ) ) ];
markersInNode.forEach( marker => removedMarkers.add( marker.name ) );
} );
// Remove results & markers from the changed part of content.
model.change( writer => {
removedMarkers.forEach( markerName => {
// Remove the result first - in order to prevent rendering a removed marker.
if ( results.has( markerName ) ) {
results.remove( markerName );
}
writer.removeMarker( markerName );
} );
} );
// Run search callback again on updated nodes.
changedNodes.forEach( nodeToCheck => {
(0,_utils__WEBPACK_IMPORTED_MODULE_1__.updateFindResultFromRange)( model.createRangeOn( nodeToCheck ), model, searchCallback, results );
} );
}
/**
* Implements the editing part for find and replace plugin. For example conversion, commands etc.
*
* @extends module:core/plugin~Plugin
*/
class FindAndReplaceEditing extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'FindAndReplaceEditing';
}
/**
* @inheritDoc
*/
init() {
/**
* The collection of currently highlighted search results.
*
* @private
* @member {module:utils/collection~Collection} #_activeResults
*/
this._activeResults = null;
/**
* An object storing the find and replace state within a given editor instance.
*
* @member {module:find-and-replace/findandreplacestate~FindAndReplaceState} #state
*/
this.state = new _findandreplacestate__WEBPACK_IMPORTED_MODULE_7__["default"]( this.editor.model );
this._defineConverters();
this._defineCommands();
this.listenTo( this.state, 'change:highlightedResult', ( eventInfo, name, newValue, oldValue ) => {
const { model } = this.editor;
model.change( writer => {
if ( oldValue ) {
const oldMatchId = oldValue.marker.name.split( ':' )[ 1 ];
const oldMarker = model.markers.get( `findResultHighlighted:${ oldMatchId }` );
if ( oldMarker ) {
writer.removeMarker( oldMarker );
}
}
if ( newValue ) {
const newMatchId = newValue.marker.name.split( ':' )[ 1 ];
writer.addMarker( `findResultHighlighted:${ newMatchId }`, {
usingOperation: false,
affectsData: false,
range: newValue.marker.getRange()
} );
}
} );
} );
const debouncedScrollListener = (0,lodash_es__WEBPACK_IMPORTED_MODULE_10__["default"])( scrollToHighlightedResult.bind( this ), 32 );
// Debounce scroll as highlight might be changed very frequently, e.g. when there's a replace all command.
this.listenTo( this.state, 'change:highlightedResult', debouncedScrollListener, { priority: 'low' } );
// It's possible that the editor will get destroyed before debounced call kicks in.
// This would result with accessing a view three that is no longer in DOM.
this.listenTo( this.editor, 'destroy', debouncedScrollListener.cancel );
/* istanbul ignore next */
function scrollToHighlightedResult( eventInfo, name, newValue ) {
if ( newValue ) {
const domConverter = this.editor.editing.view.domConverter;
const viewRange = this.editor.editing.mapper.toViewRange( newValue.marker.getRange() );
(0,_ckeditor_ckeditor5_utils_src_dom_scroll__WEBPACK_IMPORTED_MODULE_8__.scrollViewportToShowTarget)( {
target: domConverter.viewRangeToDom( viewRange ),
viewportOffset: 40
} );
}
}
}
/**
* Initiate a search.
*
* @param {Function|String} callbackOrText
* @returns {module:utils/collection~Collection}
*/
find( callbackOrText ) {
const { editor } = this;
const { model } = editor;
const { findCallback, results } = editor.execute( 'find', callbackOrText );
this._activeResults = results;
// @todo: handle this listener, another copy is in findcommand.js file.
this.listenTo( model.document, 'change:data', () => onDocumentChange( this._activeResults, model, findCallback ) );
return this._activeResults;
}
/**
* Stops active results from updating, and clears out the results.
*/
stop() {
if ( !this._activeResults ) {
return;
}
this.stopListening( this.editor.model.document );
this.state.clear( this.editor.model );
this._activeResults = null;
}
/**
* Sets up the commands.
*
* @private
*/
_defineCommands() {
this.editor.commands.add( 'find', new _findcommand__WEBPACK_IMPORTED_MODULE_2__["default"]( this.editor, this.state ) );
this.editor.commands.add( 'findNext', new _findnextcommand__WEBPACK_IMPORTED_MODULE_5__["default"]( this.editor, this.state ) );
this.editor.commands.add( 'findPrevious', new _findpreviouscommand__WEBPACK_IMPORTED_MODULE_6__["default"]( this.editor, this.state ) );
this.editor.commands.add( 'replace', new _replacecommand__WEBPACK_IMPORTED_MODULE_3__["default"]( this.editor, this.state ) );
this.editor.commands.add( 'replaceAll', new _replaceallcommand__WEBPACK_IMPORTED_MODULE_4__["default"]( this.editor, this.state ) );
}
/**
* Sets up the marker downcast converters for search results highlighting.
*
* @private
*/
_defineConverters() {
const { editor } = this;
// Setup the marker highlighting conversion.
editor.conversion.for( 'editingDowncast' ).markerToHighlight( {
model: 'findResult',
view: ( { markerName } ) => {
const [ , id ] = markerName.split( ':' );
// Marker removal from the view has a bug: https://github.com/ckeditor/ckeditor5/issues/7499
// A minimal option is to return a new object for each converted marker...
return {
name: 'span',
classes: [ 'ck-find-result' ],
attributes: {
// ...however, adding a unique attribute should be future-proof..
'data-find-result': id
}
};
}
} );
editor.conversion.for( 'editingDowncast' ).markerToHighlight( {
model: 'findResultHighlighted',
view: ( { markerName } ) => {
const [ , id ] = markerName.split( ':' );
// Marker removal from the view has a bug: https://github.com/ckeditor/ckeditor5/issues/7499
// A minimal option is to return a new object for each converted marker...
return {
name: 'span',
classes: [ HIGHLIGHT_CLASS ],
attributes: {
// ...however, adding a unique attribute should be future-proof..
'data-find-result': id
}
};
}
} );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-find-and-replace/src/findandreplacestate.js":
/*!**************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-find-and-replace/src/findandreplacestate.js ***!
\**************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ FindAndReplaceState)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module find-and-replace/findandreplacestate
*/
/**
* The object storing find and replace plugin state for a given editor instance.
*
* @mixes module:utils/observablemixin~ObservableMixin
*/
class FindAndReplaceState {
/**
* Creates an instance of the state.
*
* @param {module:engine/model/model~Model} model
*/
constructor( model ) {
/**
* A collection of find matches.
*
* @protected
* @observable
* @member {module:utils/collection~Collection} #results
*/
this.set( 'results', new ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_0__.Collection() );
/**
* Currently highlighted search result in {@link #results matched results}.
*
* @readonly
* @observable
* @member {Object|null} #highlightedResult
*/
this.set( 'highlightedResult', null );
/**
* Searched text value.
*
* @readonly
* @observable
* @member {String} #searchText
*/
this.set( 'searchText', '' );
/**
* Replace text value.
*
* @readonly
* @observable
* @member {String} #replaceText
*/
this.set( 'replaceText', '' );
/**
* Indicates whether the matchCase checkbox has been checked.
*
* @readonly
* @observable
* @member {Boolean} #matchCase
*/
this.set( 'matchCase', false );
/**
* Indicates whether the matchWholeWords checkbox has been checked.
*
* @readonly
* @observable
* @member {Boolean} #matchWholeWords
*/
this.set( 'matchWholeWords', false );
this.results.on( 'change', ( eventInfo, { removed, index } ) => {
removed = Array.from( removed );
if ( removed.length ) {
let highlightedResultRemoved = false;
model.change( writer => {
for ( const removedResult of removed ) {
if ( this.highlightedResult === removedResult ) {
highlightedResultRemoved = true;
}
if ( model.markers.has( removedResult.marker.name ) ) {
writer.removeMarker( removedResult.marker );
}
}
} );
if ( highlightedResultRemoved ) {
const nextHighlightedIndex = index >= this.results.length ? 0 : index;
this.highlightedResult = this.results.get( nextHighlightedIndex );
}
}
} );
}
/**
* Cleans the state up and removes markers from the model.
*
* @param {module:engine/model/model~Model} model
*/
clear( model ) {
this.searchText = '';
model.change( writer => {
if ( this.highlightedResult ) {
const oldMatchId = this.highlightedResult.marker.name.split( ':' )[ 1 ];
const oldMarker = model.markers.get( `findResultHighlighted:${ oldMatchId }` );
if ( oldMarker ) {
writer.removeMarker( oldMarker );
}
}
[ ...this.results ].forEach( ( { marker } ) => {
writer.removeMarker( marker );
} );
} );
this.results.clear();
}
}
(0,ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_0__.mix)( FindAndReplaceState, ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_0__.ObservableMixin );
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-find-and-replace/src/findandreplaceui.js":
/*!***********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-find-and-replace/src/findandreplaceui.js ***!
\***********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ FindAndReplaceUI)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/ui */ "./node_modules/ckeditor5/src/ui.js");
/* harmony import */ var _ui_findandreplaceformview__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./ui/findandreplaceformview */ "./node_modules/@ckeditor/ckeditor5-find-and-replace/src/ui/findandreplaceformview.js");
/* harmony import */ var _theme_icons_find_replace_svg__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../theme/icons/find-replace.svg */ "./node_modules/@ckeditor/ckeditor5-find-and-replace/theme/icons/find-replace.svg");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module find-and-replace/findandreplaceui
*/
/**
* The default find and replace UI.
*
* It registers the `'findAndReplace'` UI button in the editor's {@link module:ui/componentfactory~ComponentFactory component factory}.
* that uses the {@link module:find-and-replace/findandreplace~FindAndReplace FindAndReplace} plugin API.
*
* @extends module:core/plugin~Plugin
*/
class FindAndReplaceUI extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'FindAndReplaceUI';
}
/**
* @inheritDoc
*/
constructor( editor ) {
super( editor );
/**
* A reference to the find and replace form view.
*
* @member {module:find-and-replace/ui/findandreplaceformview~FindAndReplaceFormView} #formView
*/
this.formView = null;
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
// Register the toolbar dropdown component.
editor.ui.componentFactory.add( 'findAndReplace', locale => {
const dropdown = (0,ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.createDropdown)( locale );
const formView = this.formView = new _ui_findandreplaceformview__WEBPACK_IMPORTED_MODULE_2__["default"]( editor.locale );
// Dropdown should be disabled when in source editing mode. See #10001.
dropdown.bind( 'isEnabled' ).to( editor.commands.get( 'find' ) );
dropdown.panelView.children.add( formView );
// Every time a dropdown is opened, the search text field should get focused and selected for better UX.
// Note: Using the low priority here to make sure the following listener starts working after
// the default action of the drop-down is executed (i.e. the panel showed up). Otherwise,
// the invisible form/input cannot be focused/selected.
//
// Each time a dropdown is closed, move the focus back to the find and replace toolbar button
// and let the find and replace editing feature know that all search results can be invalidated
// and no longer should be marked in the content.
dropdown.on( 'change:isOpen', ( event, name, isOpen ) => {
if ( isOpen ) {
formView.disableCssTransitions();
formView.reset();
formView._findInputView.fieldView.select();
formView.focus();
formView.enableCssTransitions();
} else {
formView.focus();
this.fire( 'searchReseted' );
}
}, { priority: 'low' } );
this._setupDropdownButton( dropdown );
this._setupFormView( formView );
return dropdown;
} );
}
/**
* Sets up the find and replace button.
*
* @private
* @param {module:ui/dropdown/dropdownview~DropdownView} dropdown
*/
_setupDropdownButton( dropdown ) {
const editor = this.editor;
const t = editor.locale.t;
dropdown.buttonView.set( {
icon: _theme_icons_find_replace_svg__WEBPACK_IMPORTED_MODULE_3__["default"],
label: t( 'Find and replace' ),
keystroke: 'CTRL+F',
tooltip: true
} );
editor.keystrokes.set( 'Ctrl+F', ( data, cancelEvent ) => {
dropdown.isOpen = true;
cancelEvent();
} );
}
/**
* Sets up the form view for the find and replace.
*
* @private
* @param {module:find-and-replace/ui/findandreplaceformview~FindAndReplaceFormView} formView A related form view.
*/
_setupFormView( formView ) {
const editor = this.editor;
const commands = editor.commands;
const findAndReplaceEditing = this.editor.plugins.get( 'FindAndReplaceEditing' );
const editingState = findAndReplaceEditing.state;
const sortMapping = { before: -1, same: 0, after: 1 };
// Let the form know which result is being highlighted.
formView.bind( 'highlightOffset' ).to( editingState, 'highlightedResult', highlightedResult => {
if ( !highlightedResult ) {
return 0;
}
return Array.from( editingState.results )
.sort( ( a, b ) => sortMapping[ a.marker.getStart().compareWith( b.marker.getStart() ) ] )
.indexOf( highlightedResult ) + 1;
} );
// Let the form know how many results were found in total.
formView.listenTo( editingState.results, 'change', () => {
formView.matchCount = editingState.results.length;
} );
// Command states are used to enable/disable individual form controls.
// To keep things simple, instead of binding 4 individual observables, there's only one that combines every
// commands' isEnabled state. Yes, it will change more often but this simplifies the structure of the form.
formView.bind( '_areCommandsEnabled' ).to(
commands.get( 'findNext' ), 'isEnabled',
commands.get( 'findPrevious' ), 'isEnabled',
commands.get( 'replace' ), 'isEnabled',
commands.get( 'replaceAll' ), 'isEnabled',
( findNext, findPrevious, replace, replaceAll ) => ( { findNext, findPrevious, replace, replaceAll } )
);
// The UI plugin works as an interface between the form and the editing part of the feature.
formView.delegate( 'findNext', 'findPrevious', 'replace', 'replaceAll' ).to( this );
// Let the feature know that search results are no longer relevant because the user changed the searched phrase
// (or options) but didn't hit the "Find" button yet (e.g. still typing).
formView.on( 'change:isDirty', ( evt, data, isDirty ) => {
if ( isDirty ) {
this.fire( 'searchReseted' );
}
} );
}
}
/**
* Fired when the find next button is triggered.
*
* @event findNext
* @param {String} searchText Search text.
*/
/**
* Fired when the find previous button is triggered.
*
* @event findPrevious
* @param {String} searchText Search text.
*/
/**
* Fired when the replace button is triggered.
*
* @event replace
* @param {String} replaceText Replacement text.
*/
/**
* Fired when the replaceAll button is triggered.
*
* @event replaceAll
* @param {String} replaceText Replacement text.
*/
/**
* Fired when the UI was reset and the search results marked in the editing root should be invalidated,
* for instance, because the user changed the searched phrase (or options) but didn't hit
* the "Find" button yet.
*
* @event searchReseted
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-find-and-replace/src/findcommand.js":
/*!******************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-find-and-replace/src/findcommand.js ***!
\******************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ FindCommand)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./utils */ "./node_modules/@ckeditor/ckeditor5-find-and-replace/src/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module find-and-replace/findcommand
*/
/**
* The find command. It is used by the {@link module:find-and-replace/findandreplace~FindAndReplace find and replace feature}.
*
* @extends module:core/command~Command
*/
class FindCommand extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Command {
/**
* Creates a new `FindCommand` instance.
*
* @param {module:core/editor/editor~Editor} editor The editor on which this command will be used.
* @param {module:find-and-replace/findandreplacestate~FindAndReplaceState} state An object to hold plugin state.
*/
constructor( editor, state ) {
super( editor );
// The find command is always enabled.
this.isEnabled = true;
// It does not affect data so should be enabled in read-only mode.
this.affectsData = false;
/**
* The find and replace state object used for command operations.
*
* @private
* @member {module:find-and-replace/findandreplacestate~FindAndReplaceState} #_state
*/
this._state = state;
}
/**
* Executes the command.
*
* @param {Function|String} callbackOrText
* @param {Object} [options]
* @param {Boolean} [options.matchCase=false] If set to `true`, the letter case will be matched.
* @param {Boolean} [options.wholeWords=false] If set to `true`, only whole words that match `callbackOrText` will be matched.
*
* @fires execute
*/
execute( callbackOrText, { matchCase, wholeWords } = {} ) {
const { editor } = this;
const { model } = editor;
let findCallback;
// Allow to execute `find()` on a plugin with a keyword only.
if ( typeof callbackOrText === 'string' ) {
findCallback = (0,_utils__WEBPACK_IMPORTED_MODULE_1__.findByTextCallback)( callbackOrText, { matchCase, wholeWords } );
this._state.searchText = callbackOrText;
} else {
findCallback = callbackOrText;
}
// Initial search is done on all nodes in all roots inside the content.
const results = model.document.getRootNames()
.reduce( ( ( currentResults, rootName ) => (0,_utils__WEBPACK_IMPORTED_MODULE_1__.updateFindResultFromRange)(
model.createRangeIn( model.document.getRoot( rootName ) ),
model,
findCallback,
currentResults
) ), null );
this._state.clear( model );
this._state.results.addMany( Array.from( results ) );
this._state.highlightedResult = results.get( 0 );
if ( typeof callbackOrText === 'string' ) {
this._state.searchText = callbackOrText;
}
this._state.matchCase = !!matchCase;
this._state.matchWholeWords = !!wholeWords;
return {
results,
findCallback
};
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-find-and-replace/src/findnextcommand.js":
/*!**********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-find-and-replace/src/findnextcommand.js ***!
\**********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ FindNextCommand)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module find-and-replace/findnextcommand
*/
/**
* The find next command. Moves the highlight to the next search result.
*
* It is used by the {@link module:find-and-replace/findandreplace~FindAndReplace find and replace feature}.
*
* @extends module:core/command~Command
*/
class FindNextCommand extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Command {
/**
* Creates a new `FindNextCommand` instance.
*
* @param {module:core/editor/editor~Editor} editor The editor on which this command will be used.
* @param {module:find-and-replace/findandreplacestate~FindAndReplaceState} state An object to hold plugin state.
*/
constructor( editor, state ) {
super( editor );
// It does not affect data so should be enabled in read-only mode.
this.affectsData = false;
/**
* The find and replace state object used for command operations.
*
* @protected
* @member {module:find-and-replace/findandreplacestate~FindAndReplaceState} #_state
*/
this._state = state;
this.isEnabled = false;
this.listenTo( this._state.results, 'change', () => {
this.isEnabled = this._state.results.length > 1;
} );
}
/**
* @inheritDoc
*/
refresh() {
this.isEnabled = this._state.results.length > 1;
}
/**
* @inheritDoc
*/
execute() {
const results = this._state.results;
const currentIndex = results.getIndex( this._state.highlightedResult );
const nextIndex = currentIndex + 1 >= results.length ?
0 : currentIndex + 1;
this._state.highlightedResult = this._state.results.get( nextIndex );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-find-and-replace/src/findpreviouscommand.js":
/*!**************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-find-and-replace/src/findpreviouscommand.js ***!
\**************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ FindPreviousCommand)
/* harmony export */ });
/* harmony import */ var _findnextcommand__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./findnextcommand */ "./node_modules/@ckeditor/ckeditor5-find-and-replace/src/findnextcommand.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module find-and-replace/findpreviouscommand
*/
/**
* The find previous command. Moves the highlight to the previous search result.
*
* It is used by the {@link module:find-and-replace/findandreplace~FindAndReplace find and replace feature}.
*
* @extends module:find-and-replace/findnextcommand~FindNextCommand
*/
class FindPreviousCommand extends _findnextcommand__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* @inheritDoc
*/
execute() {
const results = this._state.results;
const currentIndex = results.getIndex( this._state.highlightedResult );
const previousIndex = currentIndex - 1 < 0 ?
this._state.results.length - 1 : currentIndex - 1;
this._state.highlightedResult = this._state.results.get( previousIndex );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-find-and-replace/src/replaceallcommand.js":
/*!************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-find-and-replace/src/replaceallcommand.js ***!
\************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ReplaceAllCommand)
/* harmony export */ });
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./utils */ "./node_modules/@ckeditor/ckeditor5-find-and-replace/src/utils.js");
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/* harmony import */ var _replacecommand__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./replacecommand */ "./node_modules/@ckeditor/ckeditor5-find-and-replace/src/replacecommand.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module find-and-replace/replaceallcommand
*/
/**
* The replace all command. It is used by the {@link module:find-and-replace/findandreplace~FindAndReplace find and replace feature}.
*
* @extends module:find-and-replace/replacecommand~ReplaceCommand
*/
class ReplaceAllCommand extends _replacecommand__WEBPACK_IMPORTED_MODULE_2__["default"] {
/**
* Replaces all the occurrences of `textToReplace` with a given `newText` string.
*
* ```js
* replaceAllCommand.execute( 'replaceAll', 'new text replacement', 'text to replace' );
* ```
*
* Alternatively you can call it from editor instance:
*
* ```js
* editor.execute( 'replaceAll', 'new text', 'old text' );
* ```
*
* @param {String} newText Text that will be inserted to the editor for each match.
* @param {String|module:utils/collection~Collection} textToReplace Text to be replaced or a collection of matches
* as returned by the find command.
*
* @fires module:core/command~Command#event:execute
*/
execute( newText, textToReplace ) {
const { editor } = this;
const { model } = editor;
const results = textToReplace instanceof ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__.Collection ?
textToReplace : model.document.getRootNames()
.reduce( ( ( currentResults, rootName ) => (0,_utils__WEBPACK_IMPORTED_MODULE_0__.updateFindResultFromRange)(
model.createRangeIn( model.document.getRoot( rootName ) ),
model,
(0,_utils__WEBPACK_IMPORTED_MODULE_0__.findByTextCallback)( textToReplace, this._state ),
currentResults
) ), null );
if ( results.length ) {
model.change( () => {
[ ...results ].forEach( searchResult => {
// Just reuse logic from the replace command to replace a single match.
super.execute( newText, searchResult );
} );
} );
}
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-find-and-replace/src/replacecommand.js":
/*!*********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-find-and-replace/src/replacecommand.js ***!
\*********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ReplaceCommand)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module find-and-replace/replacecommand
*/
/**
* The replace command. It is used by the {@link module:find-and-replace/findandreplace~FindAndReplace find and replace feature}.
*
* @extends module:core/command~Command
*/
class ReplaceCommand extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Command {
/**
* Creates a new `ReplaceCommand` instance.
*
* @param {module:core/editor/editor~Editor} editor Editor on which this command will be used.
* @param {module:find-and-replace/findandreplacestate~FindAndReplaceState} state An object to hold plugin state.
*/
constructor( editor, state ) {
super( editor );
// The replace command is always enabled.
this.isEnabled = true;
/**
* The find and replace state object used for command operations.
*
* @protected
* @member {module:find-and-replace/findandreplacestate~FindAndReplaceState} #_state
*/
this._state = state;
}
/**
* Replace a given find result by a string or a callback.
*
* @param {String} replacementText
* @param {Object} result A single result from the find command.
*
* @fires module:core/command~Command#event:execute
*/
execute( replacementText, result ) {
const { model } = this.editor;
model.change( writer => {
const range = result.marker.getRange();
// Don't replace a result (marker) that found its way into the $graveyard (e.g. removed by collaborators).
if ( range.root.rootName === '$graveyard' ) {
this._state.results.remove( result );
return;
}
let textAttributes = {};
for ( const item of range.getItems() ) {
if ( item.is( '$text' ) || item.is( '$textProxy' ) ) {
textAttributes = item.getAttributes();
break;
}
}
model.insertContent( writer.createText( replacementText, textAttributes ), range );
if ( this._state.results.has( result ) ) {
this._state.results.remove( result );
}
} );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-find-and-replace/src/ui/findandreplaceformview.js":
/*!********************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-find-and-replace/src/ui/findandreplaceformview.js ***!
\********************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ FindAndReplaceFormView)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/ui */ "./node_modules/ckeditor5/src/ui.js");
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/* harmony import */ var _ckeditor_ckeditor5_ui_theme_components_responsive_form_responsiveform_css__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @ckeditor/ckeditor5-ui/theme/components/responsive-form/responsiveform.css */ "./node_modules/@ckeditor/ckeditor5-ui/theme/components/responsive-form/responsiveform.css");
/* harmony import */ var _theme_findandreplaceform_css__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../../theme/findandreplaceform.css */ "./node_modules/@ckeditor/ckeditor5-find-and-replace/theme/findandreplaceform.css");
/* harmony import */ var _ckeditor_ckeditor5_ui_theme_icons_previous_arrow_svg__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! @ckeditor/ckeditor5-ui/theme/icons/previous-arrow.svg */ "./node_modules/@ckeditor/ckeditor5-ui/theme/icons/previous-arrow.svg");
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module find-and-replace/ui/findandreplaceformview
*/
// See: #8833.
// eslint-disable-next-line ckeditor5-rules/ckeditor-imports
// eslint-disable-next-line ckeditor5-rules/ckeditor-imports
/**
* The find and replace form view class.
*
* See {@link module:find-and-replace/ui/findandreplaceformview~FindAndReplaceFormView}.
*
* @extends module:ui/view~View
*/
class FindAndReplaceFormView extends ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.View {
/**
* Creates a view of find and replace form.
*
* @param {module:utils/locale~Locale} [locale] The localization services instance.
*/
constructor( locale ) {
super( locale );
const t = locale.t;
/**
* Stores the number of matched search results.
*
* @readonly
* @observable
* @member {Number} #matchCount
*/
this.set( 'matchCount', 0 );
/**
* The offset of currently highlighted search result in {@link #matchCount matched results}.
*
* @readonly
* @observable
* @member {Number|null} #highlightOffset
*/
this.set( 'highlightOffset', 0 );
/**
* `true` when the search params (find text, options) has been changed by the user since
* the last time find was executed. `false` otherwise.
*
* @readonly
* @observable
* @member {Boolean} #isDirty
*/
this.set( 'isDirty', false );
/**
* A live object with the aggregated `isEnabled` states of editor commands related to find and
* replace. For instance, it may look as follows:
*
* {
* findNext: true,
* findPrevious: true,
* replace: false,
* replaceAll: false
* }
*
* @protected
* @readonly
* @observable
* @member {Object} #_areCommandsEnabled
*/
this.set( '_areCommandsEnabled', {} );
/**
* The content of the counter label displaying the index of the current highlighted match
* on top of the find input, for instance "3 of 50".
*
* @protected
* @readonly
* @observable
* @member {String} #_resultsCounterText
*/
this.set( '_resultsCounterText', '' );
/**
* The flag reflecting the state of the "Match case" switch button in the search options
* dropdown.
*
* @protected
* @readonly
* @observable
* @member {Boolean} #_matchCase
*/
this.set( '_matchCase', false );
/**
* The flag reflecting the state of the "Whole words only" switch button in the search options
* dropdown.
*
* @protected
* @readonly
* @observable
* @member {Boolean} #_wholeWordsOnly
*/
this.set( '_wholeWordsOnly', false );
/**
* This flag is set `true` when some matches were found and the user didn't change the search
* params (text to find, options) yet. This is only possible immediately after hitting the "Find" button.
* `false` when there were no matches (see {@link #matchCount}) or the user changed the params (see {@link #isDirty}).
*
* It is used to control the enabled state of the replace UI (input and buttons); replacing text is only possible
* if this flag is `true`.
*
* @protected
* @readonly
* @observable
* @member {Boolean} #_searchResultsFound
*/
this.bind( '_searchResultsFound' ).to(
this, 'matchCount',
this, 'isDirty',
( matchCount, isDirty ) => {
return matchCount > 0 && !isDirty;
}
);
/**
* The find in text input view that stores the searched string.
*
* @protected
* @readonly
* @member {module:ui/labeledfield/labeledfieldview~LabeledFieldView}
*/
this._findInputView = this._createInputField( t( 'Find in text…' ) );
/**
* The replace input view.
*
* @protected
* @readonly
* @member {module:ui/labeledfield/labeledfieldview~LabeledFieldView}
*/
this._replaceInputView = this._createInputField( t( 'Replace with…' ) );
/**
* The find button view that initializes the search process.
*
* @protected
* @readonly
* @member {module:ui/button/buttonview~ButtonView}
*/
this._findButtonView = this._createButton( {
label: t( 'Find' ),
class: 'ck-button-find ck-button-action',
withText: true
} );
/**
* The find previous button view.
*
* @protected
* @readonly
* @member {module:ui/button/buttonview~ButtonView}
*/
this._findPrevButtonView = this._createButton( {
label: t( 'Previous result' ),
class: 'ck-button-prev',
icon: _ckeditor_ckeditor5_ui_theme_icons_previous_arrow_svg__WEBPACK_IMPORTED_MODULE_4__["default"],
keystroke: 'Shift+F3',
tooltip: true
} );
/**
* The find next button view.
*
* @protected
* @readonly
* @member {module:ui/button/buttonview~ButtonView}
*/
this._findNextButtonView = this._createButton( {
label: t( 'Next result' ),
class: 'ck-button-next',
icon: _ckeditor_ckeditor5_ui_theme_icons_previous_arrow_svg__WEBPACK_IMPORTED_MODULE_4__["default"],
keystroke: 'F3',
tooltip: true
} );
/**
* The find options dropdown.
*
* @protected
* @readonly
* @member {module:ui/dropdown/dropdownview~DropdownView}
*/
this._optionsDropdown = this._createOptionsDropdown();
/**
* The replace button view.
*
* @protected
* @readonly
* @member {module:ui/button/buttonview~ButtonView}
*/
this._replaceButtonView = this._createButton( {
label: t( 'Replace' ),
class: 'ck-button-replace',
withText: true
} );
/**
* The replace all button view.
*
* @protected
* @readonly
* @member {module:ui/button/buttonview~ButtonView}
*/
this._replaceAllButtonView = this._createButton( {
label: t( 'Replace all' ),
class: 'ck-button-replaceall',
withText: true
} );
/**
* The fieldset aggregating the find UI.
*
* @protected
* @readonly
* @member {module:ui/view/view~View}
*/
this._findFieldsetView = this._createFindFieldset();
/**
* The fieldset aggregating the replace UI.
*
* @protected
* @readonly
* @member {module:ui/view/view~View}
*/
this._replaceFieldsetView = this._createReplaceFieldset();
/**
* Tracks information about the DOM focus in the form.
*
* @readonly
* @protected
* @member {module:utils/focustracker~FocusTracker}
*/
this._focusTracker = new ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__.FocusTracker();
/**
* An instance of the {@link module:utils/keystrokehandler~KeystrokeHandler}.
*
* @readonly
* @protected
* @member {module:utils/keystrokehandler~KeystrokeHandler}
*/
this._keystrokes = new ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__.KeystrokeHandler();
/**
* A collection of views that can be focused in the form.
*
* @readonly
* @protected
* @member {module:ui/viewcollection~ViewCollection}
*/
this._focusables = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.ViewCollection();
/**
* Helps cycling over {@link #_focusables} in the form.
*
* @readonly
* @protected
* @member {module:ui/focuscycler~FocusCycler}
*/
this._focusCycler = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.FocusCycler( {
focusables: this._focusables,
focusTracker: this._focusTracker,
keystrokeHandler: this._keystrokes,
actions: {
// Navigate form fields backwards using the <kbd>Shift</kbd> + <kbd>Tab</kbd> keystroke.
focusPrevious: 'shift + tab',
// Navigate form fields forwards using the <kbd>Tab</kbd> key.
focusNext: 'tab'
}
} );
this.setTemplate( {
tag: 'form',
attributes: {
class: [
'ck',
'ck-find-and-replace-form'
],
tabindex: '-1'
},
children: [
new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.FormHeaderView( locale, {
label: t( 'Find and replace' )
} ),
this._findFieldsetView,
this._replaceFieldsetView
]
} );
(0,ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.injectCssTransitionDisabler)( this );
}
/**
* @inheritDoc
*/
render() {
super.render();
(0,ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.submitHandler)( { view: this } );
this._initFocusCycling();
this._initKeystrokeHandling();
}
/**
* @inheritDoc
*/
destroy() {
super.destroy();
this._focusTracker.destroy();
this._keystrokes.destroy();
}
/**
* Focuses the fist {@link #_focusables} in the form.
*/
focus() {
this._focusCycler.focusFirst();
}
/**
* Resets the form before re-appearing.
*
* It clears error messages, hides the match counter and disables the replace feature
* until the next hit of the "Find" button.
*
* **Note**: It does not reset inputs and options, though. This way the form works better in editors with
* disappearing toolbar (e.g. BalloonEditor): hiding the toolbar by accident (together with the find and replace UI)
* does not require filling the entire form again.
*/
reset() {
this._findInputView.errorText = null;
this.isDirty = true;
}
/**
* Returns the value of the find input.
*
* @protected
* @returns {String}
*/
get _textToFind() {
return this._findInputView.fieldView.element.value;
}
/**
* Returns the value of the replace input.
*
* @protected
* @returns {String}
*/
get _textToReplace() {
return this._replaceInputView.fieldView.element.value;
}
/**
* Configures and returns the `<fieldset>` aggregating all find controls.
*
* @private
* @returns {module:ui/view~View}
*/
_createFindFieldset() {
const locale = this.locale;
const fieldsetView = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.View( locale );
// Typing in the find field invalidates all previous results (the form is "dirty").
this._findInputView.fieldView.on( 'input', () => {
this.isDirty = true;
} );
this._findButtonView.on( 'execute', this._onFindButtonExecute.bind( this ) );
// Pressing prev/next buttons fires related event on the form.
this._findPrevButtonView.delegate( 'execute' ).to( this, 'findPrevious' );
this._findNextButtonView.delegate( 'execute' ).to( this, 'findNext' );
// Prev/next buttons will be disabled when related editor command gets disabled.
this._findPrevButtonView.bind( 'isEnabled' ).to( this, '_areCommandsEnabled', ( { findPrevious } ) => findPrevious );
this._findNextButtonView.bind( 'isEnabled' ).to( this, '_areCommandsEnabled', ( { findNext } ) => findNext );
this._injectFindResultsCounter();
fieldsetView.setTemplate( {
tag: 'fieldset',
attributes: {
class: [ 'ck', 'ck-find-and-replace-form__find' ]
},
children: [
this._findInputView,
this._findButtonView,
this._findPrevButtonView,
this._findNextButtonView
]
} );
return fieldsetView;
}
/**
* The action performed when the {@link #_findButtonView} is pressed.
*
* @private
*/
_onFindButtonExecute() {
// When hitting "Find" in an empty input, an error should be displayed.
// Also, if the form was "dirty", it should remain so.
if ( !this._textToFind ) {
const t = this.t;
this._findInputView.errorText = t( 'Text to find must not be empty.' );
return;
}
// Hitting "Find" automatically clears the dirty state.
this.isDirty = false;
this.fire( 'findNext', {
searchText: this._textToFind,
matchCase: this._matchCase,
wholeWords: this._wholeWordsOnly
} );
}
/**
* Configures an injects the find results counter displaying a "N of M" label of the {@link #_findInputView}.
*
* @private
*/
_injectFindResultsCounter() {
const locale = this.locale;
const t = locale.t;
const bind = this.bindTemplate;
const resultsCounterView = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.View( locale );
this.bind( '_resultsCounterText' ).to( this, 'highlightOffset', this, 'matchCount',
( highlightOffset, matchCount ) => t( '%0 of %1', [ highlightOffset, matchCount ] )
);
resultsCounterView.setTemplate( {
tag: 'span',
attributes: {
class: [
'ck',
'ck-results-counter',
// The counter only makes sense when the field text corresponds to search results in the editing.
bind.if( 'isDirty', 'ck-hidden' )
]
},
children: [
{
text: bind.to( '_resultsCounterText' )
}
]
} );
// The whole idea is that when the text of the counter changes, its width also increases/decreases and
// it consumes more or less space over the input. The input, on the other hand, should adjust it's right
// padding so its *entire* text always remains visible and available to the user.
const updateFindInputPadding = () => {
const inputElement = this._findInputView.fieldView.element;
// Don't adjust the padding if the input (also: counter) were not rendered or not inserted into DOM yet.
if ( !inputElement || !(0,ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__.isVisible)( inputElement ) ) {
return;
}
const counterWidth = new ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__.Rect( resultsCounterView.element ).width;
const paddingPropertyName = locale.uiLanguageDirection === 'ltr' ? 'paddingRight' : 'paddingLeft';
if ( !counterWidth ) {
inputElement.style[ paddingPropertyName ] = null;
} else {
inputElement.style[ paddingPropertyName ] = `calc( 2 * var(--ck-spacing-standard) + ${ counterWidth }px )`;
}
};
// Adjust the input padding when the text of the counter changes, for instance "1 of 200" is narrower than "123 of 200".
// Using "low" priority to let the text be set by the template binding first.
this.on( 'change:_resultsCounterText', updateFindInputPadding, { priority: 'low' } );
// Adjust the input padding when the counter shows or hides. When hidden, there should be no padding. When it shows, the
// padding should be set according to the text of the counter.
// Using "low" priority to let the text be set by the template binding first.
this.on( 'change:isDirty', updateFindInputPadding, { priority: 'low' } );
// Put the counter element next to the <input> in the find field.
this._findInputView.template.children[ 0 ].children.push( resultsCounterView );
}
/**
* Configures and returns the `<fieldset>` aggregating all replace controls.
*
* @private
* @returns {module:ui/view~View}
*/
_createReplaceFieldset() {
const locale = this.locale;
const t = locale.t;
const fieldsetView = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.View( locale );
this._replaceButtonView.bind( 'isEnabled' ).to(
this, '_areCommandsEnabled',
this, '_searchResultsFound',
( { replace }, resultsFound ) => replace && resultsFound );
this._replaceAllButtonView.bind( 'isEnabled' ).to(
this, '_areCommandsEnabled',
this, '_searchResultsFound',
( { replaceAll }, resultsFound ) => replaceAll && resultsFound );
this._replaceInputView.bind( 'isEnabled' ).to(
this, '_areCommandsEnabled',
this, '_searchResultsFound',
( { replace }, resultsFound ) => replace && resultsFound );
this._replaceInputView.bind( 'infoText' ).to(
this._replaceInputView, 'isEnabled',
this._replaceInputView, 'isFocused',
( isEnabled, isFocused ) => {
if ( isEnabled || !isFocused ) {
return '';
}
return t( 'Tip: Find some text first in order to replace it.' );
} );
this._replaceButtonView.on( 'execute', () => {
this.fire( 'replace', {
searchText: this._textToFind,
replaceText: this._textToReplace
} );
} );
this._replaceAllButtonView.on( 'execute', () => {
this.fire( 'replaceAll', {
searchText: this._textToFind,
replaceText: this._textToReplace
} );
this.focus();
} );
fieldsetView.setTemplate( {
tag: 'fieldset',
attributes: {
class: [ 'ck', 'ck-find-and-replace-form__replace' ]
},
children: [
this._replaceInputView,
this._optionsDropdown,
this._replaceButtonView,
this._replaceAllButtonView
]
} );
return fieldsetView;
}
/**
* Creates, configures and returns and instance of a dropdown allowing users to narrow
* the search criteria down. The dropdown has a list with switch buttons for each option.
*
* @private
* @returns {module:ui/dropdown/dropdownview~DropdownView}
*/
_createOptionsDropdown() {
const locale = this.locale;
const t = locale.t;
const dropdownView = (0,ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.createDropdown)( locale );
dropdownView.class = 'ck-options-dropdown';
dropdownView.buttonView.set( {
withText: false,
label: t( 'Show options' ),
icon: ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_5__.icons.cog,
tooltip: true
} );
const matchCaseModel = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.Model( {
withText: true,
label: t( 'Match case' ),
// A dummy read-only prop to make it easy to tell which switch was toggled.
_isMatchCaseSwitch: true
} );
const wholeWordsOnlyModel = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.Model( {
withText: true,
label: t( 'Whole words only' )
} );
// Let the switches be controlled by form's observable properties.
matchCaseModel.bind( 'isOn' ).to( this, '_matchCase' );
wholeWordsOnlyModel.bind( 'isOn' ).to( this, '_wholeWordsOnly' );
// Update the state of the form when a switch is toggled.
dropdownView.on( 'execute', evt => {
if ( evt.source._isMatchCaseSwitch ) {
this._matchCase = !this._matchCase;
} else {
this._wholeWordsOnly = !this._wholeWordsOnly;
}
// Toggling a switch makes the form dirty because this changes search criteria
// just like typing text of the find input.
this.isDirty = true;
} );
(0,ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.addListToDropdown)( dropdownView, new ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__.Collection( [
{ type: 'switchbutton', model: matchCaseModel },
{ type: 'switchbutton', model: wholeWordsOnlyModel }
] ) );
return dropdownView;
}
/**
* Initializes the {@link #_focusables} and {@link #_focusTracker} to allow navigation
* using <kbd>Tab</kbd> and <kbd>Shift</kbd>+<kbd>Tab</kbd> keystrokes in the right order.
*
* @private
*/
_initFocusCycling() {
const childViews = [
this._findInputView,
this._findButtonView,
this._findPrevButtonView,
this._findNextButtonView,
this._replaceInputView,
this._optionsDropdown,
this._replaceButtonView,
this._replaceAllButtonView
];
childViews.forEach( v => {
// Register the view as focusable.
this._focusables.add( v );
// Register the view in the focus tracker.
this._focusTracker.add( v.element );
} );
}
/**
* Initializes the keystroke handling in the form.
*
* @private
*/
_initKeystrokeHandling() {
const stopPropagation = data => data.stopPropagation();
const stopPropagationAndPreventDefault = data => {
data.stopPropagation();
data.preventDefault();
};
// Start listening for the keystrokes coming from #element.
this._keystrokes.listenTo( this.element );
// Find the next result upon F3.
this._keystrokes.set( 'f3', event => {
stopPropagationAndPreventDefault( event );
this._findNextButtonView.fire( 'execute' );
} );
// Find the previous result upon F3.
this._keystrokes.set( 'shift+f3', event => {
stopPropagationAndPreventDefault( event );
this._findPrevButtonView.fire( 'execute' );
} );
// Find or replace upon pressing Enter in the find and replace fields.
this._keystrokes.set( 'enter', event => {
const target = event.target;
if ( target === this._findInputView.fieldView.element ) {
if ( this._areCommandsEnabled.findNext ) {
this._findNextButtonView.fire( 'execute' );
} else {
this._findButtonView.fire( 'execute' );
}
stopPropagationAndPreventDefault( event );
} else if ( target === this._replaceInputView.fieldView.element && !this.isDirty ) {
this._replaceButtonView.fire( 'execute' );
stopPropagationAndPreventDefault( event );
}
} );
// Find previous upon pressing Shift+Enter in the find field.
this._keystrokes.set( 'shift+enter', event => {
const target = event.target;
if ( target !== this._findInputView.fieldView.element ) {
return;
}
if ( this._areCommandsEnabled.findPrevious ) {
this._findPrevButtonView.fire( 'execute' );
} else {
this._findButtonView.fire( 'execute' );
}
stopPropagationAndPreventDefault( event );
} );
// Since the form is in the dropdown panel which is a child of the toolbar, the toolbar's
// keystroke handler would take over the key management in the URL input.
// We need to prevent this ASAP. Otherwise, the basic caret movement using the arrow keys will be impossible.
this._keystrokes.set( 'arrowright', stopPropagation );
this._keystrokes.set( 'arrowleft', stopPropagation );
this._keystrokes.set( 'arrowup', stopPropagation );
this._keystrokes.set( 'arrowdown', stopPropagation );
// Intercept the `selectstart` event, which is blocked by default because of the default behavior
// of the DropdownView#panelView. This blocking prevents the native select all on Ctrl+A.
this.listenTo( this._findInputView.element, 'selectstart', ( evt, domEvt ) => {
domEvt.stopPropagation();
}, { priority: 'high' } );
this.listenTo( this._replaceInputView.element, 'selectstart', ( evt, domEvt ) => {
domEvt.stopPropagation();
}, { priority: 'high' } );
}
/**
* Creates a button view.
*
* @private
* @param {Object} options The properties of the `ButtonView`.
* @returns {module:ui/button/buttonview~ButtonView} The button view instance.
*/
_createButton( options ) {
const button = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.ButtonView( this.locale );
button.set( options );
return button;
}
/**
* Creates a labeled input view.
*
* @private
* @param {String} label The input label.
* @returns {module:ui/labeledfield/labeledfieldview~LabeledFieldView} The labeled input view instance.
*/
_createInputField( label ) {
const labeledInput = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.LabeledFieldView( this.locale, ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.createLabeledInputText );
labeledInput.label = label;
return labeledInput;
}
}
/**
* Fired when the find next button is triggered.
*
* @event findNext
* @param {String} searchText Search text.
*/
/**
* Fired when the find previous button is triggered.
*
* @event findPrevious
* @param {String} searchText Search text.
*/
/**
* Fired when the replace button is triggered.
*
* @event replace
* @param {String} replaceText Replacement text.
*/
/**
* Fired when the replaceAll button is triggered.
*
* @event replaceAll
* @param {String} replaceText Replacement text.
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-find-and-replace/src/utils.js":
/*!************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-find-and-replace/src/utils.js ***!
\************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "findByTextCallback": () => (/* binding */ findByTextCallback),
/* harmony export */ "rangeToText": () => (/* binding */ rangeToText),
/* harmony export */ "updateFindResultFromRange": () => (/* binding */ updateFindResultFromRange)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/* harmony import */ var lodash_es__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! lodash-es */ "./node_modules/lodash-es/escapeRegExp.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module find-and-replace/utils
*/
/**
* Executes findCallback and updates search results list.
*
* @param {module:engine/model/range~Range} range The model range to scan for matches.
* @param {module:engine/model/model~Model} model The model.
* @param {Function} findCallback The callback that should return `true` if provided text matches the search term.
* @param {module:utils/collection~Collection} [startResults] An optional collection of find matches that the function should
* start with. This would be a collection returned by a previous `updateFindResultFromRange()` call.
* @returns {module:utils/collection~Collection} A collection of objects describing find match.
*
* An example structure:
*
* ```js
* {
* id: resultId,
* label: foundItem.label,
* marker
* }
* ```
*/
function updateFindResultFromRange( range, model, findCallback, startResults ) {
const results = startResults || new ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_0__.Collection();
model.change( writer => {
[ ...range ].forEach( ( { type, item } ) => {
if ( type === 'elementStart' ) {
if ( model.schema.checkChild( item, '$text' ) ) {
const foundItems = findCallback( {
item,
text: rangeToText( model.createRangeIn( item ) )
} );
if ( !foundItems ) {
return;
}
foundItems.forEach( foundItem => {
const resultId = `findResult:${ (0,ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_0__.uid)() }`;
const marker = writer.addMarker( resultId, {
usingOperation: false,
affectsData: false,
range: writer.createRange(
writer.createPositionAt( item, foundItem.start ),
writer.createPositionAt( item, foundItem.end )
)
} );
const index = findInsertIndex( results, marker );
results.add(
{
id: resultId,
label: foundItem.label,
marker
},
index
);
} );
}
}
} );
} );
return results;
}
/**
* Returns text representation of a range. The returned text length should be the same as range length.
* In order to achieve this this function will replace inline elements (text-line) as new line character ("\n").
*
* @param {module:engine/model/range~Range} range The model range.
* @returns {String} The text content of the provided range.
*/
function rangeToText( range ) {
return Array.from( range.getItems() ).reduce( ( rangeText, node ) => {
// Trim text to a last occurrence of an inline element and update range start.
if ( !( node.is( 'text' ) || node.is( 'textProxy' ) ) ) {
// Editor has only one inline element defined in schema: `<softBreak>` which is treated as new line character in blocks.
// Special handling might be needed for other inline elements (inline widgets).
return `${ rangeText }\n`;
}
return rangeText + node.data;
}, '' );
}
// Finds the appropriate index in the resultsList Collection.
function findInsertIndex( resultsList, markerToInsert ) {
const result = resultsList.find( ( { marker } ) => {
return markerToInsert.getStart().isBefore( marker.getStart() );
} );
return result ? resultsList.getIndex( result ) : resultsList.length;
}
// Maps RegExp match result to find result.
function regexpMatchToFindResult( matchResult ) {
const lastGroupIndex = matchResult.length - 1;
let startOffset = matchResult.index;
// Searches with match all flag have an extra matching group with empty string or white space matched before the word.
// If the search term starts with the space already, there is no extra group even with match all flag on.
if ( matchResult.length === 3 ) {
startOffset += matchResult[ 1 ].length;
}
return {
label: matchResult[ lastGroupIndex ],
start: startOffset,
end: startOffset + matchResult[ lastGroupIndex ].length
};
}
/**
* Creates a text matching callback for a specified search term and matching options.
*
* @param {String} searchTerm The search term.
* @param {Object} [options] Matching options.
* @param {Boolean} [options.matchCase=false] If set to `true` letter casing will be ignored.
* @param {Boolean} [options.wholeWords=false] If set to `true` only whole words that match `callbackOrText` will be matched.
* @returns {Function}
*/
function findByTextCallback( searchTerm, options ) {
let flags = 'gu';
if ( !options.matchCase ) {
flags += 'i';
}
let regExpQuery = `(${ (0,lodash_es__WEBPACK_IMPORTED_MODULE_1__["default"])( searchTerm ) })`;
if ( options.wholeWords ) {
const nonLetterGroup = '[^a-zA-Z\u00C0-\u024F\u1E00-\u1EFF]';
if ( !new RegExp( '^' + nonLetterGroup ).test( searchTerm ) ) {
regExpQuery = `(^|${ nonLetterGroup }|_)${ regExpQuery }`;
}
if ( !new RegExp( nonLetterGroup + '$' ).test( searchTerm ) ) {
regExpQuery = `${ regExpQuery }(?=_|${ nonLetterGroup }|$)`;
}
}
const regExp = new RegExp( regExpQuery, flags );
function findCallback( { text } ) {
const matches = [ ...text.matchAll( regExp ) ];
return matches.map( regexpMatchToFindResult );
}
return findCallback;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-font/src/documentcolorcollection.js":
/*!******************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-font/src/documentcolorcollection.js ***!
\******************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ DocumentColorCollection)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module font/documentcolorcollection
*/
/**
* A collection to store document colors. It enforces colors to be unique.
*
* @mixes module:utils/observablemixin~ObservableMixin
* @extends module:utils/collection~Collection
*/
class DocumentColorCollection extends ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_0__.Collection {
constructor( options ) {
super( options );
/**
* Indicates whether the document color collection is empty.
*
* @observable
* @readonly
* @member {Boolean} #isEmpty
*/
this.set( 'isEmpty', true );
this.on( 'change', () => {
this.set( 'isEmpty', this.length === 0 );
} );
}
/**
* Adds a color to the document color collection.
*
* This method ensures that no color duplicates are inserted (compared using
* the color value of the {@link module:ui/colorgrid/colorgrid~ColorDefinition}).
*
* If the item does not have an ID, it will be automatically generated and set on the item.
*
* @chainable
* @param {module:ui/colorgrid/colorgrid~ColorDefinition} item
* @param {Number} [index] The position of the item in the collection. The item
* is pushed to the collection when `index` is not specified.
* @fires add
* @fires change
*/
add( item, index ) {
if ( this.find( element => element.color === item.color ) ) {
// No duplicates are allowed.
return;
}
super.add( item, index );
}
/**
* Checks if an object with given colors is present in the document color collection.
*
* @param {String} color
* @returns {Boolean}
*/
hasColor( color ) {
return !!this.find( item => item.color === color );
}
}
(0,ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_0__.mix)( DocumentColorCollection, ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_0__.ObservableMixin );
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-font/src/fontbackgroundcolor.js":
/*!**************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-font/src/fontbackgroundcolor.js ***!
\**************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ FontBackgroundColor)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var _fontbackgroundcolor_fontbackgroundcolorediting__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./fontbackgroundcolor/fontbackgroundcolorediting */ "./node_modules/@ckeditor/ckeditor5-font/src/fontbackgroundcolor/fontbackgroundcolorediting.js");
/* harmony import */ var _fontbackgroundcolor_fontbackgroundcolorui__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./fontbackgroundcolor/fontbackgroundcolorui */ "./node_modules/@ckeditor/ckeditor5-font/src/fontbackgroundcolor/fontbackgroundcolorui.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module font/fontbackgroundcolor
*/
/**
* The font background color plugin.
*
* For a detailed overview, check the {@glink features/font font feature} documentation
* and the {@glink api/font package page}.
*
* This is a "glue" plugin which loads
* the {@link module:font/fontbackgroundcolor/fontbackgroundcolorediting~FontBackgroundColorEditing} and
* {@link module:font/fontbackgroundcolor/fontbackgroundcolorui~FontBackgroundColorUI} features in the editor.
*
* @extends module:core/plugin~Plugin
*/
class FontBackgroundColor extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [ _fontbackgroundcolor_fontbackgroundcolorediting__WEBPACK_IMPORTED_MODULE_1__["default"], _fontbackgroundcolor_fontbackgroundcolorui__WEBPACK_IMPORTED_MODULE_2__["default"] ];
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'FontBackgroundColor';
}
}
/**
* The configuration of the font background color feature.
* It is introduced by the {@link module:font/fontbackgroundcolor/fontbackgroundcolorediting~FontBackgroundColorEditing} feature.
*
* Read more in {@link module:font/fontbackgroundcolor~FontBackgroundColorConfig}.
*
* @member {module:font/fontbackgroundcolor~FontBackgroundColorConfig} module:core/editor/editorconfig~EditorConfig#fontBackgroundColor
*/
/**
* The configuration of the font background color feature.
* This option is used by the {@link module:font/fontbackgroundcolor/fontbackgroundcolorediting~FontBackgroundColorEditing} feature.
*
* ClassicEditor
* .create( {
* fontBackgroundColor: ... // Font background color feature configuration.
* } )
* .then( ... )
* .catch( ... );
*
* See {@link module:core/editor/editorconfig~EditorConfig all editor options}.
*
* @interface module:font/fontbackgroundcolor~FontBackgroundColorConfig
*/
/**
* Available font background colors defined as an array of strings or objects.
*
* The default value registers the following colors:
*
* const fontBackgroundColorConfig = {
* colors: [
* {
* color: 'hsl(0, 0%, 0%)',
* label: 'Black'
* },
* {
* color: 'hsl(0, 0%, 30%)',
* label: 'Dim grey'
* },
* {
* color: 'hsl(0, 0%, 60%)',
* label: 'Grey'
* },
* {
* color: 'hsl(0, 0%, 90%)',
* label: 'Light grey'
* },
* {
* color: 'hsl(0, 0%, 100%)',
* label: 'White',
* hasBorder: true
* },
* {
* color: 'hsl(0, 75%, 60%)',
* label: 'Red'
* },
* {
* color: 'hsl(30, 75%, 60%)',
* label: 'Orange'
* },
* {
* color: 'hsl(60, 75%, 60%)',
* label: 'Yellow'
* },
* {
* color: 'hsl(90, 75%, 60%)',
* label: 'Light green'
* },
* {
* color: 'hsl(120, 75%, 60%)',
* label: 'Green'
* },
* {
* color: 'hsl(150, 75%, 60%)',
* label: 'Aquamarine'
* },
* {
* color: 'hsl(180, 75%, 60%)',
* label: 'Turquoise'
* },
* {
* color: 'hsl(210, 75%, 60%)',
* label: 'Light blue'
* },
* {
* color: 'hsl(240, 75%, 60%)',
* label: 'Blue'
* },
* {
* color: 'hsl(270, 75%, 60%)',
* label: 'Purple'
* }
* ]
* };
*
* **Note**: The colors are displayed in the `'fontBackgroundColor'` dropdown.
*
* @member {Array.<String|Object>} module:font/fontbackgroundcolor~FontBackgroundColorConfig#colors
*/
/**
* Represents the number of columns in the font background color dropdown.
*
* The default value is:
*
* const fontBackgroundColorConfig = {
* columns: 5
* }
*
* @member {Number} module:font/fontbackgroundcolor~FontBackgroundColorConfig#columns
*/
/**
* Determines the maximum number of available document colors.
* Setting it to `0` will disable the document colors feature.
*
* By default it equals to the {@link module:font/fontbackgroundcolor~FontBackgroundColorConfig#columns} value.
*
* Examples:
*
* // 1) Neither document colors nor columns are defined in the configuration.
* // Document colors will equal 5,
* // because the value will be inherited from columns,
* // which has a predefined value of 5.
* const fontBackgroundColorConfig = {}
*
* // 2) Document colors will equal 8, because the value will be inherited from columns.
* const fontBackgroundColorConfig = {
* columns: 8
* }
*
* // 3) Document colors will equal 24, because it has its own value defined.
* const fontBackgroundColorConfig = {
* columns: 8,
* documentColors: 24
* }
*
* // 4) The document colors feature will be disabled.
* const fontBackgroundColorConfig = {
* columns: 8,
* documentColors: 0
* }
*
* @member {Number} module:font/fontbackgroundcolor~FontBackgroundColorConfig#documentColors
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-font/src/fontbackgroundcolor/fontbackgroundcolorcommand.js":
/*!*****************************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-font/src/fontbackgroundcolor/fontbackgroundcolorcommand.js ***!
\*****************************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ FontBackgroundColorCommand)
/* harmony export */ });
/* harmony import */ var _fontcommand__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../fontcommand */ "./node_modules/@ckeditor/ckeditor5-font/src/fontcommand.js");
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../utils */ "./node_modules/@ckeditor/ckeditor5-font/src/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module font/fontbackgroundcolor/fontbackgroundcolorcommand
*/
/**
* The font background color command. It is used by
* {@link module:font/fontbackgroundcolor/fontbackgroundcolorediting~FontBackgroundColorEditing}
* to apply the font background color.
*
* editor.execute( 'fontBackgroundColor', { value: 'rgb(250, 20, 20)' } );
*
* **Note**: Executing the command with the `null` value removes the attribute from the model.
*
* @extends module:font/fontcommand~FontCommand
*/
class FontBackgroundColorCommand extends _fontcommand__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* @inheritDoc
*/
constructor( editor ) {
super( editor, _utils__WEBPACK_IMPORTED_MODULE_1__.FONT_BACKGROUND_COLOR );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-font/src/fontbackgroundcolor/fontbackgroundcolorediting.js":
/*!*****************************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-font/src/fontbackgroundcolor/fontbackgroundcolorediting.js ***!
\*****************************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ FontBackgroundColorEditing)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/engine */ "./node_modules/ckeditor5/src/engine.js");
/* harmony import */ var _fontbackgroundcolorcommand__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./fontbackgroundcolorcommand */ "./node_modules/@ckeditor/ckeditor5-font/src/fontbackgroundcolor/fontbackgroundcolorcommand.js");
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../utils */ "./node_modules/@ckeditor/ckeditor5-font/src/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module font/fontbackgroundcolor/fontbackgroundcolorediting
*/
/**
* The font background color editing feature.
*
* It introduces the {@link module:font/fontbackgroundcolor/fontbackgroundcolorcommand~FontBackgroundColorCommand command} and
* the `fontBackgroundColor` attribute in the {@link module:engine/model/model~Model model} which renders
* in the {@link module:engine/view/view view} as a `<span>` element (`<span style="background-color: ...">`),
* depending on the {@link module:font/fontbackgroundcolor~FontBackgroundColorConfig configuration}.
*
* @extends module:core/plugin~Plugin
*/
class FontBackgroundColorEditing extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'FontBackgroundColorEditing';
}
/**
* @inheritDoc
*/
constructor( editor ) {
super( editor );
editor.config.define( _utils__WEBPACK_IMPORTED_MODULE_3__.FONT_BACKGROUND_COLOR, {
colors: [
{
color: 'hsl(0, 0%, 0%)',
label: 'Black'
},
{
color: 'hsl(0, 0%, 30%)',
label: 'Dim grey'
},
{
color: 'hsl(0, 0%, 60%)',
label: 'Grey'
},
{
color: 'hsl(0, 0%, 90%)',
label: 'Light grey'
},
{
color: 'hsl(0, 0%, 100%)',
label: 'White',
hasBorder: true
},
{
color: 'hsl(0, 75%, 60%)',
label: 'Red'
},
{
color: 'hsl(30, 75%, 60%)',
label: 'Orange'
},
{
color: 'hsl(60, 75%, 60%)',
label: 'Yellow'
},
{
color: 'hsl(90, 75%, 60%)',
label: 'Light green'
},
{
color: 'hsl(120, 75%, 60%)',
label: 'Green'
},
{
color: 'hsl(150, 75%, 60%)',
label: 'Aquamarine'
},
{
color: 'hsl(180, 75%, 60%)',
label: 'Turquoise'
},
{
color: 'hsl(210, 75%, 60%)',
label: 'Light blue'
},
{
color: 'hsl(240, 75%, 60%)',
label: 'Blue'
},
{
color: 'hsl(270, 75%, 60%)',
label: 'Purple'
}
],
columns: 5
} );
editor.data.addStyleProcessorRules( ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_1__.addBackgroundRules );
editor.conversion.for( 'upcast' ).elementToAttribute( {
view: {
name: 'span',
styles: {
'background-color': /[\s\S]+/
}
},
model: {
key: _utils__WEBPACK_IMPORTED_MODULE_3__.FONT_BACKGROUND_COLOR,
value: (0,_utils__WEBPACK_IMPORTED_MODULE_3__.renderUpcastAttribute)( 'background-color' )
}
} );
editor.conversion.for( 'downcast' ).attributeToElement( {
model: _utils__WEBPACK_IMPORTED_MODULE_3__.FONT_BACKGROUND_COLOR,
view: (0,_utils__WEBPACK_IMPORTED_MODULE_3__.renderDowncastElement)( 'background-color' )
} );
editor.commands.add( _utils__WEBPACK_IMPORTED_MODULE_3__.FONT_BACKGROUND_COLOR, new _fontbackgroundcolorcommand__WEBPACK_IMPORTED_MODULE_2__["default"]( editor ) );
// Allow the font backgroundColor attribute on text nodes.
editor.model.schema.extend( '$text', { allowAttributes: _utils__WEBPACK_IMPORTED_MODULE_3__.FONT_BACKGROUND_COLOR } );
editor.model.schema.setAttributeProperties( _utils__WEBPACK_IMPORTED_MODULE_3__.FONT_BACKGROUND_COLOR, {
isFormatting: true,
copyOnEnter: true
} );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-font/src/fontbackgroundcolor/fontbackgroundcolorui.js":
/*!************************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-font/src/fontbackgroundcolor/fontbackgroundcolorui.js ***!
\************************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ FontBackgroundColorUI)
/* harmony export */ });
/* harmony import */ var _ui_colorui__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../ui/colorui */ "./node_modules/@ckeditor/ckeditor5-font/src/ui/colorui.js");
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../utils */ "./node_modules/@ckeditor/ckeditor5-font/src/utils.js");
/* harmony import */ var _theme_icons_font_background_svg__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../theme/icons/font-background.svg */ "./node_modules/@ckeditor/ckeditor5-font/theme/icons/font-background.svg");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module font/fontbackgroundcolor/fontbackgroundcolorui
*/
/**
* The font background color UI plugin. It introduces the `'fontBackgroundColor'` dropdown.
*
* @extends module:core/plugin~Plugin
*/
class FontBackgroundColorUI extends _ui_colorui__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* @inheritDoc
*/
constructor( editor ) {
const t = editor.locale.t;
super( editor, {
commandName: _utils__WEBPACK_IMPORTED_MODULE_1__.FONT_BACKGROUND_COLOR,
componentName: _utils__WEBPACK_IMPORTED_MODULE_1__.FONT_BACKGROUND_COLOR,
icon: _theme_icons_font_background_svg__WEBPACK_IMPORTED_MODULE_2__["default"],
dropdownLabel: t( 'Font Background Color' )
} );
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'FontBackgroundColorUI';
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-font/src/fontcolor.js":
/*!****************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-font/src/fontcolor.js ***!
\****************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ FontColor)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var _fontcolor_fontcolorediting__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./fontcolor/fontcolorediting */ "./node_modules/@ckeditor/ckeditor5-font/src/fontcolor/fontcolorediting.js");
/* harmony import */ var _fontcolor_fontcolorui__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./fontcolor/fontcolorui */ "./node_modules/@ckeditor/ckeditor5-font/src/fontcolor/fontcolorui.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module font/fontcolor
*/
/**
* The font color plugin.
*
* For a detailed overview, check the {@glink features/font font feature} documentation
* and the {@glink api/font package page}.
*
* This is a "glue" plugin which loads the {@link module:font/fontcolor/fontcolorediting~FontColorEditing} and
* {@link module:font/fontcolor/fontcolorui~FontColorUI} features in the editor.
*
* @extends module:core/plugin~Plugin
*/
class FontColor extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [ _fontcolor_fontcolorediting__WEBPACK_IMPORTED_MODULE_1__["default"], _fontcolor_fontcolorui__WEBPACK_IMPORTED_MODULE_2__["default"] ];
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'FontColor';
}
}
/**
* The configuration of the font color feature.
* It is introduced by the {@link module:font/fontcolor/fontcolorediting~FontColorEditing} feature.
*
* Read more in {@link module:font/fontcolor~FontColorConfig}.
*
* @member {module:font/fontcolor~FontColorConfig} module:core/editor/editorconfig~EditorConfig#fontColor
*/
/**
* The configuration of the font color feature.
* This option is used by the {@link module:font/fontcolor/fontcolorediting~FontColorEditing} feature.
*
* ClassicEditor
* .create( {
* fontColor: ... // Font color feature configuration.
* } )
* .then( ... )
* .catch( ... );
*
* See {@link module:core/editor/editorconfig~EditorConfig all editor options}.
*
* @interface module:font/fontcolor~FontColorConfig
*/
/**
* Available font colors defined as an array of strings or objects.
*
* The default value registers the following colors:
*
* const fontColorConfig = {
* colors: [
* {
* color: 'hsl(0, 0%, 0%)',
* label: 'Black'
* },
* {
* color: 'hsl(0, 0%, 30%)',
* label: 'Dim grey'
* },
* {
* color: 'hsl(0, 0%, 60%)',
* label: 'Grey'
* },
* {
* color: 'hsl(0, 0%, 90%)',
* label: 'Light grey'
* },
* {
* color: 'hsl(0, 0%, 100%)',
* label: 'White',
* hasBorder: true
* },
* {
* color: 'hsl(0, 75%, 60%)',
* label: 'Red'
* },
* {
* color: 'hsl(30, 75%, 60%)',
* label: 'Orange'
* },
* {
* color: 'hsl(60, 75%, 60%)',
* label: 'Yellow'
* },
* {
* color: 'hsl(90, 75%, 60%)',
* label: 'Light green'
* },
* {
* color: 'hsl(120, 75%, 60%)',
* label: 'Green'
* },
* {
* color: 'hsl(150, 75%, 60%)',
* label: 'Aquamarine'
* },
* {
* color: 'hsl(180, 75%, 60%)',
* label: 'Turquoise'
* },
* {
* color: 'hsl(210, 75%, 60%)',
* label: 'Light blue'
* },
* {
* color: 'hsl(240, 75%, 60%)',
* label: 'Blue'
* },
* {
* color: 'hsl(270, 75%, 60%)',
* label: 'Purple'
* }
* ]
* };
*
* **Note**: The colors are displayed in the `'fontColor'` dropdown.
*
* @member {Array.<String|Object>} module:font/fontcolor~FontColorConfig#colors
*/
/**
* Represents the number of columns in the font color dropdown.
*
* The default value is:
*
* const fontColorConfig = {
* columns: 5
* }
*
* @member {Number} module:font/fontcolor~FontColorConfig#columns
*/
/**
* Determines the maximum number of available document colors.
* Setting it to `0` will disable the document colors feature.
*
* By default it equals to the {@link module:font/fontcolor~FontColorConfig#columns} value.
*
* Examples:
*
* // 1) Neither document colors nor columns are defined in the configuration.
* // Document colors will equal 5,
* // because the value will be inherited from columns,
* // which has a predefined value of 5.
* const fontColorConfig = {}
*
* // 2) Document colors will equal 8, because the value will be inherited from columns.
* const fontColorConfig = {
* columns: 8
* }
*
* // 3) Document colors will equal 24, because it has its own value defined.
* const fontColorConfig = {
* columns: 8,
* documentColors: 24
* }
*
* // 4) The document colors feature will be disabled.
* const fontColorConfig = {
* columns: 8,
* documentColors: 0
* }
*
* @member {Number} module:font/fontcolor~FontColorConfig#documentColors
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-font/src/fontcolor/fontcolorcommand.js":
/*!*********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-font/src/fontcolor/fontcolorcommand.js ***!
\*********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ FontColorCommand)
/* harmony export */ });
/* harmony import */ var _fontcommand__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../fontcommand */ "./node_modules/@ckeditor/ckeditor5-font/src/fontcommand.js");
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../utils */ "./node_modules/@ckeditor/ckeditor5-font/src/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module font/fontcolor/fontcolorcommand
*/
/**
* The font color command. It is used by {@link module:font/fontcolor/fontcolorediting~FontColorEditing}
* to apply the font color.
*
* editor.execute( 'fontColor', { value: 'rgb(250, 20, 20)' } );
*
* **Note**: Executing the command with the `null` value removes the attribute from the model.
*
* @extends module:font/fontcommand~FontCommand
*/
class FontColorCommand extends _fontcommand__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* @inheritDoc
*/
constructor( editor ) {
super( editor, _utils__WEBPACK_IMPORTED_MODULE_1__.FONT_COLOR );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-font/src/fontcolor/fontcolorediting.js":
/*!*********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-font/src/fontcolor/fontcolorediting.js ***!
\*********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ FontColorEditing)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var _fontcolorcommand__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./fontcolorcommand */ "./node_modules/@ckeditor/ckeditor5-font/src/fontcolor/fontcolorcommand.js");
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../utils */ "./node_modules/@ckeditor/ckeditor5-font/src/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module font/fontcolor/fontcolorediting
*/
/**
* The font color editing feature.
*
* It introduces the {@link module:font/fontcolor/fontcolorcommand~FontColorCommand command} and
* the `fontColor` attribute in the {@link module:engine/model/model~Model model} which renders
* in the {@link module:engine/view/view view} as a `<span>` element (`<span style="color: ...">`),
* depending on the {@link module:font/fontcolor~FontColorConfig configuration}.
*
* @extends module:core/plugin~Plugin
*/
class FontColorEditing extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'FontColorEditing';
}
/**
* @inheritDoc
*/
constructor( editor ) {
super( editor );
editor.config.define( _utils__WEBPACK_IMPORTED_MODULE_2__.FONT_COLOR, {
colors: [
{
color: 'hsl(0, 0%, 0%)',
label: 'Black'
},
{
color: 'hsl(0, 0%, 30%)',
label: 'Dim grey'
},
{
color: 'hsl(0, 0%, 60%)',
label: 'Grey'
},
{
color: 'hsl(0, 0%, 90%)',
label: 'Light grey'
},
{
color: 'hsl(0, 0%, 100%)',
label: 'White',
hasBorder: true
},
{
color: 'hsl(0, 75%, 60%)',
label: 'Red'
},
{
color: 'hsl(30, 75%, 60%)',
label: 'Orange'
},
{
color: 'hsl(60, 75%, 60%)',
label: 'Yellow'
},
{
color: 'hsl(90, 75%, 60%)',
label: 'Light green'
},
{
color: 'hsl(120, 75%, 60%)',
label: 'Green'
},
{
color: 'hsl(150, 75%, 60%)',
label: 'Aquamarine'
},
{
color: 'hsl(180, 75%, 60%)',
label: 'Turquoise'
},
{
color: 'hsl(210, 75%, 60%)',
label: 'Light blue'
},
{
color: 'hsl(240, 75%, 60%)',
label: 'Blue'
},
{
color: 'hsl(270, 75%, 60%)',
label: 'Purple'
}
],
columns: 5
} );
editor.conversion.for( 'upcast' ).elementToAttribute( {
view: {
name: 'span',
styles: {
'color': /[\s\S]+/
}
},
model: {
key: _utils__WEBPACK_IMPORTED_MODULE_2__.FONT_COLOR,
value: (0,_utils__WEBPACK_IMPORTED_MODULE_2__.renderUpcastAttribute)( 'color' )
}
} );
// Support legacy `<font color="..">` formatting.
editor.conversion.for( 'upcast' ).elementToAttribute( {
view: {
name: 'font',
attributes: {
'color': /^#?\w+$/
}
},
model: {
key: _utils__WEBPACK_IMPORTED_MODULE_2__.FONT_COLOR,
value: viewElement => viewElement.getAttribute( 'color' )
}
} );
editor.conversion.for( 'downcast' ).attributeToElement( {
model: _utils__WEBPACK_IMPORTED_MODULE_2__.FONT_COLOR,
view: (0,_utils__WEBPACK_IMPORTED_MODULE_2__.renderDowncastElement)( 'color' )
} );
editor.commands.add( _utils__WEBPACK_IMPORTED_MODULE_2__.FONT_COLOR, new _fontcolorcommand__WEBPACK_IMPORTED_MODULE_1__["default"]( editor ) );
// Allow the font color attribute on text nodes.
editor.model.schema.extend( '$text', { allowAttributes: _utils__WEBPACK_IMPORTED_MODULE_2__.FONT_COLOR } );
editor.model.schema.setAttributeProperties( _utils__WEBPACK_IMPORTED_MODULE_2__.FONT_COLOR, {
isFormatting: true,
copyOnEnter: true
} );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-font/src/fontcolor/fontcolorui.js":
/*!****************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-font/src/fontcolor/fontcolorui.js ***!
\****************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ FontColorUI)
/* harmony export */ });
/* harmony import */ var _ui_colorui__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../ui/colorui */ "./node_modules/@ckeditor/ckeditor5-font/src/ui/colorui.js");
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../utils */ "./node_modules/@ckeditor/ckeditor5-font/src/utils.js");
/* harmony import */ var _theme_icons_font_color_svg__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../theme/icons/font-color.svg */ "./node_modules/@ckeditor/ckeditor5-font/theme/icons/font-color.svg");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module font/fontcolor/fontcolorui
*/
/**
* The font color UI plugin. It introduces the `'fontColor'` dropdown.
*
* @extends module:core/plugin~Plugin
*/
class FontColorUI extends _ui_colorui__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* @inheritDoc
*/
constructor( editor ) {
const t = editor.locale.t;
super( editor, {
commandName: _utils__WEBPACK_IMPORTED_MODULE_1__.FONT_COLOR,
componentName: _utils__WEBPACK_IMPORTED_MODULE_1__.FONT_COLOR,
icon: _theme_icons_font_color_svg__WEBPACK_IMPORTED_MODULE_2__["default"],
dropdownLabel: t( 'Font Color' )
} );
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'FontColorUI';
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-font/src/fontcommand.js":
/*!******************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-font/src/fontcommand.js ***!
\******************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ FontCommand)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module font/fontcommand
*/
/**
* The base font command.
*
* @extends module:core/command~Command
*/
class FontCommand extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Command {
/**
* Creates an instance of the command.
*
* @param {module:core/editor/editor~Editor} editor Editor instance.
* @param {String} attributeKey The name of a model attribute on which this command operates.
*/
constructor( editor, attributeKey ) {
super( editor );
/**
* When set, it reflects the {@link #attributeKey} value of the selection.
*
* @observable
* @readonly
* @member {String} module:font/fontcommand~FontCommand#value
*/
/**
* A model attribute on which this command operates.
*
* @readonly
* @member {Boolean} module:font/fontcommand~FontCommand#attributeKey
*/
this.attributeKey = attributeKey;
}
/**
* @inheritDoc
*/
refresh() {
const model = this.editor.model;
const doc = model.document;
this.value = doc.selection.getAttribute( this.attributeKey );
this.isEnabled = model.schema.checkAttributeInSelection( doc.selection, this.attributeKey );
}
/**
* Executes the command. Applies the `value` of the {@link #attributeKey} to the selection.
* If no `value` is passed, it removes the attribute from the selection.
*
* @protected
* @param {Object} [options] Options for the executed command.
* @param {String} [options.value] The value to apply.
* @fires execute
*/
execute( options = {} ) {
const model = this.editor.model;
const document = model.document;
const selection = document.selection;
const value = options.value;
model.change( writer => {
if ( selection.isCollapsed ) {
if ( value ) {
writer.setSelectionAttribute( this.attributeKey, value );
} else {
writer.removeSelectionAttribute( this.attributeKey );
}
} else {
const ranges = model.schema.getValidRanges( selection.getRanges(), this.attributeKey );
for ( const range of ranges ) {
if ( value ) {
writer.setAttribute( this.attributeKey, value, range );
} else {
writer.removeAttribute( this.attributeKey, range );
}
}
}
} );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-font/src/fontfamily.js":
/*!*****************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-font/src/fontfamily.js ***!
\*****************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ FontFamily)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var _fontfamily_fontfamilyediting__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./fontfamily/fontfamilyediting */ "./node_modules/@ckeditor/ckeditor5-font/src/fontfamily/fontfamilyediting.js");
/* harmony import */ var _fontfamily_fontfamilyui__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./fontfamily/fontfamilyui */ "./node_modules/@ckeditor/ckeditor5-font/src/fontfamily/fontfamilyui.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module font/fontfamily
*/
/**
* The font family plugin.
*
* For a detailed overview, check the {@glink features/font font feature} documentatiom
* and the {@glink api/font package page}.
*
* This is a "glue" plugin which loads the {@link module:font/fontfamily/fontfamilyediting~FontFamilyEditing} and
* {@link module:font/fontfamily/fontfamilyui~FontFamilyUI} features in the editor.
*
* @extends module:core/plugin~Plugin
*/
class FontFamily extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [ _fontfamily_fontfamilyediting__WEBPACK_IMPORTED_MODULE_1__["default"], _fontfamily_fontfamilyui__WEBPACK_IMPORTED_MODULE_2__["default"] ];
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'FontFamily';
}
}
/**
* The font family option descriptor.
*
* @typedef {Object} module:font/fontfamily~FontFamilyOption
*
* @property {String} title The user-readable title of the option.
* @property {String} model The attribute's unique value in the model.
* @property {module:engine/view/elementdefinition~ElementDefinition} view View element configuration.
* @property {Array.<module:engine/view/elementdefinition~ElementDefinition>} [upcastAlso] An array with all matched elements that
* the view-to-model conversion should also accept.
*/
/**
* The configuration of the font family feature.
* It is introduced by the {@link module:font/fontfamily/fontfamilyediting~FontFamilyEditing} feature.
*
* Read more in {@link module:font/fontfamily~FontFamilyConfig}.
*
* @member {module:font/fontfamily~FontFamilyConfig} module:core/editor/editorconfig~EditorConfig#fontFamily
*/
/**
* The configuration of the font family feature.
* This option is used by the {@link module:font/fontfamily/fontfamilyediting~FontFamilyEditing} feature.
*
* ClassicEditor
* .create( {
* fontFamily: ... // Font family feature configuration.
* } )
* .then( ... )
* .catch( ... );
*
* See {@link module:core/editor/editorconfig~EditorConfig all editor options}.
*
* @interface module:font/fontfamily~FontFamilyConfig
*/
/**
* Available font family options defined as an array of strings. The default value is:
*
* const fontFamilyConfig = {
* options: [
* 'default',
* 'Arial, Helvetica, sans-serif',
* 'Courier New, Courier, monospace',
* 'Georgia, serif',
* 'Lucida Sans Unicode, Lucida Grande, sans-serif',
* 'Tahoma, Geneva, sans-serif',
* 'Times New Roman, Times, serif',
* 'Trebuchet MS, Helvetica, sans-serif',
* 'Verdana, Geneva, sans-serif'
* ]
* };
*
* which configures 8 font family options. Each option consists of one or more comma–separated font family names. The first font name is
* used as the dropdown item description in the UI.
*
* **Note:** The family names that consist of spaces should not have quotes (as opposed to the CSS standard). The necessary quotes
* will be added automatically in the view. For example, the `"Lucida Sans Unicode"` will render as follows:
*
* <span style="font-family:'Lucida Sans Unicode', 'Lucida Grande', sans-serif">...</span>
*
* The "default" option removes the `fontFamily` attribute from the selection. In such case, the text will
* be rendered in the view using the default font family defined in the styles of the web page.
*
* Font family can be applied using the command API. To do that, use the `fontFamily` command and pass the desired family as a `value`.
* For example, the following code will apply the `fontFamily` attribute with the `'Arial'` `value` to the current selection:
*
* editor.execute( 'fontFamily', { value: 'Arial' } );
*
* Executing the `'fontFamily'` command without any value will remove the `fontFamily` attribute from the current selection.
*
* @member {Array.<String|module:font/fontfamily~FontFamilyOption>} module:font/fontfamily~FontFamilyConfig#options
*/
/**
* By default the plugin removes any `font-family` value that does not match the plugin's configuration. It means that if you paste content
* with font families that the editor does not understand, the `font-family` attribute will be removed and the content will be displayed
* with the default font.
*
* You can preserve pasted font family values by switching the `supportAllValues` option to `true`:
*
* const fontFamilyConfig = {
* supportAllValues: true
* };
*
* With this configuration font families not specified in the editor configuration will not be removed when pasting the content.
*
* @member {Boolean} module:font/fontfamily~FontFamilyConfig#supportAllValues
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-font/src/fontfamily/fontfamilycommand.js":
/*!***********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-font/src/fontfamily/fontfamilycommand.js ***!
\***********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ FontFamilyCommand)
/* harmony export */ });
/* harmony import */ var _fontcommand__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../fontcommand */ "./node_modules/@ckeditor/ckeditor5-font/src/fontcommand.js");
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../utils */ "./node_modules/@ckeditor/ckeditor5-font/src/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module font/fontfamily/fontfamilycommand
*/
/**
* The font family command. It is used by {@link module:font/fontfamily/fontfamilyediting~FontFamilyEditing}
* to apply the font family.
*
* editor.execute( 'fontFamily', { value: 'Arial' } );
*
* **Note**: Executing the command without the value removes the attribute from the model.
*
* @extends module:font/fontcommand~FontCommand
*/
class FontFamilyCommand extends _fontcommand__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* @inheritDoc
*/
constructor( editor ) {
super( editor, _utils__WEBPACK_IMPORTED_MODULE_1__.FONT_FAMILY );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-font/src/fontfamily/fontfamilyediting.js":
/*!***********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-font/src/fontfamily/fontfamilyediting.js ***!
\***********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ FontFamilyEditing)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var _fontfamilycommand__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./fontfamilycommand */ "./node_modules/@ckeditor/ckeditor5-font/src/fontfamily/fontfamilycommand.js");
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./utils */ "./node_modules/@ckeditor/ckeditor5-font/src/fontfamily/utils.js");
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../utils */ "./node_modules/@ckeditor/ckeditor5-font/src/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module font/fontfamily/fontfamilyediting
*/
/**
* The font family editing feature.
*
* It introduces the {@link module:font/fontfamily/fontfamilycommand~FontFamilyCommand command} and
* the `fontFamily` attribute in the {@link module:engine/model/model~Model model} which renders
* in the {@link module:engine/view/view view} as an inline `<span>` element (`<span style="font-family: Arial">`),
* depending on the {@link module:font/fontfamily~FontFamilyConfig configuration}.
*
* @extends module:core/plugin~Plugin
*/
class FontFamilyEditing extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'FontFamilyEditing';
}
/**
* @inheritDoc
*/
constructor( editor ) {
super( editor );
// Define default configuration using font families shortcuts.
editor.config.define( _utils__WEBPACK_IMPORTED_MODULE_3__.FONT_FAMILY, {
options: [
'default',
'Arial, Helvetica, sans-serif',
'Courier New, Courier, monospace',
'Georgia, serif',
'Lucida Sans Unicode, Lucida Grande, sans-serif',
'Tahoma, Geneva, sans-serif',
'Times New Roman, Times, serif',
'Trebuchet MS, Helvetica, sans-serif',
'Verdana, Geneva, sans-serif'
],
supportAllValues: false
} );
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
// Allow fontFamily attribute on text nodes.
editor.model.schema.extend( '$text', { allowAttributes: _utils__WEBPACK_IMPORTED_MODULE_3__.FONT_FAMILY } );
editor.model.schema.setAttributeProperties( _utils__WEBPACK_IMPORTED_MODULE_3__.FONT_FAMILY, {
isFormatting: true,
copyOnEnter: true
} );
// Get configured font family options without "default" option.
const options = (0,_utils__WEBPACK_IMPORTED_MODULE_2__.normalizeOptions)( editor.config.get( 'fontFamily.options' ) ).filter( item => item.model );
const definition = (0,_utils__WEBPACK_IMPORTED_MODULE_3__.buildDefinition)( _utils__WEBPACK_IMPORTED_MODULE_3__.FONT_FAMILY, options );
// Set-up the two-way conversion.
if ( editor.config.get( 'fontFamily.supportAllValues' ) ) {
this._prepareAnyValueConverters();
this._prepareCompatibilityConverter();
} else {
editor.conversion.attributeToElement( definition );
}
editor.commands.add( _utils__WEBPACK_IMPORTED_MODULE_3__.FONT_FAMILY, new _fontfamilycommand__WEBPACK_IMPORTED_MODULE_1__["default"]( editor ) );
}
/**
* These converters enable keeping any value found as `style="font-family: *"` as a value of an attribute on a text even
* if it is not defined in the plugin configuration.
*
* @private
*/
_prepareAnyValueConverters() {
const editor = this.editor;
editor.conversion.for( 'downcast' ).attributeToElement( {
model: _utils__WEBPACK_IMPORTED_MODULE_3__.FONT_FAMILY,
view: ( attributeValue, { writer } ) => {
return writer.createAttributeElement( 'span', { style: 'font-family:' + attributeValue }, { priority: 7 } );
}
} );
editor.conversion.for( 'upcast' ).elementToAttribute( {
model: {
key: _utils__WEBPACK_IMPORTED_MODULE_3__.FONT_FAMILY,
value: viewElement => viewElement.getStyle( 'font-family' )
},
view: {
name: 'span',
styles: {
'font-family': /.*/
}
}
} );
}
/**
* Adds support for legacy `<font face="..">` formatting.
*
* @private
*/
_prepareCompatibilityConverter() {
const editor = this.editor;
editor.conversion.for( 'upcast' ).elementToAttribute( {
view: {
name: 'font',
attributes: {
'face': /.*/
}
},
model: {
key: _utils__WEBPACK_IMPORTED_MODULE_3__.FONT_FAMILY,
value: viewElement => viewElement.getAttribute( 'face' )
}
} );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-font/src/fontfamily/fontfamilyui.js":
/*!******************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-font/src/fontfamily/fontfamilyui.js ***!
\******************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ FontFamilyUI)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/* harmony import */ var ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ckeditor5/src/ui */ "./node_modules/ckeditor5/src/ui.js");
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./utils */ "./node_modules/@ckeditor/ckeditor5-font/src/fontfamily/utils.js");
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../utils */ "./node_modules/@ckeditor/ckeditor5-font/src/utils.js");
/* harmony import */ var _theme_icons_font_family_svg__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../../theme/icons/font-family.svg */ "./node_modules/@ckeditor/ckeditor5-font/theme/icons/font-family.svg");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module font/fontfamily/fontfamilyui
*/
/**
* The font family UI plugin. It introduces the `'fontFamily'` dropdown.
*
* @extends module:core/plugin~Plugin
*/
class FontFamilyUI extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'FontFamilyUI';
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const t = editor.t;
const options = this._getLocalizedOptions();
const command = editor.commands.get( _utils__WEBPACK_IMPORTED_MODULE_4__.FONT_FAMILY );
// Register UI component.
editor.ui.componentFactory.add( _utils__WEBPACK_IMPORTED_MODULE_4__.FONT_FAMILY, locale => {
const dropdownView = (0,ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_2__.createDropdown)( locale );
(0,ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_2__.addListToDropdown)( dropdownView, _prepareListOptions( options, command ) );
dropdownView.buttonView.set( {
label: t( 'Font Family' ),
icon: _theme_icons_font_family_svg__WEBPACK_IMPORTED_MODULE_5__["default"],
tooltip: true
} );
dropdownView.extendTemplate( {
attributes: {
class: 'ck-font-family-dropdown'
}
} );
dropdownView.bind( 'isEnabled' ).to( command );
// Execute command when an item from the dropdown is selected.
this.listenTo( dropdownView, 'execute', evt => {
editor.execute( evt.source.commandName, { value: evt.source.commandParam } );
editor.editing.view.focus();
} );
return dropdownView;
} );
}
/**
* Returns options as defined in `config.fontFamily.options` but processed to account for
* editor localization, i.e. to display {@link module:font/fontfamily~FontFamilyOption}
* in the correct language.
*
* Note: The reason behind this method is that there is no way to use {@link module:utils/locale~Locale#t}
* when the user configuration is defined because the editor does not exist yet.
*
* @private
* @returns {Array.<module:font/fontfamily~FontFamilyOption>}.
*/
_getLocalizedOptions() {
const editor = this.editor;
const t = editor.t;
const options = (0,_utils__WEBPACK_IMPORTED_MODULE_3__.normalizeOptions)( editor.config.get( _utils__WEBPACK_IMPORTED_MODULE_4__.FONT_FAMILY ).options );
return options.map( option => {
// The only title to localize is "Default" others are font names.
if ( option.title === 'Default' ) {
option.title = t( 'Default' );
}
return option;
} );
}
}
// Prepares FontFamily dropdown items.
// @private
// @param {Array.<module:font/fontsize~FontSizeOption>} options
// @param {module:font/fontsize/fontsizecommand~FontSizeCommand} command
function _prepareListOptions( options, command ) {
const itemDefinitions = new ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__.Collection();
// Create dropdown items.
for ( const option of options ) {
const def = {
type: 'button',
model: new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_2__.Model( {
commandName: _utils__WEBPACK_IMPORTED_MODULE_4__.FONT_FAMILY,
commandParam: option.model,
label: option.title,
withText: true
} )
};
def.model.bind( 'isOn' ).to( command, 'value', value => {
// "Default" or check in strict font-family converters mode.
if ( value === option.model ) {
return true;
}
if ( !value || !option.model ) {
return false;
}
return value.split( ',' )[ 0 ].replace( /'/g, '' ).toLowerCase() === option.model.toLowerCase();
} );
// Try to set a dropdown list item style.
if ( option.view && option.view.styles ) {
def.model.set( 'labelStyle', `font-family: ${ option.view.styles[ 'font-family' ] }` );
}
itemDefinitions.add( def );
}
return itemDefinitions;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-font/src/fontfamily/utils.js":
/*!***********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-font/src/fontfamily/utils.js ***!
\***********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "normalizeOptions": () => (/* binding */ normalizeOptions)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module font/fontfamily/utils
*/
/**
* Normalizes the {@link module:font/fontfamily~FontFamilyConfig#options configuration options}
* to the {@link module:font/fontfamily~FontFamilyOption} format.
*
* @param {Array.<String|Object>} configuredOptions An array of options taken from the configuration.
* @returns {Array.<module:font/fontfamily~FontFamilyOption>}
*/
function normalizeOptions( configuredOptions ) {
// Convert options to objects.
return configuredOptions
.map( getOptionDefinition )
// Filter out undefined values that `getOptionDefinition` might return.
.filter( option => !!option );
}
// Returns an option definition either created from string shortcut.
// If object is passed then this method will return it without alternating it. Returns undefined for item than cannot be parsed.
//
// @param {String|Object} option
// @returns {undefined|module:font/fontfamily~FontFamilyOption}
function getOptionDefinition( option ) {
// Treat any object as full item definition provided by user in configuration.
if ( typeof option === 'object' ) {
return option;
}
// Handle 'default' string as a special case. It will be used to remove the fontFamily attribute.
if ( option === 'default' ) {
return {
title: 'Default',
model: undefined
};
}
// Ignore values that we cannot parse to a definition.
if ( typeof option !== 'string' ) {
return;
}
// Return font family definition from font string.
return generateFontPreset( option );
}
// Creates a predefined preset for pixel size. It deconstructs font-family like string into full configuration option.
// A font definition is passed as coma delimited set of font family names. Font names might be quoted.
//
// @param {String} A font definition form configuration.
function generateFontPreset( fontDefinition ) {
// Remove quotes from font names. They will be normalized later.
const fontNames = fontDefinition.replace( /"|'/g, '' ).split( ',' );
// The first matched font name will be used as dropdown list item title and as model value.
const firstFontName = fontNames[ 0 ];
// CSS-compatible font names.
const cssFontNames = fontNames.map( normalizeFontNameForCSS ).join( ', ' );
return {
title: firstFontName,
model: cssFontNames,
view: {
name: 'span',
styles: {
'font-family': cssFontNames
},
priority: 7
}
};
}
// Normalizes font name for the style attribute. It adds braces (') if font name contains spaces.
//
// @param {String} fontName
// @returns {String}
function normalizeFontNameForCSS( fontName ) {
fontName = fontName.trim();
// Compound font names should be quoted.
if ( fontName.indexOf( ' ' ) > 0 ) {
fontName = `'${ fontName }'`;
}
return fontName;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-font/src/fontsize.js":
/*!***************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-font/src/fontsize.js ***!
\***************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ FontSize)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var _fontsize_fontsizeediting__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./fontsize/fontsizeediting */ "./node_modules/@ckeditor/ckeditor5-font/src/fontsize/fontsizeediting.js");
/* harmony import */ var _fontsize_fontsizeui__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./fontsize/fontsizeui */ "./node_modules/@ckeditor/ckeditor5-font/src/fontsize/fontsizeui.js");
/* harmony import */ var _fontsize_utils__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./fontsize/utils */ "./node_modules/@ckeditor/ckeditor5-font/src/fontsize/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module font/fontsize
*/
/**
* The font size plugin.
*
* For a detailed overview, check the {@glink features/font font feature} documentation
* and the {@glink api/font package page}.
*
* This is a "glue" plugin which loads the {@link module:font/fontsize/fontsizeediting~FontSizeEditing} and
* {@link module:font/fontsize/fontsizeui~FontSizeUI} features in the editor.
*
* @extends module:core/plugin~Plugin
*/
class FontSize extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [ _fontsize_fontsizeediting__WEBPACK_IMPORTED_MODULE_1__["default"], _fontsize_fontsizeui__WEBPACK_IMPORTED_MODULE_2__["default"] ];
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'FontSize';
}
/**
* Normalizes and translates the {@link module:font/fontsize~FontSizeConfig#options configuration options}
* to the {@link module:font/fontsize~FontSizeOption} format.
*
* @param {Array.<String|Number|Object>} configuredOptions An array of options taken from the configuration.
* @returns {Array.<module:font/fontsize~FontSizeOption>}
*/
normalizeSizeOptions( options ) {
return (0,_fontsize_utils__WEBPACK_IMPORTED_MODULE_3__.normalizeOptions)( options );
}
}
/**
* The font size option descriptor.
*
* @typedef {Object} module:font/fontsize~FontSizeOption
*
* @property {String} title The user-readable title of the option.
* @property {String} model The attribute's unique value in the model.
* @property {module:engine/view/elementdefinition~ElementDefinition} view View element configuration.
* @property {Array.<module:engine/view/elementdefinition~ElementDefinition>} [upcastAlso] An array with all matched elements that
* the view-to-model conversion should also accept.
*/
/**
* The configuration of the font size feature.
* It is introduced by the {@link module:font/fontsize/fontsizeediting~FontSizeEditing} feature.
*
* Read more in {@link module:font/fontsize~FontSizeConfig}.
*
* @member {module:font/fontsize~FontSizeConfig} module:core/editor/editorconfig~EditorConfig#fontSize
*/
/**
* The configuration of the font size feature.
* This option is used by the {@link module:font/fontsize/fontsizeediting~FontSizeEditing} feature.
*
* ClassicEditor
* .create( {
* fontSize: ... // Font size feature configuration.
* } )
* .then( ... )
* .catch( ... );
*
* See {@link module:core/editor/editorconfig~EditorConfig all editor options}.
*
* @interface module:font/fontsize~FontSizeConfig
*/
/**
* Available font size options. Expressed as predefined presets, numerical "pixel" values
* or the {@link module:font/fontsize~FontSizeOption}.
*
* The default value is:
*
* const fontSizeConfig = {
* options: [
* 'tiny',
* 'small',
* 'default',
* 'big',
* 'huge'
* ]
* };
*
* It defines 4 sizes: **tiny**, **small**, **big**, and **huge**. These values will be rendered as `<span>` elements in the view.
* The **default** defines a text without the `fontSize` attribute.
*
* Each `<span>` has the the `class` attribute set to the corresponding size name. For instance, this is what the **small** size looks
* like in the view:
*
* <span class="text-small">...</span>
*
* As an alternative, the font size might be defined using numerical values (either as a `Number` or as a `String`):
*
* const fontSizeConfig = {
* options: [ 9, 10, 11, 12, 13, 14, 15 ]
* };
*
* Also, you can define a label in the dropdown for numerical values:
*
* const fontSizeConfig = {
* options: [
* {
* title: 'Small',
* model: '8px'
* },
* 'default',
* {
* title: 'Big',
* model: '14px'
* }
* ]
* };
*
* Font size can be applied using the command API. To do that, use the `'fontSize'` command and pass the desired font size as a `value`.
* For example, the following code will apply the `fontSize` attribute with the **tiny** value to the current selection:
*
* editor.execute( 'fontSize', { value: 'tiny' } );
*
* Executing the `fontSize` command without value will remove the `fontSize` attribute from the current selection.
*
* @member {Array.<String|Number|module:font/fontsize~FontSizeOption>} module:font/fontsize~FontSizeConfig#options
*/
/**
* By default the plugin removes any `font-size` value that does not match the plugin's configuration. It means that if you paste content
* with font sizes that the editor does not understand, the `font-size` attribute will be removed and the content will be displayed
* with the default size.
*
* You can preserve pasted font size values by switching the `supportAllValues` option to `true`:
*
* const fontSizeConfig = {
* options: [ 9, 10, 11, 12, 'default', 14, 15 ],
* supportAllValues: true
* };
*
* **Note:** This option can only be used with numerical values as font size options.
*
* With this configuration font sizes not specified in the editor configuration will not be removed when pasting the content.
*
* @member {Boolean} module:font/fontsize~FontSizeConfig#supportAllValues
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-font/src/fontsize/fontsizecommand.js":
/*!*******************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-font/src/fontsize/fontsizecommand.js ***!
\*******************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ FontSizeCommand)
/* harmony export */ });
/* harmony import */ var _fontcommand__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../fontcommand */ "./node_modules/@ckeditor/ckeditor5-font/src/fontcommand.js");
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../utils */ "./node_modules/@ckeditor/ckeditor5-font/src/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module font/fontsize/fontsizecommand
*/
/**
* The font size command. It is used by {@link module:font/fontsize/fontsizeediting~FontSizeEditing}
* to apply the font size.
*
* editor.execute( 'fontSize', { value: 'small' } );
*
* **Note**: Executing the command without the value removes the attribute from the model.
*
* @extends module:font/fontcommand~FontCommand
*/
class FontSizeCommand extends _fontcommand__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* @inheritDoc
*/
constructor( editor ) {
super( editor, _utils__WEBPACK_IMPORTED_MODULE_1__.FONT_SIZE );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-font/src/fontsize/fontsizeediting.js":
/*!*******************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-font/src/fontsize/fontsizeediting.js ***!
\*******************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ FontSizeEditing)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/* harmony import */ var ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ckeditor5/src/engine */ "./node_modules/ckeditor5/src/engine.js");
/* harmony import */ var _fontsizecommand__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./fontsizecommand */ "./node_modules/@ckeditor/ckeditor5-font/src/fontsize/fontsizecommand.js");
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./utils */ "./node_modules/@ckeditor/ckeditor5-font/src/fontsize/utils.js");
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../utils */ "./node_modules/@ckeditor/ckeditor5-font/src/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module font/fontsize/fontsizeediting
*/
// Mapping of `<font size="..">` styling to CSS's `font-size` values.
const styleFontSize = [
'x-small', // Size "0" equal to "1".
'x-small',
'small',
'medium',
'large',
'x-large',
'xx-large',
'xxx-large'
];
/**
* The font size editing feature.
*
* It introduces the {@link module:font/fontsize/fontsizecommand~FontSizeCommand command} and the `fontSize`
* attribute in the {@link module:engine/model/model~Model model} which renders in the {@link module:engine/view/view view}
* as a `<span>` element with either:
* * a style attribute (`<span style="font-size:12px">...</span>`),
* * or a class attribute (`<span class="text-small">...</span>`)
*
* depending on the {@link module:font/fontsize~FontSizeConfig configuration}.
*
* @extends module:core/plugin~Plugin
*/
class FontSizeEditing extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'FontSizeEditing';
}
/**
* @inheritDoc
*/
constructor( editor ) {
super( editor );
// Define default configuration using named presets.
editor.config.define( _utils__WEBPACK_IMPORTED_MODULE_5__.FONT_SIZE, {
options: [
'tiny',
'small',
'default',
'big',
'huge'
],
supportAllValues: false
} );
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
// Allow fontSize attribute on text nodes.
editor.model.schema.extend( '$text', { allowAttributes: _utils__WEBPACK_IMPORTED_MODULE_5__.FONT_SIZE } );
editor.model.schema.setAttributeProperties( _utils__WEBPACK_IMPORTED_MODULE_5__.FONT_SIZE, {
isFormatting: true,
copyOnEnter: true
} );
const supportAllValues = editor.config.get( 'fontSize.supportAllValues' );
// Define view to model conversion.
const options = (0,_utils__WEBPACK_IMPORTED_MODULE_4__.normalizeOptions)( this.editor.config.get( 'fontSize.options' ) )
.filter( item => item.model );
const definition = (0,_utils__WEBPACK_IMPORTED_MODULE_5__.buildDefinition)( _utils__WEBPACK_IMPORTED_MODULE_5__.FONT_SIZE, options );
// Set-up the two-way conversion.
if ( supportAllValues ) {
this._prepareAnyValueConverters( definition );
this._prepareCompatibilityConverter();
} else {
editor.conversion.attributeToElement( definition );
}
// Add FontSize command.
editor.commands.add( _utils__WEBPACK_IMPORTED_MODULE_5__.FONT_SIZE, new _fontsizecommand__WEBPACK_IMPORTED_MODULE_3__["default"]( editor ) );
}
/**
* These converters enable keeping any value found as `style="font-size: *"` as a value of an attribute on a text even
* if it is not defined in the plugin configuration.
*
* @param {Object} definition {@link module:engine/conversion/conversion~ConverterDefinition Converter definition} out of input data.
* @private
*/
_prepareAnyValueConverters( definition ) {
const editor = this.editor;
// If `fontSize.supportAllValues=true`, we do not allow to use named presets in the plugin's configuration.
const presets = definition.model.values.filter( value => {
return !(0,ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_2__.isLength)( String( value ) ) && !(0,ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_2__.isPercentage)( String( value ) );
} );
if ( presets.length ) {
/**
* If {@link module:font/fontsize~FontSizeConfig#supportAllValues `config.fontSize.supportAllValues`} is `true`,
* you need to use numerical values as font size options.
*
* See valid examples described in the {@link module:font/fontsize~FontSizeConfig#options plugin configuration}.
*
* @error font-size-invalid-use-of-named-presets
* @param {Array.<String>} presets Invalid values.
*/
throw new ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__.CKEditorError(
'font-size-invalid-use-of-named-presets',
null, { presets }
);
}
editor.conversion.for( 'downcast' ).attributeToElement( {
model: _utils__WEBPACK_IMPORTED_MODULE_5__.FONT_SIZE,
view: ( attributeValue, { writer } ) => {
if ( !attributeValue ) {
return;
}
return writer.createAttributeElement( 'span', { style: 'font-size:' + attributeValue }, { priority: 7 } );
}
} );
editor.conversion.for( 'upcast' ).elementToAttribute( {
model: {
key: _utils__WEBPACK_IMPORTED_MODULE_5__.FONT_SIZE,
value: viewElement => viewElement.getStyle( 'font-size' )
},
view: {
name: 'span',
styles: {
'font-size': /.*/
}
}
} );
}
/**
* Adds support for legacy `<font size="..">` formatting.
*
* @private
*/
_prepareCompatibilityConverter() {
const editor = this.editor;
editor.conversion.for( 'upcast' ).elementToAttribute( {
view: {
name: 'font',
attributes: {
// Documentation mentions sizes from 1 to 7. To handle old content we support all values
// up to 999 but clamp it to the valid range. Why 999? It should cover accidental values
// similar to percentage, e.g. 100%, 200% which could be the usual mistake for font size.
'size': /^[+-]?\d{1,3}$/
}
},
model: {
key: _utils__WEBPACK_IMPORTED_MODULE_5__.FONT_SIZE,
value: viewElement => {
const value = viewElement.getAttribute( 'size' );
const isRelative = value[ 0 ] === '-' || value[ 0 ] === '+';
let size = parseInt( value, 10 );
if ( isRelative ) {
// Add relative size (which can be negative) to the default size = 3.
size = 3 + size;
}
const maxSize = styleFontSize.length - 1;
const clampedSize = Math.min( Math.max( size, 0 ), maxSize );
return styleFontSize[ clampedSize ];
}
}
} );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-font/src/fontsize/fontsizeui.js":
/*!**************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-font/src/fontsize/fontsizeui.js ***!
\**************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ FontSizeUI)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/ui */ "./node_modules/ckeditor5/src/ui.js");
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./utils */ "./node_modules/@ckeditor/ckeditor5-font/src/fontsize/utils.js");
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../utils */ "./node_modules/@ckeditor/ckeditor5-font/src/utils.js");
/* harmony import */ var _theme_icons_font_size_svg__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../../theme/icons/font-size.svg */ "./node_modules/@ckeditor/ckeditor5-font/theme/icons/font-size.svg");
/* harmony import */ var _theme_fontsize_css__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ../../theme/fontsize.css */ "./node_modules/@ckeditor/ckeditor5-font/theme/fontsize.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module font/fontsize/fontsizeui
*/
/**
* The font size UI plugin. It introduces the `'fontSize'` dropdown.
*
* @extends module:core/plugin~Plugin
*/
class FontSizeUI extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'FontSizeUI';
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const t = editor.t;
const options = this._getLocalizedOptions();
const command = editor.commands.get( _utils__WEBPACK_IMPORTED_MODULE_4__.FONT_SIZE );
// Register UI component.
editor.ui.componentFactory.add( _utils__WEBPACK_IMPORTED_MODULE_4__.FONT_SIZE, locale => {
const dropdownView = (0,ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.createDropdown)( locale );
(0,ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.addListToDropdown)( dropdownView, _prepareListOptions( options, command ) );
// Create dropdown model.
dropdownView.buttonView.set( {
label: t( 'Font Size' ),
icon: _theme_icons_font_size_svg__WEBPACK_IMPORTED_MODULE_5__["default"],
tooltip: true
} );
dropdownView.extendTemplate( {
attributes: {
class: [
'ck-font-size-dropdown'
]
}
} );
dropdownView.bind( 'isEnabled' ).to( command );
// Execute command when an item from the dropdown is selected.
this.listenTo( dropdownView, 'execute', evt => {
editor.execute( evt.source.commandName, { value: evt.source.commandParam } );
editor.editing.view.focus();
} );
return dropdownView;
} );
}
/**
* Returns options as defined in `config.fontSize.options` but processed to account for
* editor localization, i.e. to display {@link module:font/fontsize~FontSizeOption}
* in the correct language.
*
* Note: The reason behind this method is that there is no way to use {@link module:utils/locale~Locale#t}
* when the user configuration is defined because the editor does not exist yet.
*
* @private
* @returns {Array.<module:font/fontsize~FontSizeOption>}.
*/
_getLocalizedOptions() {
const editor = this.editor;
const t = editor.t;
const localizedTitles = {
Default: t( 'Default' ),
Tiny: t( 'Tiny' ),
Small: t( 'Small' ),
Big: t( 'Big' ),
Huge: t( 'Huge' )
};
const options = (0,_utils__WEBPACK_IMPORTED_MODULE_3__.normalizeOptions)( editor.config.get( _utils__WEBPACK_IMPORTED_MODULE_4__.FONT_SIZE ).options );
return options.map( option => {
const title = localizedTitles[ option.title ];
if ( title && title != option.title ) {
// Clone the option to avoid altering the original `namedPresets` from `./utils.js`.
option = Object.assign( {}, option, { title } );
}
return option;
} );
}
}
// Prepares FontSize dropdown items.
// @private
// @param {Array.<module:font/fontsize~FontSizeOption>} options
// @param {module:font/fontsize/fontsizecommand~FontSizeCommand} command
function _prepareListOptions( options, command ) {
const itemDefinitions = new ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_2__.Collection();
for ( const option of options ) {
const def = {
type: 'button',
model: new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.Model( {
commandName: _utils__WEBPACK_IMPORTED_MODULE_4__.FONT_SIZE,
commandParam: option.model,
label: option.title,
class: 'ck-fontsize-option',
withText: true
} )
};
if ( option.view && option.view.styles ) {
def.model.set( 'labelStyle', `font-size:${ option.view.styles[ 'font-size' ] }` );
}
if ( option.view && option.view.classes ) {
def.model.set( 'class', `${ def.model.class } ${ option.view.classes }` );
}
def.model.bind( 'isOn' ).to( command, 'value', value => value === option.model );
// Add the option to the collection.
itemDefinitions.add( def );
}
return itemDefinitions;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-font/src/fontsize/utils.js":
/*!*********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-font/src/fontsize/utils.js ***!
\*********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "normalizeOptions": () => (/* binding */ normalizeOptions)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module font/fontsize/utils
*/
/**
* Normalizes and translates the {@link module:font/fontsize~FontSizeConfig#options configuration options}
* to the {@link module:font/fontsize~FontSizeOption} format.
*
* @param {Array.<String|Number|Object>} configuredOptions An array of options taken from the configuration.
* @returns {Array.<module:font/fontsize~FontSizeOption>}
*/
function normalizeOptions( configuredOptions ) {
// Convert options to objects.
return configuredOptions
.map( item => getOptionDefinition( item ) )
// Filter out undefined values that `getOptionDefinition` might return.
.filter( option => !!option );
}
// Default named presets map. Always create a new instance of the preset object in order to avoid modifying references.
const namedPresets = {
get tiny() {
return {
title: 'Tiny',
model: 'tiny',
view: {
name: 'span',
classes: 'text-tiny',
priority: 7
}
};
},
get small() {
return {
title: 'Small',
model: 'small',
view: {
name: 'span',
classes: 'text-small',
priority: 7
}
};
},
get big() {
return {
title: 'Big',
model: 'big',
view: {
name: 'span',
classes: 'text-big',
priority: 7
}
};
},
get huge() {
return {
title: 'Huge',
model: 'huge',
view: {
name: 'span',
classes: 'text-huge',
priority: 7
}
};
}
};
// Returns an option definition either from preset or creates one from number shortcut.
// If object is passed then this method will return it without alternating it. Returns undefined for item than cannot be parsed.
//
// @param {String|Number|Object} item
// @returns {undefined|module:font/fontsize~FontSizeOption}
function getOptionDefinition( option ) {
// Check whether passed option is a full item definition provided by user in configuration.
if ( isFullItemDefinition( option ) ) {
return attachPriority( option );
}
const preset = findPreset( option );
// Item is a named preset.
if ( preset ) {
return attachPriority( preset );
}
// 'Default' font size. It will be used to remove the fontSize attribute.
if ( option === 'default' ) {
return {
model: undefined,
title: 'Default'
};
}
// At this stage we probably have numerical value to generate a preset so parse it's value.
// Discard any faulty values.
if ( isNumericalDefinition( option ) ) {
return;
}
// Return font size definition from size value.
return generatePixelPreset( option );
}
// Creates a predefined preset for pixel size.
//
// @param {Number} definition Font size in pixels.
// @returns {module:font/fontsize~FontSizeOption}
function generatePixelPreset( definition ) {
// Extend a short (numeric value) definition.
if ( typeof definition === 'number' || typeof definition === 'string' ) {
definition = {
title: String( definition ),
model: `${ parseFloat( definition ) }px`
};
}
definition.view = {
name: 'span',
styles: {
'font-size': definition.model
}
};
return attachPriority( definition );
}
// Adds the priority to the view element definition if missing. It's required due to ckeditor/ckeditor5#2291
//
// @param {Object} definition
// @param {Object} definition.title
// @param {Object} definition.model
// @param {Object} definition.view
// @returns {Object}
function attachPriority( definition ) {
if ( !definition.view.priority ) {
definition.view.priority = 7;
}
return definition;
}
// Returns a prepared preset definition. If passed an object, a name of preset should be defined as `model` value.
//
// @param {String|Object} definition
// @param {String} definition.model A preset name.
// @returns {Object|undefined}
function findPreset( definition ) {
return namedPresets[ definition ] || namedPresets[ definition.model ];
}
// We treat `definition` as completed if it is an object that contains `title`, `model` and `view` values.
//
// @param {Object} definition
// @param {String} definition.title
// @param {String} definition.model
// @param {Object} definition.view
// @returns {Boolean}
function isFullItemDefinition( definition ) {
return typeof definition === 'object' && definition.title && definition.model && definition.view;
}
// We treat `definition` as numerical if it is a number, number-like (string) or an object with the `title` key.
//
// @param {Object|Number|String} definition
// @param {Object} definition.title
// @returns {Boolean}
function isNumericalDefinition( definition ) {
let numberValue;
if ( typeof definition === 'object' ) {
if ( !definition.model ) {
/**
* Provided value as an option for {@link module:font/fontsize~FontSize} seems to invalid.
*
* See valid examples described in the {@link module:font/fontsize~FontSizeConfig#options plugin configuration}.
*
* @error font-size-invalid-definition
*/
throw new ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_0__.CKEditorError( 'font-size-invalid-definition', null, definition );
} else {
numberValue = parseFloat( definition.model );
}
} else {
numberValue = parseFloat( definition );
}
return isNaN( numberValue );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-font/src/ui/colortableview.js":
/*!************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-font/src/ui/colortableview.js ***!
\************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ColorTableView)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/ui */ "./node_modules/ckeditor5/src/ui.js");
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/* harmony import */ var _documentcolorcollection__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../documentcolorcollection */ "./node_modules/@ckeditor/ckeditor5-font/src/documentcolorcollection.js");
/* harmony import */ var _theme_fontcolor_css__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../../theme/fontcolor.css */ "./node_modules/@ckeditor/ckeditor5-font/theme/fontcolor.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module font/ui/colortableview
*/
/**
* A class which represents a view with the following sub–components:
*
* * A remove color button,
* * A static {@link module:ui/colorgrid/colorgrid~ColorGridView} of colors defined in the configuration,
* * A dynamic {@link module:ui/colorgrid/colorgrid~ColorGridView} of colors used in the document.
*
* @extends module:ui/view~View
*/
class ColorTableView extends ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.View {
/**
* Creates a view to be inserted as a child of {@link module:ui/dropdown/dropdownview~DropdownView}.
*
* @param {module:utils/locale~Locale} [locale] The localization services instance.
* @param {Object} config The configuration object.
* @param {Array.<module:ui/colorgrid/colorgrid~ColorDefinition>} config.colors An array with definitions of colors to
* be displayed in the table.
* @param {Number} config.columns The number of columns in the color grid.
* @param {String} config.removeButtonLabel The label of the button responsible for removing the color.
* @param {String} config.documentColorsLabel The label for the section with the document colors.
* @param {Number} config.documentColorsCount The number of colors in the document colors section inside the color dropdown.
*/
constructor( locale, { colors, columns, removeButtonLabel, documentColorsLabel, documentColorsCount } ) {
super( locale );
/**
* A collection of the children of the table.
*
* @readonly
* @member {module:ui/viewcollection~ViewCollection}
*/
this.items = this.createCollection();
/**
* An array with objects representing colors to be displayed in the grid.
*
* @type {Array.<module:ui/colorgrid/colorgrid~ColorDefinition>}
*/
this.colorDefinitions = colors;
/**
* Tracks information about the DOM focus in the list.
*
* @readonly
* @member {module:utils/focustracker~FocusTracker}
*/
this.focusTracker = new ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_2__.FocusTracker();
/**
* An instance of the {@link module:utils/keystrokehandler~KeystrokeHandler}.
*
* @readonly
* @member {module:utils/keystrokehandler~KeystrokeHandler}
*/
this.keystrokes = new ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_2__.KeystrokeHandler();
/**
* Keeps the value of the command associated with the table for the current selection.
*
* @type {String}
*/
this.set( 'selectedColor' );
/**
* The label of the button responsible for removing color attributes.
*
* @type {String}
*/
this.removeButtonLabel = removeButtonLabel;
/**
* The number of columns in the color grid.
*
* @type {Number}
*/
this.columns = columns;
/**
* A collection of definitions that store the document colors.
*
* @readonly
* @member {module:font/documentcolorcollection~DocumentColorCollection}
*/
this.documentColors = new _documentcolorcollection__WEBPACK_IMPORTED_MODULE_3__["default"]();
/**
* The maximum number of colors in the document colors section.
* If it equals 0, the document colors section is not added.
*
* @readonly
* @type {Number}
*/
this.documentColorsCount = documentColorsCount;
/**
* Preserves the reference to {@link module:ui/colorgrid/colorgrid~ColorGridView} used to create
* the default (static) color set.
*
* The property is loaded once the the parent dropdown is opened the first time.
*
* @readonly
* @member {module:ui/colorgrid/colorgrid~ColorGridView|undefined} #staticColorsGrid
*/
/**
* Preserves the reference to {@link module:ui/colorgrid/colorgrid~ColorGridView} used to create
* the document colors. It remains undefined if the document colors feature is disabled.
*
* The property is loaded once the the parent dropdown is opened the first time.
*
* @readonly
* @member {module:ui/colorgrid/colorgrid~ColorGridView|undefined} #documentColorsGrid
*/
/**
* Helps cycling over focusable {@link #items} in the list.
*
* @readonly
* @protected
* @member {module:ui/focuscycler~FocusCycler}
*/
this._focusCycler = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.FocusCycler( {
focusables: this.items,
focusTracker: this.focusTracker,
keystrokeHandler: this.keystrokes,
actions: {
// Navigate list items backwards using the Arrow Up key.
focusPrevious: 'arrowup',
// Navigate list items forwards using the Arrow Down key.
focusNext: 'arrowdown'
}
} );
/**
* Document color section's label.
*
* @private
* @readonly
* @type {String}
*/
this._documentColorsLabel = documentColorsLabel;
this.setTemplate( {
tag: 'div',
attributes: {
class: [
'ck',
'ck-color-table'
]
},
children: this.items
} );
this.items.add( this._removeColorButton() );
}
/**
* Scans through the editor model and searches for text node attributes with the given attribute name.
* Found entries are set as document colors.
*
* All the previously stored document colors will be lost in the process.
*
* @param {module:engine/model/model~Model} model The model used as a source to obtain the document colors.
* @param {String} attributeName Determines the name of the related model's attribute for a given dropdown.
*/
updateDocumentColors( model, attributeName ) {
const document = model.document;
const maxCount = this.documentColorsCount;
this.documentColors.clear();
for ( const rootName of document.getRootNames() ) {
const root = document.getRoot( rootName );
const range = model.createRangeIn( root );
for ( const node of range.getItems() ) {
if ( node.is( '$textProxy' ) && node.hasAttribute( attributeName ) ) {
this._addColorToDocumentColors( node.getAttribute( attributeName ) );
if ( this.documentColors.length >= maxCount ) {
return;
}
}
}
}
}
/**
* Refreshes the state of the selected color in one or both {@link module:ui/colorgrid/colorgrid~ColorGridView}s
* available in the {@link module:font/ui/colortableview~ColorTableView}. It guarantees that the selection will occur only in one
* of them.
*/
updateSelectedColors() {
const documentColorsGrid = this.documentColorsGrid;
const staticColorsGrid = this.staticColorsGrid;
const selectedColor = this.selectedColor;
staticColorsGrid.selectedColor = selectedColor;
if ( documentColorsGrid ) {
documentColorsGrid.selectedColor = selectedColor;
}
}
/**
* @inheritDoc
*/
render() {
super.render();
// Items added before rendering should be known to the #focusTracker.
for ( const item of this.items ) {
this.focusTracker.add( item.element );
}
// Start listening for the keystrokes coming from #element.
this.keystrokes.listenTo( this.element );
}
/**
* @inheritDoc
*/
destroy() {
super.destroy();
this.focusTracker.destroy();
this.keystrokes.destroy();
}
/**
* Appends {@link #staticColorsGrid} and {@link #documentColorsGrid} views.
*/
appendGrids() {
if ( this.staticColorsGrid ) {
return;
}
this.staticColorsGrid = this._createStaticColorsGrid();
this.items.add( this.staticColorsGrid );
if ( this.documentColorsCount ) {
// Create a label for document colors.
const bind = ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.Template.bind( this.documentColors, this.documentColors );
const label = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.LabelView( this.locale );
label.text = this._documentColorsLabel;
label.extendTemplate( {
attributes: {
class: [
'ck',
'ck-color-grid__label',
bind.if( 'isEmpty', 'ck-hidden' )
]
}
} );
this.items.add( label );
this.documentColorsGrid = this._createDocumentColorsGrid();
this.items.add( this.documentColorsGrid );
}
}
/**
* Focuses the first focusable element in {@link #items}.
*/
focus() {
this._focusCycler.focusFirst();
}
/**
* Focuses the last focusable element in {@link #items}.
*/
focusLast() {
this._focusCycler.focusLast();
}
/**
* Adds the remove color button as a child of the current view.
*
* @private
* @returns {module:ui/button/buttonview~ButtonView}
*/
_removeColorButton() {
const buttonView = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.ButtonView();
buttonView.set( {
withText: true,
icon: ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.icons.eraser,
tooltip: true,
label: this.removeButtonLabel
} );
buttonView.class = 'ck-color-table__remove-color';
buttonView.on( 'execute', () => {
this.fire( 'execute', { value: null } );
} );
return buttonView;
}
/**
* Creates a static color table grid based on the editor configuration.
*
* @private
* @returns {module:ui/colorgrid/colorgrid~ColorGridView}
*/
_createStaticColorsGrid() {
const colorGrid = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.ColorGridView( this.locale, {
colorDefinitions: this.colorDefinitions,
columns: this.columns
} );
colorGrid.delegate( 'execute' ).to( this );
return colorGrid;
}
/**
* Creates the document colors section view and binds it to {@link #documentColors}.
*
* @private
* @returns {module:ui/colorgrid/colorgrid~ColorGridView}
*/
_createDocumentColorsGrid() {
const bind = ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.Template.bind( this.documentColors, this.documentColors );
const documentColorsGrid = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.ColorGridView( this.locale, {
columns: this.columns
} );
documentColorsGrid.delegate( 'execute' ).to( this );
documentColorsGrid.extendTemplate( {
attributes: {
class: bind.if( 'isEmpty', 'ck-hidden' )
}
} );
documentColorsGrid.items.bindTo( this.documentColors ).using(
colorObj => {
const colorTile = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.ColorTileView();
colorTile.set( {
color: colorObj.color,
hasBorder: colorObj.options && colorObj.options.hasBorder
} );
if ( colorObj.label ) {
colorTile.set( {
label: colorObj.label,
tooltip: true
} );
}
colorTile.on( 'execute', () => {
this.fire( 'execute', {
value: colorObj.color
} );
} );
return colorTile;
}
);
// Selected color should be cleared when document colors became empty.
this.documentColors.on( 'change:isEmpty', ( evt, name, val ) => {
if ( val ) {
documentColorsGrid.selectedColor = null;
}
} );
return documentColorsGrid;
}
/**
* Adds a given color to the document colors list. If possible, the method will attempt to use
* data from the {@link #colorDefinitions} (label, color options).
*
* @private
* @param {String} color A string that stores the value of the recently applied color.
*/
_addColorToDocumentColors( color ) {
const predefinedColor = this.colorDefinitions
.find( definition => definition.color === color );
if ( !predefinedColor ) {
this.documentColors.add( {
color,
label: color,
options: {
hasBorder: false
}
} );
} else {
this.documentColors.add( Object.assign( {}, predefinedColor ) );
}
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-font/src/ui/colorui.js":
/*!*****************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-font/src/ui/colorui.js ***!
\*****************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ColorUI)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/ui */ "./node_modules/ckeditor5/src/ui.js");
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../utils */ "./node_modules/@ckeditor/ckeditor5-font/src/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module font/ui/colorui
*/
/**
* The color UI plugin which isolates the common logic responsible for displaying dropdowns with color grids.
*
* It is used to create the `'fontBackgroundColor'` and `'fontColor'` dropdowns, each hosting
* a {@link module:font/ui/colortableview~ColorTableView}.
*
* @extends module:core/plugin~Plugin
*/
class ColorUI extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* Creates a plugin which introduces a dropdown with a pre–configured {@link module:font/ui/colortableview~ColorTableView}.
*
* @param {module:core/editor/editor~Editor} editor
* @param {Object} config The configuration object.
* @param {String} config.commandName The name of the command which will be executed when a color tile is clicked.
* @param {String} config.componentName The name of the dropdown in the {@link module:ui/componentfactory~ComponentFactory}
* and the configuration scope name in `editor.config`.
* @param {String} config.icon The SVG icon used by the dropdown.
* @param {String} config.dropdownLabel The label used by the dropdown.
*/
constructor( editor, { commandName, icon, componentName, dropdownLabel } ) {
super( editor );
/**
* The name of the command which will be executed when a color tile is clicked.
*
* @type {String}
*/
this.commandName = commandName;
/**
* The name of this component in the {@link module:ui/componentfactory~ComponentFactory}.
* Also the configuration scope name in `editor.config`.
*
* @type {String}
*/
this.componentName = componentName;
/**
* The SVG icon used by the dropdown.
* @type {String}
*/
this.icon = icon;
/**
* The label used by the dropdown.
*
* @type {String}
*/
this.dropdownLabel = dropdownLabel;
/**
* The number of columns in the color grid.
*
* @type {Number}
*/
this.columns = editor.config.get( `${ this.componentName }.columns` );
/**
* Keeps a reference to {@link module:font/ui/colortableview~ColorTableView}.
*
* @member {module:font/ui/colortableview~ColorTableView}
*/
this.colorTableView = undefined;
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const locale = editor.locale;
const t = locale.t;
const command = editor.commands.get( this.commandName );
const colorsConfig = (0,ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.normalizeColorOptions)( editor.config.get( this.componentName ).colors );
const localizedColors = (0,ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.getLocalizedColorOptions)( locale, colorsConfig );
const documentColorsCount = editor.config.get( `${ this.componentName }.documentColors` );
// Register the UI component.
editor.ui.componentFactory.add( this.componentName, locale => {
const dropdownView = (0,ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.createDropdown)( locale );
this.colorTableView = (0,_utils__WEBPACK_IMPORTED_MODULE_2__.addColorTableToDropdown)( {
dropdownView,
colors: localizedColors.map( option => ( {
label: option.label,
color: option.model,
options: {
hasBorder: option.hasBorder
}
} ) ),
columns: this.columns,
removeButtonLabel: t( 'Remove color' ),
documentColorsLabel: documentColorsCount !== 0 ? t( 'Document colors' ) : undefined,
documentColorsCount: documentColorsCount === undefined ? this.columns : documentColorsCount
} );
this.colorTableView.bind( 'selectedColor' ).to( command, 'value' );
dropdownView.buttonView.set( {
label: this.dropdownLabel,
icon: this.icon,
tooltip: true
} );
dropdownView.extendTemplate( {
attributes: {
class: 'ck-color-ui-dropdown'
}
} );
dropdownView.bind( 'isEnabled' ).to( command );
dropdownView.on( 'execute', ( evt, data ) => {
editor.execute( this.commandName, data );
editor.editing.view.focus();
} );
dropdownView.on( 'change:isOpen', ( evt, name, isVisible ) => {
// Grids rendering is deferred (#6192).
dropdownView.colorTableView.appendGrids();
if ( isVisible ) {
if ( documentColorsCount !== 0 ) {
this.colorTableView.updateDocumentColors( editor.model, this.componentName );
}
this.colorTableView.updateSelectedColors();
}
} );
return dropdownView;
} );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-font/src/utils.js":
/*!************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-font/src/utils.js ***!
\************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "FONT_BACKGROUND_COLOR": () => (/* binding */ FONT_BACKGROUND_COLOR),
/* harmony export */ "FONT_COLOR": () => (/* binding */ FONT_COLOR),
/* harmony export */ "FONT_FAMILY": () => (/* binding */ FONT_FAMILY),
/* harmony export */ "FONT_SIZE": () => (/* binding */ FONT_SIZE),
/* harmony export */ "addColorTableToDropdown": () => (/* binding */ addColorTableToDropdown),
/* harmony export */ "buildDefinition": () => (/* binding */ buildDefinition),
/* harmony export */ "renderDowncastElement": () => (/* binding */ renderDowncastElement),
/* harmony export */ "renderUpcastAttribute": () => (/* binding */ renderUpcastAttribute)
/* harmony export */ });
/* harmony import */ var _ui_colortableview__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./ui/colortableview */ "./node_modules/@ckeditor/ckeditor5-font/src/ui/colortableview.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module font/utils
*/
/**
* The name of the font size plugin.
*/
const FONT_SIZE = 'fontSize';
/**
* The name of the font family plugin.
*/
const FONT_FAMILY = 'fontFamily';
/**
* The name of the font color plugin.
*/
const FONT_COLOR = 'fontColor';
/**
* The name of the font background color plugin.
*/
const FONT_BACKGROUND_COLOR = 'fontBackgroundColor';
/**
* Builds a proper {@link module:engine/conversion/conversion~ConverterDefinition converter definition} out of input data.
*
* @param {String} modelAttributeKey Key
* @param {Array.<module:font/fontfamily~FontFamilyOption>|Array.<module:font/fontsize~FontSizeOption>} options
* @returns {module:engine/conversion/conversion~ConverterDefinition}
*/
function buildDefinition( modelAttributeKey, options ) {
const definition = {
model: {
key: modelAttributeKey,
values: []
},
view: {},
upcastAlso: {}
};
for ( const option of options ) {
definition.model.values.push( option.model );
definition.view[ option.model ] = option.view;
if ( option.upcastAlso ) {
definition.upcastAlso[ option.model ] = option.upcastAlso;
}
}
return definition;
}
/**
* A {@link module:font/fontcolor~FontColor font color} and
* {@link module:font/fontbackgroundcolor~FontBackgroundColor font background color} helper
* responsible for upcasting data to the model.
*
* **Note**: The `styleAttr` parameter should be either `'color'` or `'background-color'`.
*
* @param {String} styleAttr
* @return {String}
*/
function renderUpcastAttribute( styleAttr ) {
return viewElement => normalizeColorCode( viewElement.getStyle( styleAttr ) );
}
/**
* A {@link module:font/fontcolor~FontColor font color} and
* {@link module:font/fontbackgroundcolor~FontBackgroundColor font background color} helper
* responsible for downcasting a color attribute to a `<span>` element.
*
* **Note**: The `styleAttr` parameter should be either `'color'` or `'background-color'`.
*
* @param {String} styleAttr
*/
function renderDowncastElement( styleAttr ) {
return ( modelAttributeValue, { writer } ) => writer.createAttributeElement( 'span', {
style: `${ styleAttr }:${ modelAttributeValue }`
}, { priority: 7 } );
}
/**
* A helper that adds {@link module:font/ui/colortableview~ColorTableView} to the color dropdown with proper initial values.
*
* @param {Object} config The configuration object.
* @param {module:ui/dropdown/dropdownview~DropdownView} config.dropdownView The dropdown view to which
* a {@link module:font/ui/colortableview~ColorTableView} will be added.
* @param {Array.<module:ui/colorgrid/colorgrid~ColorDefinition>} config.colors An array with definitions
* representing colors to be displayed in the color table.
* @param {String} config.removeButtonLabel The label for the button responsible for removing the color.
* @param {String} config.documentColorsLabel The label for the section with document colors.
* @param {String} config.documentColorsCount The number of document colors inside the dropdown.
* @returns {module:font/ui/colortableview~ColorTableView} The new color table view.
*/
function addColorTableToDropdown( { dropdownView, colors, columns, removeButtonLabel, documentColorsLabel, documentColorsCount } ) {
const locale = dropdownView.locale;
const colorTableView = new _ui_colortableview__WEBPACK_IMPORTED_MODULE_0__["default"]( locale, { colors, columns, removeButtonLabel, documentColorsLabel, documentColorsCount } );
dropdownView.colorTableView = colorTableView;
dropdownView.panelView.children.add( colorTableView );
colorTableView.delegate( 'execute' ).to( dropdownView, 'execute' );
return colorTableView;
}
// Fixes the color value string.
//
// @param {String} value
// @returns {String}
function normalizeColorCode( value ) {
return value.replace( /\s/g, '' );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-heading/src/heading.js":
/*!*****************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-heading/src/heading.js ***!
\*****************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ Heading)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var _headingediting__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./headingediting */ "./node_modules/@ckeditor/ckeditor5-heading/src/headingediting.js");
/* harmony import */ var _headingui__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./headingui */ "./node_modules/@ckeditor/ckeditor5-heading/src/headingui.js");
/* harmony import */ var _theme_heading_css__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../theme/heading.css */ "./node_modules/@ckeditor/ckeditor5-heading/theme/heading.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module heading/heading
*/
/**
* The headings feature.
*
* For a detailed overview, check the {@glink features/headings Headings feature documentation}
* and the {@glink api/heading package page}.
*
* This is a "glue" plugin which loads the {@link module:heading/headingediting~HeadingEditing heading editing feature}
* and {@link module:heading/headingui~HeadingUI heading UI feature}.
*
* @extends module:core/plugin~Plugin
*/
class Heading extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [ _headingediting__WEBPACK_IMPORTED_MODULE_1__["default"], _headingui__WEBPACK_IMPORTED_MODULE_2__["default"] ];
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'Heading';
}
}
/**
* The configuration of the heading feature. Introduced by the {@link module:heading/headingediting~HeadingEditing} feature.
*
* Read more in {@link module:heading/heading~HeadingConfig}.
*
* @member {module:heading/heading~HeadingConfig} module:core/editor/editorconfig~EditorConfig#heading
*/
/**
* The configuration of the heading feature.
* The option is used by the {@link module:heading/headingediting~HeadingEditing} feature.
*
* ClassicEditor
* .create( {
* heading: ... // Heading feature config.
* } )
* .then( ... )
* .catch( ... );
*
* See {@link module:core/editor/editorconfig~EditorConfig all editor options}.
*
* @interface HeadingConfig
*/
/**
* The available heading options.
*
* The default value is:
*
* const headingConfig = {
* options: [
* { model: 'paragraph', title: 'Paragraph', class: 'ck-heading_paragraph' },
* { model: 'heading1', view: 'h2', title: 'Heading 1', class: 'ck-heading_heading1' },
* { model: 'heading2', view: 'h3', title: 'Heading 2', class: 'ck-heading_heading2' },
* { model: 'heading3', view: 'h4', title: 'Heading 3', class: 'ck-heading_heading3' }
* ]
* };
*
* It defines 3 levels of headings. In the editor model they will use `heading1`, `heading2`, and `heading3` elements.
* Their respective view elements (so the elements output by the editor) will be: `h2`, `h3`, and `h4`. This means that
* if you choose "Heading 1" in the headings dropdown the editor will turn the current block to `<heading1>` in the model
* which will result in rendering (and outputting to data) the `<h2>` element.
*
* The `title` and `class` properties will be used by the `headings` dropdown to render available options.
* Usually, the first option in the headings dropdown is the "Paragraph" option, hence it's also defined on the list.
* However, you don't need to define its view representation because it's handled by
* the {@link module:paragraph/paragraph~Paragraph} feature (which is required by
* the {@link module:heading/headingediting~HeadingEditing} feature).
*
* You can **read more** about configuring heading levels and **see more examples** in
* the {@glink features/headings Headings} guide.
*
* Note: In the model you should always start from `heading1`, regardless of how the headings are represented in the view.
* That's assumption is used by features like {@link module:autoformat/autoformat~Autoformat} to know which element
* they should use when applying the first level heading.
*
* The defined headings are also available as values passed to the `'heading'` command under their model names.
* For example, the below code will apply `<heading1>` to the current selection:
*
* editor.execute( 'heading', { value: 'heading1' } );
*
* @member {Array.<module:heading/heading~HeadingOption>} module:heading/heading~HeadingConfig#options
*/
/**
* Heading option descriptor.
*
* @typedef {Object} module:heading/heading~HeadingOption
* @property {String} model Name of the model element to convert.
* @property {module:engine/view/elementdefinition~ElementDefinition} view Definition of a view element to convert from/to.
* @property {String} title The user-readable title of the option.
* @property {String} class The class which will be added to the dropdown item representing this option.
* @property {String} [icon] Icon used by {@link module:heading/headingbuttonsui~HeadingButtonsUI}. It can be omitted when using
* the default configuration.
* @extends module:engine/conversion/conversion~ConverterDefinition
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-heading/src/headingcommand.js":
/*!************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-heading/src/headingcommand.js ***!
\************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ HeadingCommand)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module heading/headingcommand
*/
/**
* The heading command. It is used by the {@link module:heading/heading~Heading heading feature} to apply headings.
*
* @extends module:core/command~Command
*/
class HeadingCommand extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Command {
/**
* Creates an instance of the command.
*
* @param {module:core/editor/editor~Editor} editor Editor instance.
* @param {Array.<String>} modelElements Names of the element which this command can apply in the model.
*/
constructor( editor, modelElements ) {
super( editor );
/**
* If the selection starts in a heading (which {@link #modelElements is supported by this command})
* the value is set to the name of that heading model element.
* It is set to `false` otherwise.
*
* @observable
* @readonly
* @member {Boolean|String} #value
*/
/**
* Set of defined model's elements names that this command support.
* See {@link module:heading/heading~HeadingOption}.
*
* @readonly
* @member {Array.<String>}
*/
this.modelElements = modelElements;
}
/**
* @inheritDoc
*/
refresh() {
const block = (0,ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__.first)( this.editor.model.document.selection.getSelectedBlocks() );
this.value = !!block && this.modelElements.includes( block.name ) && block.name;
this.isEnabled = !!block && this.modelElements.some( heading => checkCanBecomeHeading( block, heading, this.editor.model.schema ) );
}
/**
* Executes the command. Applies the heading to the selected blocks or, if the first selected
* block is a heading already, turns selected headings (of this level only) to paragraphs.
*
* @param {Object} options
* @param {String} options.value Name of the element which this command will apply in the model.
* @fires execute
*/
execute( options ) {
const model = this.editor.model;
const document = model.document;
const modelElement = options.value;
model.change( writer => {
const blocks = Array.from( document.selection.getSelectedBlocks() )
.filter( block => {
return checkCanBecomeHeading( block, modelElement, model.schema );
} );
for ( const block of blocks ) {
if ( !block.is( 'element', modelElement ) ) {
writer.rename( block, modelElement );
}
}
} );
}
}
// Checks whether the given block can be replaced by a specific heading.
//
// @private
// @param {module:engine/model/element~Element} block A block to be tested.
// @param {module:heading/headingcommand~HeadingCommand#modelElement} heading Command element name in the model.
// @param {module:engine/model/schema~Schema} schema The schema of the document.
// @returns {Boolean}
function checkCanBecomeHeading( block, heading, schema ) {
return schema.checkChild( block.parent, heading ) && !schema.isObject( block );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-heading/src/headingediting.js":
/*!************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-heading/src/headingediting.js ***!
\************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ HeadingEditing)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_paragraph__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/paragraph */ "./node_modules/ckeditor5/src/paragraph.js");
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/* harmony import */ var _headingcommand__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./headingcommand */ "./node_modules/@ckeditor/ckeditor5-heading/src/headingcommand.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module heading/headingediting
*/
const defaultModelElement = 'paragraph';
/**
* The headings engine feature. It handles switching between block formats – headings and paragraph.
* This class represents the engine part of the heading feature. See also {@link module:heading/heading~Heading}.
* It introduces `heading1`-`headingN` commands which allow to convert paragraphs into headings.
*
* @extends module:core/plugin~Plugin
*/
class HeadingEditing extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'HeadingEditing';
}
/**
* @inheritDoc
*/
constructor( editor ) {
super( editor );
editor.config.define( 'heading', {
options: [
{ model: 'paragraph', title: 'Paragraph', class: 'ck-heading_paragraph' },
{ model: 'heading1', view: 'h2', title: 'Heading 1', class: 'ck-heading_heading1' },
{ model: 'heading2', view: 'h3', title: 'Heading 2', class: 'ck-heading_heading2' },
{ model: 'heading3', view: 'h4', title: 'Heading 3', class: 'ck-heading_heading3' }
]
} );
}
/**
* @inheritDoc
*/
static get requires() {
return [ ckeditor5_src_paragraph__WEBPACK_IMPORTED_MODULE_1__.Paragraph ];
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const options = editor.config.get( 'heading.options' );
const modelElements = [];
for ( const option of options ) {
// Skip paragraph - it is defined in required Paragraph feature.
if ( option.model !== defaultModelElement ) {
// Schema.
editor.model.schema.register( option.model, {
inheritAllFrom: '$block'
} );
editor.conversion.elementToElement( option );
modelElements.push( option.model );
}
}
this._addDefaultH1Conversion( editor );
// Register the heading command for this option.
editor.commands.add( 'heading', new _headingcommand__WEBPACK_IMPORTED_MODULE_3__["default"]( editor, modelElements ) );
}
/**
* @inheritDoc
*/
afterInit() {
// If the enter command is added to the editor, alter its behavior.
// Enter at the end of a heading element should create a paragraph.
const editor = this.editor;
const enterCommand = editor.commands.get( 'enter' );
const options = editor.config.get( 'heading.options' );
if ( enterCommand ) {
this.listenTo( enterCommand, 'afterExecute', ( evt, data ) => {
const positionParent = editor.model.document.selection.getFirstPosition().parent;
const isHeading = options.some( option => positionParent.is( 'element', option.model ) );
if ( isHeading && !positionParent.is( 'element', defaultModelElement ) && positionParent.childCount === 0 ) {
data.writer.rename( positionParent, defaultModelElement );
}
} );
}
}
/**
* Adds default conversion for `h1` -> `heading1` with a low priority.
*
* @private
* @param {module:core/editor/editor~Editor} editor Editor instance on which to add the `h1` conversion.
*/
_addDefaultH1Conversion( editor ) {
editor.conversion.for( 'upcast' ).elementToElement( {
model: 'heading1',
view: 'h1',
// With a `low` priority, `paragraph` plugin autoparagraphing mechanism is executed. Make sure
// this listener is called before it. If not, `h1` will be transformed into a paragraph.
converterPriority: ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_2__.priorities.get( 'low' ) + 1
} );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-heading/src/headingui.js":
/*!*******************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-heading/src/headingui.js ***!
\*******************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ HeadingUI)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/ui */ "./node_modules/ckeditor5/src/ui.js");
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./utils */ "./node_modules/@ckeditor/ckeditor5-heading/src/utils.js");
/* harmony import */ var _theme_heading_css__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../theme/heading.css */ "./node_modules/@ckeditor/ckeditor5-heading/theme/heading.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module heading/headingui
*/
/**
* The headings UI feature. It introduces the `headings` dropdown.
*
* @extends module:core/plugin~Plugin
*/
class HeadingUI extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'HeadingUI';
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const t = editor.t;
const options = (0,_utils__WEBPACK_IMPORTED_MODULE_3__.getLocalizedOptions)( editor );
const defaultTitle = t( 'Choose heading' );
const dropdownTooltip = t( 'Heading' );
// Register UI component.
editor.ui.componentFactory.add( 'heading', locale => {
const titles = {};
const itemDefinitions = new ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_2__.Collection();
const headingCommand = editor.commands.get( 'heading' );
const paragraphCommand = editor.commands.get( 'paragraph' );
const commands = [ headingCommand ];
for ( const option of options ) {
const def = {
type: 'button',
model: new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.Model( {
label: option.title,
class: option.class,
withText: true
} )
};
if ( option.model === 'paragraph' ) {
def.model.bind( 'isOn' ).to( paragraphCommand, 'value' );
def.model.set( 'commandName', 'paragraph' );
commands.push( paragraphCommand );
} else {
def.model.bind( 'isOn' ).to( headingCommand, 'value', value => value === option.model );
def.model.set( {
commandName: 'heading',
commandValue: option.model
} );
}
// Add the option to the collection.
itemDefinitions.add( def );
titles[ option.model ] = option.title;
}
const dropdownView = (0,ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.createDropdown)( locale );
(0,ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.addListToDropdown)( dropdownView, itemDefinitions );
dropdownView.buttonView.set( {
isOn: false,
withText: true,
tooltip: dropdownTooltip
} );
dropdownView.extendTemplate( {
attributes: {
class: [
'ck-heading-dropdown'
]
}
} );
dropdownView.bind( 'isEnabled' ).toMany( commands, 'isEnabled', ( ...areEnabled ) => {
return areEnabled.some( isEnabled => isEnabled );
} );
dropdownView.buttonView.bind( 'label' ).to( headingCommand, 'value', paragraphCommand, 'value', ( value, para ) => {
const whichModel = value || para && 'paragraph';
// If none of the commands is active, display default title.
return titles[ whichModel ] ? titles[ whichModel ] : defaultTitle;
} );
// Execute command when an item from the dropdown is selected.
this.listenTo( dropdownView, 'execute', evt => {
editor.execute( evt.source.commandName, evt.source.commandValue ? { value: evt.source.commandValue } : undefined );
editor.editing.view.focus();
} );
return dropdownView;
} );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-heading/src/utils.js":
/*!***************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-heading/src/utils.js ***!
\***************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "getLocalizedOptions": () => (/* binding */ getLocalizedOptions)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module heading/utils
*/
/**
* Returns heading options as defined in `config.heading.options` but processed to consider
* the editor localization, i.e. to display {@link module:heading/heading~HeadingOption}
* in the correct language.
*
* Note: The reason behind this method is that there is no way to use {@link module:utils/locale~Locale#t}
* when the user configuration is defined because the editor does not exist yet.
*
* @param {module:core/editor/editor~Editor} editor
* @returns {Array.<module:heading/heading~HeadingOption>}.
*/
function getLocalizedOptions( editor ) {
const t = editor.t;
const localizedTitles = {
Paragraph: t( 'Paragraph' ),
'Heading 1': t( 'Heading 1' ),
'Heading 2': t( 'Heading 2' ),
'Heading 3': t( 'Heading 3' ),
'Heading 4': t( 'Heading 4' ),
'Heading 5': t( 'Heading 5' ),
'Heading 6': t( 'Heading 6' )
};
return editor.config.get( 'heading.options' ).map( option => {
const title = localizedTitles[ option.title ];
if ( title && title != option.title ) {
option.title = title;
}
return option;
} );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-highlight/src/highlight.js":
/*!*********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-highlight/src/highlight.js ***!
\*********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ Highlight)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var _highlightediting__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./highlightediting */ "./node_modules/@ckeditor/ckeditor5-highlight/src/highlightediting.js");
/* harmony import */ var _highlightui__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./highlightui */ "./node_modules/@ckeditor/ckeditor5-highlight/src/highlightui.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module highlight/highlight
*/
/**
* The highlight plugin.
*
* For a detailed overview, check the {@glink features/highlight Highlight feature} documentation.
*
* This is a "glue" plugin which loads the {@link module:highlight/highlightediting~HighlightEditing} and
* {@link module:highlight/highlightui~HighlightUI} plugins.
*
* @extends module:core/plugin~Plugin
*/
class Highlight extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [ _highlightediting__WEBPACK_IMPORTED_MODULE_1__["default"], _highlightui__WEBPACK_IMPORTED_MODULE_2__["default"] ];
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'Highlight';
}
}
/**
* The highlight option descriptor. See {@link module:highlight/highlight~HighlightConfig} to learn more.
*
* {
* model: 'pinkMarker',
* class: 'marker-pink',
* title: 'Pink Marker',
* color: 'var(--ck-highlight-marker-pink)',
* type: 'marker'
* }
*
* @typedef {Object} module:highlight/highlight~HighlightOption
* @property {String} title The user-readable title of the option.
* @property {String} model The unique attribute value in the model.
* @property {String} color The CSS `var()` used for the highlighter. The color is used in the user interface to represent the highlighter.
* There is a possibility to use the default color format like rgb, hex or hsl, but you need to care about the color of `<mark>`
* by adding CSS classes definition.
* @property {String} class The CSS class used on the `<mark>` element in the view. It should match the `color` setting.
* @property {'marker'|'pen'} type The type of highlighter:
*
* * `'marker'` – Uses the `color` as the `background-color` style,
* * `'pen'` – Uses the `color` as the font `color` style.
*/
/**
* The configuration of the {@link module:highlight/highlight~Highlight} feature.
*
* Read more in {@link module:highlight/highlight~HighlightConfig}.
*
* @member {module:highlight/highlight~HighlightConfig} module:core/editor/editorconfig~EditorConfig#highlight
*/
/**
* The configuration of the {@link module:highlight/highlight~Highlight highlight feature}.
*
* ClassicEditor
* .create( editorElement, {
* highlight: ... // Highlight feature configuration.
* } )
* .then( ... )
* .catch( ... );
*
* See {@link module:core/editor/editorconfig~EditorConfig all editor options}.
*
* @interface HighlightConfig
*/
/**
* The available highlight options. The default value is:
*
* options: [
* {
* model: 'yellowMarker',
* class: 'marker-yellow',
* title: 'Yellow marker',
* color: 'var(--ck-highlight-marker-yellow)',
* type: 'marker'
* },
* {
* model: 'greenMarker',
* class: 'marker-green',
* title: 'Green marker',
* color: 'var(--ck-highlight-marker-green)',
* type: 'marker'
* },
* {
* model: 'pinkMarker',
* class: 'marker-pink',
* title: 'Pink marker',
* color: 'var(--ck-highlight-marker-pink)',
* type: 'marker'
* },
* {
* model: 'blueMarker',
* class: 'marker-blue',
* title: 'Blue marker',
* color: 'var(--ck-highlight-marker-blue)',
* type: 'marker'
* },
* {
* model: 'redPen',
* class: 'pen-red',
* title: 'Red pen',
* color: 'var(--ck-highlight-pen-red)',
* type: 'pen'
* },
* {
* model: 'greenPen',
* class: 'pen-green',
* title: 'Green pen',
* color: 'var(--ck-highlight-pen-green)',
* type: 'pen'
* }
* ]
*
* There are two types of highlighters available:
*
* * `'marker'` – Rendered as a `<mark>` element, styled with the `background-color`.
* * `'pen'` – Rendered as a `<mark>` element, styled with the font `color`.
*
* **Note**: The highlight feature provides a stylesheet with the CSS classes and corresponding colors defined
* as CSS variables.
*
* :root {
* --ck-highlight-marker-yellow: #fdfd77;
* --ck-highlight-marker-green: #63f963;
* --ck-highlight-marker-pink: #fc7999;
* --ck-highlight-marker-blue: #72cdfd;
* --ck-highlight-pen-red: #e91313;
* --ck-highlight-pen-green: #118800;
* }
*
* .marker-yellow { ... }
* .marker-green { ... }
* .marker-pink { ... }
* .marker-blue { ... }
* .pen-red { ... }
* .pen-green { ... }
*
* It is possible to define the `color` property directly as `rgba(R, G, B, A)`,
* `#RRGGBB[AA]` or `hsla(H, S, L, A)`. In such situation, the color will **only** apply to the UI of
* the editor and the `<mark>` elements in the content must be styled by custom classes provided by
* a dedicated stylesheet.
*
* **Note**: It is recommended for the `color` property to correspond to the class in the content
* stylesheet because it represents the highlighter in the user interface of the editor.
*
* ClassicEditor
* .create( editorElement, {
* highlight: {
* options: [
* {
* model: 'pinkMarker',
* class: 'marker-pink',
* title: 'Pink Marker',
* color: 'var(--ck-highlight-marker-pink)',
* type: 'marker'
* },
* {
* model: 'redPen',
* class: 'pen-red',
* title: 'Red Pen',
* color: 'var(--ck-highlight-pen-red)',
* type: 'pen'
* },
* ]
* }
* } )
* .then( ... )
* .catch( ... );
*
* @member {Array.<module:highlight/highlight~HighlightOption>} module:highlight/highlight~HighlightConfig#options
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-highlight/src/highlightcommand.js":
/*!****************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-highlight/src/highlightcommand.js ***!
\****************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ HighlightCommand)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module highlight/highlightcommand
*/
/**
* The highlight command. It is used by the {@link module:highlight/highlightediting~HighlightEditing highlight feature}
* to apply the text highlighting.
*
* editor.execute( 'highlight', { value: 'greenMarker' } );
*
* **Note**: Executing the command without a value removes the attribute from the model. If the selection is collapsed
* inside a text with the highlight attribute, the command will remove the attribute from the entire range
* of that text.
*
* @extends module:core/command~Command
*/
class HighlightCommand extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Command {
/**
* @inheritDoc
*/
refresh() {
const model = this.editor.model;
const doc = model.document;
/**
* A value indicating whether the command is active. If the selection has some highlight attribute,
* it corresponds to the value of that attribute.
*
* @observable
* @readonly
* @member {undefined|String} module:highlight/highlightcommand~HighlightCommand#value
*/
this.value = doc.selection.getAttribute( 'highlight' );
this.isEnabled = model.schema.checkAttributeInSelection( doc.selection, 'highlight' );
}
/**
* Executes the command.
*
* @param {Object} [options] Options for the executed command.
* @param {String} [options.value] The value to apply.
*
* @fires execute
*/
execute( options = {} ) {
const model = this.editor.model;
const document = model.document;
const selection = document.selection;
const highlighter = options.value;
model.change( writer => {
if ( selection.isCollapsed ) {
const position = selection.getFirstPosition();
// When selection is inside text with `highlight` attribute.
if ( selection.hasAttribute( 'highlight' ) ) {
// Find the full highlighted range.
const isSameHighlight = value => {
return value.item.hasAttribute( 'highlight' ) && value.item.getAttribute( 'highlight' ) === this.value;
};
const highlightStart = position.getLastMatchingPosition( isSameHighlight, { direction: 'backward' } );
const highlightEnd = position.getLastMatchingPosition( isSameHighlight );
const highlightRange = writer.createRange( highlightStart, highlightEnd );
// Then depending on current value...
if ( !highlighter || this.value === highlighter ) {
// ...remove attribute when passing highlighter different then current or executing "eraser".
// If we're at the end of the highlighted range, we don't want to remove highlight of the range.
if ( !position.isEqual( highlightEnd ) ) {
writer.removeAttribute( 'highlight', highlightRange );
}
writer.removeSelectionAttribute( 'highlight' );
} else {
// ...update `highlight` value.
// If we're at the end of the highlighted range, we don't want to change the highlight of the range.
if ( !position.isEqual( highlightEnd ) ) {
writer.setAttribute( 'highlight', highlighter, highlightRange );
}
writer.setSelectionAttribute( 'highlight', highlighter );
}
} else if ( highlighter ) {
writer.setSelectionAttribute( 'highlight', highlighter );
}
} else {
const ranges = model.schema.getValidRanges( selection.getRanges(), 'highlight' );
for ( const range of ranges ) {
if ( highlighter ) {
writer.setAttribute( 'highlight', highlighter, range );
} else {
writer.removeAttribute( 'highlight', range );
}
}
}
} );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-highlight/src/highlightediting.js":
/*!****************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-highlight/src/highlightediting.js ***!
\****************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ HighlightEditing)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var _highlightcommand__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./highlightcommand */ "./node_modules/@ckeditor/ckeditor5-highlight/src/highlightcommand.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module highlight/highlightediting
*/
/**
* The highlight editing feature. It introduces the {@link module:highlight/highlightcommand~HighlightCommand command} and the `highlight`
* attribute in the {@link module:engine/model/model~Model model} which renders in the {@link module:engine/view/view view}
* as a `<mark>` element with a `class` attribute (`<mark class="marker-green">...</mark>`) depending
* on the {@link module:highlight/highlight~HighlightConfig configuration}.
*
* @extends module:core/plugin~Plugin
*/
class HighlightEditing extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'HighlightEditing';
}
/**
* @inheritDoc
*/
constructor( editor ) {
super( editor );
editor.config.define( 'highlight', {
options: [
{
model: 'yellowMarker',
class: 'marker-yellow',
title: 'Yellow marker',
color: 'var(--ck-highlight-marker-yellow)',
type: 'marker'
},
{
model: 'greenMarker',
class: 'marker-green',
title: 'Green marker',
color: 'var(--ck-highlight-marker-green)',
type: 'marker'
},
{
model: 'pinkMarker',
class: 'marker-pink',
title: 'Pink marker',
color: 'var(--ck-highlight-marker-pink)',
type: 'marker'
},
{
model: 'blueMarker',
class: 'marker-blue',
title: 'Blue marker',
color: 'var(--ck-highlight-marker-blue)',
type: 'marker'
},
{
model: 'redPen',
class: 'pen-red',
title: 'Red pen',
color: 'var(--ck-highlight-pen-red)',
type: 'pen'
},
{
model: 'greenPen',
class: 'pen-green',
title: 'Green pen',
color: 'var(--ck-highlight-pen-green)',
type: 'pen'
}
]
} );
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
// Allow highlight attribute on text nodes.
editor.model.schema.extend( '$text', { allowAttributes: 'highlight' } );
const options = editor.config.get( 'highlight.options' );
// Set-up the two-way conversion.
editor.conversion.attributeToElement( _buildDefinition( options ) );
editor.commands.add( 'highlight', new _highlightcommand__WEBPACK_IMPORTED_MODULE_1__["default"]( editor ) );
}
}
// Converts the options array to a converter definition.
//
// @param {Array.<module:highlight/highlight~HighlightOption>} options An array with configured options.
// @returns {module:engine/conversion/conversion~ConverterDefinition}
function _buildDefinition( options ) {
const definition = {
model: {
key: 'highlight',
values: []
},
view: {}
};
for ( const option of options ) {
definition.model.values.push( option.model );
definition.view[ option.model ] = {
name: 'mark',
classes: option.class
};
}
return definition;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-highlight/src/highlightui.js":
/*!***********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-highlight/src/highlightui.js ***!
\***********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ HighlightUI)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/ui */ "./node_modules/ckeditor5/src/ui.js");
/* harmony import */ var _theme_icons_marker_svg__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./../theme/icons/marker.svg */ "./node_modules/@ckeditor/ckeditor5-highlight/theme/icons/marker.svg");
/* harmony import */ var _theme_icons_pen_svg__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./../theme/icons/pen.svg */ "./node_modules/@ckeditor/ckeditor5-highlight/theme/icons/pen.svg");
/* harmony import */ var _theme_highlight_css__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./../theme/highlight.css */ "./node_modules/@ckeditor/ckeditor5-highlight/theme/highlight.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module highlight/highlightui
*/
/**
* The default highlight UI plugin. It introduces:
*
* * The `'highlight'` dropdown,
* * The `'removeHighlight'` and `'highlight:*'` buttons.
*
* The default configuration includes the following buttons:
*
* * `'highlight:yellowMarker'`
* * `'highlight:greenMarker'`
* * `'highlight:pinkMarker'`
* * `'highlight:blueMarker'`
* * `'highlight:redPen'`
* * `'highlight:greenPen'`
*
* See the {@link module:highlight/highlight~HighlightConfig#options configuration} to learn more
* about the defaults.
*
* @extends module:core/plugin~Plugin
*/
class HighlightUI extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* Returns the localized option titles provided by the plugin.
*
* The following localized titles corresponding with default
* {@link module:highlight/highlight~HighlightConfig#options} are available:
*
* * `'Yellow marker'`,
* * `'Green marker'`,
* * `'Pink marker'`,
* * `'Blue marker'`,
* * `'Red pen'`,
* * `'Green pen'`.
*
* @readonly
* @type {Object.<String,String>}
*/
get localizedOptionTitles() {
const t = this.editor.t;
return {
'Yellow marker': t( 'Yellow marker' ),
'Green marker': t( 'Green marker' ),
'Pink marker': t( 'Pink marker' ),
'Blue marker': t( 'Blue marker' ),
'Red pen': t( 'Red pen' ),
'Green pen': t( 'Green pen' )
};
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'HighlightUI';
}
/**
* @inheritDoc
*/
init() {
const options = this.editor.config.get( 'highlight.options' );
for ( const option of options ) {
this._addHighlighterButton( option );
}
this._addRemoveHighlightButton();
this._addDropdown( options );
}
/**
* Creates the "Remove highlight" button.
*
* @private
*/
_addRemoveHighlightButton() {
const t = this.editor.t;
const command = this.editor.commands.get( 'highlight' );
this._addButton( 'removeHighlight', t( 'Remove highlight' ), ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.icons.eraser, null, button => {
button.bind( 'isEnabled' ).to( command, 'isEnabled' );
} );
}
/**
* Creates a toolbar button from the provided highlight option.
*
* @param {module:highlight/highlight~HighlightOption} option
* @private
*/
_addHighlighterButton( option ) {
const command = this.editor.commands.get( 'highlight' );
// TODO: change naming
this._addButton( 'highlight:' + option.model, option.title, getIconForType( option.type ), option.model, decorateHighlightButton );
function decorateHighlightButton( button ) {
button.bind( 'isEnabled' ).to( command, 'isEnabled' );
button.bind( 'isOn' ).to( command, 'value', value => value === option.model );
button.iconView.fillColor = option.color;
button.isToggleable = true;
}
}
/**
* Internal method for creating highlight buttons.
*
* @param {String} name The name of the button.
* @param {String} label The label for the button.
* @param {String} icon The button icon.
* @param {*} value The `value` property passed to the executed command.
* @param {Function} decorateButton A callback getting ButtonView instance so that it can be further customized.
* @private
*/
_addButton( name, label, icon, value, decorateButton ) {
const editor = this.editor;
editor.ui.componentFactory.add( name, locale => {
const buttonView = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.ButtonView( locale );
const localized = this.localizedOptionTitles[ label ] ? this.localizedOptionTitles[ label ] : label;
buttonView.set( {
label: localized,
icon,
tooltip: true
} );
buttonView.on( 'execute', () => {
editor.execute( 'highlight', { value } );
editor.editing.view.focus();
} );
// Add additional behavior for buttonView.
decorateButton( buttonView );
return buttonView;
} );
}
/**
* Creates the split button dropdown UI from the provided highlight options.
*
* @param {Array.<module:highlight/highlight~HighlightOption>} options
* @private
*/
_addDropdown( options ) {
const editor = this.editor;
const t = editor.t;
const componentFactory = editor.ui.componentFactory;
const startingHighlighter = options[ 0 ];
const optionsMap = options.reduce( ( retVal, option ) => {
retVal[ option.model ] = option;
return retVal;
}, {} );
componentFactory.add( 'highlight', locale => {
const command = editor.commands.get( 'highlight' );
const dropdownView = (0,ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.createDropdown)( locale, ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.SplitButtonView );
const splitButtonView = dropdownView.buttonView;
splitButtonView.set( {
tooltip: t( 'Highlight' ),
// Holds last executed highlighter.
lastExecuted: startingHighlighter.model,
// Holds current highlighter to execute (might be different then last used).
commandValue: startingHighlighter.model,
isToggleable: true
} );
// Dropdown button changes to selection (command.value):
// - If selection is in highlight it get active highlight appearance (icon, color) and is activated.
// - Otherwise it gets appearance (icon, color) of last executed highlight.
splitButtonView.bind( 'icon' ).to( command, 'value', value => getIconForType( getActiveOption( value, 'type' ) ) );
splitButtonView.bind( 'color' ).to( command, 'value', value => getActiveOption( value, 'color' ) );
splitButtonView.bind( 'commandValue' ).to( command, 'value', value => getActiveOption( value, 'model' ) );
splitButtonView.bind( 'isOn' ).to( command, 'value', value => !!value );
splitButtonView.delegate( 'execute' ).to( dropdownView );
// Create buttons array.
const buttons = options.map( option => {
// Get existing highlighter button.
const buttonView = componentFactory.create( 'highlight:' + option.model );
// Update lastExecutedHighlight on execute.
this.listenTo( buttonView, 'execute', () => dropdownView.buttonView.set( { lastExecuted: option.model } ) );
return buttonView;
} );
// Make toolbar button enabled when any button in dropdown is enabled before adding separator and eraser.
dropdownView.bind( 'isEnabled' ).toMany( buttons, 'isEnabled', ( ...areEnabled ) => areEnabled.some( isEnabled => isEnabled ) );
// Add separator and eraser buttons to dropdown.
buttons.push( new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.ToolbarSeparatorView() );
buttons.push( componentFactory.create( 'removeHighlight' ) );
(0,ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.addToolbarToDropdown)( dropdownView, buttons );
bindToolbarIconStyleToActiveColor( dropdownView );
dropdownView.toolbarView.ariaLabel = t( 'Text highlight toolbar' );
// Execute current action from dropdown's split button action button.
splitButtonView.on( 'execute', () => {
editor.execute( 'highlight', { value: splitButtonView.commandValue } );
editor.editing.view.focus();
} );
// Returns active highlighter option depending on current command value.
// If current is not set or it is the same as last execute this method will return the option key (like icon or color)
// of last executed highlighter. Otherwise it will return option key for current one.
function getActiveOption( current, key ) {
const whichHighlighter = !current ||
current === splitButtonView.lastExecuted ? splitButtonView.lastExecuted : current;
return optionsMap[ whichHighlighter ][ key ];
}
return dropdownView;
} );
}
}
// Extends split button icon style to reflect last used button style.
function bindToolbarIconStyleToActiveColor( dropdownView ) {
const actionView = dropdownView.buttonView.actionView;
actionView.iconView.bind( 'fillColor' ).to( dropdownView.buttonView, 'color' );
}
// Returns icon for given highlighter type.
function getIconForType( type ) {
return type === 'marker' ? _theme_icons_marker_svg__WEBPACK_IMPORTED_MODULE_2__["default"] : _theme_icons_pen_svg__WEBPACK_IMPORTED_MODULE_3__["default"];
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-horizontal-line/src/horizontalline.js":
/*!********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-horizontal-line/src/horizontalline.js ***!
\********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ HorizontalLine)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_widget__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/widget */ "./node_modules/ckeditor5/src/widget.js");
/* harmony import */ var _horizontallineediting__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./horizontallineediting */ "./node_modules/@ckeditor/ckeditor5-horizontal-line/src/horizontallineediting.js");
/* harmony import */ var _horizontallineui__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./horizontallineui */ "./node_modules/@ckeditor/ckeditor5-horizontal-line/src/horizontallineui.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module horizontal-line/horizontalline
*/
/**
* The horizontal line feature.
*
* It provides the possibility to insert a horizontal line into the rich-text editor.
*
* For a detailed overview, check the {@glink features/horizontal-line Horizontal line feature} documentation.
*
* @extends module:core/plugin~Plugin
*/
class HorizontalLine extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [ _horizontallineediting__WEBPACK_IMPORTED_MODULE_2__["default"], _horizontallineui__WEBPACK_IMPORTED_MODULE_3__["default"], ckeditor5_src_widget__WEBPACK_IMPORTED_MODULE_1__.Widget ];
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'HorizontalLine';
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-horizontal-line/src/horizontallinecommand.js":
/*!***************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-horizontal-line/src/horizontallinecommand.js ***!
\***************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ HorizontalLineCommand)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_widget__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/widget */ "./node_modules/ckeditor5/src/widget.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module horizontal-line/horizontallinecommand
*/
/**
* The horizontal line command.
*
* The command is registered by {@link module:horizontal-line/horizontallineediting~HorizontalLineEditing} as `'horizontalLine'`.
*
* To insert a horizontal line at the current selection, execute the command:
*
* editor.execute( 'horizontalLine' );
*
* @extends module:core/command~Command
*/
class HorizontalLineCommand extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Command {
/**
* @inheritDoc
*/
refresh() {
const model = this.editor.model;
const schema = model.schema;
const selection = model.document.selection;
this.isEnabled = isHorizontalLineAllowedInParent( selection, schema, model );
}
/**
* Executes the command.
*
* @fires execute
*/
execute() {
const model = this.editor.model;
model.change( writer => {
const horizontalElement = writer.createElement( 'horizontalLine' );
model.insertContent( horizontalElement );
let nextElement = horizontalElement.nextSibling;
// Check whether an element next to the inserted horizontal line is defined and can contain a text.
const canSetSelection = nextElement && model.schema.checkChild( nextElement, '$text' );
// If the element is missing, but a paragraph could be inserted next to the horizontal line, let's add it.
if ( !canSetSelection && model.schema.checkChild( horizontalElement.parent, 'paragraph' ) ) {
nextElement = writer.createElement( 'paragraph' );
model.insertContent( nextElement, writer.createPositionAfter( horizontalElement ) );
}
// Put the selection inside the element, at the beginning.
if ( nextElement ) {
writer.setSelection( nextElement, 0 );
}
} );
}
}
// Checks if a horizontal line is allowed by the schema in the optimal insertion parent.
//
// @param {module:engine/model/selection~Selection|module:engine/model/documentselection~DocumentSelection} selection
// @param {module:engine/model/schema~Schema} schema
// @param {module:engine/model/model~Model} model Model instance.
// @returns {Boolean}
function isHorizontalLineAllowedInParent( selection, schema, model ) {
const parent = getInsertHorizontalLineParent( selection, model );
return schema.checkChild( parent, 'horizontalLine' );
}
// Returns a node that will be used to insert a horizontal line with `model.insertContent` to check if the horizontal line can be
// placed there.
//
// @param {module:engine/model/selection~Selection|module:engine/model/documentselection~DocumentSelection} selection
// @param {module:engine/model/model~Model} model Model instance.
// @returns {module:engine/model/element~Element}
function getInsertHorizontalLineParent( selection, model ) {
const insertionRange = (0,ckeditor5_src_widget__WEBPACK_IMPORTED_MODULE_1__.findOptimalInsertionRange)( selection, model );
const parent = insertionRange.start.parent;
if ( parent.isEmpty && !parent.is( 'element', '$root' ) ) {
return parent.parent;
}
return parent;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-horizontal-line/src/horizontallineediting.js":
/*!***************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-horizontal-line/src/horizontallineediting.js ***!
\***************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ HorizontalLineEditing)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_widget__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/widget */ "./node_modules/ckeditor5/src/widget.js");
/* harmony import */ var _horizontallinecommand__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./horizontallinecommand */ "./node_modules/@ckeditor/ckeditor5-horizontal-line/src/horizontallinecommand.js");
/* harmony import */ var _theme_horizontalline_css__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../theme/horizontalline.css */ "./node_modules/@ckeditor/ckeditor5-horizontal-line/theme/horizontalline.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module horizontal-line/horizontallineediting
*/
/**
* The horizontal line editing feature.
*
* @extends module:core/plugin~Plugin
*/
class HorizontalLineEditing extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'HorizontalLineEditing';
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const schema = editor.model.schema;
const t = editor.t;
const conversion = editor.conversion;
schema.register( 'horizontalLine', {
isObject: true,
allowWhere: '$block'
} );
conversion.for( 'dataDowncast' ).elementToElement( {
model: 'horizontalLine',
view: ( modelElement, { writer } ) => {
return writer.createEmptyElement( 'hr' );
}
} );
conversion.for( 'editingDowncast' ).elementToStructure( {
model: 'horizontalLine',
view: ( modelElement, { writer } ) => {
const label = t( 'Horizontal line' );
const viewWrapper = writer.createContainerElement( 'div', null,
writer.createEmptyElement( 'hr' )
);
writer.addClass( 'ck-horizontal-line', viewWrapper );
writer.setCustomProperty( 'hr', true, viewWrapper );
return toHorizontalLineWidget( viewWrapper, writer, label );
}
} );
conversion.for( 'upcast' ).elementToElement( { view: 'hr', model: 'horizontalLine' } );
editor.commands.add( 'horizontalLine', new _horizontallinecommand__WEBPACK_IMPORTED_MODULE_2__["default"]( editor ) );
}
}
// Converts a given {@link module:engine/view/element~Element} to a horizontal line widget:
// * Adds a {@link module:engine/view/element~Element#_setCustomProperty custom property} allowing to
// recognize the horizontal line widget element.
// * Calls the {@link module:widget/utils~toWidget} function with the proper element's label creator.
//
// @param {module:engine/view/element~Element} viewElement
// @param {module:engine/view/downcastwriter~DowncastWriter} writer An instance of the view writer.
// @param {String} label The element's label.
// @returns {module:engine/view/element~Element}
function toHorizontalLineWidget( viewElement, writer, label ) {
writer.setCustomProperty( 'horizontalLine', true, viewElement );
return (0,ckeditor5_src_widget__WEBPACK_IMPORTED_MODULE_1__.toWidget)( viewElement, writer, { label } );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-horizontal-line/src/horizontallineui.js":
/*!**********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-horizontal-line/src/horizontallineui.js ***!
\**********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ HorizontalLineUI)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/ui */ "./node_modules/ckeditor5/src/ui.js");
/* harmony import */ var _theme_icons_horizontalline_svg__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../theme/icons/horizontalline.svg */ "./node_modules/@ckeditor/ckeditor5-horizontal-line/theme/icons/horizontalline.svg");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module horizontal-line/horizontallineui
*/
/**
* The horizontal line UI plugin.
*
* @extends module:core/plugin~Plugin
*/
class HorizontalLineUI extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'HorizontalLineUI';
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const t = editor.t;
// Add the `horizontalLine` button to feature components.
editor.ui.componentFactory.add( 'horizontalLine', locale => {
const command = editor.commands.get( 'horizontalLine' );
const view = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.ButtonView( locale );
view.set( {
label: t( 'Horizontal line' ),
icon: _theme_icons_horizontalline_svg__WEBPACK_IMPORTED_MODULE_2__["default"],
tooltip: true
} );
view.bind( 'isEnabled' ).to( command, 'isEnabled' );
// Execute the command.
this.listenTo( view, 'execute', () => {
editor.execute( 'horizontalLine' );
editor.editing.view.focus();
} );
return view;
} );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-html-embed/src/htmlembed.js":
/*!**********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-html-embed/src/htmlembed.js ***!
\**********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ HtmlEmbed)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_widget__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/widget */ "./node_modules/ckeditor5/src/widget.js");
/* harmony import */ var _htmlembedediting__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./htmlembedediting */ "./node_modules/@ckeditor/ckeditor5-html-embed/src/htmlembedediting.js");
/* harmony import */ var _htmlembedui__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./htmlembedui */ "./node_modules/@ckeditor/ckeditor5-html-embed/src/htmlembedui.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module html-embed/htmlembed
*/
/**
* The HTML embed feature.
*
* It allows inserting HTML snippets directly into the editor.
*
* For a detailed overview, check the {@glink features/html-embed HTML embed feature} documentation.
*
* @extends module:core/plugin~Plugin
*/
class HtmlEmbed extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [ _htmlembedediting__WEBPACK_IMPORTED_MODULE_2__["default"], _htmlembedui__WEBPACK_IMPORTED_MODULE_3__["default"], ckeditor5_src_widget__WEBPACK_IMPORTED_MODULE_1__.Widget ];
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'HtmlEmbed';
}
}
/**
* The configuration of the HTML embed feature.
*
* ClassicEditor
* .create( editorElement, {
* htmlEmbed: ... // HTML embed feature options.
* } )
* .then( ... )
* .catch( ... );
*
* See {@link module:core/editor/editorconfig~EditorConfig all editor options}.
*
* @interface HtmlEmbedConfig
*/
/**
* Whether the feature should render previews of the embedded HTML.
*
* When set to `true`, the feature will produce a preview of the inserted HTML based on a sanitized
* version of the HTML provided by the user.
*
* The function responsible for sanitizing the HTML needs to be specified in
* {@link module:html-embed/htmlembed~HtmlEmbedConfig#sanitizeHtml `config.htmlEmbed.sanitizeHtml()`}.
*
* Read more about the security aspect of this feature in the {@glink features/html-embed#security "Security"} section of
* the {@glink features/html-embed HTML embed} feature guide.
*
* @member {Boolean} [module:html-embed/htmlembed~HtmlEmbedConfig#showPreviews=false]
*/
/**
* Callback used to sanitize the HTML provided by the user when generating previews of it in the editor.
*
* We strongly recommend overwriting the default function to avoid XSS vulnerabilities.
*
* Read more about the security aspect of this feature in the {@glink features/html-embed#security "Security"} section of
* the {@glink features/html-embed HTML embed} feature guide.
*
* The function receives the input HTML (as a string), and should return an object
* that matches the {@link module:html-embed/htmlembed~HtmlEmbedSanitizeOutput} interface.
*
* ClassicEditor
* .create( editorElement, {
* htmlEmbed: {
* showPreviews: true,
* sanitizeHtml( inputHtml ) {
* // Strip unsafe elements and attributes, e.g.:
* // the `<script>` elements and `on*` attributes.
* const outputHtml = sanitize( inputHtml );
*
* return {
* html: outputHtml,
* // true or false depending on whether the sanitizer stripped anything.
* hasChanged: ...
* };
* },
* }
* } )
* .then( ... )
* .catch( ... );
*
* **Note:** The function is used only when the feature
* {@link module:html-embed/htmlembed~HtmlEmbedConfig#showPreviews is configured to render previews}.
*
* @member {Function} [module:html-embed/htmlembed~HtmlEmbedConfig#sanitizeHtml]
*/
/**
* An object returned by the {@link module:html-embed/htmlembed~HtmlEmbedConfig#sanitizeHtml} function.
*
* @interface HtmlEmbedSanitizeOutput
*/
/**
* An output (safe) HTML that will be inserted into the {@glink framework/guides/architecture/editing-engine editing view}.
*
* @member {String} module:html-embed/htmlembed~HtmlEmbedSanitizeOutput#html
*/
/**
* A flag that indicates whether the output HTML is different than the input value.
*
* @member {Boolean} [module:html-embed/htmlembed~HtmlEmbedSanitizeOutput#hasChanged]
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-html-embed/src/htmlembedcommand.js":
/*!*****************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-html-embed/src/htmlembedcommand.js ***!
\*****************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ HtmlEmbedCommand)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_widget__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/widget */ "./node_modules/ckeditor5/src/widget.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module html-embed/htmlembedcommand
*/
/**
* The insert HTML embed element command.
*
* The command is registered by {@link module:html-embed/htmlembedediting~HtmlEmbedEditing} as `'htmlEmbed'`.
*
* To insert an empty HTML embed element at the current selection, execute the command:
*
* editor.execute( 'htmlEmbed' );
*
* You can specify the initial content of a new HTML embed in the argument:
*
* editor.execute( 'htmlEmbed', '<b>Initial content.</b>' );
*
* To update the content of the HTML embed, select it in the model and pass the content in the argument:
*
* editor.execute( 'htmlEmbed', '<b>New content of an existing embed.</b>' );
*
* @extends module:core/command~Command
*/
class HtmlEmbedCommand extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Command {
/**
* @inheritDoc
*/
refresh() {
const model = this.editor.model;
const schema = model.schema;
const selection = model.document.selection;
const selectedRawHtmlElement = getSelectedRawHtmlModelWidget( selection );
this.isEnabled = isHtmlEmbedAllowedInParent( selection, schema, model );
this.value = selectedRawHtmlElement ? selectedRawHtmlElement.getAttribute( 'value' ) || '' : null;
}
/**
* Executes the command, which either:
*
* * creates and inserts a new HTML embed element if none was selected,
* * updates the content of the HTML embed if one was selected.
*
* @fires execute
* @param {String} [value] When passed, the value (content) will be set on a new embed or a selected one.
*/
execute( value ) {
const model = this.editor.model;
const selection = model.document.selection;
model.change( writer => {
let htmlEmbedElement;
// If the command has a non-null value, there must be some HTML embed selected in the model.
if ( this.value !== null ) {
htmlEmbedElement = getSelectedRawHtmlModelWidget( selection );
} else {
htmlEmbedElement = writer.createElement( 'rawHtml' );
model.insertContent( htmlEmbedElement );
writer.setSelection( htmlEmbedElement, 'on' );
}
writer.setAttribute( 'value', value, htmlEmbedElement );
} );
}
}
// Checks if an HTML embed is allowed by the schema in the optimal insertion parent.
//
// @param {module:engine/model/selection~Selection|module:engine/model/documentselection~DocumentSelection} selection
// @param {module:engine/model/schema~Schema} schema
// @param {module:engine/model/model~Model} model
// @returns {Boolean}
function isHtmlEmbedAllowedInParent( selection, schema, model ) {
const parent = getInsertHtmlEmbedParent( selection, model );
return schema.checkChild( parent, 'rawHtml' );
}
// Returns a node that will be used to insert a html embed with `model.insertContent` to check if a html embed element can be placed there.
//
// @param {module:engine/model/selection~Selection|module:engine/model/documentselection~DocumentSelection} selection
// @param {module:engine/model/model~Model} model
// @returns {module:engine/model/element~Element}
function getInsertHtmlEmbedParent( selection, model ) {
const insertionRange = (0,ckeditor5_src_widget__WEBPACK_IMPORTED_MODULE_1__.findOptimalInsertionRange)( selection, model );
const parent = insertionRange.start.parent;
if ( parent.isEmpty && !parent.is( 'element', '$root' ) ) {
return parent.parent;
}
return parent;
}
// Returns the selected HTML embed element in the model, if any.
//
// @param {module:engine/model/selection~Selection} selection
// @returns {module:engine/model/element~Element|null}
function getSelectedRawHtmlModelWidget( selection ) {
const selectedElement = selection.getSelectedElement();
if ( selectedElement && selectedElement.is( 'element', 'rawHtml' ) ) {
return selectedElement;
}
return null;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-html-embed/src/htmlembedediting.js":
/*!*****************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-html-embed/src/htmlembedediting.js ***!
\*****************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ HtmlEmbedEditing)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/ui */ "./node_modules/ckeditor5/src/ui.js");
/* harmony import */ var ckeditor5_src_widget__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ckeditor5/src/widget */ "./node_modules/ckeditor5/src/widget.js");
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/* harmony import */ var _htmlembedcommand__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./htmlembedcommand */ "./node_modules/@ckeditor/ckeditor5-html-embed/src/htmlembedcommand.js");
/* harmony import */ var _theme_htmlembed_css__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../theme/htmlembed.css */ "./node_modules/@ckeditor/ckeditor5-html-embed/theme/htmlembed.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module html-embed/htmlembedediting
*/
/**
* The HTML embed editing feature.
*
* @extends module:core/plugin~Plugin
*/
class HtmlEmbedEditing extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'HtmlEmbedEditing';
}
/**
* @inheritDoc
*/
constructor( editor ) {
super( editor );
editor.config.define( 'htmlEmbed', {
showPreviews: false,
sanitizeHtml: rawHtml => {
/**
* When using the HTML embed feature with the `htmlEmbed.showPreviews=true` option, it is strongly recommended to
* define a sanitize function that will clean up the input HTML in order to avoid XSS vulnerability.
*
* For a detailed overview, check the {@glink features/html-embed HTML embed feature} documentation.
*
* @error html-embed-provide-sanitize-function
*/
(0,ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_3__.logWarning)( 'html-embed-provide-sanitize-function' );
return {
html: rawHtml,
hasChanged: false
};
}
} );
/**
* Keeps references to {@link module:ui/button/buttonview~ButtonView edit, save, and cancel} button instances created for
* each widget so they can be destroyed if they are no longer in DOM after the editing view was re-rendered.
*
* @private
* @member {Set.<module:ui/button/buttonview~ButtonView>} #_widgetButtonViewReferences
*/
this._widgetButtonViewReferences = new Set();
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const schema = editor.model.schema;
schema.register( 'rawHtml', {
isObject: true,
allowWhere: '$block',
allowAttributes: [ 'value' ]
} );
editor.commands.add( 'htmlEmbed', new _htmlembedcommand__WEBPACK_IMPORTED_MODULE_4__["default"]( editor ) );
this._setupConversion();
}
/**
* Prepares converters for the feature.
*
* @private
*/
_setupConversion() {
const editor = this.editor;
const t = editor.t;
const view = editor.editing.view;
const widgetButtonViewReferences = this._widgetButtonViewReferences;
const htmlEmbedConfig = editor.config.get( 'htmlEmbed' );
// Destroy UI buttons created for widgets that have been removed from the view document (e.g. in the previous conversion).
// This prevents unexpected memory leaks from UI views.
this.editor.editing.view.on( 'render', () => {
for ( const buttonView of widgetButtonViewReferences ) {
if ( buttonView.element.isConnected ) {
return;
}
buttonView.destroy();
widgetButtonViewReferences.delete( buttonView );
}
}, { priority: 'lowest' } );
// Register div.raw-html-embed as a raw content element so all of it's content will be provided
// as a view element's custom property while data upcasting.
editor.data.registerRawContentMatcher( {
name: 'div',
classes: 'raw-html-embed'
} );
editor.conversion.for( 'upcast' ).elementToElement( {
view: {
name: 'div',
classes: 'raw-html-embed'
},
model: ( viewElement, { writer } ) => {
// The div.raw-html-embed is registered as a raw content element,
// so all it's content is available in a custom property.
return writer.createElement( 'rawHtml', {
value: viewElement.getCustomProperty( '$rawContent' )
} );
}
} );
editor.conversion.for( 'dataDowncast' ).elementToElement( {
model: 'rawHtml',
view: ( modelElement, { writer } ) => {
return writer.createRawElement( 'div', { class: 'raw-html-embed' }, function( domElement ) {
domElement.innerHTML = modelElement.getAttribute( 'value' ) || '';
} );
}
} );
editor.conversion.for( 'editingDowncast' ).elementToStructure( {
model: { name: 'rawHtml', attributes: [ 'value' ] },
view: ( modelElement, { writer } ) => {
let domContentWrapper, state, props;
const viewContentWrapper = writer.createRawElement( 'div', {
class: 'raw-html-embed__content-wrapper'
}, function( domElement ) {
domContentWrapper = domElement;
renderContent( { domElement, editor, state, props } );
// Since there is a `data-cke-ignore-events` attribute set on the wrapper element in the editable mode,
// the explicit `mousedown` handler on the `capture` phase is needed to move the selection onto the whole
// HTML embed widget.
domContentWrapper.addEventListener( 'mousedown', () => {
if ( state.isEditable ) {
const model = editor.model;
const selectedElement = model.document.selection.getSelectedElement();
// Move the selection onto the whole HTML embed widget if it's currently not selected.
if ( selectedElement !== modelElement ) {
model.change( writer => writer.setSelection( modelElement, 'on' ) );
}
}
}, true );
} );
// API exposed on each raw HTML embed widget so other features can control a particular widget.
const rawHtmlApi = {
makeEditable() {
state = Object.assign( {}, state, {
isEditable: true
} );
renderContent( { domElement: domContentWrapper, editor, state, props } );
view.change( writer => {
writer.setAttribute( 'data-cke-ignore-events', 'true', viewContentWrapper );
} );
// This could be potentially pulled to a separate method called focusTextarea().
domContentWrapper.querySelector( 'textarea' ).focus();
},
save( newValue ) {
// If the value didn't change, we just cancel. If it changed,
// it's enough to update the model – the entire widget will be reconverted.
if ( newValue !== state.getRawHtmlValue() ) {
editor.execute( 'htmlEmbed', newValue );
editor.editing.view.focus();
} else {
this.cancel();
}
},
cancel() {
state = Object.assign( {}, state, {
isEditable: false
} );
renderContent( { domElement: domContentWrapper, editor, state, props } );
editor.editing.view.focus();
view.change( writer => {
writer.removeAttribute( 'data-cke-ignore-events', viewContentWrapper );
} );
}
};
state = {
showPreviews: htmlEmbedConfig.showPreviews,
isEditable: false,
getRawHtmlValue: () => modelElement.getAttribute( 'value' ) || ''
};
props = {
sanitizeHtml: htmlEmbedConfig.sanitizeHtml,
textareaPlaceholder: t( 'Paste raw HTML here...' ),
onEditClick() {
rawHtmlApi.makeEditable();
},
onSaveClick( newValue ) {
rawHtmlApi.save( newValue );
},
onCancelClick() {
rawHtmlApi.cancel();
}
};
const viewContainer = writer.createContainerElement( 'div', {
class: 'raw-html-embed',
'data-html-embed-label': t( 'HTML snippet' ),
dir: editor.locale.uiLanguageDirection
}, viewContentWrapper );
writer.setCustomProperty( 'rawHtmlApi', rawHtmlApi, viewContainer );
writer.setCustomProperty( 'rawHtml', true, viewContainer );
return (0,ckeditor5_src_widget__WEBPACK_IMPORTED_MODULE_2__.toWidget)( viewContainer, writer, {
widgetLabel: t( 'HTML snippet' ),
hasSelectionHandle: true
} );
}
} );
function renderContent( { domElement, editor, state, props } ) {
// Remove all children;
domElement.textContent = '';
const domDocument = domElement.ownerDocument;
let domTextarea;
if ( state.isEditable ) {
const textareaProps = {
isDisabled: false,
placeholder: props.textareaPlaceholder
};
domTextarea = createDomTextarea( { domDocument, state, props: textareaProps } );
domElement.append( domTextarea );
} else if ( state.showPreviews ) {
const previewContainerProps = {
sanitizeHtml: props.sanitizeHtml
};
domElement.append( createPreviewContainer( { domDocument, state, props: previewContainerProps, editor } ) );
} else {
const textareaProps = {
isDisabled: true,
placeholder: props.textareaPlaceholder
};
domElement.append( createDomTextarea( { domDocument, state, props: textareaProps } ) );
}
const buttonsWrapperProps = {
onEditClick: props.onEditClick,
onSaveClick: () => {
props.onSaveClick( domTextarea.value );
},
onCancelClick: props.onCancelClick
};
domElement.prepend( createDomButtonsWrapper( { editor, domDocument, state, props: buttonsWrapperProps } ) );
}
function createDomButtonsWrapper( { editor, domDocument, state, props } ) {
const domButtonsWrapper = (0,ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_3__.createElement)( domDocument, 'div', {
class: 'raw-html-embed__buttons-wrapper'
} );
if ( state.isEditable ) {
const saveButtonView = createUIButton( editor, 'save', props.onSaveClick );
const cancelButtonView = createUIButton( editor, 'cancel', props.onCancelClick );
domButtonsWrapper.append( saveButtonView.element, cancelButtonView.element );
widgetButtonViewReferences.add( saveButtonView ).add( cancelButtonView );
} else {
const editButtonView = createUIButton( editor, 'edit', props.onEditClick );
domButtonsWrapper.append( editButtonView.element );
widgetButtonViewReferences.add( editButtonView );
}
return domButtonsWrapper;
}
function createDomTextarea( { domDocument, state, props } ) {
const domTextarea = (0,ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_3__.createElement)( domDocument, 'textarea', {
placeholder: props.placeholder,
class: 'ck ck-reset ck-input ck-input-text raw-html-embed__source'
} );
domTextarea.disabled = props.isDisabled;
domTextarea.value = state.getRawHtmlValue();
return domTextarea;
}
function createPreviewContainer( { domDocument, state, props, editor } ) {
const sanitizedOutput = props.sanitizeHtml( state.getRawHtmlValue() );
const placeholderText = state.getRawHtmlValue().length > 0 ?
t( 'No preview available' ) :
t( 'Empty snippet content' );
const domPreviewPlaceholder = (0,ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_3__.createElement)( domDocument, 'div', {
class: 'ck ck-reset_all raw-html-embed__preview-placeholder'
}, placeholderText );
const domPreviewContent = (0,ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_3__.createElement)( domDocument, 'div', {
class: 'raw-html-embed__preview-content',
dir: editor.locale.contentLanguageDirection
} );
// Creating a contextual document fragment allows executing scripts when inserting into the preview element.
// See: #8326.
const domRange = domDocument.createRange();
const domDocumentFragment = domRange.createContextualFragment( sanitizedOutput.html );
domPreviewContent.appendChild( domDocumentFragment );
const domPreviewContainer = (0,ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_3__.createElement)( domDocument, 'div', {
class: 'raw-html-embed__preview'
}, [
domPreviewPlaceholder, domPreviewContent
] );
return domPreviewContainer;
}
}
}
// Returns a UI button view that can be used in conversion.
//
// @param {module:utils/locale~Locale} locale Editor locale.
// @param {'edit'|'save'|'cancel'} type Type of button to create.
// @param {Function} onClick The callback executed on button click.
// @returns {module:ui/button/buttonview~ButtonView}
function createUIButton( editor, type, onClick ) {
const t = editor.locale.t;
const buttonView = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.ButtonView( editor.locale );
const command = editor.commands.get( 'htmlEmbed' );
buttonView.set( {
class: `raw-html-embed__${ type }-button`,
icon: ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.icons.pencil,
tooltip: true,
tooltipPosition: editor.locale.uiLanguageDirection === 'rtl' ? 'e' : 'w'
} );
buttonView.render();
if ( type === 'edit' ) {
buttonView.set( {
icon: ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.icons.pencil,
label: t( 'Edit source' )
} );
buttonView.bind( 'isEnabled' ).to( command );
} else if ( type === 'save' ) {
buttonView.set( {
icon: ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.icons.check,
label: t( 'Save changes' )
} );
buttonView.bind( 'isEnabled' ).to( command );
} else {
buttonView.set( {
icon: ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.icons.cancel,
label: t( 'Cancel' )
} );
}
buttonView.on( 'execute', onClick );
return buttonView;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-html-embed/src/htmlembedui.js":
/*!************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-html-embed/src/htmlembedui.js ***!
\************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ HtmlEmbedUI)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/ui */ "./node_modules/ckeditor5/src/ui.js");
/* harmony import */ var _theme_icons_html_svg__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../theme/icons/html.svg */ "./node_modules/@ckeditor/ckeditor5-html-embed/theme/icons/html.svg");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module html-embed/htmlembedui
*/
/**
* The HTML embed UI plugin.
*
* @extends module:core/plugin~Plugin
*/
class HtmlEmbedUI extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'HtmlEmbedUI';
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const t = editor.t;
// Add the `htmlEmbed` button to feature components.
editor.ui.componentFactory.add( 'htmlEmbed', locale => {
const command = editor.commands.get( 'htmlEmbed' );
const view = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.ButtonView( locale );
view.set( {
label: t( 'Insert HTML' ),
icon: _theme_icons_html_svg__WEBPACK_IMPORTED_MODULE_2__["default"],
tooltip: true
} );
view.bind( 'isEnabled' ).to( command, 'isEnabled' );
// Execute the command.
this.listenTo( view, 'execute', () => {
editor.execute( 'htmlEmbed' );
editor.editing.view.focus();
const widgetWrapper = editor.editing.view.document.selection.getSelectedElement();
widgetWrapper.getCustomProperty( 'rawHtmlApi' ).makeEditable();
} );
return view;
} );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-html-support/src/conversionutils.js":
/*!******************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-html-support/src/conversionutils.js ***!
\******************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "mergeViewElementAttributes": () => (/* binding */ mergeViewElementAttributes),
/* harmony export */ "setViewAttributes": () => (/* binding */ setViewAttributes)
/* harmony export */ });
/* harmony import */ var lodash_es__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! lodash-es */ "./node_modules/lodash-es/cloneDeep.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module html-support/conversionutils
*/
/**
* Helper function for downcast converter. Sets attributes on the given view element.
*
* @param {module:engine/view/downcastwriter~DowncastWriter} writer
* @param {Object} viewAttributes
* @param {module:engine/view/element~Element} viewElement
*/
function setViewAttributes( writer, viewAttributes, viewElement ) {
if ( viewAttributes.attributes ) {
for ( const [ key, value ] of Object.entries( viewAttributes.attributes ) ) {
writer.setAttribute( key, value, viewElement );
}
}
if ( viewAttributes.styles ) {
writer.setStyle( viewAttributes.styles, viewElement );
}
if ( viewAttributes.classes ) {
writer.addClass( viewAttributes.classes, viewElement );
}
}
/**
* Merges view element attribute objects.
*
* @param {Object} target
* @param {Object} source
* @returns {Object}
*/
function mergeViewElementAttributes( target, source ) {
const result = (0,lodash_es__WEBPACK_IMPORTED_MODULE_0__["default"])( target );
for ( const key in source ) {
// Merge classes.
if ( Array.isArray( source[ key ] ) ) {
result[ key ] = Array.from( new Set( [ ...target[ key ], ...source[ key ] ] ) );
}
// Merge attributes or styles.
else {
result[ key ] = { ...target[ key ], ...source[ key ] };
}
}
return result;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-html-support/src/converters.js":
/*!*************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-html-support/src/converters.js ***!
\*************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "attributeToViewInlineConverter": () => (/* binding */ attributeToViewInlineConverter),
/* harmony export */ "createObjectView": () => (/* binding */ createObjectView),
/* harmony export */ "modelToViewBlockAttributeConverter": () => (/* binding */ modelToViewBlockAttributeConverter),
/* harmony export */ "toObjectWidgetConverter": () => (/* binding */ toObjectWidgetConverter),
/* harmony export */ "viewToAttributeInlineConverter": () => (/* binding */ viewToAttributeInlineConverter),
/* harmony export */ "viewToModelBlockAttributeConverter": () => (/* binding */ viewToModelBlockAttributeConverter),
/* harmony export */ "viewToModelObjectConverter": () => (/* binding */ viewToModelObjectConverter)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_widget__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/widget */ "./node_modules/ckeditor5/src/widget.js");
/* harmony import */ var _conversionutils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./conversionutils */ "./node_modules/@ckeditor/ckeditor5-html-support/src/conversionutils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module html-support/converters
*/
/**
* View-to-model conversion helper for object elements.
*
* Preserves object element content in `htmlContent` attribute.
*
* @param {module:html-support/dataschema~DataSchemaDefinition} definition
* @returns {Function} Returns a conversion callback.
*/
function viewToModelObjectConverter( { model: modelName } ) {
return ( viewElement, conversionApi ) => {
// Let's keep element HTML and its attributes, so we can rebuild element in downcast conversions.
return conversionApi.writer.createElement( modelName, {
htmlContent: viewElement.getCustomProperty( '$rawContent' )
} );
};
}
/**
* Conversion helper converting object element to HTML object widget.
*
* @param {module:core/editor/editor~Editor} editor
* @param {module:html-support/dataschema~DataSchemaInlineElementDefinition} definition
* @returns {Function} Returns a conversion callback.
*/
function toObjectWidgetConverter( editor, { view: viewName, isInline } ) {
const t = editor.t;
return ( modelElement, { writer, consumable } ) => {
const widgetLabel = t( 'HTML object' );
const viewElement = createObjectView( viewName, modelElement, writer );
writer.addClass( 'html-object-embed__content', viewElement );
const viewAttributes = modelElement.getAttribute( 'htmlAttributes' );
if ( viewAttributes && consumable.consume( modelElement, `attribute:htmlAttributes:${ modelElement.name }` ) ) {
(0,_conversionutils__WEBPACK_IMPORTED_MODULE_1__.setViewAttributes)( writer, viewAttributes, viewElement );
}
// Widget cannot be a raw element because the widget system would not be able
// to add its UI to it. Thus, we need separate view container.
const viewContainer = writer.createContainerElement( isInline ? 'span' : 'div',
{
class: 'html-object-embed',
'data-html-object-embed-label': widgetLabel
},
viewElement,
{
isAllowedInsideAttributeElement: isInline
}
);
return (0,ckeditor5_src_widget__WEBPACK_IMPORTED_MODULE_0__.toWidget)( viewContainer, writer, { widgetLabel } );
};
}
/**
* Creates object view element from the given model element.
*
* @param {String} viewName
* @param {module:engine/model/element~Element} modelElement
* @param {module:engine/view/downcastwriter~DowncastWriter} writer
* @returns {module:engine/view/element~Element}
*/
function createObjectView( viewName, modelElement, writer ) {
return writer.createRawElement( viewName, null, ( domElement, domConverter ) => {
domConverter.setContentOf( domElement, modelElement.getAttribute( 'htmlContent' ) );
} );
}
/**
* View-to-attribute conversion helper preserving inline element attributes on `$text`.
*
* @param {module:html-support/dataschema~DataSchemaInlineElementDefinition} definition
* @param {module:html-support/datafilter~DataFilter} dataFilter
* @returns {Function} Returns a conversion callback.
*/
function viewToAttributeInlineConverter( { view: viewName, model: attributeKey }, dataFilter ) {
return dispatcher => {
dispatcher.on( `element:${ viewName }`, ( evt, data, conversionApi ) => {
const viewAttributes = dataFilter._consumeAllowedAttributes( data.viewItem, conversionApi );
// Since we are converting to attribute we need a range on which we will set the attribute.
// If the range is not created yet, we will create it.
if ( !data.modelRange ) {
data = Object.assign( data, conversionApi.convertChildren( data.viewItem, data.modelCursor ) );
}
// Set attribute on each item in range according to the schema.
for ( const node of data.modelRange.getItems() ) {
if ( conversionApi.schema.checkAttribute( node, attributeKey ) ) {
// Node's children are converted recursively, so node can already include model attribute.
// We want to extend it, not replace.
const nodeAttributes = node.getAttribute( attributeKey );
const attributesToAdd = (0,_conversionutils__WEBPACK_IMPORTED_MODULE_1__.mergeViewElementAttributes)( viewAttributes || {}, nodeAttributes || {} );
conversionApi.writer.setAttribute( attributeKey, attributesToAdd, node );
}
}
}, { priority: 'low' } );
};
}
/**
* Attribute-to-view conversion helper applying attributes to view element preserved on `$text`.
*
* @param {module:html-support/dataschema~DataSchemaInlineElementDefinition} definition
* @returns {Function} Returns a conversion callback.
*/
function attributeToViewInlineConverter( { priority, view: viewName } ) {
return ( attributeValue, conversionApi ) => {
if ( !attributeValue ) {
return;
}
const { writer } = conversionApi;
const viewElement = writer.createAttributeElement( viewName, null, { priority } );
(0,_conversionutils__WEBPACK_IMPORTED_MODULE_1__.setViewAttributes)( writer, attributeValue, viewElement );
return viewElement;
};
}
/**
* View-to-model conversion helper preserving allowed attributes on block element.
*
* All matched attributes will be preserved on `htmlAttributes` attribute.
*
* @param {module:html-support/dataschema~DataSchemaBlockElementDefinition} definition
* @param {module:html-support/datafilter~DataFilter} dataFilter
* @returns {Function} Returns a conversion callback.
*/
function viewToModelBlockAttributeConverter( { view: viewName }, dataFilter ) {
return dispatcher => {
dispatcher.on( `element:${ viewName }`, ( evt, data, conversionApi ) => {
if ( !data.modelRange ) {
return;
}
const viewAttributes = dataFilter._consumeAllowedAttributes( data.viewItem, conversionApi );
if ( viewAttributes ) {
conversionApi.writer.setAttribute( 'htmlAttributes', viewAttributes, data.modelRange );
}
}, { priority: 'low' } );
};
}
/**
* Model-to-view conversion helper applying attributes preserved in `htmlAttributes` attribute
* for block elements.
*
* @param {module:html-support/dataschema~DataSchemaBlockElementDefinition} definition
* @returns {Function} Returns a conversion callback.
*/
function modelToViewBlockAttributeConverter( { model: modelName } ) {
return dispatcher => {
dispatcher.on( `attribute:htmlAttributes:${ modelName }`, ( evt, data, conversionApi ) => {
const viewAttributes = data.attributeNewValue;
if ( !conversionApi.consumable.consume( data.item, evt.name ) ) {
return;
}
const viewWriter = conversionApi.writer;
const viewElement = conversionApi.mapper.toViewElement( data.item );
(0,_conversionutils__WEBPACK_IMPORTED_MODULE_1__.setViewAttributes)( viewWriter, viewAttributes, viewElement );
} );
};
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-html-support/src/datafilter.js":
/*!*************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-html-support/src/datafilter.js ***!
\*************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ DataFilter)
/* harmony export */ });
/* harmony import */ var _dataschema__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./dataschema */ "./node_modules/@ckeditor/ckeditor5-html-support/src/dataschema.js");
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ckeditor5/src/engine */ "./node_modules/ckeditor5/src/engine.js");
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/* harmony import */ var ckeditor5_src_widget__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ckeditor5/src/widget */ "./node_modules/ckeditor5/src/widget.js");
/* harmony import */ var _converters__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./converters */ "./node_modules/@ckeditor/ckeditor5-html-support/src/converters.js");
/* harmony import */ var lodash_es__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! lodash-es */ "./node_modules/lodash-es/pull.js");
/* harmony import */ var lodash_es__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! lodash-es */ "./node_modules/lodash-es/isPlainObject.js");
/* harmony import */ var _theme_datafilter_css__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ../theme/datafilter.css */ "./node_modules/@ckeditor/ckeditor5-html-support/theme/datafilter.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module html-support/datafilter
*/
/**
* Allows to validate elements and element attributes registered by {@link module:html-support/dataschema~DataSchema}.
*
* To enable registered element in the editor, use {@link module:html-support/datafilter~DataFilter#allowElement} method:
*
* dataFilter.allowElement( 'section' );
*
* You can also allow or disallow specific element attributes:
*
* // Allow `data-foo` attribute on `section` element.
* dataFilter.allowAttributes( {
* name: 'section',
* attributes: {
* 'data-foo': true
* }
* } );
*
* // Disallow `color` style attribute on 'section' element.
* dataFilter.disallowAttributes( {
* name: 'section',
* styles: {
* color: /[\s\S]+/
* }
* } );
*
* @extends module:core/plugin~Plugin
*/
class DataFilter extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_1__.Plugin {
constructor( editor ) {
super( editor );
/**
* An instance of the {@link module:html-support/dataschema~DataSchema}.
*
* @readonly
* @private
* @member {module:html-support/dataschema~DataSchema} #_dataSchema
*/
this._dataSchema = editor.plugins.get( 'DataSchema' );
/**
* {@link module:engine/view/matcher~Matcher Matcher} instance describing rules upon which
* content attributes should be allowed.
*
* @readonly
* @private
* @member {module:engine/view/matcher~Matcher} #_allowedAttributes
*/
this._allowedAttributes = new ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_2__.Matcher();
/**
* {@link module:engine/view/matcher~Matcher Matcher} instance describing rules upon which
* content attributes should be disallowed.
*
* @readonly
* @private
* @member {module:engine/view/matcher~Matcher} #_disallowedAttributes
*/
this._disallowedAttributes = new ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_2__.Matcher();
/**
* Allowed element definitions by {@link module:html-support/datafilter~DataFilter#allowElement} method.
*
* @readonly
* @private
* @member {Set.<module:html-support/dataschema~DataSchemaDefinition>} #_allowedElements
*/
this._allowedElements = new Set();
/**
* Indicates if {@link module:engine/controller/datacontroller~DataController editor's data controller}
* data has been already initialized.
*
* @private
* @member {Boolean} [#_dataInitialized=false]
*/
this._dataInitialized = false;
this._registerElementsAfterInit();
this._registerElementHandlers();
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'DataFilter';
}
/**
* @inheritDoc
*/
static get requires() {
return [ _dataschema__WEBPACK_IMPORTED_MODULE_0__["default"], ckeditor5_src_widget__WEBPACK_IMPORTED_MODULE_4__.Widget ];
}
/**
* Load a configuration of one or many elements, where their attributes should be allowed.
*
* @param {Array.<module:engine/view/matcher~MatcherPattern>} config Configuration of elements
* that should have their attributes accepted in the editor.
*/
loadAllowedConfig( config ) {
this._loadConfig( config, pattern => this.allowAttributes( pattern ) );
}
/**
* Load a configuration of one or many elements, where their attributes should be disallowed.
*
* @param {Array.<module:engine/view/matcher~MatcherPattern>} config Configuration of elements
* that should have their attributes rejected from the editor.
*/
loadDisallowedConfig( config ) {
this._loadConfig( config, pattern => this.disallowAttributes( pattern ) );
}
/**
* Allow the given element in the editor context.
*
* This method will only allow elements described by the {@link module:html-support/dataschema~DataSchema} used
* to create data filter.
*
* @param {String|RegExp} viewName String or regular expression matching view name.
*/
allowElement( viewName ) {
for ( const definition of this._dataSchema.getDefinitionsForView( viewName, true ) ) {
if ( this._allowedElements.has( definition ) ) {
continue;
}
this._allowedElements.add( definition );
// We need to wait for all features to be initialized before we can register
// element, so we can access existing features model schemas.
// If the data has not been initialized yet, _registerElementsAfterInit() method will take care of
// registering elements.
if ( this._dataInitialized ) {
this._fireRegisterEvent( definition );
}
}
}
/**
* Allow the given attributes for view element allowed by {@link #allowElement} method.
*
* @param {module:engine/view/matcher~MatcherPattern} config Pattern matching all attributes which should be allowed.
*/
allowAttributes( config ) {
this._allowedAttributes.add( config );
}
/**
* Disallow the given attributes for view element allowed by {@link #allowElement} method.
*
* @param {module:engine/view/matcher~MatcherPattern} config Pattern matching all attributes which should be disallowed.
*/
disallowAttributes( config ) {
this._disallowedAttributes.add( config );
}
/**
* Batch load of the filtering configuration.
*
* @private
* @param {Array.<module:engine/view/matcher~MatcherPattern>} config Filtering configuration.
* @param {Function} handleAttributes Callback handling the way the attributes should be processed.
*/
_loadConfig( config, handleAttributes ) {
for ( const pattern of config ) {
// MatcherPattern allows omitting `name` to widen the search of elements.
// Let's keep it consistent and match every element if a `name` has not been provided.
const elementName = pattern.name || /[\s\S]+/;
this.allowElement( elementName );
splitRules( pattern ).forEach( handleAttributes );
}
}
/**
* Matches and consumes allowed and disallowed view attributes and returns the allowed ones.
*
* @protected
* @param {module:engine/view/element~Element} viewElement
* @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi
* @returns {Object} [result]
* @returns {Object} result.attributes Set with matched attribute names.
* @returns {Object} result.styles Set with matched style names.
* @returns {Array.<String>} result.classes Set with matched class names.
*/
_consumeAllowedAttributes( viewElement, conversionApi ) {
// Make sure that the disabled attributes are handled before the allowed attributes are called.
// For example, for block images the <figure> converter triggers conversion for <img> first and then for other elements, i.e. <a>.
consumeAttributes( viewElement, conversionApi, this._disallowedAttributes );
return consumeAttributes( viewElement, conversionApi, this._allowedAttributes );
}
/**
* Registers elements allowed by {@link module:html-support/datafilter~DataFilter#allowElement} method
* once {@link module:engine/controller/datacontroller~DataController editor's data controller} is initialized.
*
* @private
*/
_registerElementsAfterInit() {
this.editor.data.on( 'init', () => {
this._dataInitialized = true;
for ( const definition of this._allowedElements ) {
this._fireRegisterEvent( definition );
}
}, {
// With highest priority listener we are able to register elements right before
// running data conversion. Also:
// * Make sure that priority is higher than the one used by `RealTimeCollaborationClient`,
// as RTC is stopping event propagation.
// * Make sure no other features hook into this event before GHS because otherwise the
// downcast conversion (for these features) could run before GHS registered its converters
// (https://github.com/ckeditor/ckeditor5/issues/11356).
priority: ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_3__.priorities.get( 'highest' ) + 1
} );
}
/**
* Registers default element handlers.
*
* @private
*/
_registerElementHandlers() {
this.on( 'register', ( evt, definition ) => {
const schema = this.editor.model.schema;
// Object element should be only registered for new features.
// If the model schema is already registered, it should be handled by
// #_registerBlockElement() or #_registerObjectElement() attribute handlers.
if ( definition.isObject && !schema.isRegistered( definition.model ) ) {
this._registerObjectElement( definition );
} else if ( definition.isBlock ) {
this._registerBlockElement( definition );
} else if ( definition.isInline ) {
this._registerInlineElement( definition );
} else {
/**
* The definition cannot be handled by the data filter.
*
* Make sure that the registered definition is correct.
*
* @error data-filter-invalid-definition
*/
throw new ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_3__.CKEditorError(
'data-filter-invalid-definition',
null,
definition
);
}
evt.stop();
}, { priority: 'lowest' } );
}
/**
* Fires `register` event for the given element definition.
*
* @private
* @param {module:html-support/dataschema~DataSchemaDefinition} definition
*/
_fireRegisterEvent( definition ) {
this.fire( definition.view ? `register:${ definition.view }` : 'register', definition );
}
/**
* Registers object element and attribute converters for the given data schema definition.
*
* @private
* @param {module:html-support/dataschema~DataSchemaDefinition} definition
*/
_registerObjectElement( definition ) {
const editor = this.editor;
const schema = editor.model.schema;
const conversion = editor.conversion;
const { view: viewName, model: modelName } = definition;
schema.register( modelName, definition.modelSchema );
if ( !viewName ) {
return;
}
schema.extend( definition.model, {
allowAttributes: [ 'htmlAttributes', 'htmlContent' ]
} );
// Store element content in special `$rawContent` custom property to
// avoid editor's data filtering mechanism.
editor.data.registerRawContentMatcher( {
name: viewName
} );
conversion.for( 'upcast' ).elementToElement( {
view: viewName,
model: (0,_converters__WEBPACK_IMPORTED_MODULE_5__.viewToModelObjectConverter)( definition ),
// With a `low` priority, `paragraph` plugin auto-paragraphing mechanism is executed. Make sure
// this listener is called before it. If not, some elements will be transformed into a paragraph.
converterPriority: ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_3__.priorities.get( 'low' ) + 1
} );
conversion.for( 'upcast' ).add( (0,_converters__WEBPACK_IMPORTED_MODULE_5__.viewToModelBlockAttributeConverter)( definition, this ) );
conversion.for( 'editingDowncast' ).elementToStructure( {
model: modelName,
view: (0,_converters__WEBPACK_IMPORTED_MODULE_5__.toObjectWidgetConverter)( editor, definition )
} );
conversion.for( 'dataDowncast' ).elementToElement( {
model: modelName,
view: ( modelElement, { writer } ) => {
return (0,_converters__WEBPACK_IMPORTED_MODULE_5__.createObjectView)( viewName, modelElement, writer );
}
} );
conversion.for( 'dataDowncast' ).add( (0,_converters__WEBPACK_IMPORTED_MODULE_5__.modelToViewBlockAttributeConverter)( definition ) );
}
/**
* Registers block element and attribute converters for the given data schema definition.
*
* @private
* @param {module:html-support/dataschema~DataSchemaBlockElementDefinition} definition
*/
_registerBlockElement( definition ) {
const editor = this.editor;
const schema = editor.model.schema;
const conversion = editor.conversion;
const { view: viewName, model: modelName } = definition;
if ( !schema.isRegistered( definition.model ) ) {
schema.register( definition.model, definition.modelSchema );
if ( !viewName ) {
return;
}
conversion.for( 'upcast' ).elementToElement( {
model: modelName,
view: viewName,
// With a `low` priority, `paragraph` plugin auto-paragraphing mechanism is executed. Make sure
// this listener is called before it. If not, some elements will be transformed into a paragraph.
converterPriority: ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_3__.priorities.get( 'low' ) + 1
} );
conversion.for( 'downcast' ).elementToElement( {
model: modelName,
view: viewName
} );
}
if ( !viewName ) {
return;
}
schema.extend( definition.model, {
allowAttributes: 'htmlAttributes'
} );
conversion.for( 'upcast' ).add( (0,_converters__WEBPACK_IMPORTED_MODULE_5__.viewToModelBlockAttributeConverter)( definition, this ) );
conversion.for( 'downcast' ).add( (0,_converters__WEBPACK_IMPORTED_MODULE_5__.modelToViewBlockAttributeConverter)( definition ) );
}
/**
* Registers inline element and attribute converters for the given data schema definition.
*
* Extends `$text` model schema to allow the given definition model attribute and its properties.
*
* @private
* @param {module:html-support/dataschema~DataSchemaInlineElementDefinition} definition
*/
_registerInlineElement( definition ) {
const editor = this.editor;
const schema = editor.model.schema;
const conversion = editor.conversion;
const attributeKey = definition.model;
schema.extend( '$text', {
allowAttributes: attributeKey
} );
if ( definition.attributeProperties ) {
schema.setAttributeProperties( attributeKey, definition.attributeProperties );
}
conversion.for( 'upcast' ).add( (0,_converters__WEBPACK_IMPORTED_MODULE_5__.viewToAttributeInlineConverter)( definition, this ) );
conversion.for( 'downcast' ).attributeToElement( {
model: attributeKey,
view: (0,_converters__WEBPACK_IMPORTED_MODULE_5__.attributeToViewInlineConverter)( definition )
} );
}
/**
* Fired when {@link module:html-support/datafilter~DataFilter} is registering element and attribute
* converters for the {@link module:html-support/dataschema~DataSchemaDefinition element definition}.
*
* The event also accepts {@link module:html-support/dataschema~DataSchemaDefinition#view} value
* as an event namespace, e.g. `register:span`.
*
* dataFilter.on( 'register', ( evt, definition ) => {
* editor.schema.register( definition.model, definition.modelSchema );
* editor.conversion.elementToElement( { model: definition.model, view: definition.view } );
*
* evt.stop();
* } );
*
* dataFilter.on( 'register:span', ( evt, definition ) => {
* editor.schema.extend( '$text', { allowAttributes: 'htmlSpan' } );
*
* editor.conversion.for( 'upcast' ).elementToAttribute( { view: 'span', model: 'htmlSpan' } );
* editor.conversion.for( 'downcast' ).attributeToElement( { view: 'span', model: 'htmlSpan' } );
*
* evt.stop();
* }, { priority: 'high' } )
*
* @event register
* @param {module:html-support/dataschema~DataSchemaDefinition} definition
*/
}
// Matches and consumes the given view attributes.
//
// @private
// @param {module:engine/view/element~Element} viewElement
// @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi
// @param {module:engine/view/matcher~Matcher Matcher} matcher
// @returns {Object} [result]
// @returns {Object} result.attributes
// @returns {Object} result.styles
// @returns {Array.<String>} result.classes
function consumeAttributes( viewElement, conversionApi, matcher ) {
const matches = consumeAttributeMatches( viewElement, conversionApi, matcher );
const { attributes, styles, classes } = mergeMatchResults( matches );
const viewAttributes = {};
if ( attributes.size ) {
viewAttributes.attributes = iterableToObject( attributes, key => viewElement.getAttribute( key ) );
}
if ( styles.size ) {
viewAttributes.styles = iterableToObject( styles, key => viewElement.getStyle( key ) );
}
if ( classes.size ) {
viewAttributes.classes = Array.from( classes );
}
if ( !Object.keys( viewAttributes ).length ) {
return null;
}
return viewAttributes;
}
// Consumes matched attributes.
//
// @private
// @param {module:engine/view/element~Element} viewElement
// @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi
// @param {module:engine/view/matcher~Matcher Matcher} matcher
// @returns {Array.<Object>} Array with match information about found attributes.
function consumeAttributeMatches( viewElement, { consumable }, matcher ) {
const matches = matcher.matchAll( viewElement ) || [];
const consumedMatches = [];
for ( const match of matches ) {
removeConsumedAttributes( consumable, viewElement, match );
// We only want to consume attributes, so element can be still processed by other converters.
delete match.match.name;
if ( consumable.consume( viewElement, match.match ) ) {
consumedMatches.push( match );
}
}
return consumedMatches;
}
// Removes attributes from the given match that were already consumed by other converters.
//
// @private
// @param {module:engine/view/element~Element} viewElement
// @param {module:engine/conversion/modelconsumable~ModelConsumable} consumable
// @param {Object} match
function removeConsumedAttributes( consumable, viewElement, match ) {
for ( const key of [ 'attributes', 'classes', 'styles' ] ) {
const attributes = match.match[ key ];
if ( !attributes ) {
continue;
}
for ( const value of attributes ) {
if ( !consumable.test( viewElement, ( { [ key ]: [ value ] } ) ) ) {
(0,lodash_es__WEBPACK_IMPORTED_MODULE_7__["default"])( attributes, value );
}
}
}
}
// Merges the result of {@link module:engine/view/matcher~Matcher#matchAll} method.
//
// @private
// @param {Array.<Object>} matches
// @returns {Object} result
// @returns {Set.<Object>} result.attributes Set with matched attribute names.
// @returns {Set.<Object>} result.styles Set with matched style names.
// @returns {Set.<String>} result.classes Set with matched class names.
function mergeMatchResults( matches ) {
const matchResult = {
attributes: new Set(),
classes: new Set(),
styles: new Set()
};
for ( const match of matches ) {
for ( const key in matchResult ) {
const values = match.match[ key ] || [];
values.forEach( value => matchResult[ key ].add( value ) );
}
}
return matchResult;
}
// Converts the given iterable object into an object.
//
// @private
// @param {Iterable.<String>} iterable
// @param {Function} getValue Should result with value for the given object key.
// @returns {Object}
function iterableToObject( iterable, getValue ) {
const attributesObject = {};
for ( const prop of iterable ) {
const value = getValue( prop );
if ( value !== undefined ) {
attributesObject[ prop ] = getValue( prop );
}
}
return attributesObject;
}
// Matcher by default has to match **all** patterns to count it as an actual match. Splitting the pattern
// into separate patterns means that any matched pattern will be count as a match.
//
// @private
// @param {module:engine/view/matcher~MatcherPattern} pattern Pattern to split.
// @param {String} attributeName Name of the attribute to split (e.g. 'attributes', 'classes', 'styles').
// @returns {Array.<module:engine/view/matcher~MatcherPattern>}
function splitPattern( pattern, attributeName ) {
const { name } = pattern;
if ( (0,lodash_es__WEBPACK_IMPORTED_MODULE_8__["default"])( pattern[ attributeName ] ) ) {
return Object.entries( pattern[ attributeName ] ).map(
( [ key, value ] ) => ( {
name,
[ attributeName ]: {
[ key ]: value
}
} ) );
}
if ( Array.isArray( pattern[ attributeName ] ) ) {
return pattern[ attributeName ].map(
value => ( {
name,
[ attributeName ]: [ value ]
} )
);
}
return [ pattern ];
}
// Rules are matched in conjunction (AND operation), but we want to have a match if *any* of the rules is matched (OR operation).
// By splitting the rules we force the latter effect.
//
// @private
// @param {module:engine/view/matcher~MatcherPattern} rules
// @returns {Array.<module:engine/view/matcher~MatcherPattern>}
function splitRules( rules ) {
const { name, attributes, classes, styles } = rules;
const splittedRules = [];
if ( attributes ) {
splittedRules.push( ...splitPattern( { name, attributes }, 'attributes' ) );
}
if ( classes ) {
splittedRules.push( ...splitPattern( { name, classes }, 'classes' ) );
}
if ( styles ) {
splittedRules.push( ...splitPattern( { name, styles }, 'styles' ) );
}
return splittedRules;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-html-support/src/dataschema.js":
/*!*************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-html-support/src/dataschema.js ***!
\*************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ DataSchema)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/* harmony import */ var _schemadefinitions__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./schemadefinitions */ "./node_modules/@ckeditor/ckeditor5-html-support/src/schemadefinitions.js");
/* harmony import */ var lodash_es__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! lodash-es */ "./node_modules/lodash-es/mergeWith.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module html-support/dataschema
*/
/**
* Holds representation of the extended HTML document type definitions to be used by the
* editor in HTML support.
*
* Data schema is represented by data schema definitions.
*
* To add new definition for block element,
* use {@link module:html-support/dataschema~DataSchema#registerBlockElement} method:
*
* dataSchema.registerBlockElement( {
* view: 'section',
* model: 'my-section',
* modelSchema: {
* inheritAllFrom: '$block'
* }
* } );
*
* To add new definition for inline element,
* use {@link module:html-support/dataschema~DataSchema#registerInlineElement} method:
*
* dataSchema.registerInlineElement( {
* view: 'span',
* model: 'my-span',
* attributeProperties: {
* copyOnEnter: true
* }
* } );
*
* @extends module:core/plugin~Plugin
*/
class DataSchema extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
constructor( editor ) {
super( editor );
/**
* A map of registered data schema definitions.
*
* @readonly
* @private
* @member {Map.<String, module:html-support/dataschema~DataSchemaDefinition>} #_definitions
*/
this._definitions = new Map();
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'DataSchema';
}
/**
* @inheritDoc
*/
init() {
for ( const definition of _schemadefinitions__WEBPACK_IMPORTED_MODULE_2__["default"].block ) {
this.registerBlockElement( definition );
}
for ( const definition of _schemadefinitions__WEBPACK_IMPORTED_MODULE_2__["default"].inline ) {
this.registerInlineElement( definition );
}
}
/**
* Add new data schema definition describing block element.
*
* @param {module:html-support/dataschema~DataSchemaBlockElementDefinition} definition
*/
registerBlockElement( definition ) {
this._definitions.set( definition.model, { ...definition, isBlock: true } );
}
/**
* Add new data schema definition describing inline element.
*
* @param {module:html-support/dataschema~DataSchemaInlineElementDefinition} definition
*/
registerInlineElement( definition ) {
this._definitions.set( definition.model, { ...definition, isInline: true } );
}
/**
* Updates schema definition describing block element with new properties.
*
* Creates new scheme if it doesn't exist.
* Array properties are concatenated with original values.
*
* @param {module:html-support/dataschema~DataSchemaBlockElementDefinition} definition Definition update.
*/
extendBlockElement( definition ) {
this._extendDefinition( { ...definition, isBlock: true } );
}
/**
* Updates schema definition describing inline element with new properties.
*
* Creates new scheme if it doesn't exist.
* Array properties are concatenated with original values.
*
* @param {module:html-support/dataschema~DataSchemaInlineElementDefinition} definition Definition update.
*/
extendInlineElement( definition ) {
this._extendDefinition( { ...definition, isInline: true } );
}
/**
* Returns all definitions matching the given view name.
*
* @param {String|RegExp} viewName
* @param {Boolean} [includeReferences] Indicates if this method should also include definitions of referenced models.
* @returns {Set.<module:html-support/dataschema~DataSchemaDefinition>}
*/
getDefinitionsForView( viewName, includeReferences ) {
const definitions = new Set();
for ( const definition of this._getMatchingViewDefinitions( viewName ) ) {
if ( includeReferences ) {
for ( const reference of this._getReferences( definition.model ) ) {
definitions.add( reference );
}
}
definitions.add( definition );
}
return definitions;
}
/**
* Returns definitions matching the given view name.
*
* @private
* @param {String|RegExp} viewName
* @returns {Array.<module:html-support/dataschema~DataSchemaDefinition>}
*/
_getMatchingViewDefinitions( viewName ) {
return Array.from( this._definitions.values() )
.filter( def => def.view && testViewName( viewName, def.view ) );
}
/**
* Resolves all definition references registered for the given data schema definition.
*
* @private
* @param {String} modelName Data schema model name.
* @returns {Iterable.<module:html-support/dataschema~DataSchemaDefinition>}
*/
* _getReferences( modelName ) {
const { modelSchema } = this._definitions.get( modelName );
if ( !modelSchema ) {
return;
}
const inheritProperties = [ 'inheritAllFrom', 'inheritTypesFrom', 'allowWhere', 'allowContentOf', 'allowAttributesOf' ];
for ( const property of inheritProperties ) {
for ( const referenceName of (0,ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__.toArray)( modelSchema[ property ] || [] ) ) {
const definition = this._definitions.get( referenceName );
if ( referenceName !== modelName && definition ) {
yield* this._getReferences( definition.model );
yield definition;
}
}
}
}
/**
* Updates schema definition with new properties.
*
* Creates new scheme if it doesn't exist.
* Array properties are concatenated with original values.
*
* @private
* @param {module:html-support/dataschema~DataSchemaDefinition} definition Definition update.
*/
_extendDefinition( definition ) {
const currentDefinition = this._definitions.get( definition.model );
const mergedDefinition = (0,lodash_es__WEBPACK_IMPORTED_MODULE_3__["default"])( {}, currentDefinition, definition, ( target, source ) => {
return Array.isArray( target ) ? target.concat( source ) : undefined;
} );
this._definitions.set( definition.model, mergedDefinition );
}
}
// Test view name against the given pattern.
//
// @private
// @param {String|RegExp} pattern
// @param {String} viewName
// @returns {Boolean}
function testViewName( pattern, viewName ) {
if ( typeof pattern === 'string' ) {
return pattern === viewName;
}
if ( pattern instanceof RegExp ) {
return pattern.test( viewName );
}
return false;
}
/**
* A base definition of {@link module:html-support/dataschema~DataSchema data schema}.
*
* @typedef {Object} module:html-support/dataschema~DataSchemaDefinition
* @property {String} model Name of the model.
* @property {String} [view] Name of the view element.
* @property {Boolean} [isObject] Indicates that the definition describes object element.
* @property {module:engine/model/schema~SchemaItemDefinition} [modelSchema] The model schema item definition describing registered model.
*/
/**
* A definition of {@link module:html-support/dataschema~DataSchema data schema} for block elements.
*
* @typedef {Object} module:html-support/dataschema~DataSchemaBlockElementDefinition
* @property {Boolean} isBlock Indicates that the definition describes block element.
* Set by {@link module:html-support/dataschema~DataSchema#registerBlockElement} method.
* @property {String} [paragraphLikeModel] Should be used when an element can behave both as a sectioning element (e.g. article) and
* element accepting only inline content (e.g. paragraph).
* If an element contains only inline content, this option will be used as a model
* name.
* @extends module:html-support/dataschema~DataSchemaDefinition
*/
/**
* A definition of {@link module:html-support/dataschema~DataSchema data schema} for inline elements.
*
* @typedef {Object} module:html-support/dataschema~DataSchemaInlineElementDefinition
* @property {module:engine/model/schema~AttributeProperties} [attributeProperties] Additional metadata describing the model attribute.
* @property {Boolean} isInline Indicates that the definition describes inline element.
* @property {Number} [priority] Element priority. Decides in what order elements are wrapped by
* {@link module:engine/view/downcastwriter~DowncastWriter}.
* Set by {@link module:html-support/dataschema~DataSchema#registerInlineElement} method.
* @extends module:html-support/dataschema~DataSchemaDefinition
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-html-support/src/htmlcomment.js":
/*!**************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-html-support/src/htmlcomment.js ***!
\**************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ HtmlComment)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module html-support/htmlcomment
*/
/**
* The HTML comment feature. It preserves the HTML comments (`<!-- -->`) in the editor data.
*
* For a detailed overview, check the {@glink features/general-html-support#html-comments HTML comment feature documentation}.
*
* @extends module:core/plugin~Plugin
*/
class HtmlComment extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'HtmlComment';
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
// Allow storing comment's content as the $root attribute with the name `$comment:<unique id>`.
editor.model.schema.addAttributeCheck( ( context, attributeName ) => {
if ( context.endsWith( '$root' ) && attributeName.startsWith( '$comment' ) ) {
return true;
}
} );
// Convert the `$comment` view element to `$comment:<unique id>` marker and store its content (the comment itself) as a $root
// attribute. The comment content is needed in the `dataDowncast` pipeline to re-create the comment node.
editor.conversion.for( 'upcast' ).elementToMarker( {
view: '$comment',
model: ( viewElement, { writer } ) => {
const root = this.editor.model.document.getRoot();
const commentContent = viewElement.getCustomProperty( '$rawContent' );
const markerName = `$comment:${ (0,ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__.uid)() }`;
writer.setAttribute( markerName, commentContent, root );
return markerName;
}
} );
// Convert the `$comment` marker to `$comment` UI element with `$rawContent` custom property containing the comment content.
editor.conversion.for( 'dataDowncast' ).markerToElement( {
model: '$comment',
view: ( modelElement, { writer } ) => {
const root = this.editor.model.document.getRoot();
const markerName = modelElement.markerName;
const commentContent = root.getAttribute( markerName );
const comment = writer.createUIElement( '$comment' );
writer.setCustomProperty( '$rawContent', commentContent, comment );
return comment;
}
} );
// Remove comments' markers and their corresponding $root attributes, which are no longer present.
editor.model.document.registerPostFixer( writer => {
const root = editor.model.document.getRoot();
const changedMarkers = editor.model.document.differ.getChangedMarkers();
const changedCommentMarkers = changedMarkers.filter( marker => {
return marker.name.startsWith( '$comment' );
} );
const removedCommentMarkers = changedCommentMarkers.filter( marker => {
const newRange = marker.data.newRange;
return newRange && newRange.root.rootName === '$graveyard';
} );
if ( removedCommentMarkers.length === 0 ) {
return false;
}
for ( const marker of removedCommentMarkers ) {
writer.removeMarker( marker.name );
writer.removeAttribute( marker.name, root );
}
return true;
} );
// Delete all comment markers from the document before setting new data.
editor.data.on( 'set', () => {
for ( const commentMarker of editor.model.markers.getMarkersGroup( '$comment' ) ) {
this.removeHtmlComment( commentMarker.name );
}
}, { priority: 'high' } );
// Delete all comment markers that are within a removed range.
// Delete all comment markers at the limit element boundaries if the whole content of the limit element is removed.
editor.model.on( 'deleteContent', ( evt, [ selection ] ) => {
for ( const range of selection.getRanges() ) {
const limitElement = editor.model.schema.getLimitElement( range );
const firstPosition = editor.model.createPositionAt( limitElement, 0 );
const lastPosition = editor.model.createPositionAt( limitElement, 'end' );
let affectedCommentIDs;
if ( firstPosition.isTouching( range.start ) && lastPosition.isTouching( range.end ) ) {
affectedCommentIDs = this.getHtmlCommentsInRange( editor.model.createRange( firstPosition, lastPosition ) );
} else {
affectedCommentIDs = this.getHtmlCommentsInRange( range, { skipBoundaries: true } );
}
for ( const commentMarkerID of affectedCommentIDs ) {
this.removeHtmlComment( commentMarkerID );
}
}
}, { priority: 'high' } );
}
/**
* Creates an HTML comment on the specified position and returns its ID.
*
* *Note*: If two comments are created at the same position, the second comment will be inserted before the first one.
*
* @param {module:engine/model/position~Position} position
* @param {String} content
* @returns {String} Comment ID. This ID can be later used to e.g. remove the comment from the content.
*/
createHtmlComment( position, content ) {
const id = (0,ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__.uid)();
const editor = this.editor;
const model = editor.model;
const root = model.document.getRoot();
const markerName = `$comment:${ id }`;
return model.change( writer => {
const range = writer.createRange( position );
writer.addMarker( markerName, {
usingOperation: true,
affectsData: true,
range
} );
writer.setAttribute( markerName, content, root );
return markerName;
} );
}
/**
* Removes an HTML comment with the given comment ID.
*
* It does nothing and returns `false` if the comment with the given ID does not exist.
* Otherwise it removes the comment and returns `true`.
*
* Note that a comment can be removed also by removing the content around the comment.
*
* @param {String} commentID The ID of the comment to be removed.
* @returns {Boolean} `true` when the comment with the given ID was removed, `false` otherwise.
*/
removeHtmlComment( commentID ) {
const editor = this.editor;
const root = editor.model.document.getRoot();
const marker = editor.model.markers.get( commentID );
if ( !marker ) {
return false;
}
editor.model.change( writer => {
writer.removeMarker( marker );
writer.removeAttribute( commentID, root );
} );
return true;
}
/**
* Gets the HTML comment data for the comment with a given ID.
*
* Returns `null` if the comment does not exist.
*
* @param {String} commentID
* @returns {module:html-support/htmlcomment~HtmlCommentData}
*/
getHtmlCommentData( commentID ) {
const editor = this.editor;
const marker = editor.model.markers.get( commentID );
const root = editor.model.document.getRoot();
if ( !marker ) {
return null;
}
return {
content: root.getAttribute( commentID ),
position: marker.getStart()
};
}
/**
* Gets all HTML comments in the given range.
*
* By default it includes comments at the range boundaries.
*
* @param {module:engine/model/range~Range} range
* @param {Object} [options]
* @param {Boolean} [options.skipBoundaries=false] When set to `true` the range boundaries will be skipped.
* @returns {Array.<String>} HTML comment IDs
*/
getHtmlCommentsInRange( range, { skipBoundaries = false } = {} ) {
const includeBoundaries = !skipBoundaries;
// Unfortunately, MarkerCollection#getMarkersAtPosition() filters out collapsed markers.
return Array.from( this.editor.model.markers.getMarkersGroup( '$comment' ) )
.filter( marker => isCommentMarkerInRange( marker, range ) )
.map( marker => marker.name );
function isCommentMarkerInRange( commentMarker, range ) {
const position = commentMarker.getRange().start;
return (
( position.isAfter( range.start ) || ( includeBoundaries && position.isEqual( range.start ) ) ) &&
( position.isBefore( range.end ) || ( includeBoundaries && position.isEqual( range.end ) ) )
);
}
}
}
/**
* An interface for the HTML comments data.
*
* It consists of the {@link module:engine/model/position~Position `position`} and `content`.
*
* @typedef {Object} module:html-support/htmlcomment~HtmlCommentData
*
* @property {module:engine/model/position~Position} position
* @property {String} content
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-html-support/src/schemadefinitions.js":
/*!********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-html-support/src/schemadefinitions.js ***!
\********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module html-support/schemadefinitions
*/
// Skipped elements due to HTML deprecation:
// * noframes (not sure if we should provide support for this element. CKE4 is not supporting frameset and frame,
// but it will unpack <frameset><noframes>foobar</noframes></frameset> to <noframes>foobar</noframes>, so there
// may be some content loss. Although using noframes as a standalone element seems invalid)
// * keygen (this one is also empty)
// * applet (support is limited mostly to old IE)
// * basefont (this one is also empty)
// * isindex (basically no support for modern browsers at all)
//
// Skipped elements due to lack empty element support:
// * hr
// * area
// * br
// * command
// * map
// * wbr
// * colgroup -> col
//
// Skipped elements due to complexity:
// * datalist with option elements used as a data source for input[list] element
//
// Skipped elements as they are handled as an object content:
// * track
// * source
// * option
// * param
// * optgroup
//
// Skipped full page HTML elements:
// * body
// * html
// * title
// * head
// * meta
// * link
// * etc...
//
// Skipped hidden elements:
// noscript
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ({
block: [
// Existing features
{
model: 'codeBlock',
view: 'pre'
},
{
model: 'paragraph',
view: 'p'
},
{
model: 'blockQuote',
view: 'blockquote'
},
{
model: 'listItem',
view: 'li'
},
{
model: 'pageBreak',
view: 'div'
},
{
model: 'rawHtml',
view: 'div'
},
{
model: 'table',
view: 'table'
},
{
model: 'tableRow',
view: 'tr'
},
{
model: 'tableCell',
view: 'td'
},
{
model: 'tableCell',
view: 'th'
},
{
model: 'caption',
view: 'caption'
},
{
model: 'caption',
view: 'figcaption'
},
{
model: 'imageBlock',
view: 'img'
},
{
model: 'imageInline',
view: 'img'
},
// Compatibility features
{
model: '$htmlSection',
modelSchema: {
allowChildren: '$block',
allowIn: [ '$root', '$htmlSection' ],
isBlock: true
}
},
{
model: 'htmlP',
view: 'p',
modelSchema: {
inheritAllFrom: '$block'
}
},
{
model: 'htmlBlockquote',
view: 'blockquote',
modelSchema: {
inheritAllFrom: '$htmlSection'
}
},
{
model: 'htmlTable',
view: 'table',
modelSchema: {
allowIn: [ '$htmlSection', '$root' ],
isBlock: true
}
},
{
model: 'htmlTbody',
view: 'tbody',
modelSchema: {
allowIn: 'htmlTable',
isBlock: true
}
},
{
model: 'htmlThead',
view: 'thead',
modelSchema: {
allowIn: 'htmlTable',
isBlock: true
}
},
{
model: 'htmlTfoot',
view: 'tfoot',
modelSchema: {
allowIn: 'htmlTable',
isBlock: true
}
},
{
model: 'htmlCaption',
view: 'caption',
modelSchema: {
allowIn: 'htmlTable',
allowChildren: '$text',
isBlock: true
}
},
{
model: 'htmlTr',
view: 'tr',
modelSchema: {
allowIn: [ 'htmlTable', 'htmlThead', 'htmlTbody' ],
isBlock: true
}
},
// TODO can also include text.
{
model: 'htmlTd',
view: 'td',
modelSchema: {
allowIn: 'htmlTr',
allowChildren: [ '$block', '$htmlSection' ],
isBlock: true
}
},
// TODO can also include text.
{
model: 'htmlTh',
view: 'th',
modelSchema: {
allowIn: 'htmlTr',
allowChildren: [ '$block', '$htmlSection' ],
isBlock: true
}
},
// TODO can also include text.
{
model: 'htmlFigure',
view: 'figure',
modelSchema: {
inheritAllFrom: '$htmlSection',
isBlock: true
}
},
// TODO can also include other block elements.
{
model: 'htmlFigcaption',
view: 'figcaption',
modelSchema: {
allowIn: 'htmlFigure',
allowChildren: '$text',
isBlock: true
}
},
// TODO can also include text.
{
model: 'htmlAddress',
view: 'address',
modelSchema: {
inheritAllFrom: '$htmlSection'
}
},
// TODO can also include text.
{
model: 'htmlAside',
view: 'aside',
modelSchema: {
inheritAllFrom: '$htmlSection'
}
},
// TODO can also include text.
{
model: 'htmlMain',
view: 'main',
modelSchema: {
inheritAllFrom: '$htmlSection'
}
},
// TODO can also include text.
{
model: 'htmlDetails',
view: 'details',
modelSchema: {
inheritAllFrom: '$htmlSection'
}
},
{
model: 'htmlSummary',
view: 'summary',
modelSchema: {
allowChildren: '$text',
allowIn: 'htmlDetails',
isBlock: true
}
},
{
model: 'htmlDiv',
view: 'div',
paragraphLikeModel: 'htmlDivParagraph',
modelSchema: {
inheritAllFrom: '$htmlSection'
}
},
// TODO can also include text.
{
model: 'htmlFieldset',
view: 'fieldset',
modelSchema: {
inheritAllFrom: '$htmlSection'
}
},
// TODO can also include h1-h6.
{
model: 'htmlLegend',
view: 'legend',
modelSchema: {
allowIn: 'htmlFieldset',
allowChildren: '$text'
}
},
// TODO can also include text.
{
model: 'htmlHeader',
view: 'header',
modelSchema: {
inheritAllFrom: '$htmlSection'
}
},
// TODO can also include text.
{
model: 'htmlFooter',
view: 'footer',
modelSchema: {
inheritAllFrom: '$htmlSection'
}
},
// TODO can also include text.
{
model: 'htmlForm',
view: 'form',
modelSchema: {
inheritAllFrom: '$htmlSection'
}
},
{
model: 'htmlHgroup',
view: 'hgroup',
modelSchema: {
allowChildren: [
'htmlH1',
'htmlH2',
'htmlH3',
'htmlH4',
'htmlH5',
'htmlH6'
],
isBlock: true
}
},
{
model: 'htmlH1',
view: 'h1',
modelSchema: {
inheritAllFrom: '$block'
}
},
{
model: 'htmlH2',
view: 'h2',
modelSchema: {
inheritAllFrom: '$block'
}
},
{
model: 'htmlH3',
view: 'h3',
modelSchema: {
inheritAllFrom: '$block'
}
},
{
model: 'htmlH4',
view: 'h4',
modelSchema: {
inheritAllFrom: '$block'
}
},
{
model: 'htmlH5',
view: 'h5',
modelSchema: {
inheritAllFrom: '$block'
}
},
{
model: 'htmlH6',
view: 'h6',
modelSchema: {
inheritAllFrom: '$block'
}
},
{
model: '$htmlList',
modelSchema: {
allowWhere: '$htmlSection',
allowChildren: [ '$htmlList', 'htmlLi' ],
isBlock: true
}
},
{
model: 'htmlDir',
view: 'dir',
modelSchema: {
inheritAllFrom: '$htmlList'
}
},
{
model: 'htmlMenu',
view: 'menu',
modelSchema: {
inheritAllFrom: '$htmlList'
}
},
{
model: 'htmlUl',
view: 'ul',
modelSchema: {
inheritAllFrom: '$htmlList'
}
},
{
model: 'htmlOl',
view: 'ol',
modelSchema: {
inheritAllFrom: '$htmlList'
}
},
// TODO can also include other block elements.
{
model: 'htmlLi',
view: 'li',
modelSchema: {
allowIn: '$htmlList',
allowChildren: '$text',
isBlock: true
}
},
{
model: 'htmlPre',
view: 'pre',
modelSchema: {
inheritAllFrom: '$block'
}
},
{
model: 'htmlArticle',
view: 'article',
modelSchema: {
inheritAllFrom: '$htmlSection'
}
},
{
model: 'htmlSection',
view: 'section',
modelSchema: {
inheritAllFrom: '$htmlSection'
}
},
// TODO can also include text.
{
model: 'htmlNav',
view: 'nav',
modelSchema: {
inheritAllFrom: '$htmlSection'
}
},
{
model: 'htmlDl',
view: 'dl',
modelSchema: {
allowIn: [ '$htmlSection', '$root' ],
allowChildren: [ 'htmlDt', 'htmlDd' ],
isBlock: true
}
},
{
model: 'htmlDt',
view: 'dt',
modelSchema: {
allowChildren: '$block',
isBlock: true
}
},
{
model: 'htmlDd',
view: 'dd',
modelSchema: {
allowChildren: '$block',
isBlock: true
}
},
{
model: 'htmlCenter',
view: 'center',
modelSchema: {
inheritAllFrom: '$htmlSection'
}
},
// Objects
{
model: '$htmlObjectBlock',
isObject: true,
modelSchema: {
isObject: true,
isBlock: true,
allowWhere: '$block'
}
}
],
inline: [
{
model: 'htmlAcronym',
view: 'acronym',
attributeProperties: {
copyOnEnter: true
}
},
{
model: 'htmlTt',
view: 'tt',
attributeProperties: {
copyOnEnter: true
}
},
{
model: 'htmlFont',
view: 'font',
attributeProperties: {
copyOnEnter: true
}
},
{
model: 'htmlTime',
view: 'time',
attributeProperties: {
copyOnEnter: true
}
},
{
model: 'htmlVar',
view: 'var',
attributeProperties: {
copyOnEnter: true
}
},
{
model: 'htmlBig',
view: 'big',
attributeProperties: {
copyOnEnter: true
}
},
{
model: 'htmlSmall',
view: 'small',
attributeProperties: {
copyOnEnter: true
}
},
{
model: 'htmlSamp',
view: 'samp',
attributeProperties: {
copyOnEnter: true
}
},
{
model: 'htmlQ',
view: 'q',
attributeProperties: {
copyOnEnter: true
}
},
{
model: 'htmlOutput',
view: 'output',
attributeProperties: {
copyOnEnter: true
}
},
{
model: 'htmlKbd',
view: 'kbd',
attributeProperties: {
copyOnEnter: true
}
},
{
model: 'htmlBdi',
view: 'bdi',
attributeProperties: {
copyOnEnter: true
}
},
{
model: 'htmlBdo',
view: 'bdo',
attributeProperties: {
copyOnEnter: true
}
},
{
model: 'htmlAbbr',
view: 'abbr',
attributeProperties: {
copyOnEnter: true
}
},
{
model: 'htmlA',
view: 'a',
priority: 5,
attributeProperties: {
copyOnEnter: true
}
},
{
model: 'htmlStrong',
view: 'strong',
attributeProperties: {
copyOnEnter: true
}
},
{
model: 'htmlB',
view: 'b',
attributeProperties: {
copyOnEnter: true
}
},
{
model: 'htmlI',
view: 'i',
attributeProperties: {
copyOnEnter: true
}
},
{
model: 'htmlEm',
view: 'em',
attributeProperties: {
copyOnEnter: true
}
},
{
model: 'htmlS',
view: 's',
attributeProperties: {
copyOnEnter: true
}
},
// TODO According to HTML-spec can behave as div-like element, although CKE4 only handles it as an inline element.
{
model: 'htmlDel',
view: 'del',
attributeProperties: {
copyOnEnter: true
}
},
// TODO According to HTML-spec can behave as div-like element, although CKE4 only handles it as an inline element.
{
model: 'htmlIns',
view: 'ins',
attributeProperties: {
copyOnEnter: true
}
},
{
model: 'htmlU',
view: 'u',
attributeProperties: {
copyOnEnter: true
}
},
{
model: 'htmlSub',
view: 'sub',
attributeProperties: {
copyOnEnter: true
}
},
{
model: 'htmlSup',
view: 'sup',
attributeProperties: {
copyOnEnter: true
}
},
{
model: 'htmlCode',
view: 'code',
attributeProperties: {
copyOnEnter: true
}
},
{
model: 'htmlMark',
view: 'mark',
attributeProperties: {
copyOnEnter: true
}
},
{
model: 'htmlSpan',
view: 'span',
attributeProperties: {
copyOnEnter: true
}
},
{
model: 'htmlCite',
view: 'cite',
attributeProperties: {
copyOnEnter: true
}
},
{
model: 'htmlLabel',
view: 'label',
attributeProperties: {
copyOnEnter: true
}
},
{
model: 'htmlDfn',
view: 'dfn',
attributeProperties: {
copyOnEnter: true
}
},
// Objects
{
model: '$htmlObjectInline',
isObject: true,
modelSchema: {
isObject: true,
isInline: true,
allowWhere: '$text',
allowAttributesOf: '$text'
}
},
{
model: 'htmlObject',
view: 'object',
isObject: true,
modelSchema: {
inheritAllFrom: '$htmlObjectInline'
}
},
{
model: 'htmlIframe',
view: 'iframe',
isObject: true,
modelSchema: {
inheritAllFrom: '$htmlObjectInline'
}
},
{
model: 'htmlInput',
view: 'input',
isObject: true,
modelSchema: {
inheritAllFrom: '$htmlObjectInline'
}
},
{
model: 'htmlButton',
view: 'button',
isObject: true,
modelSchema: {
inheritAllFrom: '$htmlObjectInline'
}
},
{
model: 'htmlTextarea',
view: 'textarea',
isObject: true,
modelSchema: {
inheritAllFrom: '$htmlObjectInline'
}
},
{
model: 'htmlSelect',
view: 'select',
isObject: true,
modelSchema: {
inheritAllFrom: '$htmlObjectInline'
}
},
{
model: 'htmlVideo',
view: 'video',
isObject: true,
modelSchema: {
inheritAllFrom: '$htmlObjectInline'
}
},
{
model: 'htmlEmbed',
view: 'embed',
isObject: true,
modelSchema: {
inheritAllFrom: '$htmlObjectInline'
}
},
{
model: 'htmlOembed',
view: 'oembed',
isObject: true,
modelSchema: {
inheritAllFrom: '$htmlObjectInline'
}
},
{
model: 'htmlAudio',
view: 'audio',
isObject: true,
modelSchema: {
inheritAllFrom: '$htmlObjectInline'
}
},
{
model: 'htmlImg',
view: 'img',
isObject: true,
modelSchema: {
inheritAllFrom: '$htmlObjectInline'
}
},
{
model: 'htmlCanvas',
view: 'canvas',
isObject: true,
modelSchema: {
inheritAllFrom: '$htmlObjectInline'
}
},
// TODO it could be probably represented as non-object element, although it has graphical representation,
// so probably makes more sense to keep it as an object.
{
model: 'htmlMeter',
view: 'meter',
isObject: true,
modelSchema: {
inheritAllFrom: '$htmlObjectInline'
}
},
// TODO it could be probably represented as non-object element, although it has graphical representation,
// so probably makes more sense to keep it as an object.
{
model: 'htmlProgress',
view: 'progress',
isObject: true,
modelSchema: {
inheritAllFrom: '$htmlObjectInline'
}
},
{
model: 'htmlScript',
view: 'script',
modelSchema: {
allowWhere: [ '$text', '$block' ],
isInline: true
}
},
{
model: 'htmlStyle',
view: 'style',
modelSchema: {
allowWhere: [ '$text', '$block' ],
isInline: true
}
}
]
});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-image/src/autoimage.js":
/*!*****************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-image/src/autoimage.js ***!
\*****************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ AutoImage)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_clipboard__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/clipboard */ "./node_modules/ckeditor5/src/clipboard.js");
/* harmony import */ var ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ckeditor5/src/engine */ "./node_modules/ckeditor5/src/engine.js");
/* harmony import */ var ckeditor5_src_undo__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ckeditor5/src/undo */ "./node_modules/ckeditor5/src/undo.js");
/* harmony import */ var ckeditor5_src_typing__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ckeditor5/src/typing */ "./node_modules/ckeditor5/src/typing.js");
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/* harmony import */ var _imageutils__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./imageutils */ "./node_modules/@ckeditor/ckeditor5-image/src/imageutils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module image/autoimage
*/
// Implements the pattern: http(s)://(www.)example.com/path/to/resource.ext?query=params&maybe=too.
const IMAGE_URL_REGEXP = new RegExp( String( /^(http(s)?:\/\/)?[\w-]+\.[\w.~:/[\]@!$&'()*+,;=%-]+/.source +
/\.(jpg|jpeg|png|gif|ico|webp|JPG|JPEG|PNG|GIF|ICO|WEBP)/.source +
/(\?[\w.~:/[\]@!$&'()*+,;=%-]*)?/.source +
/(#[\w.~:/[\]@!$&'()*+,;=%-]*)?$/.source ) );
/**
* The auto-image plugin. It recognizes image links in the pasted content and embeds
* them shortly after they are injected into the document.
*
* @extends module:core/plugin~Plugin
*/
class AutoImage extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [ ckeditor5_src_clipboard__WEBPACK_IMPORTED_MODULE_1__.Clipboard, _imageutils__WEBPACK_IMPORTED_MODULE_6__["default"], ckeditor5_src_undo__WEBPACK_IMPORTED_MODULE_3__.Undo, ckeditor5_src_typing__WEBPACK_IMPORTED_MODULE_4__.Delete ];
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'AutoImage';
}
/**
* @inheritDoc
*/
constructor( editor ) {
super( editor );
/**
* The paste–to–embed `setTimeout` ID. Stored as a property to allow
* cleaning of the timeout.
*
* @private
* @member {Number} #_timeoutId
*/
this._timeoutId = null;
/**
* The position where the `<imageBlock>` element will be inserted after the timeout,
* determined each time a new content is pasted into the document.
*
* @private
* @member {module:engine/model/liveposition~LivePosition} #_positionToInsert
*/
this._positionToInsert = null;
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const modelDocument = editor.model.document;
// We need to listen on `Clipboard#inputTransformation` because we need to save positions of selection.
// After pasting, the content between those positions will be checked for a URL that could be transformed
// into an image.
this.listenTo( editor.plugins.get( 'ClipboardPipeline' ), 'inputTransformation', () => {
const firstRange = modelDocument.selection.getFirstRange();
const leftLivePosition = ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_2__.LivePosition.fromPosition( firstRange.start );
leftLivePosition.stickiness = 'toPrevious';
const rightLivePosition = ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_2__.LivePosition.fromPosition( firstRange.end );
rightLivePosition.stickiness = 'toNext';
modelDocument.once( 'change:data', () => {
this._embedImageBetweenPositions( leftLivePosition, rightLivePosition );
leftLivePosition.detach();
rightLivePosition.detach();
}, { priority: 'high' } );
} );
editor.commands.get( 'undo' ).on( 'execute', () => {
if ( this._timeoutId ) {
ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_5__.global.window.clearTimeout( this._timeoutId );
this._positionToInsert.detach();
this._timeoutId = null;
this._positionToInsert = null;
}
}, { priority: 'high' } );
}
/**
* Analyzes the part of the document between provided positions in search for a URL representing an image.
* When the URL is found, it is automatically converted into an image.
*
* @protected
* @param {module:engine/model/liveposition~LivePosition} leftPosition Left position of the selection.
* @param {module:engine/model/liveposition~LivePosition} rightPosition Right position of the selection.
*/
_embedImageBetweenPositions( leftPosition, rightPosition ) {
const editor = this.editor;
// TODO: Use a marker instead of LiveRange & LivePositions.
const urlRange = new ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_2__.LiveRange( leftPosition, rightPosition );
const walker = urlRange.getWalker( { ignoreElementEnd: true } );
const selectionAttributes = Object.fromEntries( editor.model.document.selection.getAttributes() );
const imageUtils = this.editor.plugins.get( 'ImageUtils' );
let src = '';
for ( const node of walker ) {
if ( node.item.is( '$textProxy' ) ) {
src += node.item.data;
}
}
src = src.trim();
// If the URL does not match the image URL regexp, let's skip that.
if ( !src.match( IMAGE_URL_REGEXP ) ) {
urlRange.detach();
return;
}
// Position will not be available in the `setTimeout` function so let's clone it.
this._positionToInsert = ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_2__.LivePosition.fromPosition( leftPosition );
// This action mustn't be executed if undo was called between pasting and auto-embedding.
this._timeoutId = ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_5__.global.window.setTimeout( () => {
// Do nothing if image element cannot be inserted at the current position.
// See https://github.com/ckeditor/ckeditor5/issues/2763.
// Condition must be checked after timeout - pasting may take place on an element, replacing it. The final position matters.
const imageCommand = editor.commands.get( 'insertImage' );
if ( !imageCommand.isEnabled ) {
urlRange.detach();
return;
}
editor.model.change( writer => {
this._timeoutId = null;
writer.remove( urlRange );
urlRange.detach();
let insertionPosition;
// Check if the position where the element should be inserted is still valid.
// Otherwise leave it as undefined to use the logic of insertImage().
if ( this._positionToInsert.root.rootName !== '$graveyard' ) {
insertionPosition = this._positionToInsert.toPosition();
}
imageUtils.insertImage( { ...selectionAttributes, src }, insertionPosition );
this._positionToInsert.detach();
this._positionToInsert = null;
} );
editor.plugins.get( 'Delete' ).requestUndoOnBackspace();
}, 100 );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-image/src/image.js":
/*!*************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-image/src/image.js ***!
\*************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ Image)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var _imageblock__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./imageblock */ "./node_modules/@ckeditor/ckeditor5-image/src/imageblock.js");
/* harmony import */ var _imageinline__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./imageinline */ "./node_modules/@ckeditor/ckeditor5-image/src/imageinline.js");
/* harmony import */ var _theme_image_css__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../theme/image.css */ "./node_modules/@ckeditor/ckeditor5-image/theme/image.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module image/image
*/
/**
* The image plugin.
*
* For a detailed overview, check the {@glink features/images/images-overview image feature} documentation.
*
* This is a "glue" plugin which loads the following plugins:
*
* * {@link module:image/imageblock~ImageBlock},
* * {@link module:image/imageinline~ImageInline},
*
* Usually, it is used in conjunction with other plugins from this package. See the {@glink api/image package page}
* for more information.
*
* @extends module:core/plugin~Plugin
*/
class Image extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [ _imageblock__WEBPACK_IMPORTED_MODULE_1__["default"], _imageinline__WEBPACK_IMPORTED_MODULE_2__["default"] ];
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'Image';
}
}
/**
* The configuration of the image features. Used by the image features in the `@ckeditor/ckeditor5-image` package.
*
* Read more in {@link module:image/image~ImageConfig}.
*
* @member {module:image/image~ImageConfig} module:core/editor/editorconfig~EditorConfig#image
*/
/**
* The configuration of the image features. Used by the image features in the `@ckeditor/ckeditor5-image` package.
*
* ClassicEditor
* .create( editorElement, {
* image: ... // Image feature options.
* } )
* .then( ... )
* .catch( ... );
*
* See {@link module:core/editor/editorconfig~EditorConfig all editor options}.
*
* @interface ImageConfig
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-image/src/image/converters.js":
/*!************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-image/src/image/converters.js ***!
\************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "downcastImageAttribute": () => (/* binding */ downcastImageAttribute),
/* harmony export */ "downcastSourcesAttribute": () => (/* binding */ downcastSourcesAttribute),
/* harmony export */ "downcastSrcsetAttribute": () => (/* binding */ downcastSrcsetAttribute),
/* harmony export */ "upcastImageFigure": () => (/* binding */ upcastImageFigure),
/* harmony export */ "upcastPicture": () => (/* binding */ upcastPicture)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module image/image/converters
*/
/**
* Returns a function that converts the image view representation:
*
* <figure class="image"><img src="..." alt="..."></img></figure>
*
* to the model representation:
*
* <imageBlock src="..." alt="..."></imageBlock>
*
* The entire content of the `<figure>` element except the first `<img>` is being converted as children
* of the `<imageBlock>` model element.
*
* @protected
* @param {module:image/imageutils~ImageUtils} imageUtils
* @returns {Function}
*/
function upcastImageFigure( imageUtils ) {
return dispatcher => {
dispatcher.on( 'element:figure', converter );
};
function converter( evt, data, conversionApi ) {
// Do not convert if this is not an "image figure".
if ( !conversionApi.consumable.test( data.viewItem, { name: true, classes: 'image' } ) ) {
return;
}
// Find an image element inside the figure element.
const viewImage = imageUtils.findViewImgElement( data.viewItem );
// Do not convert if image element is absent or was already converted.
if ( !viewImage || !conversionApi.consumable.test( viewImage, { name: true } ) ) {
return;
}
// Consume the figure to prevent other converters from processing it again.
conversionApi.consumable.consume( data.viewItem, { name: true, classes: 'image' } );
// Convert view image to model image.
const conversionResult = conversionApi.convertItem( viewImage, data.modelCursor );
// Get image element from conversion result.
const modelImage = (0,ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_0__.first)( conversionResult.modelRange.getItems() );
// When image wasn't successfully converted then finish conversion.
if ( !modelImage ) {
// Revert consumed figure so other features can convert it.
conversionApi.consumable.revert( data.viewItem, { name: true, classes: 'image' } );
return;
}
// Convert rest of the figure element's children as an image children.
conversionApi.convertChildren( data.viewItem, modelImage );
conversionApi.updateConversionResult( modelImage, data );
}
}
/**
* Returns a function that converts the image view representation:
*
* <picture><source ... /><source ... />...<img ... /></picture>
*
* to the model representation as the `sources` attribute:
*
* <image[Block|Inline] ... sources="..."></image[Block|Inline]>
*
* @protected
* @param {module:image/imageutils~ImageUtils} imageUtils
* @returns {Function}
*/
function upcastPicture( imageUtils ) {
const sourceAttributeNames = [ 'srcset', 'media', 'type' ];
return dispatcher => {
dispatcher.on( 'element:picture', converter );
};
function converter( evt, data, conversionApi ) {
const pictureViewElement = data.viewItem;
// Do not convert <picture> if already consumed.
if ( !conversionApi.consumable.test( pictureViewElement, { name: true } ) ) {
return;
}
const sources = new Map();
// Collect all <source /> elements attribute values.
for ( const childSourceElement of pictureViewElement.getChildren() ) {
if ( childSourceElement.is( 'element', 'source' ) ) {
const attributes = {};
for ( const name of sourceAttributeNames ) {
if ( childSourceElement.hasAttribute( name ) ) {
// Don't collect <source /> attribute if already consumed somewhere else.
if ( conversionApi.consumable.test( childSourceElement, { attributes: name } ) ) {
attributes[ name ] = childSourceElement.getAttribute( name );
}
}
}
if ( Object.keys( attributes ).length ) {
sources.set( childSourceElement, attributes );
}
}
}
const imgViewElement = imageUtils.findViewImgElement( pictureViewElement );
// Don't convert when a picture has no <img/> inside (it is broken).
if ( !imgViewElement ) {
return;
}
let modelImage = data.modelCursor.parent;
// - In case of an inline image (cursor parent in a <paragraph>), the <img/> must be converted right away
// because no converter handled it yet and otherwise there would be no model element to set the sources attribute on.
// - In case of a block image, the <figure class="image"> converter (in ImageBlockEditing) converts the
// <img/> right away on its own and the modelCursor is already inside an imageBlock and there's nothing special
// to do here.
if ( !modelImage.is( 'element', 'imageBlock' ) ) {
const conversionResult = conversionApi.convertItem( imgViewElement, data.modelCursor );
// Set image range as conversion result.
data.modelRange = conversionResult.modelRange;
// Continue conversion where image conversion ends.
data.modelCursor = conversionResult.modelCursor;
modelImage = (0,ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_0__.first)( conversionResult.modelRange.getItems() );
}
conversionApi.consumable.consume( pictureViewElement, { name: true } );
// Consume only these <source/> attributes that were actually collected and will be passed on
// to the image model element.
for ( const [ sourceElement, attributes ] of sources ) {
conversionApi.consumable.consume( sourceElement, { attributes: Object.keys( attributes ) } );
}
if ( sources.size ) {
conversionApi.writer.setAttribute( 'sources', Array.from( sources.values() ), modelImage );
}
// Convert rest of the <picture> children as an image children. Other converters may want to consume them.
conversionApi.convertChildren( pictureViewElement, modelImage );
}
}
/**
* Converter used to convert the `srcset` model image attribute to the `srcset`, `sizes` and `width` attributes in the view.
*
* @protected
* @param {module:image/imageutils~ImageUtils} imageUtils
* @param {'imageBlock'|'imageInline'} imageType The type of the image.
* @returns {Function}
*/
function downcastSrcsetAttribute( imageUtils, imageType ) {
return dispatcher => {
dispatcher.on( `attribute:srcset:${ imageType }`, converter );
};
function converter( evt, data, conversionApi ) {
if ( !conversionApi.consumable.consume( data.item, evt.name ) ) {
return;
}
const writer = conversionApi.writer;
const element = conversionApi.mapper.toViewElement( data.item );
const img = imageUtils.findViewImgElement( element );
if ( data.attributeNewValue === null ) {
const srcset = data.attributeOldValue;
if ( srcset.data ) {
writer.removeAttribute( 'srcset', img );
writer.removeAttribute( 'sizes', img );
if ( srcset.width ) {
writer.removeAttribute( 'width', img );
}
}
} else {
const srcset = data.attributeNewValue;
if ( srcset.data ) {
writer.setAttribute( 'srcset', srcset.data, img );
// Always outputting `100vw`. See https://github.com/ckeditor/ckeditor5-image/issues/2.
writer.setAttribute( 'sizes', '100vw', img );
if ( srcset.width ) {
writer.setAttribute( 'width', srcset.width, img );
}
}
}
}
}
/**
* Converts the `source` model attribute to the `<picture><source /><source />...<img /></picture>`
* view structure.
*
* @protected
* @param {module:image/imageutils~ImageUtils} imageUtils
* @returns {Function}
*/
function downcastSourcesAttribute( imageUtils ) {
return dispatcher => {
dispatcher.on( 'attribute:sources:imageBlock', converter );
dispatcher.on( 'attribute:sources:imageInline', converter );
};
function converter( evt, data, conversionApi ) {
if ( !conversionApi.consumable.consume( data.item, evt.name ) ) {
return;
}
const viewWriter = conversionApi.writer;
const element = conversionApi.mapper.toViewElement( data.item );
const imgElement = imageUtils.findViewImgElement( element );
if ( data.attributeNewValue && data.attributeNewValue.length ) {
// Make sure <picture> does not break attribute elements, for instance <a> in linked images.
const pictureElement = viewWriter.createContainerElement( 'picture', null,
data.attributeNewValue.map( sourceAttributes => {
return viewWriter.createEmptyElement( 'source', sourceAttributes );
} ),
{ isAllowedInsideAttributeElement: true }
);
// Collect all wrapping attribute elements.
const attributeElements = [];
let viewElement = imgElement.parent;
while ( viewElement && viewElement.is( 'attributeElement' ) ) {
const parentElement = viewElement.parent;
viewWriter.unwrap( viewWriter.createRangeOn( imgElement ), viewElement );
attributeElements.unshift( viewElement );
viewElement = parentElement;
}
// Insert the picture and move img into it.
viewWriter.insert( viewWriter.createPositionBefore( imgElement ), pictureElement );
viewWriter.move( viewWriter.createRangeOn( imgElement ), viewWriter.createPositionAt( pictureElement, 'end' ) );
// Apply collected attribute elements over the new picture element.
for ( const attributeElement of attributeElements ) {
viewWriter.wrap( viewWriter.createRangeOn( pictureElement ), attributeElement );
}
}
// Both setting "sources" to an empty array and removing the attribute should unwrap the <img />.
// Unwrap once if the latter followed the former, though.
else if ( imgElement.parent.is( 'element', 'picture' ) ) {
const pictureElement = imgElement.parent;
viewWriter.move( viewWriter.createRangeOn( imgElement ), viewWriter.createPositionBefore( pictureElement ) );
viewWriter.remove( pictureElement );
}
}
}
/**
* Converter used to convert a given image attribute from the model to the view.
*
* @protected
* @param {module:image/imageutils~ImageUtils} imageUtils
* @param {'imageBlock'|'imageInline'} imageType The type of the image.
* @param {String} attributeKey The name of the attribute to convert.
* @returns {Function}
*/
function downcastImageAttribute( imageUtils, imageType, attributeKey ) {
return dispatcher => {
dispatcher.on( `attribute:${ attributeKey }:${ imageType }`, converter );
};
function converter( evt, data, conversionApi ) {
if ( !conversionApi.consumable.consume( data.item, evt.name ) ) {
return;
}
const viewWriter = conversionApi.writer;
const element = conversionApi.mapper.toViewElement( data.item );
const img = imageUtils.findViewImgElement( element );
viewWriter.setAttribute( data.attributeKey, data.attributeNewValue || '', img );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-image/src/image/imageblockediting.js":
/*!*******************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-image/src/image/imageblockediting.js ***!
\*******************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ImageBlockEditing)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_clipboard__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/clipboard */ "./node_modules/ckeditor5/src/clipboard.js");
/* harmony import */ var ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ckeditor5/src/engine */ "./node_modules/ckeditor5/src/engine.js");
/* harmony import */ var _converters__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./converters */ "./node_modules/@ckeditor/ckeditor5-image/src/image/converters.js");
/* harmony import */ var _imageediting__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./imageediting */ "./node_modules/@ckeditor/ckeditor5-image/src/image/imageediting.js");
/* harmony import */ var _imagetypecommand__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./imagetypecommand */ "./node_modules/@ckeditor/ckeditor5-image/src/image/imagetypecommand.js");
/* harmony import */ var _imageutils__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ../imageutils */ "./node_modules/@ckeditor/ckeditor5-image/src/imageutils.js");
/* harmony import */ var _image_utils__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ../image/utils */ "./node_modules/@ckeditor/ckeditor5-image/src/image/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module image/image/imageblockediting
*/
/**
* The image block plugin.
*
* It registers:
*
* * `<imageBlock>` as a block element in the document schema, and allows `alt`, `src` and `srcset` attributes.
* * converters for editing and data pipelines.,
* * {@link module:image/image/imagetypecommand~ImageTypeCommand `'imageTypeBlock'`} command that converts inline images into
* block images.
*
* @extends module:core/plugin~Plugin
*/
class ImageBlockEditing extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [ _imageediting__WEBPACK_IMPORTED_MODULE_4__["default"], _imageutils__WEBPACK_IMPORTED_MODULE_6__["default"], ckeditor5_src_clipboard__WEBPACK_IMPORTED_MODULE_1__.ClipboardPipeline ];
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'ImageBlockEditing';
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const schema = editor.model.schema;
// Converters 'alt' and 'srcset' are added in 'ImageEditing' plugin.
schema.register( 'imageBlock', {
isObject: true,
isBlock: true,
allowWhere: '$block',
allowAttributes: [ 'alt', 'src', 'srcset' ]
} );
this._setupConversion();
if ( editor.plugins.has( 'ImageInlineEditing' ) ) {
editor.commands.add( 'imageTypeBlock', new _imagetypecommand__WEBPACK_IMPORTED_MODULE_5__["default"]( this.editor, 'imageBlock' ) );
this._setupClipboardIntegration();
}
}
/**
* Configures conversion pipelines to support upcasting and downcasting
* block images (block image widgets) and their attributes.
*
* @private
*/
_setupConversion() {
const editor = this.editor;
const t = editor.t;
const conversion = editor.conversion;
const imageUtils = editor.plugins.get( 'ImageUtils' );
conversion.for( 'dataDowncast' )
.elementToStructure( {
model: 'imageBlock',
view: ( modelElement, { writer } ) => (0,_image_utils__WEBPACK_IMPORTED_MODULE_7__.createBlockImageViewElement)( writer )
} );
conversion.for( 'editingDowncast' )
.elementToStructure( {
model: 'imageBlock',
view: ( modelElement, { writer } ) => imageUtils.toImageWidget(
(0,_image_utils__WEBPACK_IMPORTED_MODULE_7__.createBlockImageViewElement)( writer ), writer, t( 'image widget' )
)
} );
conversion.for( 'downcast' )
.add( (0,_converters__WEBPACK_IMPORTED_MODULE_3__.downcastImageAttribute)( imageUtils, 'imageBlock', 'src' ) )
.add( (0,_converters__WEBPACK_IMPORTED_MODULE_3__.downcastImageAttribute)( imageUtils, 'imageBlock', 'alt' ) )
.add( (0,_converters__WEBPACK_IMPORTED_MODULE_3__.downcastSrcsetAttribute)( imageUtils, 'imageBlock' ) );
// More image related upcasts are in 'ImageEditing' plugin.
conversion.for( 'upcast' )
.elementToElement( {
view: (0,_image_utils__WEBPACK_IMPORTED_MODULE_7__.getImgViewElementMatcher)( editor, 'imageBlock' ),
model: ( viewImage, { writer } ) => writer.createElement(
'imageBlock',
viewImage.hasAttribute( 'src' ) ? { src: viewImage.getAttribute( 'src' ) } : null
)
} )
.add( (0,_converters__WEBPACK_IMPORTED_MODULE_3__.upcastImageFigure)( imageUtils ) );
}
/**
* Integrates the plugin with the clipboard pipeline.
*
* Idea is that the feature should recognize the user's intent when an **inline** image is
* pasted or dropped. If such an image is pasted/dropped:
*
* * into an empty block (e.g. an empty paragraph),
* * on another object (e.g. some block widget).
*
* it gets converted into a block image on the fly. We assume this is the user's intent
* if they decided to put their image there.
*
* See the `ImageInlineEditing` for the similar integration that works in the opposite direction.
*
* @private
*/
_setupClipboardIntegration() {
const editor = this.editor;
const model = editor.model;
const editingView = editor.editing.view;
const imageUtils = editor.plugins.get( 'ImageUtils' );
this.listenTo( editor.plugins.get( 'ClipboardPipeline' ), 'inputTransformation', ( evt, data ) => {
const docFragmentChildren = Array.from( data.content.getChildren() );
let modelRange;
// Make sure only <img> elements are dropped or pasted. Otherwise, if there some other HTML
// mixed up, this should be handled as a regular paste.
if ( !docFragmentChildren.every( imageUtils.isInlineImageView ) ) {
return;
}
// When drag and dropping, data.targetRanges specifies where to drop because
// this is usually a different place than the current model selection (the user
// uses a drop marker to specify the drop location).
if ( data.targetRanges ) {
modelRange = editor.editing.mapper.toModelRange( data.targetRanges[ 0 ] );
}
// Pasting, however, always occurs at the current model selection.
else {
modelRange = model.document.selection.getFirstRange();
}
const selection = model.createSelection( modelRange );
// Convert inline images into block images only when the currently selected block is empty
// (e.g. an empty paragraph) or some object is selected (to replace it).
if ( (0,_image_utils__WEBPACK_IMPORTED_MODULE_7__.determineImageTypeForInsertionAtSelection)( model.schema, selection ) === 'imageBlock' ) {
const writer = new ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_2__.UpcastWriter( editingView.document );
// Wrap <img ... /> -> <figure class="image"><img .../></figure>
const blockViewImages = docFragmentChildren.map(
inlineViewImage => writer.createElement( 'figure', { class: 'image' }, inlineViewImage )
);
data.content = writer.createDocumentFragment( blockViewImages );
}
} );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-image/src/image/imageediting.js":
/*!**************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-image/src/image/imageediting.js ***!
\**************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ImageEditing)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var _imageloadobserver__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./imageloadobserver */ "./node_modules/@ckeditor/ckeditor5-image/src/image/imageloadobserver.js");
/* harmony import */ var _insertimagecommand__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./insertimagecommand */ "./node_modules/@ckeditor/ckeditor5-image/src/image/insertimagecommand.js");
/* harmony import */ var _imageutils__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../imageutils */ "./node_modules/@ckeditor/ckeditor5-image/src/imageutils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module image/image/imageediting
*/
/**
* The image engine plugin. This module loads common code shared between
* {@link module:image/image/imageinlineediting~ImageInlineEditing} and
* {@link module:image/image/imageblockediting~ImageBlockEditing} plugins.
*
* This plugin registers the {@link module:image/image/insertimagecommand~InsertImageCommand 'insertImage'} command.
*
* @extends module:core/plugin~Plugin
*/
class ImageEditing extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [ _imageutils__WEBPACK_IMPORTED_MODULE_3__["default"] ];
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'ImageEditing';
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const conversion = editor.conversion;
// See https://github.com/ckeditor/ckeditor5-image/issues/142.
editor.editing.view.addObserver( _imageloadobserver__WEBPACK_IMPORTED_MODULE_1__["default"] );
conversion.for( 'upcast' )
.attributeToAttribute( {
view: {
name: 'img',
key: 'alt'
},
model: 'alt'
} )
.attributeToAttribute( {
view: {
name: 'img',
key: 'srcset'
},
model: {
key: 'srcset',
value: viewImage => {
const value = {
data: viewImage.getAttribute( 'srcset' )
};
if ( viewImage.hasAttribute( 'width' ) ) {
value.width = viewImage.getAttribute( 'width' );
}
return value;
}
}
} );
const insertImageCommand = new _insertimagecommand__WEBPACK_IMPORTED_MODULE_2__["default"]( editor );
// Register `insertImage` command and add `imageInsert` command as an alias for backward compatibility.
editor.commands.add( 'insertImage', insertImageCommand );
editor.commands.add( 'imageInsert', insertImageCommand );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-image/src/image/imageinlineediting.js":
/*!********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-image/src/image/imageinlineediting.js ***!
\********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ImageInlineEditing)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_clipboard__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/clipboard */ "./node_modules/ckeditor5/src/clipboard.js");
/* harmony import */ var ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ckeditor5/src/engine */ "./node_modules/ckeditor5/src/engine.js");
/* harmony import */ var _converters__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./converters */ "./node_modules/@ckeditor/ckeditor5-image/src/image/converters.js");
/* harmony import */ var _imageediting__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./imageediting */ "./node_modules/@ckeditor/ckeditor5-image/src/image/imageediting.js");
/* harmony import */ var _imagetypecommand__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./imagetypecommand */ "./node_modules/@ckeditor/ckeditor5-image/src/image/imagetypecommand.js");
/* harmony import */ var _imageutils__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ../imageutils */ "./node_modules/@ckeditor/ckeditor5-image/src/imageutils.js");
/* harmony import */ var _image_utils__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ../image/utils */ "./node_modules/@ckeditor/ckeditor5-image/src/image/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module image/image/imageinlineediting
*/
/**
* The image inline plugin.
*
* It registers:
*
* * `<imageInline>` as an inline element in the document schema, and allows `alt`, `src` and `srcset` attributes.
* * converters for editing and data pipelines.
* * {@link module:image/image/imagetypecommand~ImageTypeCommand `'imageTypeInline'`} command that converts block images into
* inline images.
*
* @extends module:core/plugin~Plugin
*/
class ImageInlineEditing extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [ _imageediting__WEBPACK_IMPORTED_MODULE_4__["default"], _imageutils__WEBPACK_IMPORTED_MODULE_6__["default"], ckeditor5_src_clipboard__WEBPACK_IMPORTED_MODULE_1__.ClipboardPipeline ];
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'ImageInlineEditing';
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const schema = editor.model.schema;
// Converters 'alt' and 'srcset' are added in 'ImageEditing' plugin.
schema.register( 'imageInline', {
isObject: true,
isInline: true,
allowWhere: '$text',
allowAttributesOf: '$text',
allowAttributes: [ 'alt', 'src', 'srcset' ]
} );
// Disallow inline images in captions (for now). This is the best spot to do that because
// independent packages can introduce captions (ImageCaption, TableCaption, etc.) so better this
// be future-proof.
schema.addChildCheck( ( context, childDefinition ) => {
if ( context.endsWith( 'caption' ) && childDefinition.name === 'imageInline' ) {
return false;
}
} );
this._setupConversion();
if ( editor.plugins.has( 'ImageBlockEditing' ) ) {
editor.commands.add( 'imageTypeInline', new _imagetypecommand__WEBPACK_IMPORTED_MODULE_5__["default"]( this.editor, 'imageInline' ) );
this._setupClipboardIntegration();
}
}
/**
* Configures conversion pipelines to support upcasting and downcasting
* inline images (inline image widgets) and their attributes.
*
* @private
*/
_setupConversion() {
const editor = this.editor;
const t = editor.t;
const conversion = editor.conversion;
const imageUtils = editor.plugins.get( 'ImageUtils' );
conversion.for( 'dataDowncast' )
.elementToElement( {
model: 'imageInline',
view: ( modelElement, { writer } ) => writer.createEmptyElement( 'img' )
} );
conversion.for( 'editingDowncast' )
.elementToStructure( {
model: 'imageInline',
view: ( modelElement, { writer } ) => imageUtils.toImageWidget(
(0,_image_utils__WEBPACK_IMPORTED_MODULE_7__.createInlineImageViewElement)( writer ), writer, t( 'image widget' )
)
} );
conversion.for( 'downcast' )
.add( (0,_converters__WEBPACK_IMPORTED_MODULE_3__.downcastImageAttribute)( imageUtils, 'imageInline', 'src' ) )
.add( (0,_converters__WEBPACK_IMPORTED_MODULE_3__.downcastImageAttribute)( imageUtils, 'imageInline', 'alt' ) )
.add( (0,_converters__WEBPACK_IMPORTED_MODULE_3__.downcastSrcsetAttribute)( imageUtils, 'imageInline' ) );
// More image related upcasts are in 'ImageEditing' plugin.
conversion.for( 'upcast' )
.elementToElement( {
view: (0,_image_utils__WEBPACK_IMPORTED_MODULE_7__.getImgViewElementMatcher)( editor, 'imageInline' ),
model: ( viewImage, { writer } ) => writer.createElement(
'imageInline',
viewImage.hasAttribute( 'src' ) ? { src: viewImage.getAttribute( 'src' ) } : null
)
} );
}
/**
* Integrates the plugin with the clipboard pipeline.
*
* Idea is that the feature should recognize the user's intent when an **block** image is
* pasted or dropped. If such an image is pasted/dropped into a non-empty block
* (e.g. a paragraph with some text) it gets converted into an inline image on the fly.
*
* We assume this is the user's intent if they decided to put their image there.
*
* **Note**: If a block image has a caption, it will not be converted to an inline image
* to avoid the confusion. Captions are added on purpose and they should never be lost
* in the clipboard pipeline.
*
* See the `ImageBlockEditing` for the similar integration that works in the opposite direction.
*
* @private
*/
_setupClipboardIntegration() {
const editor = this.editor;
const model = editor.model;
const editingView = editor.editing.view;
const imageUtils = editor.plugins.get( 'ImageUtils' );
this.listenTo( editor.plugins.get( 'ClipboardPipeline' ), 'inputTransformation', ( evt, data ) => {
const docFragmentChildren = Array.from( data.content.getChildren() );
let modelRange;
// Make sure only <figure class="image"></figure> elements are dropped or pasted. Otherwise, if there some other HTML
// mixed up, this should be handled as a regular paste.
if ( !docFragmentChildren.every( imageUtils.isBlockImageView ) ) {
return;
}
// When drag and dropping, data.targetRanges specifies where to drop because
// this is usually a different place than the current model selection (the user
// uses a drop marker to specify the drop location).
if ( data.targetRanges ) {
modelRange = editor.editing.mapper.toModelRange( data.targetRanges[ 0 ] );
}
// Pasting, however, always occurs at the current model selection.
else {
modelRange = model.document.selection.getFirstRange();
}
const selection = model.createSelection( modelRange );
// Convert block images into inline images only when pasting or dropping into non-empty blocks
// and when the block is not an object (e.g. pasting to replace another widget).
if ( (0,_image_utils__WEBPACK_IMPORTED_MODULE_7__.determineImageTypeForInsertionAtSelection)( model.schema, selection ) === 'imageInline' ) {
const writer = new ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_2__.UpcastWriter( editingView.document );
// Unwrap <figure class="image"><img .../></figure> -> <img ... />
// but <figure class="image"><img .../><figcaption>...</figcaption></figure> -> stays the same
const inlineViewImages = docFragmentChildren.map( blockViewImage => {
// If there's just one child, it can be either <img /> or <a><img></a>.
// If there are other children than <img>, this means that the block image
// has a caption or some other features and this kind of image should be
// pasted/dropped without modifications.
if ( blockViewImage.childCount === 1 ) {
// Pass the attributes which are present only in the <figure> to the <img>
// (e.g. the style="width:10%" attribute applied by the ImageResize plugin).
Array.from( blockViewImage.getAttributes() )
.forEach( attribute => writer.setAttribute(
...attribute,
imageUtils.findViewImgElement( blockViewImage )
) );
return blockViewImage.getChild( 0 );
} else {
return blockViewImage;
}
} );
data.content = writer.createDocumentFragment( inlineViewImages );
}
} );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-image/src/image/imageloadobserver.js":
/*!*******************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-image/src/image/imageloadobserver.js ***!
\*******************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ImageLoadObserver)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/engine */ "./node_modules/ckeditor5/src/engine.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module image/image/imageloadobserver
*/
/**
* Observes all new images added to the {@link module:engine/view/document~Document},
* fires {@link module:engine/view/document~Document#event:imageLoaded} and
* {@link module:engine/view/document~Document#event:layoutChanged} event every time when the new image
* has been loaded.
*
* **Note:** This event is not fired for images that has been added to the document and rendered as `complete` (already loaded).
*
* @extends module:engine/view/observer/observer~Observer
*/
class ImageLoadObserver extends ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_0__.Observer {
/**
* @inheritDoc
*/
observe( domRoot ) {
this.listenTo( domRoot, 'load', ( event, domEvent ) => {
const domElement = domEvent.target;
if ( this.checkShouldIgnoreEventFromTarget( domElement ) ) {
return;
}
if ( domElement.tagName == 'IMG' ) {
this._fireEvents( domEvent );
}
// Use capture phase for better performance (#4504).
}, { useCapture: true } );
}
/**
* Fires {@link module:engine/view/document~Document#event:layoutChanged} and
* {@link module:engine/view/document~Document#event:imageLoaded}
* if observer {@link #isEnabled is enabled}.
*
* @protected
* @param {Event} domEvent The DOM event.
*/
_fireEvents( domEvent ) {
if ( this.isEnabled ) {
this.document.fire( 'layoutChanged' );
this.document.fire( 'imageLoaded', domEvent );
}
}
}
/**
* Fired when an <img/> DOM element has been loaded in the DOM root.
*
* Introduced by {@link module:image/image/imageloadobserver~ImageLoadObserver}.
*
* @see module:image/image/imageloadobserver~ImageLoadObserver
* @event module:engine/view/document~Document#event:imageLoaded
* @param {module:engine/view/observer/domeventdata~DomEventData} data Event data.
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-image/src/image/imagetypecommand.js":
/*!******************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-image/src/image/imagetypecommand.js ***!
\******************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ImageTypeCommand)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module image/image/imagetypecommand
*/
/**
* The image type command. It changes the type of a selected image, depending on the configuration.
*
* @extends module:core/command~Command
*/
class ImageTypeCommand extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Command {
/**
* @inheritDoc
*
* @param {module:core/editor/editor~Editor} editor
* @param {'imageBlock'|'imageInline'} modelElementName Model element name the command converts to.
*/
constructor( editor, modelElementName ) {
super( editor );
/**
* Model element name the command converts to.
*
* @readonly
* @private
* @member {'imageBlock'|'imageInline'}
*/
this._modelElementName = modelElementName;
}
/**
* @inheritDoc
*/
refresh() {
const editor = this.editor;
const imageUtils = editor.plugins.get( 'ImageUtils' );
const element = imageUtils.getClosestSelectedImageElement( this.editor.model.document.selection );
if ( this._modelElementName === 'imageBlock' ) {
this.isEnabled = imageUtils.isInlineImage( element );
} else {
this.isEnabled = imageUtils.isBlockImage( element );
}
}
/**
* Executes the command and changes the type of a selected image.
*
* @fires execute
* @returns {Object|null} An object containing references to old and new model image elements
* (for before and after the change) so external integrations can hook into the decorated
* `execute` event and handle this change. `null` if the type change failed.
*/
execute() {
const editor = this.editor;
const model = this.editor.model;
const imageUtils = editor.plugins.get( 'ImageUtils' );
const oldElement = imageUtils.getClosestSelectedImageElement( model.document.selection );
const attributes = Object.fromEntries( oldElement.getAttributes() );
// Don't change image type if "src" is missing (a broken image), unless there's "uploadId" set.
// This state may happen during image upload (before it finishes) and it should be possible to change type
// of the image in the meantime.
if ( !attributes.src && !attributes.uploadId ) {
return null;
}
return model.change( writer => {
// Get all markers that contain the old image element.
const markers = Array.from( model.markers )
.filter( marker => marker.getRange().containsItem( oldElement ) );
const newElement = imageUtils.insertImage( attributes, model.createSelection( oldElement, 'on' ), this._modelElementName );
if ( !newElement ) {
return null;
}
const newElementRange = writer.createRangeOn( newElement );
// Expand the previously intersecting markers' ranges to include the new image element.
for ( const marker of markers ) {
const markerRange = marker.getRange();
// Join the survived part of the old marker range with the new element range
// (loosely because there could be some new paragraph or the existing one might got split).
const range = markerRange.root.rootName != '$graveyard' ?
markerRange.getJoined( newElementRange, true ) : newElementRange;
writer.updateMarker( marker, { range } );
}
return {
oldElement,
newElement
};
} );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-image/src/image/insertimagecommand.js":
/*!********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-image/src/image/insertimagecommand.js ***!
\********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ InsertImageCommand)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module image/image/insertimagecommand
*/
/**
* Insert image command.
*
* The command is registered by the {@link module:image/image/imageediting~ImageEditing} plugin as `insertImage`
* and it is also available via aliased `imageInsert` name.
*
* In order to insert an image at the current selection position
* (according to the {@link module:widget/utils~findOptimalInsertionRange} algorithm),
* execute the command and specify the image source:
*
* editor.execute( 'insertImage', { source: 'http://url.to.the/image' } );
*
* It is also possible to insert multiple images at once:
*
* editor.execute( 'insertImage', {
* source: [
* 'path/to/image.jpg',
* 'path/to/other-image.jpg'
* ]
* } );
*
* If you want to take the full control over the process, you can specify individual model attributes:
*
* editor.execute( 'insertImage', {
* source: [
* { src: 'path/to/image.jpg', alt: 'First alt text' },
* { src: 'path/to/other-image.jpg', alt: 'Second alt text', customAttribute: 'My attribute value' }
* ]
* } );
*
* @extends module:core/command~Command
*/
class InsertImageCommand extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Command {
/**
* @inheritDoc
*/
constructor( editor ) {
super( editor );
const configImageInsertType = editor.config.get( 'image.insert.type' );
if ( !editor.plugins.has( 'ImageBlockEditing' ) ) {
if ( configImageInsertType === 'block' ) {
/**
* The {@link module:image/imageblock~ImageBlock} plugin must be enabled to allow inserting block images. See
* {@link module:image/imageinsert~ImageInsertConfig#type} to learn more.
*
* @error image-block-plugin-required
*/
(0,ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__.logWarning)( 'image-block-plugin-required' );
}
}
if ( !editor.plugins.has( 'ImageInlineEditing' ) ) {
if ( configImageInsertType === 'inline' ) {
/**
* The {@link module:image/imageinline~ImageInline} plugin must be enabled to allow inserting inline images. See
* {@link module:image/imageinsert~ImageInsertConfig#type} to learn more.
*
* @error image-inline-plugin-required
*/
(0,ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__.logWarning)( 'image-inline-plugin-required' );
}
}
}
/**
* @inheritDoc
*/
refresh() {
this.isEnabled = this.editor.plugins.get( 'ImageUtils' ).isImageAllowed();
}
/**
* Executes the command.
*
* @fires execute
* @param {Object} options Options for the executed command.
* @param {String|Array.<String>|Array.<Object>} options.source The image source or an array of image sources to insert.
* See the documentation of the command to learn more about accepted formats.
*/
execute( options ) {
const sourceDefinitions = (0,ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__.toArray)( options.source );
const selection = this.editor.model.document.selection;
const imageUtils = this.editor.plugins.get( 'ImageUtils' );
// In case of multiple images, each image (starting from the 2nd) will be inserted at a position that
// follows the previous one. That will move the selection and, to stay on the safe side and make sure
// all images inherit the same selection attributes, they are collected beforehand.
//
// Applying these attributes ensures, for instance, that inserting an (inline) image into a link does
// not split that link but preserves its continuity.
//
// Note: Selection attributes that do not make sense for images will be filtered out by insertImage() anyway.
const selectionAttributes = Object.fromEntries( selection.getAttributes() );
sourceDefinitions.forEach( ( sourceDefinition, index ) => {
const selectedElement = selection.getSelectedElement();
if ( typeof sourceDefinition === 'string' ) {
sourceDefinition = { src: sourceDefinition };
}
// Inserting of an inline image replace the selected element and make a selection on the inserted image.
// Therefore inserting multiple inline images requires creating position after each element.
if ( index && selectedElement && imageUtils.isImage( selectedElement ) ) {
const position = this.editor.model.createPositionAfter( selectedElement );
imageUtils.insertImage( { ...sourceDefinition, ...selectionAttributes }, position );
} else {
imageUtils.insertImage( { ...sourceDefinition, ...selectionAttributes } );
}
} );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-image/src/image/ui/utils.js":
/*!**********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-image/src/image/ui/utils.js ***!
\**********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "getBalloonPositionData": () => (/* binding */ getBalloonPositionData),
/* harmony export */ "repositionContextualBalloon": () => (/* binding */ repositionContextualBalloon)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/ui */ "./node_modules/ckeditor5/src/ui.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module image/image/ui/utils
*/
/**
* A helper utility that positions the
* {@link module:ui/panel/balloon/contextualballoon~ContextualBalloon contextual balloon} instance
* with respect to the image in the editor content, if one is selected.
*
* @param {module:core/editor/editor~Editor} editor The editor instance.
*/
function repositionContextualBalloon( editor ) {
const balloon = editor.plugins.get( 'ContextualBalloon' );
if ( editor.plugins.get( 'ImageUtils' ).getClosestSelectedImageWidget( editor.editing.view.document.selection ) ) {
const position = getBalloonPositionData( editor );
balloon.updatePosition( position );
}
}
/**
* Returns the positioning options that control the geometry of the
* {@link module:ui/panel/balloon/contextualballoon~ContextualBalloon contextual balloon} with respect
* to the selected element in the editor content.
*
* @param {module:core/editor/editor~Editor} editor The editor instance.
* @returns {module:utils/dom/position~Options}
*/
function getBalloonPositionData( editor ) {
const editingView = editor.editing.view;
const defaultPositions = ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.BalloonPanelView.defaultPositions;
const imageUtils = editor.plugins.get( 'ImageUtils' );
return {
target: editingView.domConverter.viewToDom( imageUtils.getClosestSelectedImageWidget( editingView.document.selection ) ),
positions: [
defaultPositions.northArrowSouth,
defaultPositions.northArrowSouthWest,
defaultPositions.northArrowSouthEast,
defaultPositions.southArrowNorth,
defaultPositions.southArrowNorthWest,
defaultPositions.southArrowNorthEast,
defaultPositions.viewportStickyNorth
]
};
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-image/src/image/utils.js":
/*!*******************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-image/src/image/utils.js ***!
\*******************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "createBlockImageViewElement": () => (/* binding */ createBlockImageViewElement),
/* harmony export */ "createInlineImageViewElement": () => (/* binding */ createInlineImageViewElement),
/* harmony export */ "determineImageTypeForInsertionAtSelection": () => (/* binding */ determineImageTypeForInsertionAtSelection),
/* harmony export */ "getImgViewElementMatcher": () => (/* binding */ getImgViewElementMatcher)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module image/image/utils
*/
/**
* Creates a view element representing the inline image.
*
* <span class="image-inline"><img></img></span>
*
* Note that `alt` and `src` attributes are converted separately, so they are not included.
*
* @protected
* @param {module:engine/view/downcastwriter~DowncastWriter} writer
* @returns {module:engine/view/containerelement~ContainerElement}
*/
function createInlineImageViewElement( writer ) {
return writer.createContainerElement( 'span', { class: 'image-inline' },
writer.createEmptyElement( 'img' ),
{ isAllowedInsideAttributeElement: true }
);
}
/**
* Creates a view element representing the block image.
*
* <figure class="image"><img></img></figure>
*
* Note that `alt` and `src` attributes are converted separately, so they are not included.
*
* @protected
* @param {module:engine/view/downcastwriter~DowncastWriter} writer
* @returns {module:engine/view/containerelement~ContainerElement}
*/
function createBlockImageViewElement( writer ) {
return writer.createContainerElement( 'figure', { class: 'image' }, [
writer.createEmptyElement( 'img' ),
writer.createSlot()
] );
}
/**
* A function returning a `MatcherPattern` for a particular type of View images.
*
* @protected
* @param {module:core/editor/editor~Editor} editor
* @param {'imageBlock'|'imageInline'} matchImageType The type of created image.
* @returns {module:engine/view/matcher~MatcherPattern}
*/
function getImgViewElementMatcher( editor, matchImageType ) {
if ( editor.plugins.has( 'ImageInlineEditing' ) !== editor.plugins.has( 'ImageBlockEditing' ) ) {
return { name: 'img' };
}
const imageUtils = editor.plugins.get( 'ImageUtils' );
return element => {
// Check if view element is an `img`.
if ( !imageUtils.isInlineImageView( element ) ) {
return null;
}
// The <img> can be standalone, wrapped in <figure>...</figure> (ImageBlock plugin) or
// wrapped in <figure><a>...</a></figure> (LinkImage plugin).
const imageType = element.findAncestor( imageUtils.isBlockImageView ) ? 'imageBlock' : 'imageInline';
if ( imageType !== matchImageType ) {
return null;
}
return { name: true };
};
}
/**
* Considering the current model selection, it returns the name of the model image element
* (`'imageBlock'` or `'imageInline'`) that will make most sense from the UX perspective if a new
* image was inserted (also: uploaded, dropped, pasted) at that selection.
*
* The assumption is that inserting images into empty blocks or on other block widgets should
* produce block images. Inline images should be inserted in other cases, e.g. in paragraphs
* that already contain some text.
*
* @protected
* @param {module:engine/model/schema~Schema} schema
* @param {module:engine/model/selection~Selection|module:engine/model/documentselection~DocumentSelection} selection
* @returns {'imageBlock'|'imageInline'}
*/
function determineImageTypeForInsertionAtSelection( schema, selection ) {
const firstBlock = (0,ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_0__.first)( selection.getSelectedBlocks() );
// Insert a block image if the selection is not in/on block elements or it's on a block widget.
if ( !firstBlock || schema.isObject( firstBlock ) ) {
return 'imageBlock';
}
// A block image should also be inserted into an empty block element
// (that is not an empty list item so the list won't get split).
if ( firstBlock.isEmpty && firstBlock.name != 'listItem' ) {
return 'imageBlock';
}
// Otherwise insert an inline image.
return 'imageInline';
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-image/src/imageblock.js":
/*!******************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-image/src/imageblock.js ***!
\******************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ImageBlock)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_widget__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/widget */ "./node_modules/ckeditor5/src/widget.js");
/* harmony import */ var _imagetextalternative__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./imagetextalternative */ "./node_modules/@ckeditor/ckeditor5-image/src/imagetextalternative.js");
/* harmony import */ var _image_imageblockediting__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./image/imageblockediting */ "./node_modules/@ckeditor/ckeditor5-image/src/image/imageblockediting.js");
/* harmony import */ var _theme_image_css__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../theme/image.css */ "./node_modules/@ckeditor/ckeditor5-image/theme/image.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module image/imageblock
*/
/**
* The image inline plugin.
*
* This is a "glue" plugin which loads the following plugins:
*
* * {@link module:image/image/imageblockediting~ImageBlockEditing},
* * {@link module:image/imagetextalternative~ImageTextAlternative}.
*
* Usually, it is used in conjunction with other plugins from this package. See the {@glink api/image package page}
* for more information.
*
* @extends module:core/plugin~Plugin
*/
class ImageBlock extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [ _image_imageblockediting__WEBPACK_IMPORTED_MODULE_3__["default"], ckeditor5_src_widget__WEBPACK_IMPORTED_MODULE_1__.Widget, _imagetextalternative__WEBPACK_IMPORTED_MODULE_2__["default"] ];
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'ImageBlock';
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-image/src/imagecaption.js":
/*!********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-image/src/imagecaption.js ***!
\********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ImageCaption)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var _imagecaption_imagecaptionediting__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./imagecaption/imagecaptionediting */ "./node_modules/@ckeditor/ckeditor5-image/src/imagecaption/imagecaptionediting.js");
/* harmony import */ var _imagecaption_imagecaptionui__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./imagecaption/imagecaptionui */ "./node_modules/@ckeditor/ckeditor5-image/src/imagecaption/imagecaptionui.js");
/* harmony import */ var _theme_imagecaption_css__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../theme/imagecaption.css */ "./node_modules/@ckeditor/ckeditor5-image/theme/imagecaption.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module image/imagecaption
*/
/**
* The image caption plugin.
*
* For a detailed overview, check the {@glink features/images/images-captions image caption} documentation.
*
* @extends module:core/plugin~Plugin
*/
class ImageCaption extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [ _imagecaption_imagecaptionediting__WEBPACK_IMPORTED_MODULE_1__["default"], _imagecaption_imagecaptionui__WEBPACK_IMPORTED_MODULE_2__["default"] ];
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'ImageCaption';
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-image/src/imagecaption/imagecaptionediting.js":
/*!****************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-image/src/imagecaption/imagecaptionediting.js ***!
\****************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ImageCaptionEditing)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/engine */ "./node_modules/ckeditor5/src/engine.js");
/* harmony import */ var ckeditor5_src_widget__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ckeditor5/src/widget */ "./node_modules/ckeditor5/src/widget.js");
/* harmony import */ var _toggleimagecaptioncommand__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./toggleimagecaptioncommand */ "./node_modules/@ckeditor/ckeditor5-image/src/imagecaption/toggleimagecaptioncommand.js");
/* harmony import */ var _imageutils__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../imageutils */ "./node_modules/@ckeditor/ckeditor5-image/src/imageutils.js");
/* harmony import */ var _imagecaptionutils__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./imagecaptionutils */ "./node_modules/@ckeditor/ckeditor5-image/src/imagecaption/imagecaptionutils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module image/imagecaption/imagecaptionediting
*/
/**
* The image caption engine plugin. It is responsible for:
*
* * registering converters for the caption element,
* * registering converters for the caption model attribute,
* * registering the {@link module:image/imagecaption/toggleimagecaptioncommand~ToggleImageCaptionCommand `toggleImageCaption`} command.
*
* @extends module:core/plugin~Plugin
*/
class ImageCaptionEditing extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [ _imageutils__WEBPACK_IMPORTED_MODULE_4__["default"], _imagecaptionutils__WEBPACK_IMPORTED_MODULE_5__["default"] ];
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'ImageCaptionEditing';
}
/**
* @inheritDoc
*/
constructor( editor ) {
super( editor );
/**
* A map that keeps saved JSONified image captions and image model elements they are
* associated with.
*
* To learn more about this system, see {@link #_saveCaption}.
*
* @member {WeakMap.<module:engine/model/element~Element,Object>}
*/
this._savedCaptionsMap = new WeakMap();
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const schema = editor.model.schema;
// Schema configuration.
if ( !schema.isRegistered( 'caption' ) ) {
schema.register( 'caption', {
allowIn: 'imageBlock',
allowContentOf: '$block',
isLimit: true
} );
} else {
schema.extend( 'caption', {
allowIn: 'imageBlock'
} );
}
editor.commands.add( 'toggleImageCaption', new _toggleimagecaptioncommand__WEBPACK_IMPORTED_MODULE_3__["default"]( this.editor ) );
this._setupConversion();
this._setupImageTypeCommandsIntegration();
}
/**
* Configures conversion pipelines to support upcasting and downcasting
* image captions.
*
* @private
*/
_setupConversion() {
const editor = this.editor;
const view = editor.editing.view;
const imageUtils = editor.plugins.get( 'ImageUtils' );
const imageCaptionUtils = editor.plugins.get( 'ImageCaptionUtils' );
const t = editor.t;
// View -> model converter for the data pipeline.
editor.conversion.for( 'upcast' ).elementToElement( {
view: element => imageCaptionUtils.matchImageCaptionViewElement( element ),
model: 'caption'
} );
// Model -> view converter for the data pipeline.
editor.conversion.for( 'dataDowncast' ).elementToElement( {
model: 'caption',
view: ( modelElement, { writer } ) => {
if ( !imageUtils.isBlockImage( modelElement.parent ) ) {
return null;
}
return writer.createContainerElement( 'figcaption' );
}
} );
// Model -> view converter for the editing pipeline.
editor.conversion.for( 'editingDowncast' ).elementToElement( {
model: 'caption',
view: ( modelElement, { writer } ) => {
if ( !imageUtils.isBlockImage( modelElement.parent ) ) {
return null;
}
const figcaptionElement = writer.createEditableElement( 'figcaption' );
writer.setCustomProperty( 'imageCaption', true, figcaptionElement );
(0,ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_1__.enablePlaceholder)( {
view,
element: figcaptionElement,
text: t( 'Enter image caption' ),
keepOnFocus: true
} );
return (0,ckeditor5_src_widget__WEBPACK_IMPORTED_MODULE_2__.toWidgetEditable)( figcaptionElement, writer );
}
} );
}
/**
* Integrates with {@link module:image/image/imagetypecommand~ImageTypeCommand image type commands}
* to make sure the caption is preserved when the type of an image changes so it can be restored
* in the future if the user decides they want their caption back.
*
* @private
*/
_setupImageTypeCommandsIntegration() {
const editor = this.editor;
const imageUtils = editor.plugins.get( 'ImageUtils' );
const imageCaptionUtils = editor.plugins.get( 'ImageCaptionUtils' );
const imageTypeInlineCommand = editor.commands.get( 'imageTypeInline' );
const imageTypeBlockCommand = editor.commands.get( 'imageTypeBlock' );
const handleImageTypeChange = evt => {
// The image type command execution can be unsuccessful.
if ( !evt.return ) {
return;
}
const { oldElement, newElement } = evt.return;
/* istanbul ignore if: paranoid check */
if ( !oldElement ) {
return;
}
if ( imageUtils.isBlockImage( oldElement ) ) {
const oldCaptionElement = imageCaptionUtils.getCaptionFromImageModelElement( oldElement );
// If the old element was a captioned block image (the caption was visible),
// simply save it so it can be restored.
if ( oldCaptionElement ) {
this._saveCaption( newElement, oldCaptionElement );
return;
}
}
const savedOldElementCaption = this._getSavedCaption( oldElement );
// If either:
//
// * the block image didn't have a visible caption,
// * the block image caption was hidden (and already saved),
// * the inline image was passed
//
// just try to "pass" the saved caption from the old image to the new image
// so it can be retrieved in the future if the user wants it back.
if ( savedOldElementCaption ) {
// Note: Since we're writing to a WeakMap, we don't bother with removing the
// [ oldElement, savedOldElementCaption ] pair from it.
this._saveCaption( newElement, savedOldElementCaption );
}
};
// Presence of the commands depends on the Image(Inline|Block)Editing plugins loaded in the editor.
if ( imageTypeInlineCommand ) {
this.listenTo( imageTypeInlineCommand, 'execute', handleImageTypeChange, { priority: 'low' } );
}
if ( imageTypeBlockCommand ) {
this.listenTo( imageTypeBlockCommand, 'execute', handleImageTypeChange, { priority: 'low' } );
}
}
/**
* Returns the saved {@link module:engine/model/element~Element#toJSON JSONified} caption
* of an image model element.
*
* See {@link #_saveCaption}.
*
* @protected
* @param {module:engine/model/element~Element} imageModelElement The model element the
* caption should be returned for.
* @returns {module:engine/model/element~Element|null} The model caption element or `null` if there is none.
*/
_getSavedCaption( imageModelElement ) {
const jsonObject = this._savedCaptionsMap.get( imageModelElement );
return jsonObject ? ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_1__.Element.fromJSON( jsonObject ) : null;
}
/**
* Saves a {@link module:engine/model/element~Element#toJSON JSONified} caption for
* an image element to allow restoring it in the future.
*
* A caption is saved every time it gets hidden and/or the type of an image changes. The
* user should be able to restore it on demand.
*
* **Note**: The caption cannot be stored in the image model element attribute because,
* for instance, when the model state propagates to collaborators, the attribute would get
* lost (mainly because it does not convert to anything when the caption is hidden) and
* the states of collaborators' models would de-synchronize causing numerous issues.
*
* See {@link #_getSavedCaption}.
*
* @protected
* @param {module:engine/model/element~Element} imageModelElement The model element the
* caption is saved for.
* @param {module:engine/model/element~Element} caption The caption model element to be saved.
*/
_saveCaption( imageModelElement, caption ) {
this._savedCaptionsMap.set( imageModelElement, caption.toJSON() );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-image/src/imagecaption/imagecaptionui.js":
/*!***********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-image/src/imagecaption/imagecaptionui.js ***!
\***********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ImageCaptionUI)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/ui */ "./node_modules/ckeditor5/src/ui.js");
/* harmony import */ var _imagecaptionutils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./imagecaptionutils */ "./node_modules/@ckeditor/ckeditor5-image/src/imagecaption/imagecaptionutils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module image/imagecaption/imagecaptionui
*/
/**
* The image caption UI plugin. It introduces the `'toggleImageCaption'` UI button.
*
* @extends module:core/plugin~Plugin
*/
class ImageCaptionUI extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [ _imagecaptionutils__WEBPACK_IMPORTED_MODULE_2__["default"] ];
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'ImageCaptionUI';
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const editingView = editor.editing.view;
const imageCaptionUtils = editor.plugins.get( 'ImageCaptionUtils' );
const t = editor.t;
editor.ui.componentFactory.add( 'toggleImageCaption', locale => {
const command = editor.commands.get( 'toggleImageCaption' );
const view = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.ButtonView( locale );
view.set( {
icon: ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.icons.caption,
tooltip: true,
isToggleable: true
} );
view.bind( 'isOn', 'isEnabled' ).to( command, 'value', 'isEnabled' );
view.bind( 'label' ).to( command, 'value', value => value ? t( 'Toggle caption off' ) : t( 'Toggle caption on' ) );
this.listenTo( view, 'execute', () => {
editor.execute( 'toggleImageCaption', { focusCaptionOnShow: true } );
// Scroll to the selection and highlight the caption if the caption showed up.
const modelCaptionElement = imageCaptionUtils.getCaptionFromModelSelection( editor.model.document.selection );
if ( modelCaptionElement ) {
const figcaptionElement = editor.editing.mapper.toViewElement( modelCaptionElement );
editingView.scrollToTheSelection();
editingView.change( writer => {
writer.addClass( 'image__caption_highlighted', figcaptionElement );
} );
}
} );
return view;
} );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-image/src/imagecaption/imagecaptionutils.js":
/*!**************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-image/src/imagecaption/imagecaptionutils.js ***!
\**************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ImageCaptionUtils)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var _imageutils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../imageutils */ "./node_modules/@ckeditor/ckeditor5-image/src/imageutils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module image/imagecaptionutils/utils
*/
/**
* The image caption utilities plugin.
*
* @extends module:core/plugin~Plugin
*/
class ImageCaptionUtils extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'ImageCaptionUtils';
}
/**
* @inheritDoc
*/
static get requires() {
return [ _imageutils__WEBPACK_IMPORTED_MODULE_1__["default"] ];
}
/**
* Returns the caption model element from a given image element. Returns `null` if no caption is found.
*
* @param {module:engine/model/element~Element} imageModelElement
* @returns {module:engine/model/element~Element|null}
*/
getCaptionFromImageModelElement( imageModelElement ) {
for ( const node of imageModelElement.getChildren() ) {
if ( !!node && node.is( 'element', 'caption' ) ) {
return node;
}
}
return null;
}
/**
* Returns the caption model element for a model selection. Returns `null` if the selection has no caption element ancestor.
*
* @param {module:engine/model/selection~Selection} selection
* @returns {module:engine/model/element~Element|null}
*/
getCaptionFromModelSelection( selection ) {
const imageUtils = this.editor.plugins.get( 'ImageUtils' );
const captionElement = selection.getFirstPosition().findAncestor( 'caption' );
if ( !captionElement ) {
return null;
}
if ( imageUtils.isBlockImage( captionElement.parent ) ) {
return captionElement;
}
return null;
}
/**
* {@link module:engine/view/matcher~Matcher} pattern. Checks if a given element is a `<figcaption>` element that is placed
* inside the image `<figure>` element.
*
* @param {module:engine/view/element~Element} element
* @returns {Object|null} Returns the object accepted by {@link module:engine/view/matcher~Matcher} or `null` if the element
* cannot be matched.
*/
matchImageCaptionViewElement( element ) {
const imageUtils = this.editor.plugins.get( 'ImageUtils' );
// Convert only captions for images.
if ( element.name == 'figcaption' && imageUtils.isBlockImageView( element.parent ) ) {
return { name: true };
}
return null;
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-image/src/imagecaption/toggleimagecaptioncommand.js":
/*!**********************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-image/src/imagecaption/toggleimagecaptioncommand.js ***!
\**********************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ToggleImageCaptionCommand)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var _image_imageblockediting__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../image/imageblockediting */ "./node_modules/@ckeditor/ckeditor5-image/src/image/imageblockediting.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module image/imagecaption/toggleimagecaptioncommand
*/
/**
* The toggle image caption command.
*
* This command is registered by {@link module:image/imagecaption/imagecaptionediting~ImageCaptionEditing} as the
* `'toggleImageCaption'` editor command.
*
* Executing this command:
*
* * either adds or removes the image caption of a selected image (depending on whether the caption is present or not),
* * removes the image caption if the selection is anchored in one.
*
* // Toggle the presence of the caption.
* editor.execute( 'toggleImageCaption' );
*
* **Note**: Upon executing this command, the selection will be set on the image if previously anchored in the caption element.
*
* **Note**: You can move the selection to the caption right away as it shows up upon executing this command by using
* the `focusCaptionOnShow` option:
*
* editor.execute( 'toggleImageCaption', { focusCaptionOnShow: true } );
*
* @extends module:core/command~Command
*/
class ToggleImageCaptionCommand extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Command {
/**
* @inheritDoc
*/
refresh() {
const editor = this.editor;
const imageCaptionUtils = editor.plugins.get( 'ImageCaptionUtils' );
// Only block images can get captions.
if ( !editor.plugins.has( _image_imageblockediting__WEBPACK_IMPORTED_MODULE_1__["default"] ) ) {
this.isEnabled = false;
this.value = false;
return;
}
const selection = editor.model.document.selection;
const selectedElement = selection.getSelectedElement();
if ( !selectedElement ) {
const ancestorCaptionElement = imageCaptionUtils.getCaptionFromModelSelection( selection );
this.isEnabled = !!ancestorCaptionElement;
this.value = !!ancestorCaptionElement;
return;
}
// Block images support captions by default but the command should also be enabled for inline
// images because toggling the caption when one is selected should convert it into a block image.
this.isEnabled = this.editor.plugins.get( 'ImageUtils' ).isImage( selectedElement );
if ( !this.isEnabled ) {
this.value = false;
} else {
this.value = !!imageCaptionUtils.getCaptionFromImageModelElement( selectedElement );
}
}
/**
* Executes the command.
*
* editor.execute( 'toggleImageCaption' );
*
* @param {Object} [options] Options for the executed command.
* @param {String} [options.focusCaptionOnShow] When true and the caption shows up, the selection will be moved into it straight away.
* @fires execute
*/
execute( options = {} ) {
const { focusCaptionOnShow } = options;
this.editor.model.change( writer => {
if ( this.value ) {
this._hideImageCaption( writer );
} else {
this._showImageCaption( writer, focusCaptionOnShow );
}
} );
}
/**
* Shows the caption of the `<imageBlock>` or `<imageInline>`. Also:
*
* * it converts `<imageInline>` to `<imageBlock>` to show the caption,
* * it attempts to restore the caption content from the `ImageCaptionEditing` caption registry,
* * it moves the selection to the caption right away, it the `focusCaptionOnShow` option was set.
*
* @private
* @param {module:engine/model/writer~Writer} writer
*/
_showImageCaption( writer, focusCaptionOnShow ) {
const model = this.editor.model;
const selection = model.document.selection;
const imageCaptionEditing = this.editor.plugins.get( 'ImageCaptionEditing' );
let selectedImage = selection.getSelectedElement();
const savedCaption = imageCaptionEditing._getSavedCaption( selectedImage );
// Convert imageInline -> image first.
if ( this.editor.plugins.get( 'ImageUtils' ).isInlineImage( selectedImage ) ) {
this.editor.execute( 'imageTypeBlock' );
// Executing the command created a new model element. Let's pick it again.
selectedImage = selection.getSelectedElement();
}
// Try restoring the caption from the ImageCaptionEditing plugin storage.
const newCaptionElement = savedCaption || writer.createElement( 'caption' );
writer.append( newCaptionElement, selectedImage );
if ( focusCaptionOnShow ) {
writer.setSelection( newCaptionElement, 'in' );
}
}
/**
* Hides the caption of a selected image (or an image caption the selection is anchored to).
*
* The content of the caption is stored in the `ImageCaptionEditing` caption registry to make this
* a reversible action.
*
* @private
* @param {module:engine/model/writer~Writer} writer
*/
_hideImageCaption( writer ) {
const editor = this.editor;
const selection = editor.model.document.selection;
const imageCaptionEditing = editor.plugins.get( 'ImageCaptionEditing' );
const imageCaptionUtils = editor.plugins.get( 'ImageCaptionUtils' );
let selectedImage = selection.getSelectedElement();
let captionElement;
if ( selectedImage ) {
captionElement = imageCaptionUtils.getCaptionFromImageModelElement( selectedImage );
} else {
captionElement = imageCaptionUtils.getCaptionFromModelSelection( selection );
selectedImage = captionElement.parent;
}
// Store the caption content so it can be restored quickly if the user changes their mind even if they toggle image<->imageInline.
imageCaptionEditing._saveCaption( selectedImage, captionElement );
writer.setSelection( selectedImage, 'on' );
writer.remove( captionElement );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-image/src/imageinline.js":
/*!*******************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-image/src/imageinline.js ***!
\*******************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ImageInline)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_widget__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/widget */ "./node_modules/ckeditor5/src/widget.js");
/* harmony import */ var _imagetextalternative__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./imagetextalternative */ "./node_modules/@ckeditor/ckeditor5-image/src/imagetextalternative.js");
/* harmony import */ var _image_imageinlineediting__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./image/imageinlineediting */ "./node_modules/@ckeditor/ckeditor5-image/src/image/imageinlineediting.js");
/* harmony import */ var _theme_image_css__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../theme/image.css */ "./node_modules/@ckeditor/ckeditor5-image/theme/image.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module image/imageinline
*/
/**
* The image inline plugin.
*
* This is a "glue" plugin which loads the following plugins:
*
* * {@link module:image/image/imageinlineediting~ImageInlineEditing},
* * {@link module:image/imagetextalternative~ImageTextAlternative}.
*
* Usually, it is used in conjunction with other plugins from this package. See the {@glink api/image package page}
* for more information.
*
* @extends module:core/plugin~Plugin
*/
class ImageInline extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [ _image_imageinlineediting__WEBPACK_IMPORTED_MODULE_3__["default"], ckeditor5_src_widget__WEBPACK_IMPORTED_MODULE_1__.Widget, _imagetextalternative__WEBPACK_IMPORTED_MODULE_2__["default"] ];
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'ImageInline';
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-image/src/imageinsert.js":
/*!*******************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-image/src/imageinsert.js ***!
\*******************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ImageInsert)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var _imageupload__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./imageupload */ "./node_modules/@ckeditor/ckeditor5-image/src/imageupload.js");
/* harmony import */ var _imageinsert_imageinsertui__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./imageinsert/imageinsertui */ "./node_modules/@ckeditor/ckeditor5-image/src/imageinsert/imageinsertui.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module image/imageinsert
*/
/**
* The image insert plugin.
*
* For a detailed overview, check the {@glink features/images/image-upload/image-upload Image upload feature}
* and {@glink features/images/image-upload/images-inserting#inserting-images-via-source-url Insert images via source URL} documentation.
*
* This plugin does not do anything directly, but it loads a set of specific plugins
* to enable image uploading or inserting via implemented integrations:
*
* * {@link module:image/imageupload~ImageUpload}
* * {@link module:image/imageinsert/imageinsertui~ImageInsertUI},
*
* @extends module:core/plugin~Plugin
*/
class ImageInsert extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'ImageInsert';
}
/**
* @inheritDoc
*/
static get requires() {
return [ _imageupload__WEBPACK_IMPORTED_MODULE_1__["default"], _imageinsert_imageinsertui__WEBPACK_IMPORTED_MODULE_2__["default"] ];
}
}
/**
* The image insert configuration.
*
* @member {module:image/imageinsert~ImageInsertConfig} module:image/image~ImageConfig#insert
*/
/**
* The configuration of the image insert dropdown panel view. Used by the image insert feature in the `@ckeditor/ckeditor5-image` package.
*
* ClassicEditor
* .create( editorElement, {
* image: {
* insert: {
* ... // settings for "insertImage" view goes here
* }
* }
* } )
* .then( ... )
* .catch( ... );
*
* See {@link module:core/editor/editorconfig~EditorConfig all editor options}.
*
* @interface module:image/imageinsert~ImageInsertConfig
*/
/**
* The image insert panel view configuration contains a list of {@link module:image/imageinsert~ImageInsert} integrations.
*
* The option accepts string tokens.
* * for predefined integrations, we have two special strings: `insertImageViaUrl` and `openCKFinder`.
* The former adds the **Insert image via URL** feature, while the latter adds the built-in **CKFinder** integration.
* * for custom integrations, each string should be a name of the component registered in the
* {@link module:ui/componentfactory~ComponentFactory component factory}.
* If you have a plugin `PluginX` that registers `pluginXButton` component, then the integration token
* in that case should be `pluginXButton`.
*
* // Add `insertImageViaUrl`, `openCKFinder` and custom `pluginXButton` integrations.
* const imageInsertConfig = {
* insert: {
* integrations: [
* 'insertImageViaUrl',
* 'openCKFinder',
* 'pluginXButton'
* ]
* }
* };
*
* @protected
* @member {Array.<String>} module:image/imageinsert~ImageInsertConfig#integrations
* @default [ 'insertImageViaUrl' ]
*/
/**
* This options allows to override the image type used by the {@link module:image/image/insertimagecommand~InsertImageCommand} when the user
* inserts new images into the editor content. By default, this option is unset which means the editor will choose the optimal image type
* based on the context of the insertion (e.g. the current selection and availability of plugins)
*
* Available options are:
*
* * `'block'` – all images inserted into the editor will be block (requires the {@link module:image/imageblock~ImageBlock} plugin),
* * `'inline'` – all images inserted into the editor will be inline (requires the {@link module:image/imageinline~ImageInline} plugin).
*
* @member {'inline'|'block'|undefined} module:image/imageinsert~ImageInsertConfig#type
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-image/src/imageinsert/imageinsertui.js":
/*!*********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-image/src/imageinsert/imageinsertui.js ***!
\*********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ImageInsertUI)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var _ui_imageinsertpanelview__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./ui/imageinsertpanelview */ "./node_modules/@ckeditor/ckeditor5-image/src/imageinsert/ui/imageinsertpanelview.js");
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./utils */ "./node_modules/@ckeditor/ckeditor5-image/src/imageinsert/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module image/imageinsert/imageinsertui
*/
/**
* The image insert dropdown plugin.
*
* For a detailed overview, check the {@glink features/images/image-upload/image-upload Image upload feature}
* and {@glink features/images/image-upload/images-inserting#inserting-images-via-source-url Insert images via source URL} documentation.
*
* Adds the `'insertImage'` dropdown to the {@link module:ui/componentfactory~ComponentFactory UI component factory}
* and also the `imageInsert` dropdown as an alias for backward compatibility.
*
* @extends module:core/plugin~Plugin
*/
class ImageInsertUI extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'ImageInsertUI';
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const componentCreator = locale => {
return this._createDropdownView( locale );
};
// Register `insertImage` dropdown and add `imageInsert` dropdown as an alias for backward compatibility.
editor.ui.componentFactory.add( 'insertImage', componentCreator );
editor.ui.componentFactory.add( 'imageInsert', componentCreator );
}
/**
* Creates the dropdown view.
*
* @param {module:utils/locale~Locale} locale The localization services instance.
*
* @private
* @returns {module:ui/dropdown/dropdownview~DropdownView}
*/
_createDropdownView( locale ) {
const editor = this.editor;
const imageInsertView = new _ui_imageinsertpanelview__WEBPACK_IMPORTED_MODULE_1__["default"]( locale, (0,_utils__WEBPACK_IMPORTED_MODULE_2__.prepareIntegrations)( editor ) );
const command = editor.commands.get( 'uploadImage' );
const dropdownView = imageInsertView.dropdownView;
const splitButtonView = dropdownView.buttonView;
splitButtonView.actionView = editor.ui.componentFactory.create( 'uploadImage' );
// After we replaced action button with `uploadImage` component,
// we have lost a proper styling and some minor visual quirks have appeared.
// Brining back original split button classes helps fix the button styling
// See https://github.com/ckeditor/ckeditor5/issues/7986.
splitButtonView.actionView.extendTemplate( {
attributes: {
class: 'ck ck-button ck-splitbutton__action'
}
} );
return this._setUpDropdown( dropdownView, imageInsertView, command );
}
/**
* Sets up the dropdown view.
*
* @param {module:ui/dropdown/dropdownview~DropdownView} dropdownView A dropdownView.
* @param {module:image/imageinsert/ui/imageinsertpanelview~ImageInsertPanelView} imageInsertView An imageInsertView.
* @param {module:core/command~Command} command An insertImage command
*
* @private
* @returns {module:ui/dropdown/dropdownview~DropdownView}
*/
_setUpDropdown( dropdownView, imageInsertView, command ) {
const editor = this.editor;
const t = editor.t;
const insertButtonView = imageInsertView.insertButtonView;
const insertImageViaUrlForm = imageInsertView.getIntegration( 'insertImageViaUrl' );
const panelView = dropdownView.panelView;
const imageUtils = this.editor.plugins.get( 'ImageUtils' );
dropdownView.bind( 'isEnabled' ).to( command );
// Defer the children injection to improve initial performance.
// See https://github.com/ckeditor/ckeditor5/pull/8019#discussion_r484069652.
dropdownView.buttonView.once( 'open', () => {
panelView.children.add( imageInsertView );
} );
dropdownView.on( 'change:isOpen', () => {
const selectedElement = editor.model.document.selection.getSelectedElement();
if ( dropdownView.isOpen ) {
imageInsertView.focus();
if ( imageUtils.isImage( selectedElement ) ) {
imageInsertView.imageURLInputValue = selectedElement.getAttribute( 'src' );
insertButtonView.label = t( 'Update' );
insertImageViaUrlForm.label = t( 'Update image URL' );
} else {
imageInsertView.imageURLInputValue = '';
insertButtonView.label = t( 'Insert' );
insertImageViaUrlForm.label = t( 'Insert image via URL' );
}
}
// Note: Use the low priority to make sure the following listener starts working after the
// default action of the drop-down is executed (i.e. the panel showed up). Otherwise, the
// invisible form/input cannot be focused/selected.
}, { priority: 'low' } );
imageInsertView.delegate( 'submit', 'cancel' ).to( dropdownView );
this.delegate( 'cancel' ).to( dropdownView );
dropdownView.on( 'submit', () => {
closePanel();
onSubmit();
} );
dropdownView.on( 'cancel', () => {
closePanel();
} );
function onSubmit() {
const selectedElement = editor.model.document.selection.getSelectedElement();
if ( imageUtils.isImage( selectedElement ) ) {
editor.model.change( writer => {
writer.setAttribute( 'src', imageInsertView.imageURLInputValue, selectedElement );
writer.removeAttribute( 'srcset', selectedElement );
writer.removeAttribute( 'sizes', selectedElement );
} );
} else {
editor.execute( 'insertImage', { source: imageInsertView.imageURLInputValue } );
}
}
function closePanel() {
editor.editing.view.focus();
dropdownView.isOpen = false;
}
return dropdownView;
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-image/src/imageinsert/ui/imageinsertformrowview.js":
/*!*********************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-image/src/imageinsert/ui/imageinsertformrowview.js ***!
\*********************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ImageUploadFormRowView)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/ui */ "./node_modules/ckeditor5/src/ui.js");
/* harmony import */ var _theme_imageinsertformrowview_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../../../theme/imageinsertformrowview.css */ "./node_modules/@ckeditor/ckeditor5-image/theme/imageinsertformrowview.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module image/imageinsert/ui/imageinsertformrowview
*/
/**
* The class representing a single row in a complex form,
* used by {@link module:image/imageinsert/ui/imageinsertpanelview~ImageInsertPanelView}.
*
* **Note**: For now this class is private. When more use cases appear (beyond `ckeditor5-table` and `ckeditor5-image`),
* it will become a component in `ckeditor5-ui`.
*
* @private
* @extends module:ui/view~View
*/
class ImageUploadFormRowView extends ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.View {
/**
* Creates an instance of the form row class.
*
* @param {module:utils/locale~Locale} locale The locale instance.
* @param {Object} options
* @param {Array.<module:ui/view~View>} [options.children]
* @param {String} [options.class]
* @param {module:ui/view~View} [options.labelView] When passed, the row gets the `group` and `aria-labelledby`
* DOM attributes and gets described by the label.
*/
constructor( locale, options = {} ) {
super( locale );
const bind = this.bindTemplate;
/**
* An additional CSS class added to the {@link #element}.
*
* @observable
* @member {String} #class
*/
this.set( 'class', options.class || null );
/**
* A collection of row items (buttons, dropdowns, etc.).
*
* @readonly
* @member {module:ui/viewcollection~ViewCollection}
*/
this.children = this.createCollection();
if ( options.children ) {
options.children.forEach( child => this.children.add( child ) );
}
/**
* The role property reflected by the `role` DOM attribute of the {@link #element}.
*
* **Note**: Used only when a `labelView` is passed to constructor `options`.
*
* @private
* @observable
* @member {String} #role
*/
this.set( '_role', null );
/**
* The ARIA property reflected by the `aria-labelledby` DOM attribute of the {@link #element}.
*
* **Note**: Used only when a `labelView` is passed to constructor `options`.
*
* @private
* @observable
* @member {String} #ariaLabelledBy
*/
this.set( '_ariaLabelledBy', null );
if ( options.labelView ) {
this.set( {
_role: 'group',
_ariaLabelledBy: options.labelView.id
} );
}
this.setTemplate( {
tag: 'div',
attributes: {
class: [
'ck',
'ck-form__row',
bind.to( 'class' )
],
role: bind.to( '_role' ),
'aria-labelledby': bind.to( '_ariaLabelledBy' )
},
children: this.children
} );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-image/src/imageinsert/ui/imageinsertpanelview.js":
/*!*******************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-image/src/imageinsert/ui/imageinsertpanelview.js ***!
\*******************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ImageInsertPanelView)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/ui */ "./node_modules/ckeditor5/src/ui.js");
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/* harmony import */ var _imageinsertformrowview__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./imageinsertformrowview */ "./node_modules/@ckeditor/ckeditor5-image/src/imageinsert/ui/imageinsertformrowview.js");
/* harmony import */ var _theme_imageinsert_css__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../../../theme/imageinsert.css */ "./node_modules/@ckeditor/ckeditor5-image/theme/imageinsert.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module image/imageinsert/ui/imageinsertpanelview
*/
/**
* The insert an image via URL view controller class.
*
* See {@link module:image/imageinsert/ui/imageinsertpanelview~ImageInsertPanelView}.
*
* @extends module:ui/view~View
*/
class ImageInsertPanelView extends ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.View {
/**
* Creates a view for the dropdown panel of {@link module:image/imageinsert/imageinsertui~ImageInsertUI}.
*
* @param {module:utils/locale~Locale} [locale] The localization services instance.
* @param {Object} [integrations] An integrations object that contains
* components (or tokens for components) to be shown in the panel view.
*/
constructor( locale, integrations ) {
super( locale );
const { insertButtonView, cancelButtonView } = this._createActionButtons( locale );
/**
* The "insert/update" button view.
*
* @member {module:ui/button/buttonview~ButtonView}
*/
this.insertButtonView = insertButtonView;
/**
* The "cancel" button view.
*
* @member {module:ui/button/buttonview~ButtonView}
*/
this.cancelButtonView = cancelButtonView;
/**
* The dropdown view.
*
* @member {module:ui/dropdown/dropdownview~DropdownView}
*/
this.dropdownView = this._createDropdownView( locale );
/**
* The value of the URL input.
*
* @member {String} #imageURLInputValue
* @observable
*/
this.set( 'imageURLInputValue', '' );
/**
* Tracks information about DOM focus in the form.
*
* @readonly
* @member {module:utils/focustracker~FocusTracker}
*/
this.focusTracker = new ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_2__.FocusTracker();
/**
* An instance of the {@link module:utils/keystrokehandler~KeystrokeHandler}.
*
* @readonly
* @member {module:utils/keystrokehandler~KeystrokeHandler}
*/
this.keystrokes = new ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_2__.KeystrokeHandler();
/**
* A collection of views that can be focused in the form.
*
* @readonly
* @protected
* @member {module:ui/viewcollection~ViewCollection}
*/
this._focusables = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.ViewCollection();
/**
* Helps cycling over {@link #_focusables} in the form.
*
* @readonly
* @protected
* @member {module:ui/focuscycler~FocusCycler}
*/
this._focusCycler = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.FocusCycler( {
focusables: this._focusables,
focusTracker: this.focusTracker,
keystrokeHandler: this.keystrokes,
actions: {
// Navigate form fields backwards using the Shift + Tab keystroke.
focusPrevious: 'shift + tab',
// Navigate form fields forwards using the Tab key.
focusNext: 'tab'
}
} );
/**
* A collection of the defined integrations for inserting the images.
*
* @private
* @member {module:utils/collection~Collection}
*/
this.set( '_integrations', new ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_2__.Collection() );
if ( integrations ) {
for ( const [ integration, integrationView ] of Object.entries( integrations ) ) {
if ( integration === 'insertImageViaUrl' ) {
integrationView.fieldView.bind( 'value' ).to( this, 'imageURLInputValue', value => value || '' );
integrationView.fieldView.on( 'input', () => {
this.imageURLInputValue = integrationView.fieldView.element.value.trim();
} );
}
integrationView.name = integration;
this._integrations.add( integrationView );
}
}
this.setTemplate( {
tag: 'form',
attributes: {
class: [
'ck',
'ck-image-insert-form'
],
tabindex: '-1'
},
children: [
...this._integrations,
new _imageinsertformrowview__WEBPACK_IMPORTED_MODULE_3__["default"]( locale, {
children: [
this.insertButtonView,
this.cancelButtonView
],
class: 'ck-image-insert-form__action-row'
} )
]
} );
}
/**
* @inheritDoc
*/
render() {
super.render();
(0,ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.submitHandler)( {
view: this
} );
const childViews = [
...this._integrations,
this.insertButtonView,
this.cancelButtonView
];
childViews.forEach( v => {
// Register the view as focusable.
this._focusables.add( v );
// Register the view in the focus tracker.
this.focusTracker.add( v.element );
} );
// Start listening for the keystrokes coming from #element.
this.keystrokes.listenTo( this.element );
const stopPropagation = data => data.stopPropagation();
// Since the form is in the dropdown panel which is a child of the toolbar, the toolbar's
// keystroke handler would take over the key management in the URL input. We need to prevent
// this ASAP. Otherwise, the basic caret movement using the arrow keys will be impossible.
this.keystrokes.set( 'arrowright', stopPropagation );
this.keystrokes.set( 'arrowleft', stopPropagation );
this.keystrokes.set( 'arrowup', stopPropagation );
this.keystrokes.set( 'arrowdown', stopPropagation );
// Intercept the "selectstart" event, which is blocked by default because of the default behavior
// of the DropdownView#panelView.
// TODO: blocking "selectstart" in the #panelView should be configurable per–drop–down instance.
this.listenTo( childViews[ 0 ].element, 'selectstart', ( evt, domEvt ) => {
domEvt.stopPropagation();
}, { priority: 'high' } );
}
/**
* @inheritDoc
*/
destroy() {
super.destroy();
this.focusTracker.destroy();
this.keystrokes.destroy();
}
/**
* Returns a view of the integration.
*
* @param {String} name The name of the integration.
* @returns {module:ui/view~View}
*/
getIntegration( name ) {
return this._integrations.find( integration => integration.name === name );
}
/**
* Creates the dropdown view.
*
* @param {module:utils/locale~Locale} locale The localization services instance.
*
* @private
* @returns {module:ui/dropdown/dropdownview~DropdownView}
*/
_createDropdownView( locale ) {
const t = locale.t;
const dropdownView = (0,ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.createDropdown)( locale, ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.SplitButtonView );
const splitButtonView = dropdownView.buttonView;
const panelView = dropdownView.panelView;
splitButtonView.set( {
label: t( 'Insert image' ),
icon: ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.icons.image,
tooltip: true
} );
panelView.extendTemplate( {
attributes: {
class: 'ck-image-insert__panel'
}
} );
return dropdownView;
}
/**
* Creates the following form controls:
*
* * {@link #insertButtonView},
* * {@link #cancelButtonView}.
*
* @param {module:utils/locale~Locale} locale The localization services instance.
*
* @private
* @returns {Object.<String,module:ui/view~View>}
*/
_createActionButtons( locale ) {
const t = locale.t;
const insertButtonView = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.ButtonView( locale );
const cancelButtonView = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.ButtonView( locale );
insertButtonView.set( {
label: t( 'Insert' ),
icon: ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.icons.check,
class: 'ck-button-save',
type: 'submit',
withText: true,
isEnabled: this.imageURLInputValue
} );
cancelButtonView.set( {
label: t( 'Cancel' ),
icon: ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.icons.cancel,
class: 'ck-button-cancel',
withText: true
} );
insertButtonView.bind( 'isEnabled' ).to( this, 'imageURLInputValue', value => !!value );
insertButtonView.delegate( 'execute' ).to( this, 'submit' );
cancelButtonView.delegate( 'execute' ).to( this, 'cancel' );
return { insertButtonView, cancelButtonView };
}
/**
* Focuses the first {@link #_focusables focusable} in the form.
*/
focus() {
this._focusCycler.focusFirst();
}
}
/**
* Fired when the form view is submitted (when one of the children triggered the submit event),
* e.g. by a click on {@link #insertButtonView}.
*
* @event submit
*/
/**
* Fired when the form view is canceled, e.g. by a click on {@link #cancelButtonView}.
*
* @event cancel
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-image/src/imageinsert/utils.js":
/*!*************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-image/src/imageinsert/utils.js ***!
\*************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "createLabeledInputView": () => (/* binding */ createLabeledInputView),
/* harmony export */ "prepareIntegrations": () => (/* binding */ prepareIntegrations)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/ui */ "./node_modules/ckeditor5/src/ui.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module image/imageinsert/utils
*/
/**
* Creates integrations object that will be passed to the
* {@link module:image/imageinsert/ui/imageinsertpanelview~ImageInsertPanelView}.
*
* @param {module:core/editor/editor~Editor} editor Editor instance.
*
* @returns {Object.<String, module:ui/view~View>} Integrations object.
*/
function prepareIntegrations( editor ) {
const panelItems = editor.config.get( 'image.insert.integrations' );
const imageInsertUIPlugin = editor.plugins.get( 'ImageInsertUI' );
const PREDEFINED_INTEGRATIONS = {
'insertImageViaUrl': createLabeledInputView( editor.locale )
};
if ( !panelItems ) {
return PREDEFINED_INTEGRATIONS;
}
// Prepares ckfinder component for the `openCKFinder` integration token.
if ( panelItems.find( item => item === 'openCKFinder' ) && editor.ui.componentFactory.has( 'ckfinder' ) ) {
const ckFinderButton = editor.ui.componentFactory.create( 'ckfinder' );
ckFinderButton.set( {
withText: true,
class: 'ck-image-insert__ck-finder-button'
} );
// We want to close the dropdown panel view when user clicks the ckFinderButton.
ckFinderButton.delegate( 'execute' ).to( imageInsertUIPlugin, 'cancel' );
PREDEFINED_INTEGRATIONS.openCKFinder = ckFinderButton;
}
// Creates integrations object of valid views to pass it to the ImageInsertPanelView.
return panelItems.reduce( ( object, key ) => {
if ( PREDEFINED_INTEGRATIONS[ key ] ) {
object[ key ] = PREDEFINED_INTEGRATIONS[ key ];
} else if ( editor.ui.componentFactory.has( key ) ) {
object[ key ] = editor.ui.componentFactory.create( key );
}
return object;
}, {} );
}
/**
* Creates labeled field view.
*
* @param {module:utils/locale~Locale} locale The localization services instance.
*
* @returns {module:ui/labeledfield/labeledfieldview~LabeledFieldView}
*/
function createLabeledInputView( locale ) {
const t = locale.t;
const labeledInputView = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.LabeledFieldView( locale, ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.createLabeledInputText );
labeledInputView.set( {
label: t( 'Insert image via URL' )
} );
labeledInputView.fieldView.placeholder = 'https://example.com/image.png';
return labeledInputView;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-image/src/imageresize.js":
/*!*******************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-image/src/imageresize.js ***!
\*******************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ImageResize)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var _imageresize_imageresizebuttons__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./imageresize/imageresizebuttons */ "./node_modules/@ckeditor/ckeditor5-image/src/imageresize/imageresizebuttons.js");
/* harmony import */ var _imageresize_imageresizeediting__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./imageresize/imageresizeediting */ "./node_modules/@ckeditor/ckeditor5-image/src/imageresize/imageresizeediting.js");
/* harmony import */ var _imageresize_imageresizehandles__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./imageresize/imageresizehandles */ "./node_modules/@ckeditor/ckeditor5-image/src/imageresize/imageresizehandles.js");
/* harmony import */ var _theme_imageresize_css__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../theme/imageresize.css */ "./node_modules/@ckeditor/ckeditor5-image/theme/imageresize.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module image/imageresize
*/
/**
* The image resize plugin.
*
* It adds a possibility to resize each image using handles.
*
* @extends module:core/plugin~Plugin
*/
class ImageResize extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [ _imageresize_imageresizeediting__WEBPACK_IMPORTED_MODULE_2__["default"], _imageresize_imageresizehandles__WEBPACK_IMPORTED_MODULE_3__["default"], _imageresize_imageresizebuttons__WEBPACK_IMPORTED_MODULE_1__["default"] ];
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'ImageResize';
}
}
/**
* The available options are `'px'` or `'%'`.
*
* Determines the size unit applied to the resized image.
*
* ClassicEditor
* .create( editorElement, {
* image: {
* resizeUnit: 'px'
* }
* } )
* .then( ... )
* .catch( ... );
*
*
* This option is used by the {@link module:image/imageresize~ImageResize} feature.
*
* @default '%'
* @member {String} module:image/image~ImageConfig#resizeUnit
*/
/**
* The image resize options.
*
* Each option should have at least these two properties:
*
* * name: The name of the UI component registered in the global
* {@link module:core/editor/editorui~EditorUI#componentFactory component factory} of the editor,
* representing the button a user can click to change the size of an image,
* * value: An actual image width applied when a user clicks the mentioned button
* ({@link module:image/imageresize/resizeimagecommand~ResizeImageCommand} gets executed).
* The value property is combined with the {@link module:image/image~ImageConfig#resizeUnit `config.image.resizeUnit`} (`%` by default).
* For instance: `value: '50'` and `resizeUnit: '%'` will render as `'50%'` in the UI.
*
* **Resetting the image size**
*
* If you want to set an option that will reset image to its original size, you need to pass a `null` value
* to one of the options. The `:original` token is not mandatory, you can call it anything you wish, but it must reflect
* in the standalone buttons configuration for the image toolbar.
*
* ClassicEditor
* .create( editorElement, {
* image: {
* resizeUnit: "%",
* resizeOptions: [ {
* name: 'resizeImage:original',
* value: null
* },
* {
* name: 'resizeImage:50',
* value: '50'
* },
* {
* name: 'resizeImage:75',
* value: '75'
* } ]
* }
* } )
* .then( ... )
* .catch( ... );
*
* **Resizing images using a dropdown**
*
* With resize options defined, you can decide whether you want to display them as a dropdown or as standalone buttons.
* For the dropdown, you need to pass only the `resizeImage` token to the
{@link module:image/image~ImageConfig#toolbar `config.image.toolbar`}. The dropdown contains all defined options by default:
*
* ClassicEditor
* .create( editorElement, {
* image: {
* resizeUnit: "%",
* resizeOptions: [ {
* name: 'resizeImage:original',
* value: null
* },
* {
* name: 'resizeImage:50',
* value: '50'
* },
* {
* name: 'resizeImage:75',
* value: '75'
* } ],
* toolbar: [ 'resizeImage', ... ],
* }
* } )
* .then( ... )
* .catch( ... );
*
* **Resizing images using individual buttons**
*
* If you want to have separate buttons for {@link module:image/imageresize/imageresizebuttons~ImageResizeOption each option},
* pass their names to the {@link module:image/image~ImageConfig#toolbar `config.image.toolbar`} instead. Please keep in mind
* that this time **you must define the additional
* {@link module:image/imageresize/imageresizebuttons~ImageResizeOption `icon` property}**:
*
* ClassicEditor
* .create( editorElement, {
* image: {
* resizeUnit: "%",
* resizeOptions: [ {
* name: 'resizeImage:original',
* value: null,
* icon: 'original'
* },
* {
* name: 'resizeImage:25',
* value: '25',
* icon: 'small'
* },
* {
* name: 'resizeImage:50',
* value: '50',
* icon: 'medium'
* },
* {
* name: 'resizeImage:75',
* value: '75',
* icon: 'large'
* } ],
* toolbar: [ 'resizeImage:25', 'resizeImage:50', 'resizeImage:75', 'resizeImage:original', ... ],
* }
* } )
* .then( ... )
* .catch( ... );
*
* **Customizing resize button labels**
*
* You can set your own label for each resize button. To do that, add the `label` property like in the example below.
*
* * When using the **dropdown**, the labels are displayed on the list of all options when you open the dropdown.
* * When using **standalone buttons**, the labels will are displayed as tooltips when a user hovers over the button.
*
* ClassicEditor
* .create( editorElement, {
* image: {
* resizeUnit: "%",
* resizeOptions: [ {
* name: 'resizeImage:original',
* value: null,
* label: 'Original size'
* // Note: add the "icon" property if you're configuring a standalone button.
* },
* {
* name: 'resizeImage:50',
* value: '50',
* label: 'Medium size'
* // Note: add the "icon" property if you're configuring a standalone button.
* },
* {
* name: 'resizeImage:75',
* value: '75',
* label: 'Large size'
* // Note: add the "icon" property if you're configuring a standalone button.
* } ]
* }
* } )
* .then( ... )
* .catch( ... );
*
* **Default value**
*
* The following configuration is used by default:
*
* resizeOptions = [
* {
* name: 'resizeImage:original',
* value: null,
* icon: 'original'
* },
* {
* name: 'resizeImage:25',
* value: '25',
* icon: 'small'
* },
* {
* name: 'resizeImage:50',
* value: '50',
* icon: 'medium'
* },
* {
* name: 'resizeImage:75',
* value: '75',
* icon: 'large'
* }
* ];
*
* @member {Array.<module:image/imageresize/imageresizebuttons~ImageResizeOption>} module:image/image~ImageConfig#resizeOptions
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-image/src/imageresize/imageresizebuttons.js":
/*!**************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-image/src/imageresize/imageresizebuttons.js ***!
\**************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ImageResizeButtons)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/ui */ "./node_modules/ckeditor5/src/ui.js");
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/* harmony import */ var _imageresizeediting__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./imageresizeediting */ "./node_modules/@ckeditor/ckeditor5-image/src/imageresize/imageresizeediting.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module image/imageresize/imageresizebuttons
*/
const RESIZE_ICONS = {
small: ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.icons.objectSizeSmall,
medium: ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.icons.objectSizeMedium,
large: ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.icons.objectSizeLarge,
original: ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.icons.objectSizeFull
};
/**
* The image resize buttons plugin.
*
* It adds a possibility to resize images using the toolbar dropdown or individual buttons, depending on the plugin configuration.
*
* @extends module:core/plugin~Plugin
*/
class ImageResizeButtons extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [ _imageresizeediting__WEBPACK_IMPORTED_MODULE_3__["default"] ];
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'ImageResizeButtons';
}
/**
* @inheritDoc
*/
constructor( editor ) {
super( editor );
/**
* The resize unit.
*
* @readonly
* @private
* @type {module:image/image~ImageConfig#resizeUnit}
* @default '%'
*/
this._resizeUnit = editor.config.get( 'image.resizeUnit' );
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const options = editor.config.get( 'image.resizeOptions' );
const command = editor.commands.get( 'resizeImage' );
this.bind( 'isEnabled' ).to( command );
for ( const option of options ) {
this._registerImageResizeButton( option );
}
this._registerImageResizeDropdown( options );
}
/**
* A helper function that creates a standalone button component for the plugin.
*
* @private
* @param {module:image/imageresize/imageresizebuttons~ImageResizeOption} resizeOption A model of the resize option.
*/
_registerImageResizeButton( option ) {
const editor = this.editor;
const { name, value, icon } = option;
const optionValueWithUnit = value ? value + this._resizeUnit : null;
editor.ui.componentFactory.add( name, locale => {
const button = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.ButtonView( locale );
const command = editor.commands.get( 'resizeImage' );
const labelText = this._getOptionLabelValue( option, true );
if ( !RESIZE_ICONS[ icon ] ) {
/**
* When configuring {@link module:image/image~ImageConfig#resizeOptions `config.image.resizeOptions`} for standalone
* buttons, a valid `icon` token must be set for each option.
*
* See all valid options described in the
* {@link module:image/imageresize/imageresizebuttons~ImageResizeOption plugin configuration}.
*
* @error imageresizebuttons-missing-icon
* @param {module:image/imageresize/imageresizebuttons~ImageResizeOption} option Invalid image resize option.
*/
throw new ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_2__.CKEditorError(
'imageresizebuttons-missing-icon',
editor,
option
);
}
button.set( {
// Use the `label` property for a verbose description (because of ARIA).
label: labelText,
icon: RESIZE_ICONS[ icon ],
tooltip: labelText,
isToggleable: true
} );
// Bind button to the command.
button.bind( 'isEnabled' ).to( this );
button.bind( 'isOn' ).to( command, 'value', getIsOnButtonCallback( optionValueWithUnit ) );
this.listenTo( button, 'execute', () => {
editor.execute( 'resizeImage', { width: optionValueWithUnit } );
} );
return button;
} );
}
/**
* A helper function that creates a dropdown component for the plugin containing all the resize options defined in
* the editor configuration.
*
* @private
* @param {Array.<module:image/imageresize/imageresizebuttons~ImageResizeOption>} options An array of configured options.
*/
_registerImageResizeDropdown( options ) {
const editor = this.editor;
const t = editor.t;
const originalSizeOption = options.find( option => !option.value );
const componentCreator = locale => {
const command = editor.commands.get( 'resizeImage' );
const dropdownView = (0,ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.createDropdown)( locale, ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.DropdownButtonView );
const dropdownButton = dropdownView.buttonView;
dropdownButton.set( {
tooltip: t( 'Resize image' ),
commandValue: originalSizeOption.value,
icon: RESIZE_ICONS.medium,
isToggleable: true,
label: this._getOptionLabelValue( originalSizeOption ),
withText: true,
class: 'ck-resize-image-button'
} );
dropdownButton.bind( 'label' ).to( command, 'value', commandValue => {
if ( commandValue && commandValue.width ) {
return commandValue.width;
} else {
return this._getOptionLabelValue( originalSizeOption );
}
} );
dropdownView.bind( 'isOn' ).to( command );
dropdownView.bind( 'isEnabled' ).to( this );
(0,ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.addListToDropdown)( dropdownView, this._getResizeDropdownListItemDefinitions( options, command ) );
dropdownView.listView.ariaLabel = t( 'Image resize list' );
// Execute command when an item from the dropdown is selected.
this.listenTo( dropdownView, 'execute', evt => {
editor.execute( evt.source.commandName, { width: evt.source.commandValue } );
editor.editing.view.focus();
} );
return dropdownView;
};
// Register `resizeImage` dropdown and add `imageResize` dropdown as an alias for backward compatibility.
editor.ui.componentFactory.add( 'resizeImage', componentCreator );
editor.ui.componentFactory.add( 'imageResize', componentCreator );
}
/**
* A helper function for creating an option label value string.
*
* @private
* @param {module:image/imageresize/imageresizebuttons~ImageResizeOption} option A resize option object.
* @param {Boolean} [forTooltip] An optional flag for creating a tooltip label.
* @returns {String} A user-defined label combined from the numeric value and the resize unit or the default label
* for reset options (`Original`).
*/
_getOptionLabelValue( option, forTooltip ) {
const t = this.editor.t;
if ( option.label ) {
return option.label;
} else if ( forTooltip ) {
if ( option.value ) {
return t( 'Resize image to %0', option.value + this._resizeUnit );
} else {
return t( 'Resize image to the original size' );
}
} else {
if ( option.value ) {
return option.value + this._resizeUnit;
} else {
return t( 'Original' );
}
}
}
/**
* A helper function that parses the resize options and returns list item definitions ready for use in the dropdown.
*
* @private
* @param {Array.<module:image/imageresize/imageresizebuttons~ImageResizeOption>} options The resize options.
* @param {module:image/imageresize/resizeimagecommand~ResizeImageCommand} command The resize image command.
* @returns {Iterable.<module:ui/dropdown/utils~ListDropdownItemDefinition>} Dropdown item definitions.
*/
_getResizeDropdownListItemDefinitions( options, command ) {
const itemDefinitions = new ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_2__.Collection();
options.map( option => {
const optionValueWithUnit = option.value ? option.value + this._resizeUnit : null;
const definition = {
type: 'button',
model: new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.Model( {
commandName: 'resizeImage',
commandValue: optionValueWithUnit,
label: this._getOptionLabelValue( option ),
withText: true,
icon: null
} )
};
definition.model.bind( 'isOn' ).to( command, 'value', getIsOnButtonCallback( optionValueWithUnit ) );
itemDefinitions.add( definition );
} );
return itemDefinitions;
}
}
// A helper function for setting the `isOn` state of buttons in value bindings.
function getIsOnButtonCallback( value ) {
return commandValue => {
if ( value === null && commandValue === value ) {
return true;
}
return commandValue && commandValue.width === value;
};
}
/**
* The image resize option used in the {@link module:image/image~ImageConfig#resizeOptions image resize configuration}.
*
* @typedef {Object} module:image/imageresize/imageresizebuttons~ImageResizeOption
* @property {String} name The name of the UI component that changes the image size.
* * If you configure the feature using individual resize buttons, you can refer to this name in the
* {@link module:image/image~ImageConfig#toolbar image toolbar configuration}.
* * If you configure the feature using the resize dropdown, this name will be used for a list item in the dropdown.
* @property {String} value The value of the resize option without the unit
* ({@link module:image/image~ImageConfig#resizeUnit configured separately}). `null` resets an image to its original size.
* @property {String} [icon] An icon used by an individual resize button (see the `name` property to learn more).
* Available icons are: `'small'`, `'medium'`, `'large'`, `'original'`.
* @property {String} [label] An option label displayed in the dropdown or, if the feature is configured using
* individual buttons, a {@link module:ui/button/buttonview~ButtonView#tooltip} and an ARIA attribute of a button.
* If not specified, the label is generated automatically based on the `value` option and the
* {@link module:image/image~ImageConfig#resizeUnit `config.image.resizeUnit`}.
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-image/src/imageresize/imageresizeediting.js":
/*!**************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-image/src/imageresize/imageresizeediting.js ***!
\**************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ImageResizeEditing)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var _imageutils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../imageutils */ "./node_modules/@ckeditor/ckeditor5-image/src/imageutils.js");
/* harmony import */ var _resizeimagecommand__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./resizeimagecommand */ "./node_modules/@ckeditor/ckeditor5-image/src/imageresize/resizeimagecommand.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module image/imageresize/imageresizeediting
*/
/**
* The image resize editing feature.
*
* It adds the ability to resize each image using handles or manually by
* {@link module:image/imageresize/imageresizebuttons~ImageResizeButtons} buttons.
*
* @extends module:core/plugin~Plugin
*/
class ImageResizeEditing extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [ _imageutils__WEBPACK_IMPORTED_MODULE_1__["default"] ];
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'ImageResizeEditing';
}
/**
* @inheritDoc
*/
constructor( editor ) {
super( editor );
editor.config.define( 'image', {
resizeUnit: '%',
resizeOptions: [ {
name: 'resizeImage:original',
value: null,
icon: 'original'
},
{
name: 'resizeImage:25',
value: '25',
icon: 'small'
},
{
name: 'resizeImage:50',
value: '50',
icon: 'medium'
},
{
name: 'resizeImage:75',
value: '75',
icon: 'large'
} ]
} );
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const resizeImageCommand = new _resizeimagecommand__WEBPACK_IMPORTED_MODULE_2__["default"]( editor );
this._registerSchema();
this._registerConverters( 'imageBlock' );
this._registerConverters( 'imageInline' );
// Register `resizeImage` command and add `imageResize` command as an alias for backward compatibility.
editor.commands.add( 'resizeImage', resizeImageCommand );
editor.commands.add( 'imageResize', resizeImageCommand );
}
/**
* @private
*/
_registerSchema() {
if ( this.editor.plugins.has( 'ImageBlockEditing' ) ) {
this.editor.model.schema.extend( 'imageBlock', { allowAttributes: 'width' } );
}
if ( this.editor.plugins.has( 'ImageInlineEditing' ) ) {
this.editor.model.schema.extend( 'imageInline', { allowAttributes: 'width' } );
}
}
/**
* Registers image resize converters.
*
* @private
* @param {'imageBlock'|'imageInline'} imageType The type of the image.
*/
_registerConverters( imageType ) {
const editor = this.editor;
// Dedicated converter to propagate image's attribute to the img tag.
editor.conversion.for( 'downcast' ).add( dispatcher =>
dispatcher.on( `attribute:width:${ imageType }`, ( evt, data, conversionApi ) => {
if ( !conversionApi.consumable.consume( data.item, evt.name ) ) {
return;
}
const viewWriter = conversionApi.writer;
const figure = conversionApi.mapper.toViewElement( data.item );
if ( data.attributeNewValue !== null ) {
viewWriter.setStyle( 'width', data.attributeNewValue, figure );
viewWriter.addClass( 'image_resized', figure );
} else {
viewWriter.removeStyle( 'width', figure );
viewWriter.removeClass( 'image_resized', figure );
}
} )
);
editor.conversion.for( 'upcast' )
.attributeToAttribute( {
view: {
name: imageType === 'imageBlock' ? 'figure' : 'img',
styles: {
width: /.+/
}
},
model: {
key: 'width',
value: viewElement => viewElement.getStyle( 'width' )
}
} );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-image/src/imageresize/imageresizehandles.js":
/*!**************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-image/src/imageresize/imageresizehandles.js ***!
\**************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ImageResizeHandles)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_widget__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/widget */ "./node_modules/ckeditor5/src/widget.js");
/* harmony import */ var _image_imageloadobserver__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../image/imageloadobserver */ "./node_modules/@ckeditor/ckeditor5-image/src/image/imageloadobserver.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module image/imageresize/imageresizehandles
*/
const RESIZABLE_IMAGES_CSS_SELECTOR =
'figure.image.ck-widget > img,' +
'figure.image.ck-widget > picture > img,' +
'figure.image.ck-widget > a > img,' +
'figure.image.ck-widget > a > picture > img,' +
'span.image-inline.ck-widget > img,' +
'span.image-inline.ck-widget > picture > img';
const IMAGE_WIDGETS_CLASSES_MATCH_REGEXP = /(image|image-inline)/;
const RESIZED_IMAGE_CLASS = 'image_resized';
/**
* The image resize by handles feature.
*
* It adds the ability to resize each image using handles or manually by
* {@link module:image/imageresize/imageresizebuttons~ImageResizeButtons} buttons.
*
* @extends module:core/plugin~Plugin
*/
class ImageResizeHandles extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [ ckeditor5_src_widget__WEBPACK_IMPORTED_MODULE_1__.WidgetResize ];
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'ImageResizeHandles';
}
/**
* @inheritDoc
*/
init() {
const command = this.editor.commands.get( 'resizeImage' );
this.bind( 'isEnabled' ).to( command );
this._setupResizerCreator();
}
/**
* Attaches the listeners responsible for creating a resizer for each image, except for images inside the HTML embed preview.
*
* @private
*/
_setupResizerCreator() {
const editor = this.editor;
const editingView = editor.editing.view;
editingView.addObserver( _image_imageloadobserver__WEBPACK_IMPORTED_MODULE_2__["default"] );
this.listenTo( editingView.document, 'imageLoaded', ( evt, domEvent ) => {
// The resizer must be attached only to images loaded by the `ImageInsert`, `ImageUpload` or `LinkImage` plugins.
if ( !domEvent.target.matches( RESIZABLE_IMAGES_CSS_SELECTOR ) ) {
return;
}
const domConverter = editor.editing.view.domConverter;
const imageView = domConverter.domToView( domEvent.target );
const widgetView = imageView.findAncestor( { classes: IMAGE_WIDGETS_CLASSES_MATCH_REGEXP } );
let resizer = this.editor.plugins.get( ckeditor5_src_widget__WEBPACK_IMPORTED_MODULE_1__.WidgetResize ).getResizerByViewElement( widgetView );
if ( resizer ) {
// There are rare cases when the image will be triggered multiple times for the same widget, e.g. when
// the image's source was changed after upload (https://github.com/ckeditor/ckeditor5/pull/8108#issuecomment-708302992).
resizer.redraw();
return;
}
const mapper = editor.editing.mapper;
const imageModel = mapper.toModelElement( widgetView );
resizer = editor.plugins
.get( ckeditor5_src_widget__WEBPACK_IMPORTED_MODULE_1__.WidgetResize )
.attachTo( {
unit: editor.config.get( 'image.resizeUnit' ),
modelElement: imageModel,
viewElement: widgetView,
editor,
getHandleHost( domWidgetElement ) {
return domWidgetElement.querySelector( 'img' );
},
getResizeHost() {
// Return the model image element parent to avoid setting an inline element (<a>/<span>) as a resize host.
return domConverter.viewToDom( mapper.toViewElement( imageModel.parent ) );
},
// TODO consider other positions.
isCentered() {
const imageStyle = imageModel.getAttribute( 'imageStyle' );
return !imageStyle || imageStyle == 'block' || imageStyle == 'alignCenter';
},
onCommit( newValue ) {
// Get rid of the CSS class in case the command execution that follows is unsuccessful
// (e.g. Track Changes can override it and the new dimensions will not apply). Otherwise,
// the presence of the class and the absence of the width style will cause it to take 100%
// of the horizontal space.
editingView.change( writer => {
writer.removeClass( RESIZED_IMAGE_CLASS, widgetView );
} );
editor.execute( 'resizeImage', { width: newValue } );
}
} );
resizer.on( 'updateSize', () => {
if ( !widgetView.hasClass( RESIZED_IMAGE_CLASS ) ) {
editingView.change( writer => {
writer.addClass( RESIZED_IMAGE_CLASS, widgetView );
} );
}
} );
resizer.bind( 'isEnabled' ).to( this );
} );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-image/src/imageresize/resizeimagecommand.js":
/*!**************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-image/src/imageresize/resizeimagecommand.js ***!
\**************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ResizeImageCommand)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module image/imageresize/resizeimagecommand
*/
/**
* The resize image command. Currently, it only supports the width attribute.
*
* @extends module:core/command~Command
*/
class ResizeImageCommand extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Command {
/**
* @inheritDoc
*/
refresh() {
const editor = this.editor;
const imageUtils = editor.plugins.get( 'ImageUtils' );
const element = imageUtils.getClosestSelectedImageElement( editor.model.document.selection );
this.isEnabled = !!element;
if ( !element || !element.hasAttribute( 'width' ) ) {
this.value = null;
} else {
this.value = {
width: element.getAttribute( 'width' ),
height: null
};
}
}
/**
* Executes the command.
*
* // Sets the width to 50%:
* editor.execute( 'resizeImage', { width: '50%' } );
*
* // Removes the width attribute:
* editor.execute( 'resizeImage', { width: null } );
*
* @param {Object} options
* @param {String|null} options.width The new width of the image.
* @fires execute
*/
execute( options ) {
const editor = this.editor;
const model = editor.model;
const imageUtils = editor.plugins.get( 'ImageUtils' );
const imageElement = imageUtils.getClosestSelectedImageElement( model.document.selection );
this.value = {
width: options.width,
height: null
};
if ( imageElement ) {
model.change( writer => {
writer.setAttribute( 'width', options.width, imageElement );
} );
}
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-image/src/imagestyle.js":
/*!******************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-image/src/imagestyle.js ***!
\******************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ImageStyle)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var _imagestyle_imagestyleediting__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./imagestyle/imagestyleediting */ "./node_modules/@ckeditor/ckeditor5-image/src/imagestyle/imagestyleediting.js");
/* harmony import */ var _imagestyle_imagestyleui__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./imagestyle/imagestyleui */ "./node_modules/@ckeditor/ckeditor5-image/src/imagestyle/imagestyleui.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module image/imagestyle
*/
/**
* The image style plugin.
*
* For a detailed overview of the image styles feature, check the {@glink features/images/images-styles documentation}.
*
* This is a "glue" plugin which loads the following plugins:
* * {@link module:image/imagestyle/imagestyleediting~ImageStyleEditing},
* * {@link module:image/imagestyle/imagestyleui~ImageStyleUI}
*
* It provides a default configuration, which can be extended or overwritten.
* Read more about the {@link module:image/image~ImageConfig#styles image styles configuration}.
*
* @extends module:core/plugin~Plugin
*/
class ImageStyle extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [ _imagestyle_imagestyleediting__WEBPACK_IMPORTED_MODULE_1__["default"], _imagestyle_imagestyleui__WEBPACK_IMPORTED_MODULE_2__["default"] ];
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'ImageStyle';
}
}
/**
* The configuration for the {@link module:image/imagestyle~ImageStyle} plugin that should be provided
* while creating the editor instance.
*
* A detailed information about the default configuration and customization can be found in
* {@link module:image/image~ImageConfig#styles `ImageConfig#styles`}.
*
* @interface ImageStyleConfig
*/
/**
* A list of the image style options.
*
* @member {Array.<module:image/imagestyle~ImageStyleOptionDefinition>} module:image/imagestyle~ImageStyleConfig#options
*/
/**
* The {@link module:image/imagestyle `ImageStyle`} plugin requires a list of the
* {@link module:image/imagestyle~ImageStyleConfig#options image style options} to work properly.
* The default configuration is provided (listed below) and can be customized while creating the editor instance.
*
* # **Command**
*
* The {@link module:image/imagestyle/imagestylecommand~ImageStyleCommand `imageStyleCommand`}
* is configured based on the defined options,
* so you can change the style of the selected image by executing the following command:
*
* editor.execute( 'imageStyle' { value: 'alignLeft' } );
*
* # **Buttons**
*
* All of the image style options provided in the configuration are registered
* in the {@link module:ui/componentfactory~ComponentFactory UI components factory} with the "imageStyle:" prefixes and can be used
* in the {@link module:image/image~ImageConfig#toolbar image toolbar configuration}. The buttons available by default depending
* on the loaded plugins are listed in the next section.
*
* Read more about styling images in the {@glink features/images/images-styles Image styles guide}.
*
* # **Default options and buttons**
*
* If the custom configuration is not provided, the default configuration will be used depending on the loaded
* image editing plugins.
*
* * If both {@link module:image/image/imageblockediting~ImageBlockEditing `ImageBlockEditing`} and
* {@link module:image/image/imageinlineediting~ImageInlineEditing `ImageInlineEditing`} plugins are loaded
* (which is usually the default editor configuration), the following options will be available for the toolbar
* configuration. These options will be registered as the buttons with the "imageStyle:" prefixes.
*
* const imageDefaultConfig = {
* styles: {
* options: [
* 'inline', 'alignLeft', 'alignRight',
* 'alignCenter', 'alignBlockLeft', 'alignBlockRight',
* 'block', 'side'
* ]
* }
* };
*
* * If only the {@link module:image/image/imageblockediting~ImageBlockEditing `ImageBlockEditing`} plugin is loaded,
* the following buttons (options) and groups will be available for the toolbar configuration.
* These options will be registered as the buttons with the "imageStyle:" prefixes.
*
* const imageDefaultConfig = {
* styles: {
* options: [ 'block', 'side' ]
* }
* };
*
* * If only the {@link module:image/image/imageinlineediting~ImageInlineEditing `ImageInlineEditing`} plugin is loaded,
* the following buttons (options) and groups will available for the toolbar configuration.
* These options will be registered as the buttons with the "imageStyle:" prefixes.
*
* const imageDefaultConfig = {
* styles: {
* options: [ 'inline', 'alignLeft', 'alignRight' ]
* }
* };
*
* Read more about the {@link module:image/imagestyle/utils~DEFAULT_OPTIONS default styling options}.
*
* # **Custom configuration**
*
* The image styles configuration can be customized in several ways:
*
* * Any of the {@link module:image/imagestyle/utils~DEFAULT_OPTIONS default styling options}
* can be loaded by the reference to its name as follows:
*
* ClassicEditor
* .create( editorElement, {
* image: {
* styles: {
* options: [ 'alignLeft', 'alignRight' ]
* }
* }
* } );
*
* * Each of the {@link module:image/imagestyle/utils~DEFAULT_OPTIONS default image style options} can be customized,
* e.g. to change the `icon`, `title` or CSS `className` of the style. The feature also provides several
* {@link module:image/imagestyle/utils~DEFAULT_ICONS default icons} to choose from.
*
* import customIcon from 'custom-icon.svg';
*
* // ...
*
* ClassicEditor.create( editorElement, { image:
* styles: {
* options: {
* // This will only customize the icon of the "block" style.
* // Note: 'right' is one of default icons provided by the feature.
* {
* name: 'block',
* icon: 'right'
* },
*
* // This will customize the icon, title and CSS class of the default "side" style.
* {
* name: 'side',
* icon: customIcon,
* title: 'My side style',
* className: 'custom-side-image'
* }
* }
* }
* } );
*
* * If none of the {@link module:image/imagestyle/utils~DEFAULT_OPTIONS default image style options}
* works for the integration, it is possible to define independent custom styles, too.
*
* See the documentation about the image style {@link module:image/imagestyle~ImageStyleOptionDefinition options}
* to define the custom image style configuration properly.
*
* import redIcon from 'red-icon.svg';
* import blueIcon from 'blue-icon.svg';
*
* // ...
*
* ClassicEditor.create( editorElement, { image:
* styles: {
* // A list of completely custom styling options.
* options: [
* {
* name: 'regular',
* modelElements: [ 'imageBlock', 'imageInline' ],
* title: 'Regular image',
* icon: 'full',
* isDefault: true
* }, {
* name: 'blue',
* modelElements: [ 'imageInline' ],
* title: 'Blue image',
* icon: blueIcon,
* className: 'image-blue'
* }, {
* name: 'red',
* modelElements: [ 'imageBlock' ],
* title: 'Red image',
* icon: redIcon,
* className: 'image-red'
* }
* ]
* }
* } );
*
* @member {module:image/imagestyle~ImageStyleConfig} module:image/image~ImageConfig#styles
*/
/**
* The image styling option definition descriptor.
*
* This definition should be implemented in the `Image` plugin {@link module:image/image~ImageConfig#styles configuration} for:
*
* * customizing one of the {@link module:image/imagestyle/utils~DEFAULT_OPTIONS default styling options} by providing the proper name
* of the default style and the properties that should be overridden,
* * or defining a completely custom styling option by providing a custom name and implementing the following properties.
*
* import fullSizeIcon from 'path/to/icon.svg';
*
* const imageStyleOptionDefinition = {
* name: 'fullSize',
* icon: fullSizeIcon,
* title: 'Full size image',
* className: 'image-full-size',
* modelElements: [ 'imageBlock', 'imageInline' ]
* }
*
* The styling option will be registered as the button under the name `'imageStyle:{name}'` in the
* {@link module:ui/componentfactory~ComponentFactory UI components factory} (this functionality is provided by the
* {@link module:image/imagestyle/imagestyleui~ImageStyleUI} plugin).
*
* @property {String} name The unique name of the styling option. It will be used to:
*
* * refer to one of the {@link module:image/imagestyle/utils~DEFAULT_OPTIONS default styling options} or define the custom style,
* * store the chosen style in the model by setting the `imageStyle` attribute of the model image element,
* * as a value of the {@link module:image/imagestyle/imagestylecommand~ImageStyleCommand#execute `imageStyle` command},
* * when registering a button for the style in the following manner: (`'imageStyle:{name}'`).
*
* @property {Boolean} [isDefault] When set, the style will be used as the default one for the model elements
* listed in the `modelElements` property. A default style does not apply any CSS class to the view element.
*
* If this property is not defined, its value is inherited
* from the {@link module:image/imagestyle/utils~DEFAULT_OPTIONS default styling options} addressed in the name property.
*
* @property {String} icon One of the following to be used when creating the styles's button:
*
* * an SVG icon source (as an XML string),
* * one of the keys in {@link module:image/imagestyle/utils~DEFAULT_ICONS} to use one of default icons provided by the plugin.
*
* If this property is not defined, its value is inherited
* from the {@link module:image/imagestyle/utils~DEFAULT_OPTIONS default styling options} addressed in the name property.
*
* @property {String} title The styles's title. Setting `title` to one of
* {@link module:image/imagestyle/imagestyleui~ImageStyleUI#localizedDefaultStylesTitles}
* will automatically translate it to the language of the editor.
*
* If this property is not defined, its value is inherited
* from the {@link module:image/imagestyle/utils~DEFAULT_OPTIONS default styling options} addressed in the name property.
*
* @property {String} [className] The CSS class used to represent the style in the view.
* It should be used only for the non-default styles.
*
* If this property is not defined, its value is inherited
* from the {@link module:image/imagestyle/utils~DEFAULT_OPTIONS default styling options} addressed in the name property.
*
* @property {Array.<String>} modelElements The list of the names of the model elements that are supported by the style.
* The possible values are:
* * `[ 'imageBlock' ]` if the style can be applied to the image type introduced by the
* {@link module:image/image/imageblockediting~ImageBlockEditing `ImageBlockEditing`} plugin,
* * `[ 'imageInline' ]` if the style can be applied to the image type introduced by the
* {@link module:image/image/imageinlineediting~ImageInlineEditing `ImageInlineEditing`} plugin,
* * `[ 'imageInline', 'imageBlock' ]` if the style can be applied to both image types introduced by the plugins mentioned above.
*
* This property determines which model element names work with the style. If the model element name of the currently selected
* image is different, upon executing the
* {@link module:image/imagestyle/imagestylecommand~ImageStyleCommand#execute `imageStyle`} command the image type (model element name)
* will automatically change.
*
* If this property is not defined, its value is inherited
* from the {@link module:image/imagestyle/utils~DEFAULT_OPTIONS default styling options} addressed in the name property.
*
* @typedef {Object} module:image/imagestyle~ImageStyleOptionDefinition
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-image/src/imagestyle/converters.js":
/*!*****************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-image/src/imagestyle/converters.js ***!
\*****************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "modelToViewStyleAttribute": () => (/* binding */ modelToViewStyleAttribute),
/* harmony export */ "viewToModelStyleAttribute": () => (/* binding */ viewToModelStyleAttribute)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module image/imagestyle/converters
*/
/**
* Returns a converter for the `imageStyle` attribute. It can be used for adding, changing and removing the attribute.
*
* @param {Array.<module:image/imagestyle~ImageStyleOptionDefinition>} styles
* An array containing available image style options.
* @returns {Function} A model-to-view attribute converter.
*/
function modelToViewStyleAttribute( styles ) {
return ( evt, data, conversionApi ) => {
if ( !conversionApi.consumable.consume( data.item, evt.name ) ) {
return;
}
// Check if there is class name associated with given value.
const newStyle = getStyleDefinitionByName( data.attributeNewValue, styles );
const oldStyle = getStyleDefinitionByName( data.attributeOldValue, styles );
const viewElement = conversionApi.mapper.toViewElement( data.item );
const viewWriter = conversionApi.writer;
if ( oldStyle ) {
viewWriter.removeClass( oldStyle.className, viewElement );
}
if ( newStyle ) {
viewWriter.addClass( newStyle.className, viewElement );
}
};
}
/**
* Returns a view-to-model converter converting image CSS classes to a proper value in the model.
*
* @param {Array.<module:image/imagestyle~ImageStyleOptionDefinition>} styles
* Image style options for which the converter is created.
* @returns {Function} A view-to-model converter.
*/
function viewToModelStyleAttribute( styles ) {
// Convert only non–default styles.
const nonDefaultStyles = {
imageInline: styles.filter( style => !style.isDefault && style.modelElements.includes( 'imageInline' ) ),
imageBlock: styles.filter( style => !style.isDefault && style.modelElements.includes( 'imageBlock' ) )
};
return ( evt, data, conversionApi ) => {
if ( !data.modelRange ) {
return;
}
const viewElement = data.viewItem;
const modelImageElement = (0,ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_0__.first)( data.modelRange.getItems() );
// Run this converter only if an image has been found in the model.
// In some cases it may not be found (for example if we run this on a figure with different type than image).
if ( !modelImageElement ) {
return;
}
// ...and the `imageStyle` attribute is allowed for that element, otherwise stop conversion early.
if ( !conversionApi.schema.checkAttribute( modelImageElement, 'imageStyle' ) ) {
return;
}
// Convert styles one by one.
for ( const style of nonDefaultStyles[ modelImageElement.name ] ) {
// Try to consume class corresponding with the style.
if ( conversionApi.consumable.consume( viewElement, { classes: style.className } ) ) {
// And convert this style to model attribute.
conversionApi.writer.setAttribute( 'imageStyle', style.name, modelImageElement );
}
}
};
}
// Returns the style with a given `name` from an array of styles.
//
// @param {String} name
// @param {Array.<module:image/imagestyle~ImageStyleOptionDefinition> } styles
// @returns {module:image/imagestyle~ImageStyleOptionDefinition|undefined}
function getStyleDefinitionByName( name, styles ) {
for ( const style of styles ) {
if ( style.name === name ) {
return style;
}
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-image/src/imagestyle/imagestylecommand.js":
/*!************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-image/src/imagestyle/imagestylecommand.js ***!
\************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ImageStyleCommand)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module image/imagestyle/imagestylecommand
*/
/**
* The image style command. It is used to apply {@link module:image/imagestyle~ImageStyleConfig#options image style option}
* to a selected image.
*
* **Note**: Executing this command may change the image model element if the desired style requires an image of a different
* type. See {@link module:image/imagestyle/imagestylecommand~ImageStyleCommand#execute} to learn more.
*
* @extends module:core/command~Command
*/
class ImageStyleCommand extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Command {
/**
* Creates an instance of the image style command. When executed, the command applies one of
* {@link module:image/imagestyle~ImageStyleConfig#options style options} to the currently selected image.
*
* @param {module:core/editor/editor~Editor} editor The editor instance.
* @param {Array.<module:image/imagestyle~ImageStyleOptionDefinition>} styles
* The style options that this command supports.
*/
constructor( editor, styles ) {
super( editor );
/**
* An object containing names of default style options for the inline and block images.
* If there is no default style option for the given image type in the configuration,
* the name will be `false`.
*
* @private
* @type {Object.<String,module:image/imagestyle~ImageStyleOptionDefinition#name>}
*/
this._defaultStyles = {
imageBlock: false,
imageInline: false
};
/**
* The styles handled by this command.
*
* @private
* @type {module:image/imagestyle~ImageStyleConfig#options}
*/
this._styles = new Map( styles.map( style => {
if ( style.isDefault ) {
for ( const modelElementName of style.modelElements ) {
this._defaultStyles[ modelElementName ] = style.name;
}
}
return [ style.name, style ];
} ) );
}
/**
* @inheritDoc
*/
refresh() {
const editor = this.editor;
const imageUtils = editor.plugins.get( 'ImageUtils' );
const element = imageUtils.getClosestSelectedImageElement( this.editor.model.document.selection );
this.isEnabled = !!element;
if ( !this.isEnabled ) {
this.value = false;
} else if ( element.hasAttribute( 'imageStyle' ) ) {
this.value = element.getAttribute( 'imageStyle' );
} else {
this.value = this._defaultStyles[ element.name ];
}
}
/**
* Executes the command and applies the style to the currently selected image:
*
* editor.execute( 'imageStyle', { value: 'side' } );
*
* **Note**: Executing this command may change the image model element if the desired style requires an image
* of a different type. Learn more about {@link module:image/imagestyle~ImageStyleOptionDefinition#modelElements model element}
* configuration for the style option.
*
* @param {Object} options
* @param {module:image/imagestyle~ImageStyleOptionDefinition#name} options.value The name of the style (as configured in
* {@link module:image/imagestyle~ImageStyleConfig#options}).
* @fires execute
*/
execute( options = {} ) {
const editor = this.editor;
const model = editor.model;
const imageUtils = editor.plugins.get( 'ImageUtils' );
model.change( writer => {
const requestedStyle = options.value;
let imageElement = imageUtils.getClosestSelectedImageElement( model.document.selection );
// Change the image type if a style requires it.
if ( requestedStyle && this.shouldConvertImageType( requestedStyle, imageElement ) ) {
this.editor.execute( imageUtils.isBlockImage( imageElement ) ? 'imageTypeInline' : 'imageTypeBlock' );
// Update the imageElement to the newly created image.
imageElement = imageUtils.getClosestSelectedImageElement( model.document.selection );
}
// Default style means that there is no `imageStyle` attribute in the model.
// https://github.com/ckeditor/ckeditor5-image/issues/147
if ( !requestedStyle || this._styles.get( requestedStyle ).isDefault ) {
writer.removeAttribute( 'imageStyle', imageElement );
} else {
writer.setAttribute( 'imageStyle', requestedStyle, imageElement );
}
} );
}
/**
* Returns `true` if requested style change would trigger the image type change.
*
* @param {module:image/imagestyle~ImageStyleOptionDefinition} requestedStyle The name of the style (as configured in
* {@link module:image/imagestyle~ImageStyleConfig#options}).
* @param {module:engine/model/element~Element} imageElement The image model element.
* @returns {Boolean}
*/
shouldConvertImageType( requestedStyle, imageElement ) {
const supportedTypes = this._styles.get( requestedStyle ).modelElements;
return !supportedTypes.includes( imageElement.name );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-image/src/imagestyle/imagestyleediting.js":
/*!************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-image/src/imagestyle/imagestyleediting.js ***!
\************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ImageStyleEditing)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var _imagestylecommand__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./imagestylecommand */ "./node_modules/@ckeditor/ckeditor5-image/src/imagestyle/imagestylecommand.js");
/* harmony import */ var _imageutils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../imageutils */ "./node_modules/@ckeditor/ckeditor5-image/src/imageutils.js");
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./utils */ "./node_modules/@ckeditor/ckeditor5-image/src/imagestyle/utils.js");
/* harmony import */ var _converters__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./converters */ "./node_modules/@ckeditor/ckeditor5-image/src/imagestyle/converters.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module image/imagestyle/imagestyleediting
*/
/**
* The image style engine plugin. It sets the default configuration, creates converters and registers
* {@link module:image/imagestyle/imagestylecommand~ImageStyleCommand ImageStyleCommand}.
*
* @extends module:core/plugin~Plugin
*/
class ImageStyleEditing extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'ImageStyleEditing';
}
/**
* @inheritDoc
*/
static get requires() {
return [ _imageutils__WEBPACK_IMPORTED_MODULE_2__["default"] ];
}
/**
* @inheritDoc
*/
init() {
const { normalizeStyles, getDefaultStylesConfiguration } = _utils__WEBPACK_IMPORTED_MODULE_3__["default"];
const editor = this.editor;
const isBlockPluginLoaded = editor.plugins.has( 'ImageBlockEditing' );
const isInlinePluginLoaded = editor.plugins.has( 'ImageInlineEditing' );
editor.config.define( 'image.styles', getDefaultStylesConfiguration( isBlockPluginLoaded, isInlinePluginLoaded ) );
/**
* It contains a list of the normalized and validated style options.
*
* * Each option contains a complete icon markup.
* * The style options not supported by any of the loaded image editing plugins (
* {@link module:image/image/imageinlineediting~ImageInlineEditing `ImageInlineEditing`} or
* {@link module:image/image/imageblockediting~ImageBlockEditing `ImageBlockEditing`}) are filtered out.
*
* @protected
* @readonly
* @type {module:image/imagestyle~ImageStyleConfig}
*/
this.normalizedStyles = normalizeStyles( {
configuredStyles: editor.config.get( 'image.styles' ),
isBlockPluginLoaded,
isInlinePluginLoaded
} );
this._setupConversion( isBlockPluginLoaded, isInlinePluginLoaded );
this._setupPostFixer();
// Register imageStyle command.
editor.commands.add( 'imageStyle', new _imagestylecommand__WEBPACK_IMPORTED_MODULE_1__["default"]( editor, this.normalizedStyles ) );
}
/**
* Sets the editor conversion taking the presence of
* {@link module:image/image/imageinlineediting~ImageInlineEditing `ImageInlineEditing`}
* and {@link module:image/image/imageblockediting~ImageBlockEditing `ImageBlockEditing`} plugins into consideration.
*
* @private
* @param {Boolean} isBlockPluginLoaded
* @param {Boolean} isInlinePluginLoaded
*/
_setupConversion( isBlockPluginLoaded, isInlinePluginLoaded ) {
const editor = this.editor;
const schema = editor.model.schema;
const modelToViewConverter = (0,_converters__WEBPACK_IMPORTED_MODULE_4__.modelToViewStyleAttribute)( this.normalizedStyles );
const viewToModelConverter = (0,_converters__WEBPACK_IMPORTED_MODULE_4__.viewToModelStyleAttribute)( this.normalizedStyles );
editor.editing.downcastDispatcher.on( 'attribute:imageStyle', modelToViewConverter );
editor.data.downcastDispatcher.on( 'attribute:imageStyle', modelToViewConverter );
// Allow imageStyle attribute in image and imageInline.
// We could call it 'style' but https://github.com/ckeditor/ckeditor5-engine/issues/559.
if ( isBlockPluginLoaded ) {
schema.extend( 'imageBlock', { allowAttributes: 'imageStyle' } );
// Converter for figure element from view to model.
editor.data.upcastDispatcher.on( 'element:figure', viewToModelConverter, { priority: 'low' } );
}
if ( isInlinePluginLoaded ) {
schema.extend( 'imageInline', { allowAttributes: 'imageStyle' } );
// Converter for the img element from view to model.
editor.data.upcastDispatcher.on( 'element:img', viewToModelConverter, { priority: 'low' } );
}
}
/**
* Registers a post-fixer that will make sure that the style attribute value is correct for a specific image type (block vs inline).
*
* @private
*/
_setupPostFixer() {
const editor = this.editor;
const document = editor.model.document;
const imageUtils = editor.plugins.get( _imageutils__WEBPACK_IMPORTED_MODULE_2__["default"] );
const stylesMap = new Map( this.normalizedStyles.map( style => [ style.name, style ] ) );
// Make sure that style attribute is valid for the image type.
document.registerPostFixer( writer => {
let changed = false;
for ( const change of document.differ.getChanges() ) {
if ( change.type == 'insert' || change.type == 'attribute' && change.attributeKey == 'imageStyle' ) {
let element = change.type == 'insert' ? change.position.nodeAfter : change.range.start.nodeAfter;
if ( element && element.is( 'element', 'paragraph' ) && element.childCount > 0 ) {
element = element.getChild( 0 );
}
if ( !imageUtils.isImage( element ) ) {
continue;
}
const imageStyle = element.getAttribute( 'imageStyle' );
if ( !imageStyle ) {
continue;
}
const imageStyleDefinition = stylesMap.get( imageStyle );
if ( !imageStyleDefinition || !imageStyleDefinition.modelElements.includes( element.name ) ) {
writer.removeAttribute( 'imageStyle', element );
changed = true;
}
}
}
return changed;
} );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-image/src/imagestyle/imagestyleui.js":
/*!*******************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-image/src/imagestyle/imagestyleui.js ***!
\*******************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ImageStyleUI)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/ui */ "./node_modules/ckeditor5/src/ui.js");
/* harmony import */ var _imagestyleediting__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./imagestyleediting */ "./node_modules/@ckeditor/ckeditor5-image/src/imagestyle/imagestyleediting.js");
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./utils */ "./node_modules/@ckeditor/ckeditor5-image/src/imagestyle/utils.js");
/* harmony import */ var lodash_es__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! lodash-es */ "./node_modules/lodash-es/isObject.js");
/* harmony import */ var lodash_es__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! lodash-es */ "./node_modules/lodash-es/identity.js");
/* harmony import */ var _theme_imagestyle_css__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../../theme/imagestyle.css */ "./node_modules/@ckeditor/ckeditor5-image/theme/imagestyle.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module image/imagestyle/imagestyleui
*/
/**
* The image style UI plugin.
*
* It registers buttons corresponding to the {@link module:image/image~ImageConfig#styles} configuration.
* It also registers the {@link module:image/imagestyle/utils~DEFAULT_DROPDOWN_DEFINITIONS default drop-downs} and the
* custom drop-downs defined by the developer in the {@link module:image/image~ImageConfig#toolbar} configuration.
*
* @extends module:core/plugin~Plugin
*/
class ImageStyleUI extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [ _imagestyleediting__WEBPACK_IMPORTED_MODULE_2__["default"] ];
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'ImageStyleUI';
}
/**
* Returns the default localized style titles provided by the plugin.
*
* The following localized titles corresponding with
* {@link module:image/imagestyle/utils~DEFAULT_OPTIONS} are available:
*
* * `'Wrap text'`,
* * `'Break text'`,
* * `'In line'`,
* * `'Full size image'`,
* * `'Side image'`,
* * `'Left aligned image'`,
* * `'Centered image'`,
* * `'Right aligned image'`
*
* @returns {Object.<String,String>}
*/
get localizedDefaultStylesTitles() {
const t = this.editor.t;
return {
'Wrap text': t( 'Wrap text' ),
'Break text': t( 'Break text' ),
'In line': t( 'In line' ),
'Full size image': t( 'Full size image' ),
'Side image': t( 'Side image' ),
'Left aligned image': t( 'Left aligned image' ),
'Centered image': t( 'Centered image' ),
'Right aligned image': t( 'Right aligned image' )
};
}
/**
* @inheritDoc
*/
init() {
const plugins = this.editor.plugins;
const toolbarConfig = this.editor.config.get( 'image.toolbar' ) || [];
const definedStyles = translateStyles(
plugins.get( 'ImageStyleEditing' ).normalizedStyles,
this.localizedDefaultStylesTitles
);
for ( const styleConfig of definedStyles ) {
this._createButton( styleConfig );
}
const definedDropdowns = translateStyles(
[ ...toolbarConfig.filter( lodash_es__WEBPACK_IMPORTED_MODULE_5__["default"] ), ..._utils__WEBPACK_IMPORTED_MODULE_3__["default"].getDefaultDropdownDefinitions( plugins ) ],
this.localizedDefaultStylesTitles
);
for ( const dropdownConfig of definedDropdowns ) {
this._createDropdown( dropdownConfig, definedStyles );
}
}
/**
* Creates a dropdown and stores it in the editor {@link module:ui/componentfactory~ComponentFactory}.
*
* @private
* @param {module:image/imagestyle/imagestyleui~ImageStyleDropdownDefinition} dropdownConfig
* @param {Array.<module:image/imagestyle~ImageStyleOptionDefinition>} definedStyles
*/
_createDropdown( dropdownConfig, definedStyles ) {
const factory = this.editor.ui.componentFactory;
factory.add( dropdownConfig.name, locale => {
let defaultButton;
const { defaultItem, items, title } = dropdownConfig;
const buttonViews = items
.filter( itemName => definedStyles.find( ( { name } ) => getUIComponentName( name ) === itemName ) )
.map( buttonName => {
const button = factory.create( buttonName );
if ( buttonName === defaultItem ) {
defaultButton = button;
}
return button;
} );
if ( items.length !== buttonViews.length ) {
_utils__WEBPACK_IMPORTED_MODULE_3__["default"].warnInvalidStyle( { dropdown: dropdownConfig } );
}
const dropdownView = (0,ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.createDropdown)( locale, ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.SplitButtonView );
const splitButtonView = dropdownView.buttonView;
(0,ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.addToolbarToDropdown)( dropdownView, buttonViews );
splitButtonView.set( {
label: getDropdownButtonTitle( title, defaultButton.label ),
class: null,
tooltip: true
} );
splitButtonView.bind( 'icon' ).toMany( buttonViews, 'isOn', ( ...areOn ) => {
const index = areOn.findIndex( lodash_es__WEBPACK_IMPORTED_MODULE_6__["default"] );
return ( index < 0 ) ? defaultButton.icon : buttonViews[ index ].icon;
} );
splitButtonView.bind( 'label' ).toMany( buttonViews, 'isOn', ( ...areOn ) => {
const index = areOn.findIndex( lodash_es__WEBPACK_IMPORTED_MODULE_6__["default"] );
return getDropdownButtonTitle( title, ( index < 0 ) ? defaultButton.label : buttonViews[ index ].label );
} );
splitButtonView.bind( 'isOn' ).toMany( buttonViews, 'isOn', ( ...areOn ) => areOn.some( lodash_es__WEBPACK_IMPORTED_MODULE_6__["default"] ) );
splitButtonView.bind( 'class' )
.toMany( buttonViews, 'isOn', ( ...areOn ) => areOn.some( lodash_es__WEBPACK_IMPORTED_MODULE_6__["default"] ) ? 'ck-splitbutton_flatten' : null );
splitButtonView.on( 'execute', () => {
if ( !buttonViews.some( ( { isOn } ) => isOn ) ) {
defaultButton.fire( 'execute' );
} else {
dropdownView.isOpen = !dropdownView.isOpen;
}
} );
dropdownView.bind( 'isEnabled' )
.toMany( buttonViews, 'isEnabled', ( ...areEnabled ) => areEnabled.some( lodash_es__WEBPACK_IMPORTED_MODULE_6__["default"] ) );
return dropdownView;
} );
}
/**
* Creates a button and stores it in the editor {@link module:ui/componentfactory~ComponentFactory}.
*
* @private
* @param {module:image/imagestyle~ImageStyleOptionDefinition} buttonConfig
*/
_createButton( buttonConfig ) {
const buttonName = buttonConfig.name;
this.editor.ui.componentFactory.add( getUIComponentName( buttonName ), locale => {
const command = this.editor.commands.get( 'imageStyle' );
const view = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.ButtonView( locale );
view.set( {
label: buttonConfig.title,
icon: buttonConfig.icon,
tooltip: true,
isToggleable: true
} );
view.bind( 'isEnabled' ).to( command, 'isEnabled' );
view.bind( 'isOn' ).to( command, 'value', value => value === buttonName );
view.on( 'execute', this._executeCommand.bind( this, buttonName ) );
return view;
} );
}
_executeCommand( name ) {
this.editor.execute( 'imageStyle', { value: name } );
this.editor.editing.view.focus();
}
}
// Returns the translated `title` from the passed styles array.
//
// @param {Array.<module:image/imagestyle~ImageStyleOptionDefinition|
// module:image/imagestyle/imagestyleui~ImageStyleDropdownDefinition>} styles
// @param {Object.<String,String>} titles
//
// @returns {Array.<module:image/imagestyle~ImageStyleOptionDefinition|module:image/imagestyle/imagestyleui~ImageStyleDropdownDefinition>}
function translateStyles( styles, titles ) {
for ( const style of styles ) {
// Localize the titles of the styles, if a title corresponds with
// a localized default provided by the plugin.
if ( titles[ style.title ] ) {
style.title = titles[ style.title ];
}
}
return styles;
}
// Returns the image style component name with the "imageStyle:" prefix.
//
// @param {String} name
// @returns {String}
function getUIComponentName( name ) {
return `imageStyle:${ name }`;
}
// Returns title for the splitbutton containing the dropdown title and default action item title.
//
// @param {String|undefined} dropdownTitle
// @param {String} buttonTitle
// @returns {String}
function getDropdownButtonTitle( dropdownTitle, buttonTitle ) {
return ( dropdownTitle ? dropdownTitle + ': ' : '' ) + buttonTitle;
}
/**
* # **The image style custom drop-down definition descriptor**
*
* This definition can be implemented in the {@link module:image/image~ImageConfig#toolbar image toolbar configuration}
* to define a completely custom drop-down in the image toolbar.
*
* ClassicEditor.create( editorElement, {
* image: { toolbar: [
* // One of the predefined drop-downs
* 'imageStyle:wrapText',
* // Custom drop-down
* {
* name: 'imageStyle:customDropdown',
* title: Custom drop-down title,
* items: [ 'imageStyle:alignLeft', 'imageStyle:alignRight' ],
* defaultItem: 'imageStyle:alignLeft'
* }
* ] }
* } );
*
* **Note:** At the moment it is possible to populate the custom drop-down with only the buttons registered by the `ImageStyle` plugin.
*
* The defined drop-down will be registered
* as the {@link module:ui/dropdown/dropdownview~DropdownView}
* with the {@link module:ui/dropdown/button/splitbuttonview~SplitButtonView} under the provided name in the
* {@link module:ui/componentfactory~ComponentFactory}
*
* @property {String} name The unique name of the drop-down. It is recommended to precede it with the "imageStyle:" prefix
* to avoid collision with the components' names registered by other plugins.
*
* @property {String} [title] The drop-down's title. It will be used as the split button label along with the title of the default item
* in the following manner: "Custom drop-down title: Default item title".
*
* Setting `title` to one of
* {@link module:image/imagestyle/imagestyleui~ImageStyleUI#localizedDefaultStylesTitles}
* will automatically translate it to the language of the editor.
*
* @property {Array.<String>} items The list of the names of the buttons that will be placed in the drop-down's toolbar.
* Each of the buttons has to be one of the {@link module:image/image~ImageConfig#styles default image style buttons}
* or to be defined as the {@link module:image/imagestyle~ImageStyleOptionDefinition image styling option}.
*
* @property {String} defaultItem The name of one of the buttons from the items list,
* which will be used as a default button for the drop-down's split button.
*
* @typedef {Object} module:image/imagestyle/imagestyleui~ImageStyleDropdownDefinition
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-image/src/imagestyle/utils.js":
/*!************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-image/src/imagestyle/utils.js ***!
\************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module image/imagestyle/utils
*/
const {
objectFullWidth,
objectInline,
objectLeft, objectRight, objectCenter,
objectBlockLeft, objectBlockRight
} = ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.icons;
/**
* Default image style options provided by the plugin that can be referred in the {@link module:image/image~ImageConfig#styles}
* configuration.
*
* There are available 5 styles focused on formatting:
*
* * **`'alignLeft'`** aligns the inline or block image to the left and wraps it with the text using the `image-style-align-left` class,
* * **`'alignRight'`** aligns the inline or block image to the right and wraps it with the text using the `image-style-align-right` class,
* * **`'alignCenter'`** centers the block image using the `image-style-align-center` class,
* * **`'alignBlockLeft'`** aligns the block image to the left using the `image-style-block-align-left` class,
* * **`'alignBlockRight'`** aligns the block image to the right using the `image-style-block-align-right` class,
*
* and 3 semantic styles:
*
* * **`'inline'`** is an inline image without any CSS class,
* * **`'block'`** is a block image without any CSS class,
* * **`'side'`** is a block image styled with the `image-style-side` CSS class.
*
* @readonly
* @type {Object.<String,module:image/imagestyle~ImageStyleOptionDefinition>}
*/
const DEFAULT_OPTIONS = {
// This style represents an image placed in the line of text.
get inline() {
return {
name: 'inline',
title: 'In line',
icon: objectInline,
modelElements: [ 'imageInline' ],
isDefault: true
};
},
// This style represents an image aligned to the left and wrapped with text.
get alignLeft() {
return {
name: 'alignLeft',
title: 'Left aligned image',
icon: objectLeft,
modelElements: [ 'imageBlock', 'imageInline' ],
className: 'image-style-align-left'
};
},
// This style represents an image aligned to the left.
get alignBlockLeft() {
return {
name: 'alignBlockLeft',
title: 'Left aligned image',
icon: objectBlockLeft,
modelElements: [ 'imageBlock' ],
className: 'image-style-block-align-left'
};
},
// This style represents a centered image.
get alignCenter() {
return {
name: 'alignCenter',
title: 'Centered image',
icon: objectCenter,
modelElements: [ 'imageBlock' ],
className: 'image-style-align-center'
};
},
// This style represents an image aligned to the right and wrapped with text.
get alignRight() {
return {
name: 'alignRight',
title: 'Right aligned image',
icon: objectRight,
modelElements: [ 'imageBlock', 'imageInline' ],
className: 'image-style-align-right'
};
},
// This style represents an image aligned to the right.
get alignBlockRight() {
return {
name: 'alignBlockRight',
title: 'Right aligned image',
icon: objectBlockRight,
modelElements: [ 'imageBlock' ],
className: 'image-style-block-align-right'
};
},
// This option is equal to the situation when no style is applied.
get block() {
return {
name: 'block',
title: 'Centered image',
icon: objectCenter,
modelElements: [ 'imageBlock' ],
isDefault: true
};
},
// This represents a side image.
get side() {
return {
name: 'side',
title: 'Side image',
icon: objectRight,
modelElements: [ 'imageBlock' ],
className: 'image-style-side'
};
}
};
/**
* Default image style icons provided by the plugin that can be referred in the {@link module:image/image~ImageConfig#styles}
* configuration.
*
* See {@link module:image/imagestyle~ImageStyleOptionDefinition#icon} to learn more.
*
* There are 7 default icons available: `'full'`, `'left'`, `'inlineLeft'`, `'center'`, `'right'`, `'inlineRight'`, and `'inline'`.
*
* @readonly
* @type {Object.<String,String>}
*/
const DEFAULT_ICONS = {
full: objectFullWidth,
left: objectBlockLeft,
right: objectBlockRight,
center: objectCenter,
inlineLeft: objectLeft,
inlineRight: objectRight,
inline: objectInline
};
/**
* Default drop-downs provided by the plugin that can be referred in the {@link module:image/image~ImageConfig#toolbar}
* configuration. The drop-downs are containers for the {@link module:image/imagestyle~ImageStyleConfig#options image style options}.
*
* If both of the `ImageEditing` plugins are loaded, there are 2 predefined drop-downs available:
*
* * **`'imageStyle:wrapText'`**, which contains the `alignLeft` and `alignRight` options, that is,
* those that wraps the text around the image,
* * **`'imageStyle:breakText'`**, which contains the `alignBlockLeft`, `alignCenter` and `alignBlockRight` options, that is,
* those that breaks the text around the image.
*
* @readonly
* @type {Array.<module:image/imagestyle/imagestyleui~ImageStyleDropdownDefinition>}
*/
const DEFAULT_DROPDOWN_DEFINITIONS = [ {
name: 'imageStyle:wrapText',
title: 'Wrap text',
defaultItem: 'imageStyle:alignLeft',
items: [ 'imageStyle:alignLeft', 'imageStyle:alignRight' ]
}, {
name: 'imageStyle:breakText',
title: 'Break text',
defaultItem: 'imageStyle:block',
items: [ 'imageStyle:alignBlockLeft', 'imageStyle:block', 'imageStyle:alignBlockRight' ]
} ];
/**
* Returns a list of the normalized and validated image style options.
*
* @protected
* @param {Object} config
* @param {Boolean} config.isInlinePluginLoaded
* Determines whether the {@link module:image/image/imageblockediting~ImageBlockEditing `ImageBlockEditing`} plugin has been loaded.
* @param {Boolean} config.isBlockPluginLoaded
* Determines whether the {@link module:image/image/imageinlineediting~ImageInlineEditing `ImageInlineEditing`} plugin has been loaded.
* @param {module:image/imagestyle~ImageStyleConfig} config.configuredStyles
* The image styles configuration provided in the image styles {@link module:image/image~ImageConfig#styles configuration}
* as a default or custom value.
* @returns {module:image/imagestyle~ImageStyleConfig}
* * Each of options contains a complete icon markup.
* * The image style options not supported by any of the loaded plugins are filtered out.
*/
function normalizeStyles( config ) {
const configuredStyles = config.configuredStyles.options || [];
const styles = configuredStyles
.map( arrangement => normalizeDefinition( arrangement ) )
.filter( arrangement => isValidOption( arrangement, config ) );
return styles;
}
/**
* Returns the default image styles configuration depending on the loaded image editing plugins.
* @protected
*
* @param {Boolean} isInlinePluginLoaded
* Determines whether the {@link module:image/image/imageblockediting~ImageBlockEditing `ImageBlockEditing`} plugin has been loaded.
*
* @param {Boolean} isBlockPluginLoaded
* Determines whether the {@link module:image/image/imageinlineediting~ImageInlineEditing `ImageInlineEditing`} plugin has been loaded.
*
* @returns {Object<String,Array>}
* It returns an object with the lists of the image style options and groups defined as strings related to the
* {@link module:image/imagestyle/utils~DEFAULT_OPTIONS default options}
*/
function getDefaultStylesConfiguration( isBlockPluginLoaded, isInlinePluginLoaded ) {
if ( isBlockPluginLoaded && isInlinePluginLoaded ) {
return {
options: [
'inline', 'alignLeft', 'alignRight',
'alignCenter', 'alignBlockLeft', 'alignBlockRight',
'block', 'side'
]
};
} else if ( isBlockPluginLoaded ) {
return {
options: [ 'block', 'side' ]
};
} else if ( isInlinePluginLoaded ) {
return {
options: [ 'inline', 'alignLeft', 'alignRight' ]
};
}
return {};
}
/**
* Returns a list of the available predefined drop-downs' definitions depending on the loaded image editing plugins.
* @protected
*
* @param {module:core/plugincollection~PluginCollection} pluginCollection
* @returns {Array.<module:image/imagestyle/imagestyleui~ImageStyleDropdownDefinition>}
*/
function getDefaultDropdownDefinitions( pluginCollection ) {
if ( pluginCollection.has( 'ImageBlockEditing' ) && pluginCollection.has( 'ImageInlineEditing' ) ) {
return [ ...DEFAULT_DROPDOWN_DEFINITIONS ];
} else {
return [];
}
}
// Normalizes an image style option or group provided in the {@link module:image/image~ImageConfig#styles}
// and returns it in a {@link module:image/imagestyle~ImageStyleOptionDefinition}/
//
// @param {Object|String} definition
//
// @returns {module:image/imagestyle~ImageStyleOptionDefinition}}
function normalizeDefinition( definition ) {
if ( typeof definition === 'string' ) {
// Just the name of the style has been passed, but none of the defaults.
if ( !DEFAULT_OPTIONS[ definition ] ) {
// Normalize the style anyway to prevent errors.
definition = { name: definition };
}
// Just the name of the style has been passed and it's one of the defaults, just use it.
// Clone the style to avoid overriding defaults.
else {
definition = { ...DEFAULT_OPTIONS[ definition ] };
}
} else {
// If an object style has been passed and if the name matches one of the defaults,
// extend it with defaults – the user wants to customize a default style.
// Note: Don't override the user–defined style object, clone it instead.
definition = extendStyle( DEFAULT_OPTIONS[ definition.name ], definition );
}
// If an icon is defined as a string and correspond with a name
// in default icons, use the default icon provided by the plugin.
if ( typeof definition.icon === 'string' ) {
definition.icon = DEFAULT_ICONS[ definition.icon ] || definition.icon;
}
return definition;
}
// Checks if the image style option is valid:
// * if it has the modelElements fields defined and filled,
// * if the defined modelElements are supported by any of the loaded image editing plugins.
// It also displays a console warning these conditions are not met.
//
// @param {module:image/imagestyle~ImageStyleOptionDefinition} image style option
// @param {Object.<String,Boolean>} { isBlockPluginLoaded, isInlinePluginLoaded }
//
// @returns Boolean
function isValidOption( option, { isBlockPluginLoaded, isInlinePluginLoaded } ) {
const { modelElements, name } = option;
if ( !modelElements || !modelElements.length || !name ) {
warnInvalidStyle( { style: option } );
return false;
} else {
const supportedElements = [ isBlockPluginLoaded ? 'imageBlock' : null, isInlinePluginLoaded ? 'imageInline' : null ];
// Check if the option is supported by any of the loaded plugins.
if ( !modelElements.some( elementName => supportedElements.includes( elementName ) ) ) {
/**
* In order to work correctly, each image style {@link module:image/imagestyle~ImageStyleOptionDefinition option}
* requires specific model elements (also: types of images) to be supported by the editor.
*
* Model element names to which the image style option can be applied are defined in the
* {@link module:image/imagestyle~ImageStyleOptionDefinition#modelElements} property of the style option
* definition.
*
* Explore the warning in the console to find out precisely which option is not supported and which editor plugins
* are missing. Make sure these plugins are loaded in your editor to get this image style option working.
*
* @error image-style-missing-dependency
* @param {String} [option] The name of the unsupported option.
* @param {String} [missingPlugins] The names of the plugins one of which has to be loaded for the particular option.
*/
(0,ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__.logWarning)( 'image-style-missing-dependency', {
style: option,
missingPlugins: modelElements.map( name => name === 'imageBlock' ? 'ImageBlockEditing' : 'ImageInlineEditing' )
} );
return false;
}
}
return true;
}
// Extends the default style with a style provided by the developer.
// Note: Don't override the custom–defined style object, clone it instead.
//
// @param {module:image/imagestyle~ImageStyleOptionDefinition} source
// @param {Object} style
//
// @returns {module:image/imagestyle~ImageStyleOptionDefinition}
function extendStyle( source, style ) {
const extendedStyle = { ...style };
for ( const prop in source ) {
if ( !Object.prototype.hasOwnProperty.call( style, prop ) ) {
extendedStyle[ prop ] = source[ prop ];
}
}
return extendedStyle;
}
// Displays a console warning with the 'image-style-configuration-definition-invalid' error.
// @param {Object} info
function warnInvalidStyle( info ) {
/**
* The image style definition provided in the configuration is invalid.
*
* Please make sure the definition implements properly one of the following:
*
* * {@link module:image/imagestyle~ImageStyleOptionDefinition image style option definition},
* * {@link module:image/imagestyle/imagestyleui~ImageStyleDropdownDefinition image style dropdown definition}
*
* @error image-style-configuration-definition-invalid
* @param {String} [dropdown] The name of the invalid drop-down
* @param {String} [style] The name of the invalid image style option
*/
(0,ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__.logWarning)( 'image-style-configuration-definition-invalid', info );
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ({
normalizeStyles,
getDefaultStylesConfiguration,
getDefaultDropdownDefinitions,
warnInvalidStyle,
DEFAULT_OPTIONS,
DEFAULT_ICONS,
DEFAULT_DROPDOWN_DEFINITIONS
});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-image/src/imagetextalternative.js":
/*!****************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-image/src/imagetextalternative.js ***!
\****************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ImageTextAlternative)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var _imagetextalternative_imagetextalternativeediting__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./imagetextalternative/imagetextalternativeediting */ "./node_modules/@ckeditor/ckeditor5-image/src/imagetextalternative/imagetextalternativeediting.js");
/* harmony import */ var _imagetextalternative_imagetextalternativeui__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./imagetextalternative/imagetextalternativeui */ "./node_modules/@ckeditor/ckeditor5-image/src/imagetextalternative/imagetextalternativeui.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module image/imagetextalternative
*/
/**
* The image text alternative plugin.
*
* For a detailed overview, check the {@glink features/images/images-styles image styles} documentation.
*
* This is a "glue" plugin which loads the
* {@link module:image/imagetextalternative/imagetextalternativeediting~ImageTextAlternativeEditing}
* and {@link module:image/imagetextalternative/imagetextalternativeui~ImageTextAlternativeUI} plugins.
*
* @extends module:core/plugin~Plugin
*/
class ImageTextAlternative extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [ _imagetextalternative_imagetextalternativeediting__WEBPACK_IMPORTED_MODULE_1__["default"], _imagetextalternative_imagetextalternativeui__WEBPACK_IMPORTED_MODULE_2__["default"] ];
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'ImageTextAlternative';
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-image/src/imagetextalternative/imagetextalternativecommand.js":
/*!********************************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-image/src/imagetextalternative/imagetextalternativecommand.js ***!
\********************************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ImageTextAlternativeCommand)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module image/imagetextalternative/imagetextalternativecommand
*/
/**
* The image text alternative command. It is used to change the `alt` attribute of `<imageBlock>` and `<imageInline>` model elements.
*
* @extends module:core/command~Command
*/
class ImageTextAlternativeCommand extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Command {
/**
* The command value: `false` if there is no `alt` attribute, otherwise the value of the `alt` attribute.
*
* @readonly
* @observable
* @member {String|Boolean} #value
*/
/**
* @inheritDoc
*/
refresh() {
const editor = this.editor;
const imageUtils = editor.plugins.get( 'ImageUtils' );
const element = imageUtils.getClosestSelectedImageElement( this.editor.model.document.selection );
this.isEnabled = !!element;
if ( this.isEnabled && element.hasAttribute( 'alt' ) ) {
this.value = element.getAttribute( 'alt' );
} else {
this.value = false;
}
}
/**
* Executes the command.
*
* @fires execute
* @param {Object} options
* @param {String} options.newValue The new value of the `alt` attribute to set.
*/
execute( options ) {
const editor = this.editor;
const imageUtils = editor.plugins.get( 'ImageUtils' );
const model = editor.model;
const imageElement = imageUtils.getClosestSelectedImageElement( model.document.selection );
model.change( writer => {
writer.setAttribute( 'alt', options.newValue, imageElement );
} );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-image/src/imagetextalternative/imagetextalternativeediting.js":
/*!********************************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-image/src/imagetextalternative/imagetextalternativeediting.js ***!
\********************************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ImageTextAlternativeEditing)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var _imagetextalternativecommand__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./imagetextalternativecommand */ "./node_modules/@ckeditor/ckeditor5-image/src/imagetextalternative/imagetextalternativecommand.js");
/* harmony import */ var _imageutils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../imageutils */ "./node_modules/@ckeditor/ckeditor5-image/src/imageutils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module image/imagetextalternative/imagetextalternativeediting
*/
/**
* The image text alternative editing plugin.
*
* Registers the `'imageTextAlternative'` command.
*
* @extends module:core/plugin~Plugin
*/
class ImageTextAlternativeEditing extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [ _imageutils__WEBPACK_IMPORTED_MODULE_2__["default"] ];
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'ImageTextAlternativeEditing';
}
/**
* @inheritDoc
*/
init() {
this.editor.commands.add( 'imageTextAlternative', new _imagetextalternativecommand__WEBPACK_IMPORTED_MODULE_1__["default"]( this.editor ) );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-image/src/imagetextalternative/imagetextalternativeui.js":
/*!***************************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-image/src/imagetextalternative/imagetextalternativeui.js ***!
\***************************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ImageTextAlternativeUI)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/ui */ "./node_modules/ckeditor5/src/ui.js");
/* harmony import */ var _ui_textalternativeformview__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./ui/textalternativeformview */ "./node_modules/@ckeditor/ckeditor5-image/src/imagetextalternative/ui/textalternativeformview.js");
/* harmony import */ var _image_ui_utils__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../image/ui/utils */ "./node_modules/@ckeditor/ckeditor5-image/src/image/ui/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module image/imagetextalternative/imagetextalternativeui
*/
/**
* The image text alternative UI plugin.
*
* The plugin uses the {@link module:ui/panel/balloon/contextualballoon~ContextualBalloon}.
*
* @extends module:core/plugin~Plugin
*/
class ImageTextAlternativeUI extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [ ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.ContextualBalloon ];
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'ImageTextAlternativeUI';
}
/**
* @inheritDoc
*/
init() {
this._createButton();
this._createForm();
}
/**
* @inheritDoc
*/
destroy() {
super.destroy();
// Destroy created UI components as they are not automatically destroyed (see ckeditor5#1341).
this._form.destroy();
}
/**
* Creates a button showing the balloon panel for changing the image text alternative and
* registers it in the editor {@link module:ui/componentfactory~ComponentFactory ComponentFactory}.
*
* @private
*/
_createButton() {
const editor = this.editor;
const t = editor.t;
editor.ui.componentFactory.add( 'imageTextAlternative', locale => {
const command = editor.commands.get( 'imageTextAlternative' );
const view = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.ButtonView( locale );
view.set( {
label: t( 'Change image text alternative' ),
icon: ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.icons.lowVision,
tooltip: true
} );
view.bind( 'isEnabled' ).to( command, 'isEnabled' );
this.listenTo( view, 'execute', () => {
this._showForm();
} );
return view;
} );
}
/**
* Creates the {@link module:image/imagetextalternative/ui/textalternativeformview~TextAlternativeFormView}
* form.
*
* @private
*/
_createForm() {
const editor = this.editor;
const view = editor.editing.view;
const viewDocument = view.document;
const imageUtils = editor.plugins.get( 'ImageUtils' );
/**
* The contextual balloon plugin instance.
*
* @private
* @member {module:ui/panel/balloon/contextualballoon~ContextualBalloon}
*/
this._balloon = this.editor.plugins.get( 'ContextualBalloon' );
/**
* A form containing a textarea and buttons, used to change the `alt` text value.
*
* @member {module:image/imagetextalternative/ui/textalternativeformview~TextAlternativeFormView}
*/
this._form = new _ui_textalternativeformview__WEBPACK_IMPORTED_MODULE_2__["default"]( editor.locale );
// Render the form so its #element is available for clickOutsideHandler.
this._form.render();
this.listenTo( this._form, 'submit', () => {
editor.execute( 'imageTextAlternative', {
newValue: this._form.labeledInput.fieldView.element.value
} );
this._hideForm( true );
} );
this.listenTo( this._form, 'cancel', () => {
this._hideForm( true );
} );
// Close the form on Esc key press.
this._form.keystrokes.set( 'Esc', ( data, cancel ) => {
this._hideForm( true );
cancel();
} );
// Reposition the balloon or hide the form if an image widget is no longer selected.
this.listenTo( editor.ui, 'update', () => {
if ( !imageUtils.getClosestSelectedImageWidget( viewDocument.selection ) ) {
this._hideForm( true );
} else if ( this._isVisible ) {
(0,_image_ui_utils__WEBPACK_IMPORTED_MODULE_3__.repositionContextualBalloon)( editor );
}
} );
// Close on click outside of balloon panel element.
(0,ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.clickOutsideHandler)( {
emitter: this._form,
activator: () => this._isVisible,
contextElements: [ this._balloon.view.element ],
callback: () => this._hideForm()
} );
}
/**
* Shows the {@link #_form} in the {@link #_balloon}.
*
* @private
*/
_showForm() {
if ( this._isVisible ) {
return;
}
const editor = this.editor;
const command = editor.commands.get( 'imageTextAlternative' );
const labeledInput = this._form.labeledInput;
this._form.disableCssTransitions();
if ( !this._isInBalloon ) {
this._balloon.add( {
view: this._form,
position: (0,_image_ui_utils__WEBPACK_IMPORTED_MODULE_3__.getBalloonPositionData)( editor )
} );
}
// Make sure that each time the panel shows up, the field remains in sync with the value of
// the command. If the user typed in the input, then canceled the balloon (`labeledInput#value`
// stays unaltered) and re-opened it without changing the value of the command, they would see the
// old value instead of the actual value of the command.
// https://github.com/ckeditor/ckeditor5-image/issues/114
labeledInput.fieldView.value = labeledInput.fieldView.element.value = command.value || '';
this._form.labeledInput.fieldView.select();
this._form.enableCssTransitions();
}
/**
* Removes the {@link #_form} from the {@link #_balloon}.
*
* @param {Boolean} [focusEditable=false] Controls whether the editing view is focused afterwards.
* @private
*/
_hideForm( focusEditable ) {
if ( !this._isInBalloon ) {
return;
}
// Blur the input element before removing it from DOM to prevent issues in some browsers.
// See https://github.com/ckeditor/ckeditor5/issues/1501.
if ( this._form.focusTracker.isFocused ) {
this._form.saveButtonView.focus();
}
this._balloon.remove( this._form );
if ( focusEditable ) {
this.editor.editing.view.focus();
}
}
/**
* Returns `true` when the {@link #_form} is the visible view in the {@link #_balloon}.
*
* @private
* @type {Boolean}
*/
get _isVisible() {
return this._balloon.visibleView === this._form;
}
/**
* Returns `true` when the {@link #_form} is in the {@link #_balloon}.
*
* @private
* @type {Boolean}
*/
get _isInBalloon() {
return this._balloon.hasView( this._form );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-image/src/imagetextalternative/ui/textalternativeformview.js":
/*!*******************************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-image/src/imagetextalternative/ui/textalternativeformview.js ***!
\*******************************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ TextAlternativeFormView)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/ui */ "./node_modules/ckeditor5/src/ui.js");
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var _theme_textalternativeform_css__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../../../theme/textalternativeform.css */ "./node_modules/@ckeditor/ckeditor5-image/theme/textalternativeform.css");
/* harmony import */ var _ckeditor_ckeditor5_ui_theme_components_responsive_form_responsiveform_css__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! @ckeditor/ckeditor5-ui/theme/components/responsive-form/responsiveform.css */ "./node_modules/@ckeditor/ckeditor5-ui/theme/components/responsive-form/responsiveform.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module image/imagetextalternative/ui/textalternativeformview
*/
// See: #8833.
// eslint-disable-next-line ckeditor5-rules/ckeditor-imports
/**
* The TextAlternativeFormView class.
*
* @extends module:ui/view~View
*/
class TextAlternativeFormView extends ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.View {
/**
* @inheritDoc
*/
constructor( locale ) {
super( locale );
const t = this.locale.t;
/**
* Tracks information about the DOM focus in the form.
*
* @readonly
* @member {module:utils/focustracker~FocusTracker}
*/
this.focusTracker = new ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__.FocusTracker();
/**
* An instance of the {@link module:utils/keystrokehandler~KeystrokeHandler}.
*
* @readonly
* @member {module:utils/keystrokehandler~KeystrokeHandler}
*/
this.keystrokes = new ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__.KeystrokeHandler();
/**
* An input with a label.
*
* @member {module:ui/labeledfield/labeledfieldview~LabeledFieldView} #labeledInput
*/
this.labeledInput = this._createLabeledInputView();
/**
* A button used to submit the form.
*
* @member {module:ui/button/buttonview~ButtonView} #saveButtonView
*/
this.saveButtonView = this._createButton( t( 'Save' ), ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_2__.icons.check, 'ck-button-save' );
this.saveButtonView.type = 'submit';
/**
* A button used to cancel the form.
*
* @member {module:ui/button/buttonview~ButtonView} #cancelButtonView
*/
this.cancelButtonView = this._createButton( t( 'Cancel' ), ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_2__.icons.cancel, 'ck-button-cancel', 'cancel' );
/**
* A collection of views which can be focused in the form.
*
* @readonly
* @protected
* @member {module:ui/viewcollection~ViewCollection}
*/
this._focusables = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.ViewCollection();
/**
* Helps cycling over {@link #_focusables} in the form.
*
* @readonly
* @protected
* @member {module:ui/focuscycler~FocusCycler}
*/
this._focusCycler = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.FocusCycler( {
focusables: this._focusables,
focusTracker: this.focusTracker,
keystrokeHandler: this.keystrokes,
actions: {
// Navigate form fields backwards using the Shift + Tab keystroke.
focusPrevious: 'shift + tab',
// Navigate form fields forwards using the Tab key.
focusNext: 'tab'
}
} );
this.setTemplate( {
tag: 'form',
attributes: {
class: [
'ck',
'ck-text-alternative-form',
'ck-responsive-form'
],
// https://github.com/ckeditor/ckeditor5-image/issues/40
tabindex: '-1'
},
children: [
this.labeledInput,
this.saveButtonView,
this.cancelButtonView
]
} );
(0,ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.injectCssTransitionDisabler)( this );
}
/**
* @inheritDoc
*/
render() {
super.render();
this.keystrokes.listenTo( this.element );
(0,ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.submitHandler)( { view: this } );
[ this.labeledInput, this.saveButtonView, this.cancelButtonView ]
.forEach( v => {
// Register the view as focusable.
this._focusables.add( v );
// Register the view in the focus tracker.
this.focusTracker.add( v.element );
} );
}
/**
* @inheritDoc
*/
destroy() {
super.destroy();
this.focusTracker.destroy();
this.keystrokes.destroy();
}
/**
* Creates the button view.
*
* @private
* @param {String} label The button label
* @param {String} icon The button's icon.
* @param {String} className The additional button CSS class name.
* @param {String} [eventName] The event name that the ButtonView#execute event will be delegated to.
* @returns {module:ui/button/buttonview~ButtonView} The button view instance.
*/
_createButton( label, icon, className, eventName ) {
const button = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.ButtonView( this.locale );
button.set( {
label,
icon,
tooltip: true
} );
button.extendTemplate( {
attributes: {
class: className
}
} );
if ( eventName ) {
button.delegate( 'execute' ).to( this, eventName );
}
return button;
}
/**
* Creates an input with a label.
*
* @private
* @returns {module:ui/labeledfield/labeledfieldview~LabeledFieldView} Labeled field view instance.
*/
_createLabeledInputView() {
const t = this.locale.t;
const labeledInput = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.LabeledFieldView( this.locale, ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.createLabeledInputText );
labeledInput.label = t( 'Text alternative' );
return labeledInput;
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-image/src/imagetoolbar.js":
/*!********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-image/src/imagetoolbar.js ***!
\********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ImageToolbar)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_widget__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/widget */ "./node_modules/ckeditor5/src/widget.js");
/* harmony import */ var _imageutils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./imageutils */ "./node_modules/@ckeditor/ckeditor5-image/src/imageutils.js");
/* harmony import */ var lodash_es__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! lodash-es */ "./node_modules/lodash-es/isObject.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module image/imagetoolbar
*/
/**
* The image toolbar plugin. It creates and manages the image toolbar (the toolbar displayed when an image is selected).
*
* For an overview, check the {@glink features/images/images-overview#image-contextual-toolbar image contextual toolbar} documentation.
*
* Instances of toolbar components (e.g. buttons) are created using the editor's
* {@link module:ui/componentfactory~ComponentFactory component factory}
* based on the {@link module:image/image~ImageConfig#toolbar `image.toolbar` configuration option}.
*
* The toolbar uses the {@link module:ui/panel/balloon/contextualballoon~ContextualBalloon}.
*
* @extends module:core/plugin~Plugin
*/
class ImageToolbar extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [ ckeditor5_src_widget__WEBPACK_IMPORTED_MODULE_1__.WidgetToolbarRepository, _imageutils__WEBPACK_IMPORTED_MODULE_2__["default"] ];
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'ImageToolbar';
}
/**
* @inheritDoc
*/
afterInit() {
const editor = this.editor;
const t = editor.t;
const widgetToolbarRepository = editor.plugins.get( ckeditor5_src_widget__WEBPACK_IMPORTED_MODULE_1__.WidgetToolbarRepository );
const imageUtils = editor.plugins.get( 'ImageUtils' );
widgetToolbarRepository.register( 'image', {
ariaLabel: t( 'Image toolbar' ),
items: normalizeDeclarativeConfig( editor.config.get( 'image.toolbar' ) || [] ),
getRelatedElement: selection => imageUtils.getClosestSelectedImageWidget( selection )
} );
}
}
/**
* Items to be placed in the image toolbar.
* This option is used by the {@link module:image/imagetoolbar~ImageToolbar} feature.
*
* Assuming that you use the following features:
*
* * {@link module:image/imagestyle~ImageStyle} (with a default configuration),
* * {@link module:image/imagetextalternative~ImageTextAlternative},
* * {@link module:image/imagecaption~ImageCaption},
*
* the following toolbar items will be available in {@link module:ui/componentfactory~ComponentFactory}:
* * `'imageTextAlternative'`,
* * `'toggleImageCaption'`,
* * {@link module:image/image~ImageConfig#styles buttons provided by the `ImageStyle` plugin},
* * {@link module:image/imagestyle/utils~DEFAULT_DROPDOWN_DEFINITIONS drop-downs provided by the `ImageStyle` plugin},
*
* so you can configure the toolbar like this:
*
* const imageConfig = {
* toolbar: [
* 'imageStyle:inline', 'imageStyle:wrapText', 'imageStyle:breakText', '|',
* 'toggleImageCaption', 'imageTextAlternative'
* ]
* };
*
* Besides that, the `ImageStyle` plugin allows to define a
* {@link module:image/imagestyle/imagestyleui~ImageStyleDropdownDefinition custom drop-down} while configuring the toolbar.
*
* The same items can also be used in the {@link module:core/editor/editorconfig~EditorConfig#toolbar main editor toolbar}.
*
* Read more about configuring toolbar in {@link module:core/editor/editorconfig~EditorConfig#toolbar}.
*
* @member {Array.<String>} module:image/image~ImageConfig#toolbar
*/
// Convert the dropdown definitions to their keys registered in the ComponentFactory.
// The registration precess should be handled by the plugin which handles the UI of a particular feature.
//
// @param {Array.<String|module:image/imagestyle/imagestyleui~ImageStyleDropdownDefinition>} config
//
// @returns {Array.<String>}
function normalizeDeclarativeConfig( config ) {
return config.map( item => (0,lodash_es__WEBPACK_IMPORTED_MODULE_3__["default"])( item ) ? item.name : item );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-image/src/imageupload.js":
/*!*******************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-image/src/imageupload.js ***!
\*******************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ImageUpload)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var _imageupload_imageuploadui__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./imageupload/imageuploadui */ "./node_modules/@ckeditor/ckeditor5-image/src/imageupload/imageuploadui.js");
/* harmony import */ var _imageupload_imageuploadprogress__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./imageupload/imageuploadprogress */ "./node_modules/@ckeditor/ckeditor5-image/src/imageupload/imageuploadprogress.js");
/* harmony import */ var _imageupload_imageuploadediting__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./imageupload/imageuploadediting */ "./node_modules/@ckeditor/ckeditor5-image/src/imageupload/imageuploadediting.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module image/imageupload
*/
/**
* The image upload plugin.
*
* For a detailed overview, check the {@glink features/images/image-upload/image-upload image upload feature} documentation.
*
* This plugin does not do anything directly, but it loads a set of specific plugins to enable image uploading:
*
* * {@link module:image/imageupload/imageuploadediting~ImageUploadEditing},
* * {@link module:image/imageupload/imageuploadui~ImageUploadUI},
* * {@link module:image/imageupload/imageuploadprogress~ImageUploadProgress}.
*
* @extends module:core/plugin~Plugin
*/
class ImageUpload extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'ImageUpload';
}
/**
* @inheritDoc
*/
static get requires() {
return [ _imageupload_imageuploadediting__WEBPACK_IMPORTED_MODULE_3__["default"], _imageupload_imageuploadui__WEBPACK_IMPORTED_MODULE_1__["default"], _imageupload_imageuploadprogress__WEBPACK_IMPORTED_MODULE_2__["default"] ];
}
}
/**
* The image upload configuration.
*
* @member {module:image/imageupload~ImageUploadConfig} module:image/image~ImageConfig#upload
*/
/**
* The configuration of the image upload feature. Used by the image upload feature in the `@ckeditor/ckeditor5-image` package.
*
* ClassicEditor
* .create( editorElement, {
* image: {
* upload: ... // Image upload feature options.
* }
* } )
* .then( ... )
* .catch( ... );
*
* See {@link module:core/editor/editorconfig~EditorConfig all editor options}.
*
* @interface module:image/imageupload~ImageUploadConfig
*/
/**
* The list of accepted image types.
*
* The accepted types of images can be customized to allow only certain types of images:
*
* // Allow only JPEG and PNG images:
* const imageUploadConfig = {
* types: [ 'png', 'jpeg' ]
* };
*
* The type string should match [one of the sub-types](https://www.iana.org/assignments/media-types/media-types.xhtml#image)
* of the image MIME type. For example, for the `image/jpeg` MIME type, add `'jpeg'` to your image upload configuration.
*
* **Note:** This setting only restricts some image types to be selected and uploaded through the CKEditor UI and commands. Image type
* recognition and filtering should also be implemented on the server which accepts image uploads.
*
* @member {Array.<String>} module:image/imageupload~ImageUploadConfig#types
* @default [ 'jpeg', 'png', 'gif', 'bmp', 'webp', 'tiff' ]
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-image/src/imageupload/imageuploadediting.js":
/*!**************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-image/src/imageupload/imageuploadediting.js ***!
\**************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ImageUploadEditing),
/* harmony export */ "isHtmlIncluded": () => (/* binding */ isHtmlIncluded)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/engine */ "./node_modules/ckeditor5/src/engine.js");
/* harmony import */ var ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ckeditor5/src/ui */ "./node_modules/ckeditor5/src/ui.js");
/* harmony import */ var ckeditor5_src_clipboard__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ckeditor5/src/clipboard */ "./node_modules/ckeditor5/src/clipboard.js");
/* harmony import */ var ckeditor5_src_upload__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ckeditor5/src/upload */ "./node_modules/ckeditor5/src/upload.js");
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/* harmony import */ var _imageutils__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ../imageutils */ "./node_modules/@ckeditor/ckeditor5-image/src/imageutils.js");
/* harmony import */ var _uploadimagecommand__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./uploadimagecommand */ "./node_modules/@ckeditor/ckeditor5-image/src/imageupload/uploadimagecommand.js");
/* harmony import */ var _src_imageupload_utils__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./utils */ "./node_modules/@ckeditor/ckeditor5-image/src/imageupload/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module image/imageupload/imageuploadediting
*/
/**
* The editing part of the image upload feature. It registers the `'uploadImage'` command
* and the `imageUpload` command as an aliased name.
*
* When an image is uploaded, it fires the {@link ~ImageUploadEditing#event:uploadComplete `uploadComplete`} event
* that allows adding custom attributes to the {@link module:engine/model/element~Element image element}.
*
* @extends module:core/plugin~Plugin
*/
class ImageUploadEditing extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [ ckeditor5_src_upload__WEBPACK_IMPORTED_MODULE_4__.FileRepository, ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_2__.Notification, ckeditor5_src_clipboard__WEBPACK_IMPORTED_MODULE_3__.ClipboardPipeline, _imageutils__WEBPACK_IMPORTED_MODULE_6__["default"] ];
}
static get pluginName() {
return 'ImageUploadEditing';
}
/**
* @inheritDoc
*/
constructor( editor ) {
super( editor );
editor.config.define( 'image', {
upload: {
types: [ 'jpeg', 'png', 'gif', 'bmp', 'webp', 'tiff' ]
}
} );
/**
* An internal mapping of {@link module:upload/filerepository~FileLoader#id file loader UIDs} and
* model elements during the upload.
*
* Model element of the uploaded image can change, for instance, when {@link module:image/image/imagetypecommand~ImageTypeCommand}
* is executed as a result of adding caption or changing image style. As a result, the upload logic must keep track of the model
* element (reference) and resolve the upload for the correct model element (instead of the one that landed in the `$graveyard`
* after image type changed).
*
* @private
* @readonly
* @member {Map.<String,module:engine/model/element~Element>}
*/
this._uploadImageElements = new Map();
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const doc = editor.model.document;
const conversion = editor.conversion;
const fileRepository = editor.plugins.get( ckeditor5_src_upload__WEBPACK_IMPORTED_MODULE_4__.FileRepository );
const imageUtils = editor.plugins.get( 'ImageUtils' );
const imageTypes = (0,_src_imageupload_utils__WEBPACK_IMPORTED_MODULE_8__.createImageTypeRegExp)( editor.config.get( 'image.upload.types' ) );
const uploadImageCommand = new _uploadimagecommand__WEBPACK_IMPORTED_MODULE_7__["default"]( editor );
// Register `uploadImage` command and add `imageUpload` command as an alias for backward compatibility.
editor.commands.add( 'uploadImage', uploadImageCommand );
editor.commands.add( 'imageUpload', uploadImageCommand );
// Register upcast converter for uploadId.
conversion.for( 'upcast' )
.attributeToAttribute( {
view: {
name: 'img',
key: 'uploadId'
},
model: 'uploadId'
} );
// Handle pasted images.
// For every image file, a new file loader is created and a placeholder image is
// inserted into the content. Then, those images are uploaded once they appear in the model
// (see Document#change listener below).
this.listenTo( editor.editing.view.document, 'clipboardInput', ( evt, data ) => {
// Skip if non empty HTML data is included.
// https://github.com/ckeditor/ckeditor5-upload/issues/68
if ( isHtmlIncluded( data.dataTransfer ) ) {
return;
}
const images = Array.from( data.dataTransfer.files ).filter( file => {
// See https://github.com/ckeditor/ckeditor5-image/pull/254.
if ( !file ) {
return false;
}
return imageTypes.test( file.type );
} );
if ( !images.length ) {
return;
}
evt.stop();
editor.model.change( writer => {
// Set selection to paste target.
if ( data.targetRanges ) {
writer.setSelection( data.targetRanges.map( viewRange => editor.editing.mapper.toModelRange( viewRange ) ) );
}
// Upload images after the selection has changed in order to ensure the command's state is refreshed.
editor.model.enqueueChange( () => {
editor.execute( 'uploadImage', { file: images } );
} );
} );
} );
// Handle HTML pasted with images with base64 or blob sources.
// For every image file, a new file loader is created and a placeholder image is
// inserted into the content. Then, those images are uploaded once they appear in the model
// (see Document#change listener below).
this.listenTo( editor.plugins.get( 'ClipboardPipeline' ), 'inputTransformation', ( evt, data ) => {
const fetchableImages = Array.from( editor.editing.view.createRangeIn( data.content ) )
.filter( value => (0,_src_imageupload_utils__WEBPACK_IMPORTED_MODULE_8__.isLocalImage)( imageUtils, value.item ) && !value.item.getAttribute( 'uploadProcessed' ) )
.map( value => { return { promise: (0,_src_imageupload_utils__WEBPACK_IMPORTED_MODULE_8__.fetchLocalImage)( value.item ), imageElement: value.item }; } );
if ( !fetchableImages.length ) {
return;
}
const writer = new ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_1__.UpcastWriter( editor.editing.view.document );
for ( const fetchableImage of fetchableImages ) {
// Set attribute marking that the image was processed already.
writer.setAttribute( 'uploadProcessed', true, fetchableImage.imageElement );
const loader = fileRepository.createLoader( fetchableImage.promise );
if ( loader ) {
writer.setAttribute( 'src', '', fetchableImage.imageElement );
writer.setAttribute( 'uploadId', loader.id, fetchableImage.imageElement );
}
}
} );
// Prevents from the browser redirecting to the dropped image.
editor.editing.view.document.on( 'dragover', ( evt, data ) => {
data.preventDefault();
} );
// Upload placeholder images that appeared in the model.
doc.on( 'change', () => {
// Note: Reversing changes to start with insertions and only then handle removals. If it was the other way around,
// loaders for **all** images that land in the $graveyard would abort while in fact only those that were **not** replaced
// by other images should be aborted.
const changes = doc.differ.getChanges( { includeChangesInGraveyard: true } ).reverse();
const insertedImagesIds = new Set();
for ( const entry of changes ) {
if ( entry.type == 'insert' && entry.name != '$text' ) {
const item = entry.position.nodeAfter;
const isInsertedInGraveyard = entry.position.root.rootName == '$graveyard';
for ( const imageElement of getImagesFromChangeItem( editor, item ) ) {
// Check if the image element still has upload id.
const uploadId = imageElement.getAttribute( 'uploadId' );
if ( !uploadId ) {
continue;
}
// Check if the image is loaded on this client.
const loader = fileRepository.loaders.get( uploadId );
if ( !loader ) {
continue;
}
if ( isInsertedInGraveyard ) {
// If the image was inserted to the graveyard for good (**not** replaced by another image),
// only then abort the loading process.
if ( !insertedImagesIds.has( uploadId ) ) {
loader.abort();
}
} else {
// Remember the upload id of the inserted image. If it acted as a replacement for another
// image (which landed in the $graveyard), the related loader will not be aborted because
// this is still the same image upload.
insertedImagesIds.add( uploadId );
// Keep the mapping between the upload ID and the image model element so the upload
// can later resolve in the context of the correct model element. The model element could
// change for the same upload if one image was replaced by another (e.g. image type was changed),
// so this may also replace an existing mapping.
this._uploadImageElements.set( uploadId, imageElement );
if ( loader.status == 'idle' ) {
// If the image was inserted into content and has not been loaded yet, start loading it.
this._readAndUpload( loader );
}
}
}
}
}
} );
// Set the default handler for feeding the image element with `src` and `srcset` attributes.
this.on( 'uploadComplete', ( evt, { imageElement, data } ) => {
const urls = data.urls ? data.urls : data;
this.editor.model.change( writer => {
writer.setAttribute( 'src', urls.default, imageElement );
this._parseAndSetSrcsetAttributeOnImage( urls, imageElement, writer );
} );
}, { priority: 'low' } );
}
/**
* @inheritDoc
*/
afterInit() {
const schema = this.editor.model.schema;
// Setup schema to allow uploadId and uploadStatus for images.
// Wait for ImageBlockEditing or ImageInlineEditing to register their elements first,
// that's why doing this in afterInit() instead of init().
if ( this.editor.plugins.has( 'ImageBlockEditing' ) ) {
schema.extend( 'imageBlock', {
allowAttributes: [ 'uploadId', 'uploadStatus' ]
} );
}
if ( this.editor.plugins.has( 'ImageInlineEditing' ) ) {
schema.extend( 'imageInline', {
allowAttributes: [ 'uploadId', 'uploadStatus' ]
} );
}
}
/**
* Reads and uploads an image.
*
* The image is read from the disk and as a Base64-encoded string it is set temporarily to
* `image[src]`. When the image is successfully uploaded, the temporary data is replaced with the target
* image's URL (the URL to the uploaded image on the server).
*
* @protected
* @param {module:upload/filerepository~FileLoader} loader
* @returns {Promise}
*/
_readAndUpload( loader ) {
const editor = this.editor;
const model = editor.model;
const t = editor.locale.t;
const fileRepository = editor.plugins.get( ckeditor5_src_upload__WEBPACK_IMPORTED_MODULE_4__.FileRepository );
const notification = editor.plugins.get( ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_2__.Notification );
const imageUtils = editor.plugins.get( 'ImageUtils' );
const imageUploadElements = this._uploadImageElements;
model.enqueueChange( { isUndoable: false }, writer => {
writer.setAttribute( 'uploadStatus', 'reading', imageUploadElements.get( loader.id ) );
} );
return loader.read()
.then( () => {
const promise = loader.upload();
const imageElement = imageUploadElements.get( loader.id );
// Force re–paint in Safari. Without it, the image will display with a wrong size.
// https://github.com/ckeditor/ckeditor5/issues/1975
/* istanbul ignore next */
if ( ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_5__.env.isSafari ) {
const viewFigure = editor.editing.mapper.toViewElement( imageElement );
const viewImg = imageUtils.findViewImgElement( viewFigure );
editor.editing.view.once( 'render', () => {
// Early returns just to be safe. There might be some code ran
// in between the outer scope and this callback.
if ( !viewImg.parent ) {
return;
}
const domFigure = editor.editing.view.domConverter.mapViewToDom( viewImg.parent );
if ( !domFigure ) {
return;
}
const originalDisplay = domFigure.style.display;
domFigure.style.display = 'none';
// Make sure this line will never be removed during minification for having "no effect".
domFigure._ckHack = domFigure.offsetHeight;
domFigure.style.display = originalDisplay;
} );
}
model.enqueueChange( { isUndoable: false }, writer => {
writer.setAttribute( 'uploadStatus', 'uploading', imageElement );
} );
return promise;
} )
.then( data => {
model.enqueueChange( { isUndoable: false }, writer => {
const imageElement = imageUploadElements.get( loader.id );
writer.setAttribute( 'uploadStatus', 'complete', imageElement );
/**
* An event fired when an image is uploaded. You can hook into this event to provide
* custom attributes to the {@link module:engine/model/element~Element image element} based on the data from
* the server.
*
* const imageUploadEditing = editor.plugins.get( 'ImageUploadEditing' );
*
* imageUploadEditing.on( 'uploadComplete', ( evt, { data, imageElement } ) => {
* editor.model.change( writer => {
* writer.setAttribute( 'someAttribute', 'foo', imageElement );
* } );
* } );
*
* You can also stop the default handler that sets the `src` and `srcset` attributes
* if you want to provide custom values for these attributes.
*
* imageUploadEditing.on( 'uploadComplete', ( evt, { data, imageElement } ) => {
* evt.stop();
* } );
*
* **Note**: This event is fired by the {@link module:image/imageupload/imageuploadediting~ImageUploadEditing} plugin.
*
* @event uploadComplete
* @param {Object} data The `uploadComplete` event data.
* @param {Object} data.data The data coming from the upload adapter.
* @param {module:engine/model/element~Element} data.imageElement The
* model {@link module:engine/model/element~Element image element} that can be customized.
*/
this.fire( 'uploadComplete', { data, imageElement } );
} );
clean();
} )
.catch( error => {
// If status is not 'error' nor 'aborted' - throw error because it means that something else went wrong,
// it might be generic error and it would be real pain to find what is going on.
if ( loader.status !== 'error' && loader.status !== 'aborted' ) {
throw error;
}
// Might be 'aborted'.
if ( loader.status == 'error' && error ) {
notification.showWarning( error, {
title: t( 'Upload failed' ),
namespace: 'upload'
} );
}
// Permanently remove image from insertion batch.
model.enqueueChange( { isUndoable: false }, writer => {
writer.remove( imageUploadElements.get( loader.id ) );
} );
clean();
} );
function clean() {
model.enqueueChange( { isUndoable: false }, writer => {
const imageElement = imageUploadElements.get( loader.id );
writer.removeAttribute( 'uploadId', imageElement );
writer.removeAttribute( 'uploadStatus', imageElement );
imageUploadElements.delete( loader.id );
} );
fileRepository.destroyLoader( loader );
}
}
/**
* Creates the `srcset` attribute based on a given file upload response and sets it as an attribute to a specific image element.
*
* @protected
* @param {Object} data Data object from which `srcset` will be created.
* @param {module:engine/model/element~Element} image The image element on which the `srcset` attribute will be set.
* @param {module:engine/model/writer~Writer} writer
*/
_parseAndSetSrcsetAttributeOnImage( data, image, writer ) {
// Srcset attribute for responsive images support.
let maxWidth = 0;
const srcsetAttribute = Object.keys( data )
// Filter out keys that are not integers.
.filter( key => {
const width = parseInt( key, 10 );
if ( !isNaN( width ) ) {
maxWidth = Math.max( maxWidth, width );
return true;
}
} )
// Convert each key to srcset entry.
.map( key => `${ data[ key ] } ${ key }w` )
// Join all entries.
.join( ', ' );
if ( srcsetAttribute != '' ) {
writer.setAttribute( 'srcset', {
data: srcsetAttribute,
width: maxWidth
}, image );
}
}
}
// Returns `true` if non-empty `text/html` is included in the data transfer.
//
// @param {module:clipboard/datatransfer~DataTransfer} dataTransfer
// @returns {Boolean}
function isHtmlIncluded( dataTransfer ) {
return Array.from( dataTransfer.types ).includes( 'text/html' ) && dataTransfer.getData( 'text/html' ) !== '';
}
function getImagesFromChangeItem( editor, item ) {
const imageUtils = editor.plugins.get( 'ImageUtils' );
return Array.from( editor.model.createRangeOn( item ) )
.filter( value => imageUtils.isImage( value.item ) )
.map( value => value.item );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-image/src/imageupload/imageuploadprogress.js":
/*!***************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-image/src/imageupload/imageuploadprogress.js ***!
\***************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ImageUploadProgress)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_upload__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/upload */ "./node_modules/ckeditor5/src/upload.js");
/* harmony import */ var _theme_imageuploadprogress_css__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../theme/imageuploadprogress.css */ "./node_modules/@ckeditor/ckeditor5-image/theme/imageuploadprogress.css");
/* harmony import */ var _theme_imageuploadicon_css__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../../theme/imageuploadicon.css */ "./node_modules/@ckeditor/ckeditor5-image/theme/imageuploadicon.css");
/* harmony import */ var _theme_imageuploadloader_css__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../../theme/imageuploadloader.css */ "./node_modules/@ckeditor/ckeditor5-image/theme/imageuploadloader.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module image/imageupload/imageuploadprogress
*/
/* globals setTimeout */
/**
* The image upload progress plugin.
* It shows a placeholder when the image is read from the disk and a progress bar while the image is uploading.
*
* @extends module:core/plugin~Plugin
*/
class ImageUploadProgress extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'ImageUploadProgress';
}
/**
* @inheritDoc
*/
constructor( editor ) {
super( editor );
/**
* The image placeholder that is displayed before real image data can be accessed.
*
* For the record, this image is a 1x1 px GIF with an aspect ratio set by CSS.
*
* @protected
* @member {String} #placeholder
*/
this.placeholder = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==';
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
// Upload status change - update image's view according to that status.
if ( editor.plugins.has( 'ImageBlockEditing' ) ) {
editor.editing.downcastDispatcher.on( 'attribute:uploadStatus:imageBlock', ( ...args ) => this.uploadStatusChange( ...args ) );
}
if ( editor.plugins.has( 'ImageInlineEditing' ) ) {
editor.editing.downcastDispatcher.on( 'attribute:uploadStatus:imageInline', ( ...args ) => this.uploadStatusChange( ...args ) );
}
}
/**
* This method is called each time the image `uploadStatus` attribute is changed.
*
* @param {module:utils/eventinfo~EventInfo} evt An object containing information about the fired event.
* @param {Object} data Additional information about the change.
* @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi
*/
uploadStatusChange( evt, data, conversionApi ) {
const editor = this.editor;
const modelImage = data.item;
const uploadId = modelImage.getAttribute( 'uploadId' );
if ( !conversionApi.consumable.consume( data.item, evt.name ) ) {
return;
}
const imageUtils = editor.plugins.get( 'ImageUtils' );
const fileRepository = editor.plugins.get( ckeditor5_src_upload__WEBPACK_IMPORTED_MODULE_1__.FileRepository );
const status = uploadId ? data.attributeNewValue : null;
const placeholder = this.placeholder;
const viewFigure = editor.editing.mapper.toViewElement( modelImage );
const viewWriter = conversionApi.writer;
if ( status == 'reading' ) {
// Start "appearing" effect and show placeholder with infinite progress bar on the top
// while image is read from disk.
_startAppearEffect( viewFigure, viewWriter );
_showPlaceholder( imageUtils, placeholder, viewFigure, viewWriter );
return;
}
// Show progress bar on the top of the image when image is uploading.
if ( status == 'uploading' ) {
const loader = fileRepository.loaders.get( uploadId );
// Start appear effect if needed - see https://github.com/ckeditor/ckeditor5-image/issues/191.
_startAppearEffect( viewFigure, viewWriter );
if ( !loader ) {
// There is no loader associated with uploadId - this means that image came from external changes.
// In such cases we still want to show the placeholder until image is fully uploaded.
// Show placeholder if needed - see https://github.com/ckeditor/ckeditor5-image/issues/191.
_showPlaceholder( imageUtils, placeholder, viewFigure, viewWriter );
} else {
// Hide placeholder and initialize progress bar showing upload progress.
_hidePlaceholder( viewFigure, viewWriter );
_showProgressBar( viewFigure, viewWriter, loader, editor.editing.view );
_displayLocalImage( imageUtils, viewFigure, viewWriter, loader );
}
return;
}
if ( status == 'complete' && fileRepository.loaders.get( uploadId ) ) {
_showCompleteIcon( viewFigure, viewWriter, editor.editing.view );
}
// Clean up.
_hideProgressBar( viewFigure, viewWriter );
_hidePlaceholder( viewFigure, viewWriter );
_stopAppearEffect( viewFigure, viewWriter );
}
}
// Adds ck-appear class to the image figure if one is not already applied.
//
// @param {module:engine/view/containerelement~ContainerElement} viewFigure
// @param {module:engine/view/downcastwriter~DowncastWriter} writer
function _startAppearEffect( viewFigure, writer ) {
if ( !viewFigure.hasClass( 'ck-appear' ) ) {
writer.addClass( 'ck-appear', viewFigure );
}
}
// Removes ck-appear class to the image figure if one is not already removed.
//
// @param {module:engine/view/containerelement~ContainerElement} viewFigure
// @param {module:engine/view/downcastwriter~DowncastWriter} writer
function _stopAppearEffect( viewFigure, writer ) {
writer.removeClass( 'ck-appear', viewFigure );
}
// Shows placeholder together with infinite progress bar on given image figure.
//
// @param {module:image/imageutils~ImageUtils} imageUtils
// @param {String} Data-uri with a svg placeholder.
// @param {module:engine/view/containerelement~ContainerElement} viewFigure
// @param {module:engine/view/downcastwriter~DowncastWriter} writer
function _showPlaceholder( imageUtils, placeholder, viewFigure, writer ) {
if ( !viewFigure.hasClass( 'ck-image-upload-placeholder' ) ) {
writer.addClass( 'ck-image-upload-placeholder', viewFigure );
}
const viewImg = imageUtils.findViewImgElement( viewFigure );
if ( viewImg.getAttribute( 'src' ) !== placeholder ) {
writer.setAttribute( 'src', placeholder, viewImg );
}
if ( !_getUIElement( viewFigure, 'placeholder' ) ) {
writer.insert( writer.createPositionAfter( viewImg ), _createPlaceholder( writer ) );
}
}
// Removes placeholder together with infinite progress bar on given image figure.
//
// @param {module:engine/view/containerelement~ContainerElement} viewFigure
// @param {module:engine/view/downcastwriter~DowncastWriter} writer
function _hidePlaceholder( viewFigure, writer ) {
if ( viewFigure.hasClass( 'ck-image-upload-placeholder' ) ) {
writer.removeClass( 'ck-image-upload-placeholder', viewFigure );
}
_removeUIElement( viewFigure, writer, 'placeholder' );
}
// Shows progress bar displaying upload progress.
// Attaches it to the file loader to update when upload percentace is changed.
//
// @param {module:engine/view/containerelement~ContainerElement} viewFigure
// @param {module:engine/view/downcastwriter~DowncastWriter} writer
// @param {module:upload/filerepository~FileLoader} loader
// @param {module:engine/view/view~View} view
function _showProgressBar( viewFigure, writer, loader, view ) {
const progressBar = _createProgressBar( writer );
writer.insert( writer.createPositionAt( viewFigure, 'end' ), progressBar );
// Update progress bar width when uploadedPercent is changed.
loader.on( 'change:uploadedPercent', ( evt, name, value ) => {
view.change( writer => {
writer.setStyle( 'width', value + '%', progressBar );
} );
} );
}
// Hides upload progress bar.
//
// @param {module:engine/view/containerelement~ContainerElement} viewFigure
// @param {module:engine/view/downcastwriter~DowncastWriter} writer
function _hideProgressBar( viewFigure, writer ) {
_removeUIElement( viewFigure, writer, 'progressBar' );
}
// Shows complete icon and hides after a certain amount of time.
//
// @param {module:engine/view/containerelement~ContainerElement} viewFigure
// @param {module:engine/view/downcastwriter~DowncastWriter} writer
// @param {module:engine/view/view~View} view
function _showCompleteIcon( viewFigure, writer, view ) {
const completeIcon = writer.createUIElement( 'div', { class: 'ck-image-upload-complete-icon' } );
writer.insert( writer.createPositionAt( viewFigure, 'end' ), completeIcon );
setTimeout( () => {
view.change( writer => writer.remove( writer.createRangeOn( completeIcon ) ) );
}, 3000 );
}
// Create progress bar element using {@link module:engine/view/uielement~UIElement}.
//
// @private
// @param {module:engine/view/downcastwriter~DowncastWriter} writer
// @returns {module:engine/view/uielement~UIElement}
function _createProgressBar( writer ) {
const progressBar = writer.createUIElement( 'div', { class: 'ck-progress-bar' } );
writer.setCustomProperty( 'progressBar', true, progressBar );
return progressBar;
}
// Create placeholder element using {@link module:engine/view/uielement~UIElement}.
//
// @private
// @param {module:engine/view/downcastwriter~DowncastWriter} writer
// @returns {module:engine/view/uielement~UIElement}
function _createPlaceholder( writer ) {
const placeholder = writer.createUIElement( 'div', { class: 'ck-upload-placeholder-loader' } );
writer.setCustomProperty( 'placeholder', true, placeholder );
return placeholder;
}
// Returns {@link module:engine/view/uielement~UIElement} of given unique property from image figure element.
// Returns `undefined` if element is not found.
//
// @private
// @param {module:engine/view/element~Element} imageFigure
// @param {String} uniqueProperty
// @returns {module:engine/view/uielement~UIElement|undefined}
function _getUIElement( imageFigure, uniqueProperty ) {
for ( const child of imageFigure.getChildren() ) {
if ( child.getCustomProperty( uniqueProperty ) ) {
return child;
}
}
}
// Removes {@link module:engine/view/uielement~UIElement} of given unique property from image figure element.
//
// @private
// @param {module:engine/view/element~Element} imageFigure
// @param {module:engine/view/downcastwriter~DowncastWriter} writer
// @param {String} uniqueProperty
function _removeUIElement( viewFigure, writer, uniqueProperty ) {
const element = _getUIElement( viewFigure, uniqueProperty );
if ( element ) {
writer.remove( writer.createRangeOn( element ) );
}
}
// Displays local data from file loader.
//
// @param {module:image/imageutils~ImageUtils} imageUtils
// @param {module:engine/view/element~Element} imageFigure
// @param {module:engine/view/downcastwriter~DowncastWriter} writer
// @param {module:upload/filerepository~FileLoader} loader
function _displayLocalImage( imageUtils, viewFigure, writer, loader ) {
if ( loader.data ) {
const viewImg = imageUtils.findViewImgElement( viewFigure );
writer.setAttribute( 'src', loader.data, viewImg );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-image/src/imageupload/imageuploadui.js":
/*!*********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-image/src/imageupload/imageuploadui.js ***!
\*********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ImageUploadUI)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_upload__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/upload */ "./node_modules/ckeditor5/src/upload.js");
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./utils */ "./node_modules/@ckeditor/ckeditor5-image/src/imageupload/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module image/imageupload/imageuploadui
*/
/**
* The image upload button plugin.
*
* For a detailed overview, check the {@glink features/images/image-upload/image-upload Image upload feature} documentation.
*
* Adds the `'uploadImage'` button to the {@link module:ui/componentfactory~ComponentFactory UI component factory}
* and also the `imageUpload` button as an alias for backward compatibility.
*
* @extends module:core/plugin~Plugin
*/
class ImageUploadUI extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'ImageUploadUI';
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const t = editor.t;
const componentCreator = locale => {
const view = new ckeditor5_src_upload__WEBPACK_IMPORTED_MODULE_1__.FileDialogButtonView( locale );
const command = editor.commands.get( 'uploadImage' );
const imageTypes = editor.config.get( 'image.upload.types' );
const imageTypesRegExp = (0,_utils__WEBPACK_IMPORTED_MODULE_2__.createImageTypeRegExp)( imageTypes );
view.set( {
acceptedType: imageTypes.map( type => `image/${ type }` ).join( ',' ),
allowMultipleFiles: true
} );
view.buttonView.set( {
label: t( 'Insert image' ),
icon: ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.icons.image,
tooltip: true
} );
view.buttonView.bind( 'isEnabled' ).to( command );
view.on( 'done', ( evt, files ) => {
const imagesToUpload = Array.from( files ).filter( file => imageTypesRegExp.test( file.type ) );
if ( imagesToUpload.length ) {
editor.execute( 'uploadImage', { file: imagesToUpload } );
}
} );
return view;
};
// Setup `uploadImage` button and add `imageUpload` button as an alias for backward compatibility.
editor.ui.componentFactory.add( 'uploadImage', componentCreator );
editor.ui.componentFactory.add( 'imageUpload', componentCreator );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-image/src/imageupload/uploadimagecommand.js":
/*!**************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-image/src/imageupload/uploadimagecommand.js ***!
\**************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ UploadImageCommand)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_upload__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/upload */ "./node_modules/ckeditor5/src/upload.js");
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module image/imageupload/uploadimagecommand
*/
/**
* The upload image command.
*
* The command is registered by the {@link module:image/imageupload/imageuploadediting~ImageUploadEditing} plugin as `uploadImage`
* and it is also available via aliased `imageUpload` name.
*
* In order to upload an image at the current selection position
* (according to the {@link module:widget/utils~findOptimalInsertionRange} algorithm),
* execute the command and pass the native image file instance:
*
* this.listenTo( editor.editing.view.document, 'clipboardInput', ( evt, data ) => {
* // Assuming that only images were pasted:
* const images = Array.from( data.dataTransfer.files );
*
* // Upload the first image:
* editor.execute( 'uploadImage', { file: images[ 0 ] } );
* } );
*
* It is also possible to insert multiple images at once:
*
* editor.execute( 'uploadImage', {
* file: [
* file1,
* file2
* ]
* } );
*
* @extends module:core/command~Command
*/
class UploadImageCommand extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_1__.Command {
/**
* @inheritDoc
*/
refresh() {
const editor = this.editor;
const imageUtils = editor.plugins.get( 'ImageUtils' );
const selectedElement = editor.model.document.selection.getSelectedElement();
// TODO: This needs refactoring.
this.isEnabled = imageUtils.isImageAllowed() || imageUtils.isImage( selectedElement );
}
/**
* Executes the command.
*
* @fires execute
* @param {Object} options Options for the executed command.
* @param {File|Array.<File>} options.file The image file or an array of image files to upload.
*/
execute( options ) {
const files = (0,ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_2__.toArray)( options.file );
const selection = this.editor.model.document.selection;
const imageUtils = this.editor.plugins.get( 'ImageUtils' );
// In case of multiple files, each file (starting from the 2nd) will be inserted at a position that
// follows the previous one. That will move the selection and, to stay on the safe side and make sure
// all images inherit the same selection attributes, they are collected beforehand.
//
// Applying these attributes ensures, for instance, that inserting an (inline) image into a link does
// not split that link but preserves its continuity.
//
// Note: Selection attributes that do not make sense for images will be filtered out by insertImage() anyway.
const selectionAttributes = Object.fromEntries( selection.getAttributes() );
files.forEach( ( file, index ) => {
const selectedElement = selection.getSelectedElement();
// Inserting of an inline image replace the selected element and make a selection on the inserted image.
// Therefore inserting multiple inline images requires creating position after each element.
if ( index && selectedElement && imageUtils.isImage( selectedElement ) ) {
const position = this.editor.model.createPositionAfter( selectedElement );
this._uploadImage( file, selectionAttributes, position );
} else {
this._uploadImage( file, selectionAttributes );
}
} );
}
/**
* Handles uploading single file.
*
* @private
* @param {File} file
* @param {Object} attributes
* @param {module:engine/model/position~Position} position
*/
_uploadImage( file, attributes, position ) {
const editor = this.editor;
const fileRepository = editor.plugins.get( ckeditor5_src_upload__WEBPACK_IMPORTED_MODULE_0__.FileRepository );
const loader = fileRepository.createLoader( file );
const imageUtils = editor.plugins.get( 'ImageUtils' );
// Do not throw when upload adapter is not set. FileRepository will log an error anyway.
if ( !loader ) {
return;
}
imageUtils.insertImage( { ...attributes, uploadId: loader.id }, position );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-image/src/imageupload/utils.js":
/*!*************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-image/src/imageupload/utils.js ***!
\*************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "createImageTypeRegExp": () => (/* binding */ createImageTypeRegExp),
/* harmony export */ "fetchLocalImage": () => (/* binding */ fetchLocalImage),
/* harmony export */ "isLocalImage": () => (/* binding */ isLocalImage)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module image/imageupload/utils
*/
/* global fetch, File */
/**
* Creates a regular expression used to test for image files.
*
* const imageType = createImageTypeRegExp( [ 'png', 'jpeg', 'svg+xml', 'vnd.microsoft.icon' ] );
*
* console.log( 'is supported image', imageType.test( file.type ) );
*
* @param {Array.<String>} types
* @returns {RegExp}
*/
function createImageTypeRegExp( types ) {
// Sanitize the MIME type name which may include: "+", "-" or ".".
const regExpSafeNames = types.map( type => type.replace( '+', '\\+' ) );
return new RegExp( `^image\\/(${ regExpSafeNames.join( '|' ) })$` );
}
/**
* Creates a promise that fetches the image local source (Base64 or blob) and resolves with a `File` object.
*
* @param {module:engine/view/element~Element} image Image whose source to fetch.
* @returns {Promise.<File>} A promise which resolves when an image source is fetched and converted to a `File` instance.
* It resolves with a `File` object. If there were any errors during file processing, the promise will be rejected.
*/
function fetchLocalImage( image ) {
return new Promise( ( resolve, reject ) => {
const imageSrc = image.getAttribute( 'src' );
// Fetch works asynchronously and so does not block browser UI when processing data.
fetch( imageSrc )
.then( resource => resource.blob() )
.then( blob => {
const mimeType = getImageMimeType( blob, imageSrc );
const ext = mimeType.replace( 'image/', '' );
const filename = `image.${ ext }`;
const file = new File( [ blob ], filename, { type: mimeType } );
resolve( file );
} )
.catch( err => {
// Fetch fails only, if it can't make a request due to a network failure or if anything prevented the request
// from completing, i.e. the Content Security Policy rules. It is not possible to detect the exact cause of failure,
// so we are just trying the fallback solution, if general TypeError is thrown.
return err && err.name === 'TypeError' ?
convertLocalImageOnCanvas( imageSrc ).then( resolve ).catch( reject ) :
reject( err );
} );
} );
}
/**
* Checks whether a given node is an image element with a local source (Base64 or blob).
*
* @param {module:image/imageutils~ImageUtils} imageUtils
* @param {module:engine/view/node~Node} node The node to check.
* @returns {Boolean}
*/
function isLocalImage( imageUtils, node ) {
if ( !imageUtils.isInlineImageView( node ) || !node.getAttribute( 'src' ) ) {
return false;
}
return node.getAttribute( 'src' ).match( /^data:image\/\w+;base64,/g ) ||
node.getAttribute( 'src' ).match( /^blob:/g );
}
// Extracts an image type based on its blob representation or its source.
//
// @param {String} src Image `src` attribute value.
// @param {Blob} blob Image blob representation.
// @returns {String}
function getImageMimeType( blob, src ) {
if ( blob.type ) {
return blob.type;
} else if ( src.match( /data:(image\/\w+);base64/ ) ) {
return src.match( /data:(image\/\w+);base64/ )[ 1 ].toLowerCase();
} else {
// Fallback to 'jpeg' as common extension.
return 'image/jpeg';
}
}
// Creates a promise that converts the image local source (Base64 or blob) to a blob using canvas and resolves
// with a `File` object.
//
// @param {String} imageSrc Image `src` attribute value.
// @returns {Promise.<File>} A promise which resolves when an image source is converted to a `File` instance.
// It resolves with a `File` object. If there were any errors during file processing, the promise will be rejected.
function convertLocalImageOnCanvas( imageSrc ) {
return getBlobFromCanvas( imageSrc ).then( blob => {
const mimeType = getImageMimeType( blob, imageSrc );
const ext = mimeType.replace( 'image/', '' );
const filename = `image.${ ext }`;
return new File( [ blob ], filename, { type: mimeType } );
} );
}
// Creates a promise that resolves with a `Blob` object converted from the image source (Base64 or blob).
//
// @param {String} imageSrc Image `src` attribute value.
// @returns {Promise.<Blob>}
function getBlobFromCanvas( imageSrc ) {
return new Promise( ( resolve, reject ) => {
const image = ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_0__.global.document.createElement( 'img' );
image.addEventListener( 'load', () => {
const canvas = ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_0__.global.document.createElement( 'canvas' );
canvas.width = image.width;
canvas.height = image.height;
const ctx = canvas.getContext( '2d' );
ctx.drawImage( image, 0, 0 );
canvas.toBlob( blob => blob ? resolve( blob ) : reject() );
} );
image.addEventListener( 'error', () => reject() );
image.src = imageSrc;
} );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-image/src/imageutils.js":
/*!******************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-image/src/imageutils.js ***!
\******************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ImageUtils)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_widget__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/widget */ "./node_modules/ckeditor5/src/widget.js");
/* harmony import */ var _image_utils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./image/utils */ "./node_modules/@ckeditor/ckeditor5-image/src/image/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module image/imageutils
*/
/**
* A set of helpers related to images.
*
* @extends module:core/plugin~Plugin
*/
class ImageUtils extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'ImageUtils';
}
/**
* Checks if the provided model element is an `image` or `imageInline`.
*
* @param {module:engine/model/element~Element} modelElement
* @returns {Boolean}
*/
isImage( modelElement ) {
return this.isInlineImage( modelElement ) || this.isBlockImage( modelElement );
}
/**
* Checks if the provided view element represents an inline image.
*
* Also, see {@link module:image/imageutils~ImageUtils#isImageWidget}.
*
* @param {module:engine/view/element~Element} element
* @returns {Boolean}
*/
isInlineImageView( element ) {
return !!element && element.is( 'element', 'img' );
}
/**
* Checks if the provided view element represents a block image.
*
* Also, see {@link module:image/imageutils~ImageUtils#isImageWidget}.
*
* @param {module:engine/view/element~Element} element
* @returns {Boolean}
*/
isBlockImageView( element ) {
return !!element && element.is( 'element', 'figure' ) && element.hasClass( 'image' );
}
/**
* Handles inserting single file. This method unifies image insertion using {@link module:widget/utils~findOptimalInsertionRange}
* method.
*
* const imageUtils = editor.plugins.get( 'ImageUtils' );
*
* imageUtils.insertImage( { src: 'path/to/image.jpg' } );
*
* @param {Object} [attributes={}] Attributes of the inserted image.
* This method filters out the attributes which are disallowed by the {@link module:engine/model/schema~Schema}.
* @param {module:engine/model/selection~Selectable} [selectable] Place to insert the image. If not specified,
* the {@link module:widget/utils~findOptimalInsertionRange} logic will be applied for the block images
* and `model.document.selection` for the inline images.
*
* **Note**: If `selectable` is passed, this helper will not be able to set selection attributes (such as `linkHref`)
* and apply them to the new image. In this case, make sure all selection attributes are passed in `attributes`.
*
* @param {'imageBlock'|'imageInline'} [imageType] Image type of inserted image. If not specified,
* it will be determined automatically depending of editor config or place of the insertion.
* @return {module:engine/view/element~Element|null} The inserted model image element.
*/
insertImage( attributes = {}, selectable = null, imageType = null ) {
const editor = this.editor;
const model = editor.model;
const selection = model.document.selection;
imageType = determineImageTypeForInsertion( editor, selectable || selection, imageType );
// Mix declarative attributes with selection attributes because the new image should "inherit"
// the latter for best UX. For instance, inline images inserted into existing links
// should not split them. To do that, they need to have "linkHref" inherited from the selection.
attributes = {
...Object.fromEntries( selection.getAttributes() ),
...attributes
};
for ( const attributeName in attributes ) {
if ( !model.schema.checkAttribute( imageType, attributeName ) ) {
delete attributes[ attributeName ];
}
}
return model.change( writer => {
const imageElement = writer.createElement( imageType, attributes );
// If we want to insert a block image (for whatever reason) then we don't want to split text blocks.
// This applies only when we don't have the selectable specified (i.e., we insert multiple block images at once).
if ( !selectable && imageType != 'imageInline' ) {
selectable = (0,ckeditor5_src_widget__WEBPACK_IMPORTED_MODULE_1__.findOptimalInsertionRange)( selection, model );
}
model.insertContent( imageElement, selectable );
// Inserting an image might've failed due to schema regulations.
if ( imageElement.parent ) {
writer.setSelection( imageElement, 'on' );
return imageElement;
}
return null;
} );
}
/**
* Returns an image widget editing view element if one is selected or is among the selection's ancestors.
*
* @protected
* @param {module:engine/view/selection~Selection|module:engine/view/documentselection~DocumentSelection} selection
* @returns {module:engine/view/element~Element|null}
*/
getClosestSelectedImageWidget( selection ) {
const viewElement = selection.getSelectedElement();
if ( viewElement && this.isImageWidget( viewElement ) ) {
return viewElement;
}
let parent = selection.getFirstPosition().parent;
while ( parent ) {
if ( parent.is( 'element' ) && this.isImageWidget( parent ) ) {
return parent;
}
parent = parent.parent;
}
return null;
}
/**
* Returns a image model element if one is selected or is among the selection's ancestors.
*
* @param {module:engine/model/selection~Selection|module:engine/model/documentselection~DocumentSelection} selection
* @returns {module:engine/model/element~Element|null}
*/
getClosestSelectedImageElement( selection ) {
const selectedElement = selection.getSelectedElement();
return this.isImage( selectedElement ) ? selectedElement : selection.getFirstPosition().findAncestor( 'imageBlock' );
}
/**
* Checks if image can be inserted at current model selection.
*
* @protected
* @returns {Boolean}
*/
isImageAllowed() {
const model = this.editor.model;
const selection = model.document.selection;
return isImageAllowedInParent( this.editor, selection ) && isNotInsideImage( selection );
}
/**
* Converts a given {@link module:engine/view/element~Element} to an image widget:
* * Adds a {@link module:engine/view/element~Element#_setCustomProperty custom property} allowing to recognize the image widget
* element.
* * Calls the {@link module:widget/utils~toWidget} function with the proper element's label creator.
*
* @protected
* @param {module:engine/view/element~Element} viewElement
* @param {module:engine/view/downcastwriter~DowncastWriter} writer An instance of the view writer.
* @param {String} label The element's label. It will be concatenated with the image `alt` attribute if one is present.
* @returns {module:engine/view/element~Element}
*/
toImageWidget( viewElement, writer, label ) {
writer.setCustomProperty( 'image', true, viewElement );
const labelCreator = () => {
const imgElement = this.findViewImgElement( viewElement );
const altText = imgElement.getAttribute( 'alt' );
return altText ? `${ altText } ${ label }` : label;
};
return (0,ckeditor5_src_widget__WEBPACK_IMPORTED_MODULE_1__.toWidget)( viewElement, writer, { label: labelCreator } );
}
/**
* Checks if a given view element is an image widget.
*
* @protected
* @param {module:engine/view/element~Element} viewElement
* @returns {Boolean}
*/
isImageWidget( viewElement ) {
return !!viewElement.getCustomProperty( 'image' ) && (0,ckeditor5_src_widget__WEBPACK_IMPORTED_MODULE_1__.isWidget)( viewElement );
}
/**
* Checks if the provided model element is an `image`.
*
* @param {module:engine/model/element~Element} modelElement
* @returns {Boolean}
*/
isBlockImage( modelElement ) {
return !!modelElement && modelElement.is( 'element', 'imageBlock' );
}
/**
* Checks if the provided model element is an `imageInline`.
*
* @param {module:engine/model/element~Element} modelElement
* @returns {Boolean}
*/
isInlineImage( modelElement ) {
return !!modelElement && modelElement.is( 'element', 'imageInline' );
}
/**
* Get the view `<img>` from another view element, e.g. a widget (`<figure class="image">`), a link (`<a>`).
*
* The `<img>` can be located deep in other elements, so this helper performs a deep tree search.
*
* @param {module:engine/view/element~Element} figureView
* @returns {module:engine/view/element~Element}
*/
findViewImgElement( figureView ) {
if ( this.isInlineImageView( figureView ) ) {
return figureView;
}
const editingView = this.editor.editing.view;
for ( const { item } of editingView.createRangeIn( figureView ) ) {
if ( this.isInlineImageView( item ) ) {
return item;
}
}
}
}
// Checks if image is allowed by schema in optimal insertion parent.
//
// @private
// @param {module:core/editor/editor~Editor} editor
// @param {module:engine/model/selection~Selection} selection
// @returns {Boolean}
function isImageAllowedInParent( editor, selection ) {
const imageType = determineImageTypeForInsertion( editor, selection );
if ( imageType == 'imageBlock' ) {
const parent = getInsertImageParent( selection, editor.model );
if ( editor.model.schema.checkChild( parent, 'imageBlock' ) ) {
return true;
}
} else if ( editor.model.schema.checkChild( selection.focus, 'imageInline' ) ) {
return true;
}
return false;
}
// Checks if selection is not placed inside an image (e.g. its caption).
//
// @private
// @param {module:engine/model/selection~Selectable} selection
// @returns {Boolean}
function isNotInsideImage( selection ) {
return [ ...selection.focus.getAncestors() ].every( ancestor => !ancestor.is( 'element', 'imageBlock' ) );
}
// Returns a node that will be used to insert image with `model.insertContent`.
//
// @private
// @param {module:engine/model/selection~Selection} selection
// @param {module:engine/model/model~Model} model
// @returns {module:engine/model/element~Element}
function getInsertImageParent( selection, model ) {
const insertionRange = (0,ckeditor5_src_widget__WEBPACK_IMPORTED_MODULE_1__.findOptimalInsertionRange)( selection, model );
const parent = insertionRange.start.parent;
if ( parent.isEmpty && !parent.is( 'element', '$root' ) ) {
return parent.parent;
}
return parent;
}
// Determine image element type name depending on editor config or place of insertion.
//
// @private
// @param {module:core/editor/editor~Editor} editor
// @param {module:engine/model/selection~Selectable} selectable
// @param {'imageBlock'|'imageInline'} [imageType] Image element type name. Used to force return of provided element name,
// but only if there is proper plugin enabled.
// @returns {'imageBlock'|'imageInline'} imageType
function determineImageTypeForInsertion( editor, selectable, imageType ) {
const schema = editor.model.schema;
const configImageInsertType = editor.config.get( 'image.insert.type' );
if ( !editor.plugins.has( 'ImageBlockEditing' ) ) {
return 'imageInline';
}
if ( !editor.plugins.has( 'ImageInlineEditing' ) ) {
return 'imageBlock';
}
if ( imageType ) {
return imageType;
}
if ( configImageInsertType === 'inline' ) {
return 'imageInline';
}
if ( configImageInsertType === 'block' ) {
return 'imageBlock';
}
// Try to replace the selected widget (e.g. another image).
if ( selectable.is( 'selection' ) ) {
return (0,_image_utils__WEBPACK_IMPORTED_MODULE_2__.determineImageTypeForInsertionAtSelection)( schema, selectable );
}
return schema.checkChild( selectable, 'imageInline' ) ? 'imageInline' : 'imageBlock';
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-indent/src/indent.js":
/*!***************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-indent/src/indent.js ***!
\***************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ Indent)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var _indentediting__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./indentediting */ "./node_modules/@ckeditor/ckeditor5-indent/src/indentediting.js");
/* harmony import */ var _indentui__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./indentui */ "./node_modules/@ckeditor/ckeditor5-indent/src/indentui.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module indent/indent
*/
/**
* The indent feature.
*
* This plugin acts as a single entry point plugin for other features that implement indentation of elements like lists or paragraphs.
*
* The compatible features are:
*
* * The {@link module:list/list~List} or {@link module:list/list/listediting~ListEditing} feature for list indentation.
* * The {@link module:indent/indentblock~IndentBlock} feature for block indentation.
*
* This is a "glue" plugin that loads the following plugins:
*
* * The {@link module:indent/indentediting~IndentEditing indent editing feature}.
* * The {@link module:indent/indentui~IndentUI indent UI feature}.
*
* The dependent plugins register the `'indent'` and `'outdent'` commands and introduce the `'indent'` and `'outdent'` buttons
* that allow to increase or decrease text indentation of supported elements.
*
* **Note**: In order for the commands and buttons to work, at least one of compatible features is required.
*
* @extends module:core/plugin~Plugin
*/
class Indent extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'Indent';
}
/**
* @inheritDoc
*/
static get requires() {
return [ _indentediting__WEBPACK_IMPORTED_MODULE_1__["default"], _indentui__WEBPACK_IMPORTED_MODULE_2__["default"] ];
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-indent/src/indentediting.js":
/*!**********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-indent/src/indentediting.js ***!
\**********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ IndentEditing)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module indent/indentediting
*/
/**
* The indent editing feature.
*
* This plugin registers the `'indent'` and `'outdent'` commands.
*
* **Note**: In order for the commands to work, at least one of the compatible features is required. Read more in the
* {@link module:indent/indent~Indent indent feature} API documentation.
*
* @extends module:core/plugin~Plugin
*/
class IndentEditing extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'IndentEditing';
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
editor.commands.add( 'indent', new ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.MultiCommand( editor ) );
editor.commands.add( 'outdent', new ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.MultiCommand( editor ) );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-indent/src/indentui.js":
/*!*****************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-indent/src/indentui.js ***!
\*****************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ IndentUI)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/ui */ "./node_modules/ckeditor5/src/ui.js");
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var _theme_icons_indent_svg__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../theme/icons/indent.svg */ "./node_modules/@ckeditor/ckeditor5-indent/theme/icons/indent.svg");
/* harmony import */ var _theme_icons_outdent_svg__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../theme/icons/outdent.svg */ "./node_modules/@ckeditor/ckeditor5-indent/theme/icons/outdent.svg");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module indent/indentui
*/
/**
* The indent UI feature.
*
* This plugin registers the `'indent'` and `'outdent'` buttons.
*
* **Note**: In order for the commands to work, at least one of the compatible features is required. Read more in
* the {@link module:indent/indent~Indent indent feature} API documentation.
*
* @extends module:core/plugin~Plugin
*/
class IndentUI extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_1__.Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'IndentUI';
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const locale = editor.locale;
const t = editor.t;
const localizedIndentIcon = locale.uiLanguageDirection == 'ltr' ? _theme_icons_indent_svg__WEBPACK_IMPORTED_MODULE_2__["default"] : _theme_icons_outdent_svg__WEBPACK_IMPORTED_MODULE_3__["default"];
const localizedOutdentIcon = locale.uiLanguageDirection == 'ltr' ? _theme_icons_outdent_svg__WEBPACK_IMPORTED_MODULE_3__["default"] : _theme_icons_indent_svg__WEBPACK_IMPORTED_MODULE_2__["default"];
this._defineButton( 'indent', t( 'Increase indent' ), localizedIndentIcon );
this._defineButton( 'outdent', t( 'Decrease indent' ), localizedOutdentIcon );
}
/**
* Defines a UI button.
*
* @param {String} commandName
* @param {String} label
* @param {String} icon
* @private
*/
_defineButton( commandName, label, icon ) {
const editor = this.editor;
editor.ui.componentFactory.add( commandName, locale => {
const command = editor.commands.get( commandName );
const view = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.ButtonView( locale );
view.set( {
label,
icon,
tooltip: true
} );
view.bind( 'isOn', 'isEnabled' ).to( command, 'value', 'isEnabled' );
this.listenTo( view, 'execute', () => {
editor.execute( commandName );
editor.editing.view.focus();
} );
return view;
} );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-link/src/autolink.js":
/*!***************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-link/src/autolink.js ***!
\***************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ AutoLink)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_typing__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/typing */ "./node_modules/ckeditor5/src/typing.js");
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./utils */ "./node_modules/@ckeditor/ckeditor5-link/src/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module link/autolink
*/
const MIN_LINK_LENGTH_WITH_SPACE_AT_END = 4; // Ie: "t.co " (length 5).
// This was a tweak from https://gist.github.com/dperini/729294.
const URL_REG_EXP = new RegExp(
// Group 1: Line start or after a space.
'(^|\\s)' +
// Group 2: Detected URL (or e-mail).
'(' +
// Protocol identifier or short syntax "//"
// a. Full form http://user@foo.bar.baz:8080/foo/bar.html#baz?foo=bar
'(' +
'(?:(?:(?:https?|ftp):)?\\/\\/)' +
// BasicAuth using user:pass (optional)
'(?:\\S+(?::\\S*)?@)?' +
'(?:' +
// IP address dotted notation octets
// excludes loopback network 0.0.0.0
// excludes reserved space >= 224.0.0.0
// excludes network & broadcast addresses
// (first & last IP address of each class)
'(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])' +
'(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}' +
'(?:\\.(?:[1-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))' +
'|' +
'(' +
// Do not allow `www.foo` - see https://github.com/ckeditor/ckeditor5/issues/8050.
'((?!www\\.)|(www\\.))' +
// Host & domain names.
'(?![-_])(?:[-_a-z0-9\\u00a1-\\uffff]{1,63}\\.)+' +
// TLD identifier name.
'(?:[a-z\\u00a1-\\uffff]{2,63})' +
')' +
')' +
// port number (optional)
'(?::\\d{2,5})?' +
// resource path (optional)
'(?:[/?#]\\S*)?' +
')' +
'|' +
// b. Short form (either www.example.com or example@example.com)
'(' +
'(www.|(\\S+@))' +
// Host & domain names.
'((?![-_])(?:[-_a-z0-9\\u00a1-\\uffff]{1,63}\\.))+' +
// TLD identifier name.
'(?:[a-z\\u00a1-\\uffff]{2,63})' +
')' +
')$', 'i' );
const URL_GROUP_IN_MATCH = 2;
/**
* The autolink plugin.
*
* @extends module:core/plugin~Plugin
*/
class AutoLink extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [ ckeditor5_src_typing__WEBPACK_IMPORTED_MODULE_1__.Delete ];
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'AutoLink';
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const selection = editor.model.document.selection;
selection.on( 'change:range', () => {
// Disable plugin when selection is inside a code block.
this.isEnabled = !selection.anchor.parent.is( 'element', 'codeBlock' );
} );
this._enableTypingHandling();
}
/**
* @inheritDoc
*/
afterInit() {
this._enableEnterHandling();
this._enableShiftEnterHandling();
}
/**
* Enables autolinking on typing.
*
* @private
*/
_enableTypingHandling() {
const editor = this.editor;
const watcher = new ckeditor5_src_typing__WEBPACK_IMPORTED_MODULE_1__.TextWatcher( editor.model, text => {
// 1. Detect <kbd>Space</kbd> after a text with a potential link.
if ( !isSingleSpaceAtTheEnd( text ) ) {
return;
}
// 2. Check text before last typed <kbd>Space</kbd>.
const url = getUrlAtTextEnd( text.substr( 0, text.length - 1 ) );
if ( url ) {
return { url };
}
} );
watcher.on( 'matched:data', ( evt, data ) => {
const { batch, range, url } = data;
if ( !batch.isTyping ) {
return;
}
const linkEnd = range.end.getShiftedBy( -1 ); // Executed after a space character.
const linkStart = linkEnd.getShiftedBy( -url.length );
const linkRange = editor.model.createRange( linkStart, linkEnd );
this._applyAutoLink( url, linkRange );
} );
watcher.bind( 'isEnabled' ).to( this );
}
/**
* Enables autolinking on the <kbd>Enter</kbd> key.
*
* @private
*/
_enableEnterHandling() {
const editor = this.editor;
const model = editor.model;
const enterCommand = editor.commands.get( 'enter' );
if ( !enterCommand ) {
return;
}
enterCommand.on( 'execute', () => {
const position = model.document.selection.getFirstPosition();
if ( !position.parent.previousSibling ) {
return;
}
const rangeToCheck = model.createRangeIn( position.parent.previousSibling );
this._checkAndApplyAutoLinkOnRange( rangeToCheck );
} );
}
/**
* Enables autolinking on the <kbd>Shift</kbd>+<kbd>Enter</kbd> keyboard shortcut.
*
* @private
*/
_enableShiftEnterHandling() {
const editor = this.editor;
const model = editor.model;
const shiftEnterCommand = editor.commands.get( 'shiftEnter' );
if ( !shiftEnterCommand ) {
return;
}
shiftEnterCommand.on( 'execute', () => {
const position = model.document.selection.getFirstPosition();
const rangeToCheck = model.createRange(
model.createPositionAt( position.parent, 0 ),
position.getShiftedBy( -1 )
);
this._checkAndApplyAutoLinkOnRange( rangeToCheck );
} );
}
/**
* Checks if the passed range contains a linkable text.
*
* @param {module:engine/model/range~Range} rangeToCheck
* @private
*/
_checkAndApplyAutoLinkOnRange( rangeToCheck ) {
const model = this.editor.model;
const { text, range } = (0,ckeditor5_src_typing__WEBPACK_IMPORTED_MODULE_1__.getLastTextLine)( rangeToCheck, model );
const url = getUrlAtTextEnd( text );
if ( url ) {
const linkRange = model.createRange(
range.end.getShiftedBy( -url.length ),
range.end
);
this._applyAutoLink( url, linkRange );
}
}
/**
* Applies a link on a given range.
*
* @param {String} url The URL to link.
* @param {module:engine/model/range~Range} range The text range to apply the link attribute to.
* @private
*/
_applyAutoLink( link, range ) {
const model = this.editor.model;
const deletePlugin = this.editor.plugins.get( 'Delete' );
if ( !this.isEnabled || !isLinkAllowedOnRange( range, model ) ) {
return;
}
// Enqueue change to make undo step.
model.enqueueChange( writer => {
const defaultProtocol = this.editor.config.get( 'link.defaultProtocol' );
const parsedUrl = (0,_utils__WEBPACK_IMPORTED_MODULE_2__.addLinkProtocolIfApplicable)( link, defaultProtocol );
writer.setAttribute( 'linkHref', parsedUrl, range );
model.enqueueChange( () => {
deletePlugin.requestUndoOnBackspace();
} );
} );
}
}
// Check if text should be evaluated by the plugin in order to reduce number of RegExp checks on whole text.
function isSingleSpaceAtTheEnd( text ) {
return text.length > MIN_LINK_LENGTH_WITH_SPACE_AT_END && text[ text.length - 1 ] === ' ' && text[ text.length - 2 ] !== ' ';
}
function getUrlAtTextEnd( text ) {
const match = URL_REG_EXP.exec( text );
return match ? match[ URL_GROUP_IN_MATCH ] : null;
}
function isLinkAllowedOnRange( range, model ) {
return model.schema.checkAttributeInSelection( model.createSelection( range ), 'linkHref' );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-link/src/link.js":
/*!***********************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-link/src/link.js ***!
\***********************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ Link)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var _linkediting__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./linkediting */ "./node_modules/@ckeditor/ckeditor5-link/src/linkediting.js");
/* harmony import */ var _linkui__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./linkui */ "./node_modules/@ckeditor/ckeditor5-link/src/linkui.js");
/* harmony import */ var _autolink__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./autolink */ "./node_modules/@ckeditor/ckeditor5-link/src/autolink.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module link/link
*/
/**
* The link plugin.
*
* This is a "glue" plugin that loads the {@link module:link/linkediting~LinkEditing link editing feature}
* and {@link module:link/linkui~LinkUI link UI feature}.
*
* @extends module:core/plugin~Plugin
*/
class Link extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [ _linkediting__WEBPACK_IMPORTED_MODULE_1__["default"], _linkui__WEBPACK_IMPORTED_MODULE_2__["default"], _autolink__WEBPACK_IMPORTED_MODULE_3__["default"] ];
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'Link';
}
}
/**
* The configuration of the {@link module:link/link~Link} feature.
*
* Read more in {@link module:link/link~LinkConfig}.
*
* @member {module:link/link~LinkConfig} module:core/editor/editorconfig~EditorConfig#link
*/
/**
* The configuration of the {@link module:link/link~Link link feature}.
*
* ClassicEditor
* .create( editorElement, {
* link: ... // Link feature configuration.
* } )
* .then( ... )
* .catch( ... );
*
* See {@link module:core/editor/editorconfig~EditorConfig all editor options}.
* @interface LinkConfig
*/
/**
* When set, the editor will add the given protocol to the link when the user creates a link without one.
* For example, when the user is creating a link and types `ckeditor.com` in the link form input, during link submission
* the editor will automatically add the `http://` protocol, so the link will look as follows: `http://ckeditor.com`.
*
* The feature also provides email address auto-detection. When you submit `hello@example.com`,
* the plugin will automatically change it to `mailto:hello@example.com`.
*
* ClassicEditor
* .create( editorElement, {
* link: {
* defaultProtocol: 'http://'
* }
* } )
* .then( ... )
* .catch( ... );
*
* **NOTE:** If no configuration is provided, the editor will not auto-fix the links.
*
* @member {String} module:link/link~LinkConfig#defaultProtocol
*/
/**
* When set to `true`, the `target="blank"` and `rel="noopener noreferrer"` attributes are automatically added to all external links
* in the editor. "External links" are all links in the editor content starting with `http`, `https`, or `//`.
*
* ClassicEditor
* .create( editorElement, {
* link: {
* addTargetToExternalLinks: true
* }
* } )
* .then( ... )
* .catch( ... );
*
* Internally, this option activates a predefined {@link module:link/link~LinkConfig#decorators automatic link decorator}
* that extends all external links with the `target` and `rel` attributes.
*
* **Note**: To control the `target` and `rel` attributes of specific links in the edited content, a dedicated
* {@link module:link/link~LinkDecoratorManualDefinition manual} decorator must be defined in the
* {@link module:link/link~LinkConfig#decorators `config.link.decorators`} array. In such scenario,
* the `config.link.addTargetToExternalLinks` option should remain `undefined` or `false` to not interfere with the manual decorator.
*
* It is possible to add other {@link module:link/link~LinkDecoratorAutomaticDefinition automatic}
* or {@link module:link/link~LinkDecoratorManualDefinition manual} link decorators when this option is active.
*
* More information about decorators can be found in the {@link module:link/link~LinkConfig#decorators decorators configuration}
* reference.
*
* @default false
* @member {Boolean} module:link/link~LinkConfig#addTargetToExternalLinks
*/
/**
* Decorators provide an easy way to configure and manage additional link attributes in the editor content. There are
* two types of link decorators:
*
* * {@link module:link/link~LinkDecoratorAutomaticDefinition Automatic} – They match links against pre–defined rules and
* manage their attributes based on the results.
* * {@link module:link/link~LinkDecoratorManualDefinition Manual} – They allow users to control link attributes individually,
* using the editor UI.
*
* Link decorators are defined as objects with key-value pairs, where the key is the name provided for a given decorator and the
* value is the decorator definition.
*
* The name of the decorator also corresponds to the {@glink framework/guides/architecture/editing-engine#text-attributes text attribute}
* in the model. For instance, the `isExternal` decorator below is represented as a `linkIsExternal` attribute in the model.
*
* ClassicEditor
* .create( editorElement, {
* link: {
* decorators: {
* isExternal: {
* mode: 'automatic',
* callback: url => url.startsWith( 'http://' ),
* attributes: {
* target: '_blank',
* rel: 'noopener noreferrer'
* }
* },
* isDownloadable: {
* mode: 'manual',
* label: 'Downloadable',
* attributes: {
* download: 'file.png',
* }
* },
* // ...
* }
* }
* } )
* .then( ... )
* .catch( ... );
*
* To learn more about the configuration syntax, check out the {@link module:link/link~LinkDecoratorAutomaticDefinition automatic}
* and {@link module:link/link~LinkDecoratorManualDefinition manual} decorator option reference.
*
* **Warning:** Currently, link decorators work independently of one another and no conflict resolution mechanism exists.
* For example, configuring the `target` attribute using both an automatic and a manual decorator at the same time could end up with
* quirky results. The same applies if multiple manual or automatic decorators were defined for the same attribute.
*
* **Note**: Since the `target` attribute management for external links is a common use case, there is a predefined automatic decorator
* dedicated for that purpose which can be enabled by turning a single option on. Check out the
* {@link module:link/link~LinkConfig#addTargetToExternalLinks `config.link.addTargetToExternalLinks`}
* configuration description to learn more.
*
* See also the {@glink features/link#custom-link-attributes-decorators link feature guide} for more information.
*
* @member {Object.<String, module:link/link~LinkDecoratorDefinition>} module:link/link~LinkConfig#decorators
*/
/**
* A link decorator definition. Two types implement this defition:
*
* * {@link module:link/link~LinkDecoratorManualDefinition}
* * {@link module:link/link~LinkDecoratorAutomaticDefinition}
*
* Refer to their document for more information about available options or to the
* {@glink features/link#custom-link-attributes-decorators link feature guide} for general information.
*
* @interface LinkDecoratorDefinition
*/
/**
* Link decorator type.
*
* Check out the {@glink features/link#custom-link-attributes-decorators link feature guide} for more information.
*
* @member {'manual'|'automatic'} module:link/link~LinkDecoratorDefinition#mode
*/
/**
* Describes an automatic {@link module:link/link~LinkConfig#decorators link decorator}. This decorator type matches
* all links in the editor content against a function that decides whether the link should receive a pre–defined set of attributes.
*
* It takes an object with key-value pairs of attributes and a callback function that must return a Boolean value based on the link's
* `href` (URL). When the callback returns `true`, attributes are applied to the link.
*
* For example, to add the `target="_blank"` attribute to all links in the editor starting with `http://`, the
* configuration could look like this:
*
* {
* mode: 'automatic',
* callback: url => url.startsWith( 'http://' ),
* attributes: {
* target: '_blank'
* }
* }
*
* **Note**: Since the `target` attribute management for external links is a common use case, there is a predefined automatic decorator
* dedicated for that purpose that can be enabled by turning a single option on. Check out the
* {@link module:link/link~LinkConfig#addTargetToExternalLinks `config.link.addTargetToExternalLinks`}
* configuration description to learn more.
*
* @typedef {Object} module:link/link~LinkDecoratorAutomaticDefinition
* @property {'automatic'} mode Link decorator type. It is `'automatic'` for all automatic decorators.
* @property {Function} callback Takes a `url` as a parameter and returns `true` if the `attributes` should be applied to the link.
* @property {Object} [attributes] Key-value pairs used as link attributes added to the output during the
* {@glink framework/guides/architecture/editing-engine#conversion downcasting}.
* Attributes should follow the {@link module:engine/view/elementdefinition~ElementDefinition} syntax.
* @property {Object} [styles] Key-value pairs used as link styles added to the output during the
* {@glink framework/guides/architecture/editing-engine#conversion downcasting}.
* Styles should follow the {@link module:engine/view/elementdefinition~ElementDefinition} syntax.
* @property {String|Array.<String>} [classes] Class names used as link classes added to the output during the
* {@glink framework/guides/architecture/editing-engine#conversion downcasting}.
* Classes should follow the {@link module:engine/view/elementdefinition~ElementDefinition} syntax.
*/
/**
* Describes a manual {@link module:link/link~LinkConfig#decorators link decorator}. This decorator type is represented in
* the link feature's {@link module:link/linkui user interface} as a switch that the user can use to control the presence
* of a predefined set of attributes.
*
* For instance, to allow the users to manually control the presence of the `target="_blank"` and
* `rel="noopener noreferrer"` attributes on specific links, the decorator could look as follows:
*
* {
* mode: 'manual',
* label: 'Open in a new tab',
* defaultValue: true,
* attributes: {
* target: '_blank',
* rel: 'noopener noreferrer'
* }
* }
*
* @typedef {Object} module:link/link~LinkDecoratorManualDefinition
* @property {'manual'} mode Link decorator type. It is `'manual'` for all manual decorators.
* @property {String} label The label of the UI button that the user can use to control the presence of link attributes.
* @property {Object} [attributes] Key-value pairs used as link attributes added to the output during the
* {@glink framework/guides/architecture/editing-engine#conversion downcasting}.
* Attributes should follow the {@link module:engine/view/elementdefinition~ElementDefinition} syntax.
* @property {Object} [styles] Key-value pairs used as link styles added to the output during the
* {@glink framework/guides/architecture/editing-engine#conversion downcasting}.
* Styles should follow the {@link module:engine/view/elementdefinition~ElementDefinition} syntax.
* @property {String|Array.<String>} [classes] Class names used as link classes added to the output during the
* {@glink framework/guides/architecture/editing-engine#conversion downcasting}.
* Classes should follow the {@link module:engine/view/elementdefinition~ElementDefinition} syntax.
* @property {Boolean} [defaultValue] Controls whether the decorator is "on" by default.
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-link/src/linkcommand.js":
/*!******************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-link/src/linkcommand.js ***!
\******************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ LinkCommand)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_typing__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/typing */ "./node_modules/ckeditor5/src/typing.js");
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/* harmony import */ var _utils_automaticdecorators__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./utils/automaticdecorators */ "./node_modules/@ckeditor/ckeditor5-link/src/utils/automaticdecorators.js");
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./utils */ "./node_modules/@ckeditor/ckeditor5-link/src/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module link/linkcommand
*/
/**
* The link command. It is used by the {@link module:link/link~Link link feature}.
*
* @extends module:core/command~Command
*/
class LinkCommand extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Command {
/**
* The value of the `'linkHref'` attribute if the start of the selection is located in a node with this attribute.
*
* @observable
* @readonly
* @member {Object|undefined} #value
*/
constructor( editor ) {
super( editor );
/**
* A collection of {@link module:link/utils~ManualDecorator manual decorators}
* corresponding to the {@link module:link/link~LinkConfig#decorators decorator configuration}.
*
* You can consider it a model with states of manual decorators added to the currently selected link.
*
* @readonly
* @type {module:utils/collection~Collection}
*/
this.manualDecorators = new ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_2__.Collection();
/**
* An instance of the helper that ties together all {@link module:link/link~LinkDecoratorAutomaticDefinition}
* that are used by the {@glink features/link link} and the {@glink features/images/images-linking linking images} features.
*
* @readonly
* @type {module:link/utils~AutomaticDecorators}
*/
this.automaticDecorators = new _utils_automaticdecorators__WEBPACK_IMPORTED_MODULE_3__["default"]();
}
/**
* Synchronizes the state of {@link #manualDecorators} with the currently present elements in the model.
*/
restoreManualDecoratorStates() {
for ( const manualDecorator of this.manualDecorators ) {
manualDecorator.value = this._getDecoratorStateFromModel( manualDecorator.id );
}
}
/**
* @inheritDoc
*/
refresh() {
const model = this.editor.model;
const selection = model.document.selection;
const selectedElement = selection.getSelectedElement() || (0,ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_2__.first)( selection.getSelectedBlocks() );
// A check for any integration that allows linking elements (e.g. `LinkImage`).
// Currently the selection reads attributes from text nodes only. See #7429 and #7465.
if ( (0,_utils__WEBPACK_IMPORTED_MODULE_4__.isLinkableElement)( selectedElement, model.schema ) ) {
this.value = selectedElement.getAttribute( 'linkHref' );
this.isEnabled = model.schema.checkAttribute( selectedElement, 'linkHref' );
} else {
this.value = selection.getAttribute( 'linkHref' );
this.isEnabled = model.schema.checkAttributeInSelection( selection, 'linkHref' );
}
for ( const manualDecorator of this.manualDecorators ) {
manualDecorator.value = this._getDecoratorStateFromModel( manualDecorator.id );
}
}
/**
* Executes the command.
*
* When the selection is non-collapsed, the `linkHref` attribute will be applied to nodes inside the selection, but only to
* those nodes where the `linkHref` attribute is allowed (disallowed nodes will be omitted).
*
* When the selection is collapsed and is not inside the text with the `linkHref` attribute, a
* new {@link module:engine/model/text~Text text node} with the `linkHref` attribute will be inserted in place of the caret, but
* only if such element is allowed in this place. The `_data` of the inserted text will equal the `href` parameter.
* The selection will be updated to wrap the just inserted text node.
*
* When the selection is collapsed and inside the text with the `linkHref` attribute, the attribute value will be updated.
*
* # Decorators and model attribute management
*
* There is an optional argument to this command that applies or removes model
* {@glink framework/guides/architecture/editing-engine#text-attributes text attributes} brought by
* {@link module:link/utils~ManualDecorator manual link decorators}.
*
* Text attribute names in the model correspond to the entries in the {@link module:link/link~LinkConfig#decorators configuration}.
* For every decorator configured, a model text attribute exists with the "link" prefix. For example, a `'linkMyDecorator'` attribute
* corresponds to `'myDecorator'` in the configuration.
*
* To learn more about link decorators, check out the {@link module:link/link~LinkConfig#decorators `config.link.decorators`}
* documentation.
*
* Here is how to manage decorator attributes with the link command:
*
* const linkCommand = editor.commands.get( 'link' );
*
* // Adding a new decorator attribute.
* linkCommand.execute( 'http://example.com', {
* linkIsExternal: true
* } );
*
* // Removing a decorator attribute from the selection.
* linkCommand.execute( 'http://example.com', {
* linkIsExternal: false
* } );
*
* // Adding multiple decorator attributes at the same time.
* linkCommand.execute( 'http://example.com', {
* linkIsExternal: true,
* linkIsDownloadable: true,
* } );
*
* // Removing and adding decorator attributes at the same time.
* linkCommand.execute( 'http://example.com', {
* linkIsExternal: false,
* linkFoo: true,
* linkIsDownloadable: false,
* } );
*
* **Note**: If the decorator attribute name is not specified, its state remains untouched.
*
* **Note**: {@link module:link/unlinkcommand~UnlinkCommand#execute `UnlinkCommand#execute()`} removes all
* decorator attributes.
*
* @fires execute
* @param {String} href Link destination.
* @param {Object} [manualDecoratorIds={}] The information about manual decorator attributes to be applied or removed upon execution.
*/
execute( href, manualDecoratorIds = {} ) {
const model = this.editor.model;
const selection = model.document.selection;
// Stores information about manual decorators to turn them on/off when command is applied.
const truthyManualDecorators = [];
const falsyManualDecorators = [];
for ( const name in manualDecoratorIds ) {
if ( manualDecoratorIds[ name ] ) {
truthyManualDecorators.push( name );
} else {
falsyManualDecorators.push( name );
}
}
model.change( writer => {
// If selection is collapsed then update selected link or insert new one at the place of caret.
if ( selection.isCollapsed ) {
const position = selection.getFirstPosition();
// When selection is inside text with `linkHref` attribute.
if ( selection.hasAttribute( 'linkHref' ) ) {
// Then update `linkHref` value.
const linkRange = (0,ckeditor5_src_typing__WEBPACK_IMPORTED_MODULE_1__.findAttributeRange)( position, 'linkHref', selection.getAttribute( 'linkHref' ), model );
writer.setAttribute( 'linkHref', href, linkRange );
truthyManualDecorators.forEach( item => {
writer.setAttribute( item, true, linkRange );
} );
falsyManualDecorators.forEach( item => {
writer.removeAttribute( item, linkRange );
} );
// Put the selection at the end of the updated link.
writer.setSelection( writer.createPositionAfter( linkRange.end.nodeBefore ) );
}
// If not then insert text node with `linkHref` attribute in place of caret.
// However, since selection is collapsed, attribute value will be used as data for text node.
// So, if `href` is empty, do not create text node.
else if ( href !== '' ) {
const attributes = (0,ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_2__.toMap)( selection.getAttributes() );
attributes.set( 'linkHref', href );
truthyManualDecorators.forEach( item => {
attributes.set( item, true );
} );
const { end: positionAfter } = model.insertContent( writer.createText( href, attributes ), position );
// Put the selection at the end of the inserted link.
// Using end of range returned from insertContent in case nodes with the same attributes got merged.
writer.setSelection( positionAfter );
}
// Remove the `linkHref` attribute and all link decorators from the selection.
// It stops adding a new content into the link element.
[ 'linkHref', ...truthyManualDecorators, ...falsyManualDecorators ].forEach( item => {
writer.removeSelectionAttribute( item );
} );
} else {
// If selection has non-collapsed ranges, we change attribute on nodes inside those ranges
// omitting nodes where the `linkHref` attribute is disallowed.
const ranges = model.schema.getValidRanges( selection.getRanges(), 'linkHref' );
// But for the first, check whether the `linkHref` attribute is allowed on selected blocks (e.g. the "image" element).
const allowedRanges = [];
for ( const element of selection.getSelectedBlocks() ) {
if ( model.schema.checkAttribute( element, 'linkHref' ) ) {
allowedRanges.push( writer.createRangeOn( element ) );
}
}
// Ranges that accept the `linkHref` attribute. Since we will iterate over `allowedRanges`, let's clone it.
const rangesToUpdate = allowedRanges.slice();
// For all selection ranges we want to check whether given range is inside an element that accepts the `linkHref` attribute.
// If so, we don't want to propagate applying the attribute to its children.
for ( const range of ranges ) {
if ( this._isRangeToUpdate( range, allowedRanges ) ) {
rangesToUpdate.push( range );
}
}
for ( const range of rangesToUpdate ) {
writer.setAttribute( 'linkHref', href, range );
truthyManualDecorators.forEach( item => {
writer.setAttribute( item, true, range );
} );
falsyManualDecorators.forEach( item => {
writer.removeAttribute( item, range );
} );
}
}
} );
}
/**
* Provides information whether a decorator with a given name is present in the currently processed selection.
*
* @private
* @param {String} decoratorName The name of the manual decorator used in the model
* @returns {Boolean} The information whether a given decorator is currently present in the selection.
*/
_getDecoratorStateFromModel( decoratorName ) {
const model = this.editor.model;
const selection = model.document.selection;
const selectedElement = selection.getSelectedElement();
// A check for the `LinkImage` plugin. If the selection contains an element, get values from the element.
// Currently the selection reads attributes from text nodes only. See #7429 and #7465.
if ( (0,_utils__WEBPACK_IMPORTED_MODULE_4__.isLinkableElement)( selectedElement, model.schema ) ) {
return selectedElement.getAttribute( decoratorName );
}
return selection.getAttribute( decoratorName );
}
/**
* Checks whether specified `range` is inside an element that accepts the `linkHref` attribute.
*
* @private
* @param {module:engine/view/range~Range} range A range to check.
* @param {Array.<module:engine/view/range~Range>} allowedRanges An array of ranges created on elements where the attribute is accepted.
* @returns {Boolean}
*/
_isRangeToUpdate( range, allowedRanges ) {
for ( const allowedRange of allowedRanges ) {
// A range is inside an element that will have the `linkHref` attribute. Do not modify its nodes.
if ( allowedRange.containsRange( range ) ) {
return false;
}
}
return true;
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-link/src/linkediting.js":
/*!******************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-link/src/linkediting.js ***!
\******************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ LinkEditing)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/engine */ "./node_modules/ckeditor5/src/engine.js");
/* harmony import */ var ckeditor5_src_typing__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ckeditor5/src/typing */ "./node_modules/ckeditor5/src/typing.js");
/* harmony import */ var ckeditor5_src_clipboard__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ckeditor5/src/clipboard */ "./node_modules/ckeditor5/src/clipboard.js");
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/* harmony import */ var _linkcommand__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./linkcommand */ "./node_modules/@ckeditor/ckeditor5-link/src/linkcommand.js");
/* harmony import */ var _unlinkcommand__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./unlinkcommand */ "./node_modules/@ckeditor/ckeditor5-link/src/unlinkcommand.js");
/* harmony import */ var _utils_manualdecorator__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./utils/manualdecorator */ "./node_modules/@ckeditor/ckeditor5-link/src/utils/manualdecorator.js");
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./utils */ "./node_modules/@ckeditor/ckeditor5-link/src/utils.js");
/* harmony import */ var _theme_link_css__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ../theme/link.css */ "./node_modules/@ckeditor/ckeditor5-link/theme/link.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module link/linkediting
*/
const HIGHLIGHT_CLASS = 'ck-link_selected';
const DECORATOR_AUTOMATIC = 'automatic';
const DECORATOR_MANUAL = 'manual';
const EXTERNAL_LINKS_REGEXP = /^(https?:)?\/\//;
/**
* The link engine feature.
*
* It introduces the `linkHref="url"` attribute in the model which renders to the view as a `<a href="url">` element
* as well as `'link'` and `'unlink'` commands.
*
* @extends module:core/plugin~Plugin
*/
class LinkEditing extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'LinkEditing';
}
/**
* @inheritDoc
*/
static get requires() {
// Clipboard is required for handling cut and paste events while typing over the link.
return [ ckeditor5_src_typing__WEBPACK_IMPORTED_MODULE_2__.TwoStepCaretMovement, ckeditor5_src_typing__WEBPACK_IMPORTED_MODULE_2__.Input, ckeditor5_src_clipboard__WEBPACK_IMPORTED_MODULE_3__.ClipboardPipeline ];
}
/**
* @inheritDoc
*/
constructor( editor ) {
super( editor );
editor.config.define( 'link', {
addTargetToExternalLinks: false
} );
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
// Allow link attribute on all inline nodes.
editor.model.schema.extend( '$text', { allowAttributes: 'linkHref' } );
editor.conversion.for( 'dataDowncast' )
.attributeToElement( { model: 'linkHref', view: _utils__WEBPACK_IMPORTED_MODULE_8__.createLinkElement } );
editor.conversion.for( 'editingDowncast' )
.attributeToElement( { model: 'linkHref', view: ( href, conversionApi ) => {
return (0,_utils__WEBPACK_IMPORTED_MODULE_8__.createLinkElement)( (0,_utils__WEBPACK_IMPORTED_MODULE_8__.ensureSafeUrl)( href ), conversionApi );
} } );
editor.conversion.for( 'upcast' )
.elementToAttribute( {
view: {
name: 'a',
attributes: {
href: true
}
},
model: {
key: 'linkHref',
value: viewElement => viewElement.getAttribute( 'href' )
}
} );
// Create linking commands.
editor.commands.add( 'link', new _linkcommand__WEBPACK_IMPORTED_MODULE_5__["default"]( editor ) );
editor.commands.add( 'unlink', new _unlinkcommand__WEBPACK_IMPORTED_MODULE_6__["default"]( editor ) );
const linkDecorators = (0,_utils__WEBPACK_IMPORTED_MODULE_8__.getLocalizedDecorators)( editor.t, (0,_utils__WEBPACK_IMPORTED_MODULE_8__.normalizeDecorators)( editor.config.get( 'link.decorators' ) ) );
this._enableAutomaticDecorators( linkDecorators.filter( item => item.mode === DECORATOR_AUTOMATIC ) );
this._enableManualDecorators( linkDecorators.filter( item => item.mode === DECORATOR_MANUAL ) );
// Enable two-step caret movement for `linkHref` attribute.
const twoStepCaretMovementPlugin = editor.plugins.get( ckeditor5_src_typing__WEBPACK_IMPORTED_MODULE_2__.TwoStepCaretMovement );
twoStepCaretMovementPlugin.registerAttribute( 'linkHref' );
// Setup highlight over selected link.
(0,ckeditor5_src_typing__WEBPACK_IMPORTED_MODULE_2__.inlineHighlight)( editor, 'linkHref', 'a', HIGHLIGHT_CLASS );
// Handle link following by CTRL+click or ALT+ENTER
this._enableLinkOpen();
// Change the attributes of the selection in certain situations after the link was inserted into the document.
this._enableInsertContentSelectionAttributesFixer();
// Handle a click at the beginning/end of a link element.
this._enableClickingAfterLink();
// Handle typing over the link.
this._enableTypingOverLink();
// Handle removing the content after the link element.
this._handleDeleteContentAfterLink();
}
/**
* Processes an array of configured {@link module:link/link~LinkDecoratorAutomaticDefinition automatic decorators}
* and registers a {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher downcast dispatcher}
* for each one of them. Downcast dispatchers are obtained using the
* {@link module:link/utils~AutomaticDecorators#getDispatcher} method.
*
* **Note**: This method also activates the automatic external link decorator if enabled with
* {@link module:link/link~LinkConfig#addTargetToExternalLinks `config.link.addTargetToExternalLinks`}.
*
* @private
* @param {Array.<module:link/link~LinkDecoratorAutomaticDefinition>} automaticDecoratorDefinitions
*/
_enableAutomaticDecorators( automaticDecoratorDefinitions ) {
const editor = this.editor;
// Store automatic decorators in the command instance as we do the same with manual decorators.
// Thanks to that, `LinkImageEditing` plugin can re-use the same definitions.
const command = editor.commands.get( 'link' );
const automaticDecorators = command.automaticDecorators;
// Adds a default decorator for external links.
if ( editor.config.get( 'link.addTargetToExternalLinks' ) ) {
automaticDecorators.add( {
id: 'linkIsExternal',
mode: DECORATOR_AUTOMATIC,
callback: url => EXTERNAL_LINKS_REGEXP.test( url ),
attributes: {
target: '_blank',
rel: 'noopener noreferrer'
}
} );
}
automaticDecorators.add( automaticDecoratorDefinitions );
if ( automaticDecorators.length ) {
editor.conversion.for( 'downcast' ).add( automaticDecorators.getDispatcher() );
}
}
/**
* Processes an array of configured {@link module:link/link~LinkDecoratorManualDefinition manual decorators},
* transforms them into {@link module:link/utils~ManualDecorator} instances and stores them in the
* {@link module:link/linkcommand~LinkCommand#manualDecorators} collection (a model for manual decorators state).
*
* Also registers an {@link module:engine/conversion/downcasthelpers~DowncastHelpers#attributeToElement attribute-to-element}
* converter for each manual decorator and extends the {@link module:engine/model/schema~Schema model's schema}
* with adequate model attributes.
*
* @private
* @param {Array.<module:link/link~LinkDecoratorManualDefinition>} manualDecoratorDefinitions
*/
_enableManualDecorators( manualDecoratorDefinitions ) {
if ( !manualDecoratorDefinitions.length ) {
return;
}
const editor = this.editor;
const command = editor.commands.get( 'link' );
const manualDecorators = command.manualDecorators;
manualDecoratorDefinitions.forEach( decorator => {
editor.model.schema.extend( '$text', { allowAttributes: decorator.id } );
// Keeps reference to manual decorator to decode its name to attributes during downcast.
decorator = new _utils_manualdecorator__WEBPACK_IMPORTED_MODULE_7__["default"]( decorator );
manualDecorators.add( decorator );
editor.conversion.for( 'downcast' ).attributeToElement( {
model: decorator.id,
view: ( manualDecoratorName, { writer } ) => {
if ( manualDecoratorName ) {
const element = writer.createAttributeElement( 'a', decorator.attributes, { priority: 5 } );
if ( decorator.classes ) {
writer.addClass( decorator.classes, element );
}
for ( const key in decorator.styles ) {
writer.setStyle( key, decorator.styles[ key ], element );
}
writer.setCustomProperty( 'link', true, element );
return element;
}
} } );
editor.conversion.for( 'upcast' ).elementToAttribute( {
view: {
name: 'a',
...decorator._createPattern()
},
model: {
key: decorator.id
}
} );
} );
}
/**
* Attaches handlers for {@link module:engine/view/document~Document#event:enter} and
* {@link module:engine/view/document~Document#event:click} to enable link following.
*
* @private
*/
_enableLinkOpen() {
const editor = this.editor;
const view = editor.editing.view;
const viewDocument = view.document;
const modelDocument = editor.model.document;
this.listenTo( viewDocument, 'click', ( evt, data ) => {
const shouldOpen = ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_4__.env.isMac ? data.domEvent.metaKey : data.domEvent.ctrlKey;
if ( !shouldOpen ) {
return;
}
let clickedElement = data.domTarget;
if ( clickedElement.tagName.toLowerCase() != 'a' ) {
clickedElement = clickedElement.closest( 'a' );
}
if ( !clickedElement ) {
return;
}
const url = clickedElement.getAttribute( 'href' );
if ( !url ) {
return;
}
evt.stop();
data.preventDefault();
(0,_utils__WEBPACK_IMPORTED_MODULE_8__.openLink)( url );
}, { context: '$capture' } );
this.listenTo( viewDocument, 'enter', ( evt, data ) => {
const selection = modelDocument.selection;
const selectedElement = selection.getSelectedElement();
const url = selectedElement ?
selectedElement.getAttribute( 'linkHref' ) :
selection.getAttribute( 'linkHref' );
const shouldOpen = url && data.domEvent.altKey;
if ( !shouldOpen ) {
return;
}
evt.stop();
(0,_utils__WEBPACK_IMPORTED_MODULE_8__.openLink)( url );
}, { context: 'a' } );
}
/**
* Starts listening to {@link module:engine/model/model~Model#event:insertContent} and corrects the model
* selection attributes if the selection is at the end of a link after inserting the content.
*
* The purpose of this action is to improve the overall UX because the user is no longer "trapped" by the
* `linkHref` attribute of the selection and they can type a "clean" (`linkHref`–less) text right away.
*
* See https://github.com/ckeditor/ckeditor5/issues/6053.
*
* @private
*/
_enableInsertContentSelectionAttributesFixer() {
const editor = this.editor;
const model = editor.model;
const selection = model.document.selection;
this.listenTo( model, 'insertContent', () => {
const nodeBefore = selection.anchor.nodeBefore;
const nodeAfter = selection.anchor.nodeAfter;
// NOTE: ↰ and ↱ represent the gravity of the selection.
// The only truly valid case is:
//
// ↰
// ...<$text linkHref="foo">INSERTED[]</$text>
//
// If the selection is not "trapped" by the `linkHref` attribute after inserting, there's nothing
// to fix there.
if ( !selection.hasAttribute( 'linkHref' ) ) {
return;
}
// Filter out the following case where a link with the same href (e.g. <a href="foo">INSERTED</a>) is inserted
// in the middle of an existing link:
//
// Before insertion:
// ↰
// <$text linkHref="foo">l[]ink</$text>
//
// Expected after insertion:
// ↰
// <$text linkHref="foo">lINSERTED[]ink</$text>
//
if ( !nodeBefore ) {
return;
}
// Filter out the following case where the selection has the "linkHref" attribute because the
// gravity is overridden and some text with another attribute (e.g. <b>INSERTED</b>) is inserted:
//
// Before insertion:
//
// ↱
// <$text linkHref="foo">[]link</$text>
//
// Expected after insertion:
//
// ↱
// <$text bold="true">INSERTED</$text><$text linkHref="foo">[]link</$text>
//
if ( !nodeBefore.hasAttribute( 'linkHref' ) ) {
return;
}
// Filter out the following case where a link is a inserted in the middle (or before) another link
// (different URLs, so they will not merge). In this (let's say weird) case, we can leave the selection
// attributes as they are because the user will end up writing in one link or another anyway.
//
// Before insertion:
//
// ↰
// <$text linkHref="foo">l[]ink</$text>
//
// Expected after insertion:
//
// ↰
// <$text linkHref="foo">l</$text><$text linkHref="bar">INSERTED[]</$text><$text linkHref="foo">ink</$text>
//
if ( nodeAfter && nodeAfter.hasAttribute( 'linkHref' ) ) {
return;
}
model.change( writer => {
removeLinkAttributesFromSelection( writer, getLinkAttributesAllowedOnText( model.schema ) );
} );
}, { priority: 'low' } );
}
/**
* Starts listening to {@link module:engine/view/document~Document#event:mousedown} and
* {@link module:engine/view/document~Document#event:selectionChange} and puts the selection before/after a link node
* if clicked at the beginning/ending of the link.
*
* The purpose of this action is to allow typing around the link node directly after a click.
*
* See https://github.com/ckeditor/ckeditor5/issues/1016.
*
* @private
*/
_enableClickingAfterLink() {
const editor = this.editor;
const model = editor.model;
editor.editing.view.addObserver( ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_1__.MouseObserver );
let clicked = false;
// Detect the click.
this.listenTo( editor.editing.view.document, 'mousedown', () => {
clicked = true;
} );
// When the selection has changed...
this.listenTo( editor.editing.view.document, 'selectionChange', () => {
if ( !clicked ) {
return;
}
// ...and it was caused by the click...
clicked = false;
const selection = model.document.selection;
// ...and no text is selected...
if ( !selection.isCollapsed ) {
return;
}
// ...and clicked text is the link...
if ( !selection.hasAttribute( 'linkHref' ) ) {
return;
}
const position = selection.getFirstPosition();
const linkRange = (0,ckeditor5_src_typing__WEBPACK_IMPORTED_MODULE_2__.findAttributeRange)( position, 'linkHref', selection.getAttribute( 'linkHref' ), model );
// ...check whether clicked start/end boundary of the link.
// If so, remove the `linkHref` attribute.
if ( position.isTouching( linkRange.start ) || position.isTouching( linkRange.end ) ) {
model.change( writer => {
removeLinkAttributesFromSelection( writer, getLinkAttributesAllowedOnText( model.schema ) );
} );
}
} );
}
/**
* Starts listening to {@link module:engine/model/model~Model#deleteContent} and {@link module:engine/model/model~Model#insertContent}
* and checks whether typing over the link. If so, attributes of removed text are preserved and applied to the inserted text.
*
* The purpose of this action is to allow modifying a text without loosing the `linkHref` attribute (and other).
*
* See https://github.com/ckeditor/ckeditor5/issues/4762.
*
* @private
*/
_enableTypingOverLink() {
const editor = this.editor;
const view = editor.editing.view;
// Selection attributes when started typing over the link.
let selectionAttributes;
// Whether pressed `Backspace` or `Delete`. If so, attributes should not be preserved.
let deletedContent;
// Detect pressing `Backspace` / `Delete`.
this.listenTo( view.document, 'delete', () => {
deletedContent = true;
}, { priority: 'high' } );
// Listening to `model#deleteContent` allows detecting whether selected content was a link.
// If so, before removing the element, we will copy its attributes.
this.listenTo( editor.model, 'deleteContent', () => {
const selection = editor.model.document.selection;
// Copy attributes only if anything is selected.
if ( selection.isCollapsed ) {
return;
}
// When the content was deleted, do not preserve attributes.
if ( deletedContent ) {
deletedContent = false;
return;
}
// Enabled only when typing.
if ( !isTyping( editor ) ) {
return;
}
if ( shouldCopyAttributes( editor.model ) ) {
selectionAttributes = selection.getAttributes();
}
}, { priority: 'high' } );
// Listening to `model#insertContent` allows detecting the content insertion.
// We want to apply attributes that were removed while typing over the link.
this.listenTo( editor.model, 'insertContent', ( evt, [ element ] ) => {
deletedContent = false;
// Enabled only when typing.
if ( !isTyping( editor ) ) {
return;
}
if ( !selectionAttributes ) {
return;
}
editor.model.change( writer => {
for ( const [ attribute, value ] of selectionAttributes ) {
writer.setAttribute( attribute, value, element );
}
} );
selectionAttributes = null;
}, { priority: 'high' } );
}
/**
* Starts listening to {@link module:engine/model/model~Model#deleteContent} and checks whether
* removing a content right after the "linkHref" attribute.
*
* If so, the selection should not preserve the `linkHref` attribute. However, if
* the {@link module:typing/twostepcaretmovement~TwoStepCaretMovement} plugin is active and
* the selection has the "linkHref" attribute due to overriden gravity (at the end), the `linkHref` attribute should stay untouched.
*
* The purpose of this action is to allow removing the link text and keep the selection outside the link.
*
* See https://github.com/ckeditor/ckeditor5/issues/7521.
*
* @private
*/
_handleDeleteContentAfterLink() {
const editor = this.editor;
const model = editor.model;
const selection = model.document.selection;
const view = editor.editing.view;
// A flag whether attributes `linkHref` attribute should be preserved.
let shouldPreserveAttributes = false;
// A flag whether the `Backspace` key was pressed.
let hasBackspacePressed = false;
// Detect pressing `Backspace`.
this.listenTo( view.document, 'delete', ( evt, data ) => {
hasBackspacePressed = data.domEvent.keyCode === ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_4__.keyCodes.backspace;
}, { priority: 'high' } );
// Before removing the content, check whether the selection is inside a link or at the end of link but with 2-SCM enabled.
// If so, we want to preserve link attributes.
this.listenTo( model, 'deleteContent', () => {
// Reset the state.
shouldPreserveAttributes = false;
const position = selection.getFirstPosition();
const linkHref = selection.getAttribute( 'linkHref' );
if ( !linkHref ) {
return;
}
const linkRange = (0,ckeditor5_src_typing__WEBPACK_IMPORTED_MODULE_2__.findAttributeRange)( position, 'linkHref', linkHref, model );
// Preserve `linkHref` attribute if the selection is in the middle of the link or
// the selection is at the end of the link and 2-SCM is activated.
shouldPreserveAttributes = linkRange.containsPosition( position ) || linkRange.end.isEqual( position );
}, { priority: 'high' } );
// After removing the content, check whether the current selection should preserve the `linkHref` attribute.
this.listenTo( model, 'deleteContent', () => {
// If didn't press `Backspace`.
if ( !hasBackspacePressed ) {
return;
}
hasBackspacePressed = false;
// Disable the mechanism if inside a link (`<$text url="foo">F[]oo</$text>` or <$text url="foo">Foo[]</$text>`).
if ( shouldPreserveAttributes ) {
return;
}
// Use `model.enqueueChange()` in order to execute the callback at the end of the changes process.
editor.model.enqueueChange( writer => {
removeLinkAttributesFromSelection( writer, getLinkAttributesAllowedOnText( model.schema ) );
} );
}, { priority: 'low' } );
}
}
// Make the selection free of link-related model attributes.
// All link-related model attributes start with "link". That includes not only "linkHref"
// but also all decorator attributes (they have dynamic names), or even custom plugins.
//
// @param {module:engine/model/writer~Writer} writer
// @param {Array.<String>} linkAttributes
function removeLinkAttributesFromSelection( writer, linkAttributes ) {
writer.removeSelectionAttribute( 'linkHref' );
for ( const attribute of linkAttributes ) {
writer.removeSelectionAttribute( attribute );
}
}
// Checks whether selection's attributes should be copied to the new inserted text.
//
// @param {module:engine/model/model~Model} model
// @returns {Boolean}
function shouldCopyAttributes( model ) {
const selection = model.document.selection;
const firstPosition = selection.getFirstPosition();
const lastPosition = selection.getLastPosition();
const nodeAtFirstPosition = firstPosition.nodeAfter;
// The text link node does not exist...
if ( !nodeAtFirstPosition ) {
return false;
}
// ...or it isn't the text node...
if ( !nodeAtFirstPosition.is( '$text' ) ) {
return false;
}
// ...or isn't the link.
if ( !nodeAtFirstPosition.hasAttribute( 'linkHref' ) ) {
return false;
}
// `textNode` = the position is inside the link element.
// `nodeBefore` = the position is at the end of the link element.
const nodeAtLastPosition = lastPosition.textNode || lastPosition.nodeBefore;
// If both references the same node selection contains a single text node.
if ( nodeAtFirstPosition === nodeAtLastPosition ) {
return true;
}
// If nodes are not equal, maybe the link nodes has defined additional attributes inside.
// First, we need to find the entire link range.
const linkRange = (0,ckeditor5_src_typing__WEBPACK_IMPORTED_MODULE_2__.findAttributeRange)( firstPosition, 'linkHref', nodeAtFirstPosition.getAttribute( 'linkHref' ), model );
// Then we can check whether selected range is inside the found link range. If so, attributes should be preserved.
return linkRange.containsRange( model.createRange( firstPosition, lastPosition ), true );
}
// Checks whether provided changes were caused by typing.
//
// @params {module:core/editor/editor~Editor} editor
// @returns {Boolean}
function isTyping( editor ) {
const currentBatch = editor.model.change( writer => writer.batch );
return currentBatch.isTyping;
}
// Returns an array containing names of the attributes allowed on `$text` that describes the link item.
//
// @param {module:engine/model/schema~Schema} schema
// @returns {Array.<String>}
function getLinkAttributesAllowedOnText( schema ) {
const textAttributes = schema.getDefinition( '$text' ).allowAttributes;
return textAttributes.filter( attribute => attribute.startsWith( 'link' ) );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-link/src/linkimage.js":
/*!****************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-link/src/linkimage.js ***!
\****************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ LinkImage)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var _linkimageediting__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./linkimageediting */ "./node_modules/@ckeditor/ckeditor5-link/src/linkimageediting.js");
/* harmony import */ var _linkimageui__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./linkimageui */ "./node_modules/@ckeditor/ckeditor5-link/src/linkimageui.js");
/* harmony import */ var _theme_linkimage_css__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../theme/linkimage.css */ "./node_modules/@ckeditor/ckeditor5-link/theme/linkimage.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module link/linkimage
*/
/**
* The `LinkImage` plugin.
*
* This is a "glue" plugin that loads the {@link module:link/linkimageediting~LinkImageEditing link image editing feature}
* and {@link module:link/linkimageui~LinkImageUI link image UI feature}.
*
* @extends module:core/plugin~Plugin
*/
class LinkImage extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [ _linkimageediting__WEBPACK_IMPORTED_MODULE_1__["default"], _linkimageui__WEBPACK_IMPORTED_MODULE_2__["default"] ];
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'LinkImage';
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-link/src/linkimageediting.js":
/*!***********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-link/src/linkimageediting.js ***!
\***********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ LinkImageEditing)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/engine */ "./node_modules/ckeditor5/src/engine.js");
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/* harmony import */ var _linkediting__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./linkediting */ "./node_modules/@ckeditor/ckeditor5-link/src/linkediting.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module link/linkimageediting
*/
/**
* The link image engine feature.
*
* It accepts the `linkHref="url"` attribute in the model for the {@link module:image/image~Image `<imageBlock>`} element
* which allows linking images.
*
* @extends module:core/plugin~Plugin
*/
class LinkImageEditing extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [ 'ImageEditing', 'ImageUtils', _linkediting__WEBPACK_IMPORTED_MODULE_3__["default"] ];
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'LinkImageEditing';
}
init() {
const editor = this.editor;
const schema = editor.model.schema;
if ( editor.plugins.has( 'ImageBlockEditing' ) ) {
schema.extend( 'imageBlock', { allowAttributes: [ 'linkHref' ] } );
}
editor.conversion.for( 'upcast' ).add( upcastLink( editor ) );
editor.conversion.for( 'downcast' ).add( downcastImageLink( editor ) );
// Definitions for decorators are provided by the `link` command and the `LinkEditing` plugin.
this._enableAutomaticDecorators();
this._enableManualDecorators();
}
/**
* Processes {@link module:link/link~LinkDecoratorAutomaticDefinition automatic decorators} definitions and
* attaches proper converters that will work when linking an image.`
*
* @private
*/
_enableAutomaticDecorators() {
const editor = this.editor;
const command = editor.commands.get( 'link' );
const automaticDecorators = command.automaticDecorators;
if ( automaticDecorators.length ) {
editor.conversion.for( 'downcast' ).add( automaticDecorators.getDispatcherForLinkedImage() );
}
}
/**
* Processes transformed {@link module:link/utils~ManualDecorator} instances and attaches proper converters
* that will work when linking an image.
*
* @private
*/
_enableManualDecorators() {
const editor = this.editor;
const command = editor.commands.get( 'link' );
for ( const decorator of command.manualDecorators ) {
if ( editor.plugins.has( 'ImageBlockEditing' ) ) {
editor.model.schema.extend( 'imageBlock', { allowAttributes: decorator.id } );
}
if ( editor.plugins.has( 'ImageInlineEditing' ) ) {
editor.model.schema.extend( 'imageInline', { allowAttributes: decorator.id } );
}
editor.conversion.for( 'downcast' ).add( downcastImageLinkManualDecorator( decorator ) );
editor.conversion.for( 'upcast' ).add( upcastImageLinkManualDecorator( editor, decorator ) );
}
}
}
// Returns a converter for linked block images that consumes the "href" attribute
// if a link contains an image.
//
// @private
// @param {module:core/editor/editor~Editor} editor The editor instance.
// @returns {Function}
function upcastLink( editor ) {
const isImageInlinePluginLoaded = editor.plugins.has( 'ImageInlineEditing' );
const imageUtils = editor.plugins.get( 'ImageUtils' );
return dispatcher => {
dispatcher.on( 'element:a', ( evt, data, conversionApi ) => {
const viewLink = data.viewItem;
const imageInLink = imageUtils.findViewImgElement( viewLink );
if ( !imageInLink ) {
return;
}
const blockImageView = imageInLink.findAncestor( element => imageUtils.isBlockImageView( element ) );
// There are four possible cases to consider here
//
// 1. A "root > ... > figure.image > a > img" structure.
// 2. A "root > ... > figure.image > a > picture > img" structure.
// 3. A "root > ... > block > a > img" structure.
// 4. A "root > ... > block > a > picture > img" structure.
//
// but the last 2 cases should only be considered by this converter when the inline image plugin
// is NOT loaded in the editor (because otherwise, that would be a plain, linked inline image).
if ( isImageInlinePluginLoaded && !blockImageView ) {
return;
}
// There's an image inside an <a> element - we consume it so it won't be picked up by the Link plugin.
const consumableAttributes = { attributes: [ 'href' ] };
// Consume the `href` attribute so the default one will not convert it to $text attribute.
if ( !conversionApi.consumable.consume( viewLink, consumableAttributes ) ) {
// Might be consumed by something else - i.e. other converter with priority=highest - a standard check.
return;
}
const linkHref = viewLink.getAttribute( 'href' );
// Missing the 'href' attribute.
if ( !linkHref ) {
return;
}
// A full definition of the image feature.
// figure > a > img: parent of the view link element is an image element (figure).
let modelElement = data.modelCursor.parent;
if ( !modelElement.is( 'element', 'imageBlock' ) ) {
// a > img: parent of the view link is not the image (figure) element. We need to convert it manually.
const conversionResult = conversionApi.convertItem( imageInLink, data.modelCursor );
// Set image range as conversion result.
data.modelRange = conversionResult.modelRange;
// Continue conversion where image conversion ends.
data.modelCursor = conversionResult.modelCursor;
modelElement = data.modelCursor.nodeBefore;
}
if ( modelElement && modelElement.is( 'element', 'imageBlock' ) ) {
// Set the linkHref attribute from link element on model image element.
conversionApi.writer.setAttribute( 'linkHref', linkHref, modelElement );
}
}, { priority: 'high' } );
// Using the same priority that `upcastImageLinkManualDecorator()` converter guarantees
// that manual decorators will decorate the proper element.
};
}
// Creates a converter that adds `<a>` to linked block image view elements.
//
// @private
function downcastImageLink( editor ) {
const imageUtils = editor.plugins.get( 'ImageUtils' );
return dispatcher => {
dispatcher.on( 'attribute:linkHref:imageBlock', ( evt, data, conversionApi ) => {
if ( !conversionApi.consumable.consume( data.item, evt.name ) ) {
return;
}
// The image will be already converted - so it will be present in the view.
const viewFigure = conversionApi.mapper.toViewElement( data.item );
const writer = conversionApi.writer;
// But we need to check whether the link element exists.
const linkInImage = Array.from( viewFigure.getChildren() ).find( child => child.name === 'a' );
const viewImage = imageUtils.findViewImgElement( viewFigure );
// <picture>...<img/></picture> or <img/>
const viewImgOrPicture = viewImage.parent.is( 'element', 'picture' ) ? viewImage.parent : viewImage;
// If so, update the attribute if it's defined or remove the entire link if the attribute is empty.
if ( linkInImage ) {
if ( data.attributeNewValue ) {
writer.setAttribute( 'href', data.attributeNewValue, linkInImage );
} else {
writer.move( writer.createRangeOn( viewImgOrPicture ), writer.createPositionAt( viewFigure, 0 ) );
writer.remove( linkInImage );
}
} else {
// But if it does not exist. Let's wrap already converted image by newly created link element.
// 1. Create an empty link element.
const linkElement = writer.createContainerElement( 'a', { href: data.attributeNewValue } );
// 2. Insert link inside the associated image.
writer.insert( writer.createPositionAt( viewFigure, 0 ), linkElement );
// 3. Move the image to the link.
writer.move( writer.createRangeOn( viewImgOrPicture ), writer.createPositionAt( linkElement, 0 ) );
}
}, { priority: 'high' } );
};
}
// Returns a converter that decorates the `<a>` element when the image is the link label.
//
// @private
// @returns {Function}
function downcastImageLinkManualDecorator( decorator ) {
return dispatcher => {
dispatcher.on( `attribute:${ decorator.id }:imageBlock`, ( evt, data, conversionApi ) => {
const viewFigure = conversionApi.mapper.toViewElement( data.item );
const linkInImage = Array.from( viewFigure.getChildren() ).find( child => child.name === 'a' );
// The <a> element was removed by the time this converter is executed.
// It may happen when the base `linkHref` and decorator attributes are removed
// at the same time (see #8401).
if ( !linkInImage ) {
return;
}
for ( const [ key, val ] of (0,ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_2__.toMap)( decorator.attributes ) ) {
conversionApi.writer.setAttribute( key, val, linkInImage );
}
if ( decorator.classes ) {
conversionApi.writer.addClass( decorator.classes, linkInImage );
}
for ( const key in decorator.styles ) {
conversionApi.writer.setStyle( key, decorator.styles[ key ], linkInImage );
}
} );
};
}
// Returns a converter that checks whether manual decorators should be applied to the link.
//
// @private
// @returns {Function}
function upcastImageLinkManualDecorator( editor, decorator ) {
const isImageInlinePluginLoaded = editor.plugins.has( 'ImageInlineEditing' );
const imageUtils = editor.plugins.get( 'ImageUtils' );
return dispatcher => {
dispatcher.on( 'element:a', ( evt, data, conversionApi ) => {
const viewLink = data.viewItem;
const imageInLink = imageUtils.findViewImgElement( viewLink );
// We need to check whether an image is inside a link because the converter handles
// only manual decorators for linked images. See #7975.
if ( !imageInLink ) {
return;
}
const blockImageView = imageInLink.findAncestor( element => imageUtils.isBlockImageView( element ) );
if ( isImageInlinePluginLoaded && !blockImageView ) {
return;
}
const matcher = new ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_1__.Matcher( decorator._createPattern() );
const result = matcher.match( viewLink );
// The link element does not have required attributes or/and proper values.
if ( !result ) {
return;
}
// Check whether we can consume those attributes.
if ( !conversionApi.consumable.consume( viewLink, result.match ) ) {
return;
}
// At this stage we can assume that we have the `<imageBlock>` element.
// `nodeBefore` comes after conversion: `<a><img></a>`.
// `parent` comes with full image definition: `<figure><a><img></a></figure>.
// See the body of the `upcastLink()` function.
const modelElement = data.modelCursor.nodeBefore || data.modelCursor.parent;
conversionApi.writer.setAttribute( decorator.id, true, modelElement );
}, { priority: 'high' } );
// Using the same priority that `upcastLink()` converter guarantees that the linked image was properly converted.
};
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-link/src/linkimageui.js":
/*!******************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-link/src/linkimageui.js ***!
\******************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ LinkImageUI)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/ui */ "./node_modules/ckeditor5/src/ui.js");
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var _linkui__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./linkui */ "./node_modules/@ckeditor/ckeditor5-link/src/linkui.js");
/* harmony import */ var _linkediting__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./linkediting */ "./node_modules/@ckeditor/ckeditor5-link/src/linkediting.js");
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./utils */ "./node_modules/@ckeditor/ckeditor5-link/src/utils.js");
/* harmony import */ var _theme_icons_link_svg__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../theme/icons/link.svg */ "./node_modules/@ckeditor/ckeditor5-link/theme/icons/link.svg");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module link/linkimageui
*/
/**
* The link image UI plugin.
*
* This plugin provides the `'linkImage'` button that can be displayed in the {@link module:image/imagetoolbar~ImageToolbar}.
* It can be used to wrap images in links.
*
* @extends module:core/plugin~Plugin
*/
class LinkImageUI extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_1__.Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [ _linkediting__WEBPACK_IMPORTED_MODULE_3__["default"], _linkui__WEBPACK_IMPORTED_MODULE_2__["default"], 'ImageBlockEditing' ];
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'LinkImageUI';
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const viewDocument = editor.editing.view.document;
this.listenTo( viewDocument, 'click', ( evt, data ) => {
if ( this._isSelectedLinkedImage( editor.model.document.selection ) ) {
// Prevent browser navigation when clicking a linked image.
data.preventDefault();
// Block the `LinkUI` plugin when an image was clicked.
// In such a case, we'd like to display the image toolbar.
evt.stop();
}
}, { priority: 'high' } );
this._createToolbarLinkImageButton();
}
/**
* Creates a `LinkImageUI` button view.
*
* Clicking this button shows a {@link module:link/linkui~LinkUI#_balloon} attached to the selection.
* When an image is already linked, the view shows {@link module:link/linkui~LinkUI#actionsView} or
* {@link module:link/linkui~LinkUI#formView} if it is not.
*
* @private
*/
_createToolbarLinkImageButton() {
const editor = this.editor;
const t = editor.t;
editor.ui.componentFactory.add( 'linkImage', locale => {
const button = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.ButtonView( locale );
const plugin = editor.plugins.get( 'LinkUI' );
const linkCommand = editor.commands.get( 'link' );
button.set( {
isEnabled: true,
label: t( 'Link image' ),
icon: _theme_icons_link_svg__WEBPACK_IMPORTED_MODULE_5__["default"],
keystroke: _utils__WEBPACK_IMPORTED_MODULE_4__.LINK_KEYSTROKE,
tooltip: true,
isToggleable: true
} );
// Bind button to the command.
button.bind( 'isEnabled' ).to( linkCommand, 'isEnabled' );
button.bind( 'isOn' ).to( linkCommand, 'value', value => !!value );
// Show the actionsView or formView (both from LinkUI) on button click depending on whether the image is linked already.
this.listenTo( button, 'execute', () => {
if ( this._isSelectedLinkedImage( editor.model.document.selection ) ) {
plugin._addActionsView();
} else {
plugin._showUI( true );
}
} );
return button;
} );
}
/**
* Returns true if a linked image (either block or inline) is the only selected element
* in the model document.
*
* @private
* @param {module:engine/model/selection~Selection} selection
* @returns {Boolean}
*/
_isSelectedLinkedImage( selection ) {
const selectedModelElement = selection.getSelectedElement();
const imageUtils = this.editor.plugins.get( 'ImageUtils' );
return imageUtils.isImage( selectedModelElement ) && selectedModelElement.hasAttribute( 'linkHref' );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-link/src/linkui.js":
/*!*************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-link/src/linkui.js ***!
\*************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ LinkUI)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/engine */ "./node_modules/ckeditor5/src/engine.js");
/* harmony import */ var ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ckeditor5/src/ui */ "./node_modules/ckeditor5/src/ui.js");
/* harmony import */ var ckeditor5_src_widget__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ckeditor5/src/widget */ "./node_modules/ckeditor5/src/widget.js");
/* harmony import */ var _ui_linkformview__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./ui/linkformview */ "./node_modules/@ckeditor/ckeditor5-link/src/ui/linkformview.js");
/* harmony import */ var _ui_linkactionsview__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./ui/linkactionsview */ "./node_modules/@ckeditor/ckeditor5-link/src/ui/linkactionsview.js");
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./utils */ "./node_modules/@ckeditor/ckeditor5-link/src/utils.js");
/* harmony import */ var _theme_icons_link_svg__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ../theme/icons/link.svg */ "./node_modules/@ckeditor/ckeditor5-link/theme/icons/link.svg");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module link/linkui
*/
const VISUAL_SELECTION_MARKER_NAME = 'link-ui';
/**
* The link UI plugin. It introduces the `'link'` and `'unlink'` buttons and support for the <kbd>Ctrl+K</kbd> keystroke.
*
* It uses the
* {@link module:ui/panel/balloon/contextualballoon~ContextualBalloon contextual balloon plugin}.
*
* @extends module:core/plugin~Plugin
*/
class LinkUI extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [ ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_2__.ContextualBalloon ];
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'LinkUI';
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
editor.editing.view.addObserver( ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_1__.ClickObserver );
/**
* The actions view displayed inside of the balloon.
*
* @member {module:link/ui/linkactionsview~LinkActionsView}
*/
this.actionsView = this._createActionsView();
/**
* The form view displayed inside the balloon.
*
* @member {module:link/ui/linkformview~LinkFormView}
*/
this.formView = this._createFormView();
/**
* The contextual balloon plugin instance.
*
* @private
* @member {module:ui/panel/balloon/contextualballoon~ContextualBalloon}
*/
this._balloon = editor.plugins.get( ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_2__.ContextualBalloon );
// Create toolbar buttons.
this._createToolbarLinkButton();
// Attach lifecycle actions to the the balloon.
this._enableUserBalloonInteractions();
// Renders a fake visual selection marker on an expanded selection.
editor.conversion.for( 'editingDowncast' ).markerToHighlight( {
model: VISUAL_SELECTION_MARKER_NAME,
view: {
classes: [ 'ck-fake-link-selection' ]
}
} );
// Renders a fake visual selection marker on a collapsed selection.
editor.conversion.for( 'editingDowncast' ).markerToElement( {
model: VISUAL_SELECTION_MARKER_NAME,
view: {
name: 'span',
classes: [ 'ck-fake-link-selection', 'ck-fake-link-selection_collapsed' ]
}
} );
}
/**
* @inheritDoc
*/
destroy() {
super.destroy();
// Destroy created UI components as they are not automatically destroyed (see ckeditor5#1341).
this.formView.destroy();
}
/**
* Creates the {@link module:link/ui/linkactionsview~LinkActionsView} instance.
*
* @private
* @returns {module:link/ui/linkactionsview~LinkActionsView} The link actions view instance.
*/
_createActionsView() {
const editor = this.editor;
const actionsView = new _ui_linkactionsview__WEBPACK_IMPORTED_MODULE_5__["default"]( editor.locale );
const linkCommand = editor.commands.get( 'link' );
const unlinkCommand = editor.commands.get( 'unlink' );
actionsView.bind( 'href' ).to( linkCommand, 'value' );
actionsView.editButtonView.bind( 'isEnabled' ).to( linkCommand );
actionsView.unlinkButtonView.bind( 'isEnabled' ).to( unlinkCommand );
// Execute unlink command after clicking on the "Edit" button.
this.listenTo( actionsView, 'edit', () => {
this._addFormView();
} );
// Execute unlink command after clicking on the "Unlink" button.
this.listenTo( actionsView, 'unlink', () => {
editor.execute( 'unlink' );
this._hideUI();
} );
// Close the panel on esc key press when the **actions have focus**.
actionsView.keystrokes.set( 'Esc', ( data, cancel ) => {
this._hideUI();
cancel();
} );
// Open the form view on Ctrl+K when the **actions have focus**..
actionsView.keystrokes.set( _utils__WEBPACK_IMPORTED_MODULE_6__.LINK_KEYSTROKE, ( data, cancel ) => {
this._addFormView();
cancel();
} );
return actionsView;
}
/**
* Creates the {@link module:link/ui/linkformview~LinkFormView} instance.
*
* @private
* @returns {module:link/ui/linkformview~LinkFormView} The link form view instance.
*/
_createFormView() {
const editor = this.editor;
const linkCommand = editor.commands.get( 'link' );
const defaultProtocol = editor.config.get( 'link.defaultProtocol' );
const formView = new _ui_linkformview__WEBPACK_IMPORTED_MODULE_4__["default"]( editor.locale, linkCommand );
formView.urlInputView.fieldView.bind( 'value' ).to( linkCommand, 'value' );
// Form elements should be read-only when corresponding commands are disabled.
formView.urlInputView.bind( 'isReadOnly' ).to( linkCommand, 'isEnabled', value => !value );
formView.saveButtonView.bind( 'isEnabled' ).to( linkCommand );
// Execute link command after clicking the "Save" button.
this.listenTo( formView, 'submit', () => {
const { value } = formView.urlInputView.fieldView.element;
const parsedUrl = (0,_utils__WEBPACK_IMPORTED_MODULE_6__.addLinkProtocolIfApplicable)( value, defaultProtocol );
editor.execute( 'link', parsedUrl, formView.getDecoratorSwitchesState() );
this._closeFormView();
} );
// Hide the panel after clicking the "Cancel" button.
this.listenTo( formView, 'cancel', () => {
this._closeFormView();
} );
// Close the panel on esc key press when the **form has focus**.
formView.keystrokes.set( 'Esc', ( data, cancel ) => {
this._closeFormView();
cancel();
} );
return formView;
}
/**
* Creates a toolbar Link button. Clicking this button will show
* a {@link #_balloon} attached to the selection.
*
* @private
*/
_createToolbarLinkButton() {
const editor = this.editor;
const linkCommand = editor.commands.get( 'link' );
const t = editor.t;
// Handle the `Ctrl+K` keystroke and show the panel.
editor.keystrokes.set( _utils__WEBPACK_IMPORTED_MODULE_6__.LINK_KEYSTROKE, ( keyEvtData, cancel ) => {
// Prevent focusing the search bar in FF, Chrome and Edge. See https://github.com/ckeditor/ckeditor5/issues/4811.
cancel();
if ( linkCommand.isEnabled ) {
this._showUI( true );
}
} );
editor.ui.componentFactory.add( 'link', locale => {
const button = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_2__.ButtonView( locale );
button.isEnabled = true;
button.label = t( 'Link' );
button.icon = _theme_icons_link_svg__WEBPACK_IMPORTED_MODULE_7__["default"];
button.keystroke = _utils__WEBPACK_IMPORTED_MODULE_6__.LINK_KEYSTROKE;
button.tooltip = true;
button.isToggleable = true;
// Bind button to the command.
button.bind( 'isEnabled' ).to( linkCommand, 'isEnabled' );
button.bind( 'isOn' ).to( linkCommand, 'value', value => !!value );
// Show the panel on button click.
this.listenTo( button, 'execute', () => this._showUI( true ) );
return button;
} );
}
/**
* Attaches actions that control whether the balloon panel containing the
* {@link #formView} is visible or not.
*
* @private
*/
_enableUserBalloonInteractions() {
const viewDocument = this.editor.editing.view.document;
// Handle click on view document and show panel when selection is placed inside the link element.
// Keep panel open until selection will be inside the same link element.
this.listenTo( viewDocument, 'click', () => {
const parentLink = this._getSelectedLinkElement();
if ( parentLink ) {
// Then show panel but keep focus inside editor editable.
this._showUI();
}
} );
// Focus the form if the balloon is visible and the Tab key has been pressed.
this.editor.keystrokes.set( 'Tab', ( data, cancel ) => {
if ( this._areActionsVisible && !this.actionsView.focusTracker.isFocused ) {
this.actionsView.focus();
cancel();
}
}, {
// Use the high priority because the link UI navigation is more important
// than other feature's actions, e.g. list indentation.
// https://github.com/ckeditor/ckeditor5-link/issues/146
priority: 'high'
} );
// Close the panel on the Esc key press when the editable has focus and the balloon is visible.
this.editor.keystrokes.set( 'Esc', ( data, cancel ) => {
if ( this._isUIVisible ) {
this._hideUI();
cancel();
}
} );
// Close on click outside of balloon panel element.
(0,ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_2__.clickOutsideHandler)( {
emitter: this.formView,
activator: () => this._isUIInPanel,
contextElements: [ this._balloon.view.element ],
callback: () => this._hideUI()
} );
}
/**
* Adds the {@link #actionsView} to the {@link #_balloon}.
*
* @protected
*/
_addActionsView() {
if ( this._areActionsInPanel ) {
return;
}
this._balloon.add( {
view: this.actionsView,
position: this._getBalloonPositionData()
} );
}
/**
* Adds the {@link #formView} to the {@link #_balloon}.
*
* @protected
*/
_addFormView() {
if ( this._isFormInPanel ) {
return;
}
const editor = this.editor;
const linkCommand = editor.commands.get( 'link' );
this.formView.disableCssTransitions();
this._balloon.add( {
view: this.formView,
position: this._getBalloonPositionData()
} );
// Select input when form view is currently visible.
if ( this._balloon.visibleView === this.formView ) {
this.formView.urlInputView.fieldView.select();
}
this.formView.enableCssTransitions();
// Make sure that each time the panel shows up, the URL field remains in sync with the value of
// the command. If the user typed in the input, then canceled the balloon (`urlInputView.fieldView#value` stays
// unaltered) and re-opened it without changing the value of the link command (e.g. because they
// clicked the same link), they would see the old value instead of the actual value of the command.
// https://github.com/ckeditor/ckeditor5-link/issues/78
// https://github.com/ckeditor/ckeditor5-link/issues/123
this.formView.urlInputView.fieldView.element.value = linkCommand.value || '';
}
/**
* Closes the form view. Decides whether the balloon should be hidden completely or if the action view should be shown. This is
* decided upon the link command value (which has a value if the document selection is in the link).
*
* Additionally, if any {@link module:link/link~LinkConfig#decorators} are defined in the editor configuration, the state of
* switch buttons responsible for manual decorator handling is restored.
*
* @private
*/
_closeFormView() {
const linkCommand = this.editor.commands.get( 'link' );
// Restore manual decorator states to represent the current model state. This case is important to reset the switch buttons
// when the user cancels the editing form.
linkCommand.restoreManualDecoratorStates();
if ( linkCommand.value !== undefined ) {
this._removeFormView();
} else {
this._hideUI();
}
}
/**
* Removes the {@link #formView} from the {@link #_balloon}.
*
* @protected
*/
_removeFormView() {
if ( this._isFormInPanel ) {
// Blur the input element before removing it from DOM to prevent issues in some browsers.
// See https://github.com/ckeditor/ckeditor5/issues/1501.
this.formView.saveButtonView.focus();
this._balloon.remove( this.formView );
// Because the form has an input which has focus, the focus must be brought back
// to the editor. Otherwise, it would be lost.
this.editor.editing.view.focus();
this._hideFakeVisualSelection();
}
}
/**
* Shows the correct UI type. It is either {@link #formView} or {@link #actionsView}.
*
* @param {Boolean} forceVisible
* @private
*/
_showUI( forceVisible = false ) {
// When there's no link under the selection, go straight to the editing UI.
if ( !this._getSelectedLinkElement() ) {
// Show visual selection on a text without a link when the contextual balloon is displayed.
// See https://github.com/ckeditor/ckeditor5/issues/4721.
this._showFakeVisualSelection();
this._addActionsView();
// Be sure panel with link is visible.
if ( forceVisible ) {
this._balloon.showStack( 'main' );
}
this._addFormView();
}
// If there's a link under the selection...
else {
// Go to the editing UI if actions are already visible.
if ( this._areActionsVisible ) {
this._addFormView();
}
// Otherwise display just the actions UI.
else {
this._addActionsView();
}
// Be sure panel with link is visible.
if ( forceVisible ) {
this._balloon.showStack( 'main' );
}
}
// Begin responding to ui#update once the UI is added.
this._startUpdatingUI();
}
/**
* Removes the {@link #formView} from the {@link #_balloon}.
*
* See {@link #_addFormView}, {@link #_addActionsView}.
*
* @protected
*/
_hideUI() {
if ( !this._isUIInPanel ) {
return;
}
const editor = this.editor;
this.stopListening( editor.ui, 'update' );
this.stopListening( this._balloon, 'change:visibleView' );
// Make sure the focus always gets back to the editable _before_ removing the focused form view.
// Doing otherwise causes issues in some browsers. See https://github.com/ckeditor/ckeditor5-link/issues/193.
editor.editing.view.focus();
// Remove form first because it's on top of the stack.
this._removeFormView();
// Then remove the actions view because it's beneath the form.
this._balloon.remove( this.actionsView );
this._hideFakeVisualSelection();
}
/**
* Makes the UI react to the {@link module:core/editor/editorui~EditorUI#event:update} event to
* reposition itself when the editor UI should be refreshed.
*
* See: {@link #_hideUI} to learn when the UI stops reacting to the `update` event.
*
* @protected
*/
_startUpdatingUI() {
const editor = this.editor;
const viewDocument = editor.editing.view.document;
let prevSelectedLink = this._getSelectedLinkElement();
let prevSelectionParent = getSelectionParent();
const update = () => {
const selectedLink = this._getSelectedLinkElement();
const selectionParent = getSelectionParent();
// Hide the panel if:
//
// * the selection went out of the EXISTING link element. E.g. user moved the caret out
// of the link,
// * the selection went to a different parent when creating a NEW link. E.g. someone
// else modified the document.
// * the selection has expanded (e.g. displaying link actions then pressing SHIFT+Right arrow).
//
// Note: #_getSelectedLinkElement will return a link for a non-collapsed selection only
// when fully selected.
if ( ( prevSelectedLink && !selectedLink ) ||
( !prevSelectedLink && selectionParent !== prevSelectionParent ) ) {
this._hideUI();
}
// Update the position of the panel when:
// * link panel is in the visible stack
// * the selection remains in the original link element,
// * there was no link element in the first place, i.e. creating a new link
else if ( this._isUIVisible ) {
// If still in a link element, simply update the position of the balloon.
// If there was no link (e.g. inserting one), the balloon must be moved
// to the new position in the editing view (a new native DOM range).
this._balloon.updatePosition( this._getBalloonPositionData() );
}
prevSelectedLink = selectedLink;
prevSelectionParent = selectionParent;
};
function getSelectionParent() {
return viewDocument.selection.focus.getAncestors()
.reverse()
.find( node => node.is( 'element' ) );
}
this.listenTo( editor.ui, 'update', update );
this.listenTo( this._balloon, 'change:visibleView', update );
}
/**
* Returns `true` when {@link #formView} is in the {@link #_balloon}.
*
* @readonly
* @protected
* @type {Boolean}
*/
get _isFormInPanel() {
return this._balloon.hasView( this.formView );
}
/**
* Returns `true` when {@link #actionsView} is in the {@link #_balloon}.
*
* @readonly
* @protected
* @type {Boolean}
*/
get _areActionsInPanel() {
return this._balloon.hasView( this.actionsView );
}
/**
* Returns `true` when {@link #actionsView} is in the {@link #_balloon} and it is
* currently visible.
*
* @readonly
* @protected
* @type {Boolean}
*/
get _areActionsVisible() {
return this._balloon.visibleView === this.actionsView;
}
/**
* Returns `true` when {@link #actionsView} or {@link #formView} is in the {@link #_balloon}.
*
* @readonly
* @protected
* @type {Boolean}
*/
get _isUIInPanel() {
return this._isFormInPanel || this._areActionsInPanel;
}
/**
* Returns `true` when {@link #actionsView} or {@link #formView} is in the {@link #_balloon} and it is
* currently visible.
*
* @readonly
* @protected
* @type {Boolean}
*/
get _isUIVisible() {
const visibleView = this._balloon.visibleView;
return visibleView == this.formView || this._areActionsVisible;
}
/**
* Returns positioning options for the {@link #_balloon}. They control the way the balloon is attached
* to the target element or selection.
*
* If the selection is collapsed and inside a link element, the panel will be attached to the
* entire link element. Otherwise, it will be attached to the selection.
*
* @private
* @returns {module:utils/dom/position~Options}
*/
_getBalloonPositionData() {
const view = this.editor.editing.view;
const model = this.editor.model;
const viewDocument = view.document;
let target = null;
if ( model.markers.has( VISUAL_SELECTION_MARKER_NAME ) ) {
// There are cases when we highlight selection using a marker (#7705, #4721).
const markerViewElements = Array.from( this.editor.editing.mapper.markerNameToElements( VISUAL_SELECTION_MARKER_NAME ) );
const newRange = view.createRange(
view.createPositionBefore( markerViewElements[ 0 ] ),
view.createPositionAfter( markerViewElements[ markerViewElements.length - 1 ] )
);
target = view.domConverter.viewRangeToDom( newRange );
} else {
// Make sure the target is calculated on demand at the last moment because a cached DOM range
// (which is very fragile) can desynchronize with the state of the editing view if there was
// any rendering done in the meantime. This can happen, for instance, when an inline widget
// gets unlinked.
target = () => {
const targetLink = this._getSelectedLinkElement();
return targetLink ?
// When selection is inside link element, then attach panel to this element.
view.domConverter.mapViewToDom( targetLink ) :
// Otherwise attach panel to the selection.
view.domConverter.viewRangeToDom( viewDocument.selection.getFirstRange() );
};
}
return { target };
}
/**
* Returns the link {@link module:engine/view/attributeelement~AttributeElement} under
* the {@link module:engine/view/document~Document editing view's} selection or `null`
* if there is none.
*
* **Note**: For a non–collapsed selection, the link element is returned when **fully**
* selected and the **only** element within the selection boundaries, or when
* a linked widget is selected.
*
* @private
* @returns {module:engine/view/attributeelement~AttributeElement|null}
*/
_getSelectedLinkElement() {
const view = this.editor.editing.view;
const selection = view.document.selection;
const selectedElement = selection.getSelectedElement();
// The selection is collapsed or some widget is selected (especially inline widget).
if ( selection.isCollapsed || selectedElement && (0,ckeditor5_src_widget__WEBPACK_IMPORTED_MODULE_3__.isWidget)( selectedElement ) ) {
return findLinkElementAncestor( selection.getFirstPosition() );
} else {
// The range for fully selected link is usually anchored in adjacent text nodes.
// Trim it to get closer to the actual link element.
const range = selection.getFirstRange().getTrimmed();
const startLink = findLinkElementAncestor( range.start );
const endLink = findLinkElementAncestor( range.end );
if ( !startLink || startLink != endLink ) {
return null;
}
// Check if the link element is fully selected.
if ( view.createRangeIn( startLink ).getTrimmed().isEqual( range ) ) {
return startLink;
} else {
return null;
}
}
}
/**
* Displays a fake visual selection when the contextual balloon is displayed.
*
* This adds a 'link-ui' marker into the document that is rendered as a highlight on selected text fragment.
*
* @private
*/
_showFakeVisualSelection() {
const model = this.editor.model;
model.change( writer => {
const range = model.document.selection.getFirstRange();
if ( model.markers.has( VISUAL_SELECTION_MARKER_NAME ) ) {
writer.updateMarker( VISUAL_SELECTION_MARKER_NAME, { range } );
} else {
if ( range.start.isAtEnd ) {
const startPosition = range.start.getLastMatchingPosition(
( { item } ) => !model.schema.isContent( item ),
{ boundaries: range }
);
writer.addMarker( VISUAL_SELECTION_MARKER_NAME, {
usingOperation: false,
affectsData: false,
range: writer.createRange( startPosition, range.end )
} );
} else {
writer.addMarker( VISUAL_SELECTION_MARKER_NAME, {
usingOperation: false,
affectsData: false,
range
} );
}
}
} );
}
/**
* Hides the fake visual selection created in {@link #_showFakeVisualSelection}.
*
* @private
*/
_hideFakeVisualSelection() {
const model = this.editor.model;
if ( model.markers.has( VISUAL_SELECTION_MARKER_NAME ) ) {
model.change( writer => {
writer.removeMarker( VISUAL_SELECTION_MARKER_NAME );
} );
}
}
}
// Returns a link element if there's one among the ancestors of the provided `Position`.
//
// @private
// @param {module:engine/view/position~Position} View position to analyze.
// @returns {module:engine/view/attributeelement~AttributeElement|null} Link element at the position or null.
function findLinkElementAncestor( position ) {
return position.getAncestors().find( ancestor => (0,_utils__WEBPACK_IMPORTED_MODULE_6__.isLinkElement)( ancestor ) );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-link/src/ui/linkactionsview.js":
/*!*************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-link/src/ui/linkactionsview.js ***!
\*************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ LinkActionsView)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/ui */ "./node_modules/ckeditor5/src/ui.js");
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../utils */ "./node_modules/@ckeditor/ckeditor5-link/src/utils.js");
/* harmony import */ var _ckeditor_ckeditor5_ui_theme_components_responsive_form_responsiveform_css__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! @ckeditor/ckeditor5-ui/theme/components/responsive-form/responsiveform.css */ "./node_modules/@ckeditor/ckeditor5-ui/theme/components/responsive-form/responsiveform.css");
/* harmony import */ var _theme_linkactions_css__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../../theme/linkactions.css */ "./node_modules/@ckeditor/ckeditor5-link/theme/linkactions.css");
/* harmony import */ var _theme_icons_unlink_svg__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ../../theme/icons/unlink.svg */ "./node_modules/@ckeditor/ckeditor5-link/theme/icons/unlink.svg");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module link/ui/linkactionsview
*/
// See: #8833.
// eslint-disable-next-line ckeditor5-rules/ckeditor-imports
/**
* The link actions view class. This view displays the link preview, allows
* unlinking or editing the link.
*
* @extends module:ui/view~View
*/
class LinkActionsView extends ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.View {
/**
* @inheritDoc
*/
constructor( locale ) {
super( locale );
const t = locale.t;
/**
* Tracks information about DOM focus in the actions.
*
* @readonly
* @member {module:utils/focustracker~FocusTracker}
*/
this.focusTracker = new ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__.FocusTracker();
/**
* An instance of the {@link module:utils/keystrokehandler~KeystrokeHandler}.
*
* @readonly
* @member {module:utils/keystrokehandler~KeystrokeHandler}
*/
this.keystrokes = new ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__.KeystrokeHandler();
/**
* The href preview view.
*
* @member {module:ui/view~View}
*/
this.previewButtonView = this._createPreviewButton();
/**
* The unlink button view.
*
* @member {module:ui/button/buttonview~ButtonView}
*/
this.unlinkButtonView = this._createButton( t( 'Unlink' ), _theme_icons_unlink_svg__WEBPACK_IMPORTED_MODULE_6__["default"], 'unlink' );
/**
* The edit link button view.
*
* @member {module:ui/button/buttonview~ButtonView}
*/
this.editButtonView = this._createButton( t( 'Edit link' ), ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_2__.icons.pencil, 'edit' );
/**
* The value of the "href" attribute of the link to use in the {@link #previewButtonView}.
*
* @observable
* @member {String}
*/
this.set( 'href' );
/**
* A collection of views that can be focused in the view.
*
* @readonly
* @protected
* @member {module:ui/viewcollection~ViewCollection}
*/
this._focusables = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.ViewCollection();
/**
* Helps cycling over {@link #_focusables} in the view.
*
* @readonly
* @protected
* @member {module:ui/focuscycler~FocusCycler}
*/
this._focusCycler = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.FocusCycler( {
focusables: this._focusables,
focusTracker: this.focusTracker,
keystrokeHandler: this.keystrokes,
actions: {
// Navigate fields backwards using the Shift + Tab keystroke.
focusPrevious: 'shift + tab',
// Navigate fields forwards using the Tab key.
focusNext: 'tab'
}
} );
this.setTemplate( {
tag: 'div',
attributes: {
class: [
'ck',
'ck-link-actions',
'ck-responsive-form'
],
// https://github.com/ckeditor/ckeditor5-link/issues/90
tabindex: '-1'
},
children: [
this.previewButtonView,
this.editButtonView,
this.unlinkButtonView
]
} );
}
/**
* @inheritDoc
*/
render() {
super.render();
const childViews = [
this.previewButtonView,
this.editButtonView,
this.unlinkButtonView
];
childViews.forEach( v => {
// Register the view as focusable.
this._focusables.add( v );
// Register the view in the focus tracker.
this.focusTracker.add( v.element );
} );
// Start listening for the keystrokes coming from #element.
this.keystrokes.listenTo( this.element );
}
/**
* @inheritDoc
*/
destroy() {
super.destroy();
this.focusTracker.destroy();
this.keystrokes.destroy();
}
/**
* Focuses the fist {@link #_focusables} in the actions.
*/
focus() {
this._focusCycler.focusFirst();
}
/**
* Creates a button view.
*
* @private
* @param {String} label The button label.
* @param {String} icon The button icon.
* @param {String} [eventName] An event name that the `ButtonView#execute` event will be delegated to.
* @returns {module:ui/button/buttonview~ButtonView} The button view instance.
*/
_createButton( label, icon, eventName ) {
const button = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.ButtonView( this.locale );
button.set( {
label,
icon,
tooltip: true
} );
button.delegate( 'execute' ).to( this, eventName );
return button;
}
/**
* Creates a link href preview button.
*
* @private
* @returns {module:ui/button/buttonview~ButtonView} The button view instance.
*/
_createPreviewButton() {
const button = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.ButtonView( this.locale );
const bind = this.bindTemplate;
const t = this.t;
button.set( {
withText: true,
tooltip: t( 'Open link in new tab' )
} );
button.extendTemplate( {
attributes: {
class: [
'ck',
'ck-link-actions__preview'
],
href: bind.to( 'href', href => href && (0,_utils__WEBPACK_IMPORTED_MODULE_3__.ensureSafeUrl)( href ) ),
target: '_blank',
rel: 'noopener noreferrer'
}
} );
button.bind( 'label' ).to( this, 'href', href => {
return href || t( 'This link has no URL' );
} );
button.bind( 'isEnabled' ).to( this, 'href', href => !!href );
button.template.tag = 'a';
button.template.eventListeners = {};
return button;
}
}
/**
* Fired when the {@link #editButtonView} is clicked.
*
* @event edit
*/
/**
* Fired when the {@link #unlinkButtonView} is clicked.
*
* @event unlink
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-link/src/ui/linkformview.js":
/*!**********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-link/src/ui/linkformview.js ***!
\**********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ LinkFormView)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/ui */ "./node_modules/ckeditor5/src/ui.js");
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var _ckeditor_ckeditor5_ui_theme_components_responsive_form_responsiveform_css__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @ckeditor/ckeditor5-ui/theme/components/responsive-form/responsiveform.css */ "./node_modules/@ckeditor/ckeditor5-ui/theme/components/responsive-form/responsiveform.css");
/* harmony import */ var _theme_linkform_css__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../../theme/linkform.css */ "./node_modules/@ckeditor/ckeditor5-link/theme/linkform.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module link/ui/linkformview
*/
// See: #8833.
// eslint-disable-next-line ckeditor5-rules/ckeditor-imports
/**
* The link form view controller class.
*
* See {@link module:link/ui/linkformview~LinkFormView}.
*
* @extends module:ui/view~View
*/
class LinkFormView extends ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.View {
/**
* Creates an instance of the {@link module:link/ui/linkformview~LinkFormView} class.
*
* Also see {@link #render}.
*
* @param {module:utils/locale~Locale} [locale] The localization services instance.
* @param {module:link/linkcommand~LinkCommand} linkCommand Reference to {@link module:link/linkcommand~LinkCommand}.
* @param {String} [protocol] A value of a protocol to be displayed in the input's placeholder.
*/
constructor( locale, linkCommand ) {
super( locale );
const t = locale.t;
/**
* Tracks information about DOM focus in the form.
*
* @readonly
* @member {module:utils/focustracker~FocusTracker}
*/
this.focusTracker = new ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__.FocusTracker();
/**
* An instance of the {@link module:utils/keystrokehandler~KeystrokeHandler}.
*
* @readonly
* @member {module:utils/keystrokehandler~KeystrokeHandler}
*/
this.keystrokes = new ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__.KeystrokeHandler();
/**
* The URL input view.
*
* @member {module:ui/labeledfield/labeledfieldview~LabeledFieldView}
*/
this.urlInputView = this._createUrlInput();
/**
* The Save button view.
*
* @member {module:ui/button/buttonview~ButtonView}
*/
this.saveButtonView = this._createButton( t( 'Save' ), ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_2__.icons.check, 'ck-button-save' );
this.saveButtonView.type = 'submit';
/**
* The Cancel button view.
*
* @member {module:ui/button/buttonview~ButtonView}
*/
this.cancelButtonView = this._createButton( t( 'Cancel' ), ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_2__.icons.cancel, 'ck-button-cancel', 'cancel' );
/**
* A collection of {@link module:ui/button/switchbuttonview~SwitchButtonView},
* which corresponds to {@link module:link/linkcommand~LinkCommand#manualDecorators manual decorators}
* configured in the editor.
*
* @private
* @readonly
* @type {module:ui/viewcollection~ViewCollection}
*/
this._manualDecoratorSwitches = this._createManualDecoratorSwitches( linkCommand );
/**
* A collection of child views in the form.
*
* @readonly
* @type {module:ui/viewcollection~ViewCollection}
*/
this.children = this._createFormChildren( linkCommand.manualDecorators );
/**
* A collection of views that can be focused in the form.
*
* @readonly
* @protected
* @member {module:ui/viewcollection~ViewCollection}
*/
this._focusables = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.ViewCollection();
/**
* Helps cycling over {@link #_focusables} in the form.
*
* @readonly
* @protected
* @member {module:ui/focuscycler~FocusCycler}
*/
this._focusCycler = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.FocusCycler( {
focusables: this._focusables,
focusTracker: this.focusTracker,
keystrokeHandler: this.keystrokes,
actions: {
// Navigate form fields backwards using the Shift + Tab keystroke.
focusPrevious: 'shift + tab',
// Navigate form fields forwards using the Tab key.
focusNext: 'tab'
}
} );
const classList = [ 'ck', 'ck-link-form', 'ck-responsive-form' ];
if ( linkCommand.manualDecorators.length ) {
classList.push( 'ck-link-form_layout-vertical', 'ck-vertical-form' );
}
this.setTemplate( {
tag: 'form',
attributes: {
class: classList,
// https://github.com/ckeditor/ckeditor5-link/issues/90
tabindex: '-1'
},
children: this.children
} );
(0,ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.injectCssTransitionDisabler)( this );
}
/**
* Obtains the state of the {@link module:ui/button/switchbuttonview~SwitchButtonView switch buttons} representing
* {@link module:link/linkcommand~LinkCommand#manualDecorators manual link decorators}
* in the {@link module:link/ui/linkformview~LinkFormView}.
*
* @returns {Object.<String,Boolean>} Key-value pairs, where the key is the name of the decorator and the value is
* its state.
*/
getDecoratorSwitchesState() {
return Array.from( this._manualDecoratorSwitches ).reduce( ( accumulator, switchButton ) => {
accumulator[ switchButton.name ] = switchButton.isOn;
return accumulator;
}, {} );
}
/**
* @inheritDoc
*/
render() {
super.render();
(0,ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.submitHandler)( {
view: this
} );
const childViews = [
this.urlInputView,
...this._manualDecoratorSwitches,
this.saveButtonView,
this.cancelButtonView
];
childViews.forEach( v => {
// Register the view as focusable.
this._focusables.add( v );
// Register the view in the focus tracker.
this.focusTracker.add( v.element );
} );
// Start listening for the keystrokes coming from #element.
this.keystrokes.listenTo( this.element );
}
/**
* @inheritDoc
*/
destroy() {
super.destroy();
this.focusTracker.destroy();
this.keystrokes.destroy();
}
/**
* Focuses the fist {@link #_focusables} in the form.
*/
focus() {
this._focusCycler.focusFirst();
}
/**
* Creates a labeled input view.
*
* @private
* @returns {module:ui/labeledfield/labeledfieldview~LabeledFieldView} Labeled field view instance.
*/
_createUrlInput() {
const t = this.locale.t;
const labeledInput = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.LabeledFieldView( this.locale, ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.createLabeledInputText );
labeledInput.label = t( 'Link URL' );
return labeledInput;
}
/**
* Creates a button view.
*
* @private
* @param {String} label The button label.
* @param {String} icon The button icon.
* @param {String} className The additional button CSS class name.
* @param {String} [eventName] An event name that the `ButtonView#execute` event will be delegated to.
* @returns {module:ui/button/buttonview~ButtonView} The button view instance.
*/
_createButton( label, icon, className, eventName ) {
const button = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.ButtonView( this.locale );
button.set( {
label,
icon,
tooltip: true
} );
button.extendTemplate( {
attributes: {
class: className
}
} );
if ( eventName ) {
button.delegate( 'execute' ).to( this, eventName );
}
return button;
}
/**
* Populates {@link module:ui/viewcollection~ViewCollection} of {@link module:ui/button/switchbuttonview~SwitchButtonView}
* made based on {@link module:link/linkcommand~LinkCommand#manualDecorators}.
*
* @private
* @param {module:link/linkcommand~LinkCommand} linkCommand A reference to the link command.
* @returns {module:ui/viewcollection~ViewCollection} of switch buttons.
*/
_createManualDecoratorSwitches( linkCommand ) {
const switches = this.createCollection();
for ( const manualDecorator of linkCommand.manualDecorators ) {
const switchButton = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.SwitchButtonView( this.locale );
switchButton.set( {
name: manualDecorator.id,
label: manualDecorator.label,
withText: true
} );
switchButton.bind( 'isOn' ).toMany( [ manualDecorator, linkCommand ], 'value', ( decoratorValue, commandValue ) => {
return commandValue === undefined && decoratorValue === undefined ? manualDecorator.defaultValue : decoratorValue;
} );
switchButton.on( 'execute', () => {
manualDecorator.set( 'value', !switchButton.isOn );
} );
switches.add( switchButton );
}
return switches;
}
/**
* Populates the {@link #children} collection of the form.
*
* If {@link module:link/linkcommand~LinkCommand#manualDecorators manual decorators} are configured in the editor, it creates an
* additional `View` wrapping all {@link #_manualDecoratorSwitches} switch buttons corresponding
* to these decorators.
*
* @private
* @param {module:utils/collection~Collection} manualDecorators A reference to
* the collection of manual decorators stored in the link command.
* @returns {module:ui/viewcollection~ViewCollection} The children of link form view.
*/
_createFormChildren( manualDecorators ) {
const children = this.createCollection();
children.add( this.urlInputView );
if ( manualDecorators.length ) {
const additionalButtonsView = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.View();
additionalButtonsView.setTemplate( {
tag: 'ul',
children: this._manualDecoratorSwitches.map( switchButton => ( {
tag: 'li',
children: [ switchButton ],
attributes: {
class: [
'ck',
'ck-list__item'
]
}
} ) ),
attributes: {
class: [
'ck',
'ck-reset',
'ck-list'
]
}
} );
children.add( additionalButtonsView );
}
children.add( this.saveButtonView );
children.add( this.cancelButtonView );
return children;
}
}
/**
* Fired when the form view is submitted (when one of the children triggered the submit event),
* for example with a click on {@link #saveButtonView}.
*
* @event submit
*/
/**
* Fired when the form view is canceled, for example with a click on {@link #cancelButtonView}.
*
* @event cancel
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-link/src/unlinkcommand.js":
/*!********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-link/src/unlinkcommand.js ***!
\********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ UnlinkCommand)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_typing__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/typing */ "./node_modules/ckeditor5/src/typing.js");
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./utils */ "./node_modules/@ckeditor/ckeditor5-link/src/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module link/unlinkcommand
*/
/**
* The unlink command. It is used by the {@link module:link/link~Link link plugin}.
*
* @extends module:core/command~Command
*/
class UnlinkCommand extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Command {
/**
* @inheritDoc
*/
refresh() {
const model = this.editor.model;
const selection = model.document.selection;
const selectedElement = selection.getSelectedElement();
// A check for any integration that allows linking elements (e.g. `LinkImage`).
// Currently the selection reads attributes from text nodes only. See #7429 and #7465.
if ( (0,_utils__WEBPACK_IMPORTED_MODULE_2__.isLinkableElement)( selectedElement, model.schema ) ) {
this.isEnabled = model.schema.checkAttribute( selectedElement, 'linkHref' );
} else {
this.isEnabled = model.schema.checkAttributeInSelection( selection, 'linkHref' );
}
}
/**
* Executes the command.
*
* When the selection is collapsed, it removes the `linkHref` attribute from each node with the same `linkHref` attribute value.
* When the selection is non-collapsed, it removes the `linkHref` attribute from each node in selected ranges.
*
* # Decorators
*
* If {@link module:link/link~LinkConfig#decorators `config.link.decorators`} is specified,
* all configured decorators are removed together with the `linkHref` attribute.
*
* @fires execute
*/
execute() {
const editor = this.editor;
const model = this.editor.model;
const selection = model.document.selection;
const linkCommand = editor.commands.get( 'link' );
model.change( writer => {
// Get ranges to unlink.
const rangesToUnlink = selection.isCollapsed ?
[ (0,ckeditor5_src_typing__WEBPACK_IMPORTED_MODULE_1__.findAttributeRange)(
selection.getFirstPosition(),
'linkHref',
selection.getAttribute( 'linkHref' ),
model
) ] :
model.schema.getValidRanges( selection.getRanges(), 'linkHref' );
// Remove `linkHref` attribute from specified ranges.
for ( const range of rangesToUnlink ) {
writer.removeAttribute( 'linkHref', range );
// If there are registered custom attributes, then remove them during unlink.
if ( linkCommand ) {
for ( const manualDecorator of linkCommand.manualDecorators ) {
writer.removeAttribute( manualDecorator.id, range );
}
}
}
} );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-link/src/utils.js":
/*!************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-link/src/utils.js ***!
\************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "LINK_KEYSTROKE": () => (/* binding */ LINK_KEYSTROKE),
/* harmony export */ "addLinkProtocolIfApplicable": () => (/* binding */ addLinkProtocolIfApplicable),
/* harmony export */ "createLinkElement": () => (/* binding */ createLinkElement),
/* harmony export */ "ensureSafeUrl": () => (/* binding */ ensureSafeUrl),
/* harmony export */ "getLocalizedDecorators": () => (/* binding */ getLocalizedDecorators),
/* harmony export */ "isEmail": () => (/* binding */ isEmail),
/* harmony export */ "isLinkElement": () => (/* binding */ isLinkElement),
/* harmony export */ "isLinkableElement": () => (/* binding */ isLinkableElement),
/* harmony export */ "normalizeDecorators": () => (/* binding */ normalizeDecorators),
/* harmony export */ "openLink": () => (/* binding */ openLink)
/* harmony export */ });
/* harmony import */ var lodash_es__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! lodash-es */ "./node_modules/lodash-es/upperFirst.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module link/utils
*/
/* global window */
const ATTRIBUTE_WHITESPACES = /[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205f\u3000]/g; // eslint-disable-line no-control-regex
const SAFE_URL = /^(?:(?:https?|ftps?|mailto):|[^a-z]|[a-z+.-]+(?:[^a-z+.:-]|$))/i;
// Simplified email test - should be run over previously found URL.
const EMAIL_REG_EXP = /^[\S]+@((?![-_])(?:[-\w\u00a1-\uffff]{0,63}[^-_]\.))+(?:[a-z\u00a1-\uffff]{2,})$/i;
// The regex checks for the protocol syntax ('xxxx://' or 'xxxx:')
// or non-word characters at the beginning of the link ('/', '#' etc.).
const PROTOCOL_REG_EXP = /^((\w+:(\/{2,})?)|(\W))/i;
/**
* A keystroke used by the {@link module:link/linkui~LinkUI link UI feature}.
*/
const LINK_KEYSTROKE = 'Ctrl+K';
/**
* Returns `true` if a given view node is the link element.
*
* @param {module:engine/view/node~Node} node
* @returns {Boolean}
*/
function isLinkElement( node ) {
return node.is( 'attributeElement' ) && !!node.getCustomProperty( 'link' );
}
/**
* Creates a link {@link module:engine/view/attributeelement~AttributeElement} with the provided `href` attribute.
*
* @param {String} href
* @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi
* @returns {module:engine/view/attributeelement~AttributeElement}
*/
function createLinkElement( href, { writer } ) {
// Priority 5 - https://github.com/ckeditor/ckeditor5-link/issues/121.
const linkElement = writer.createAttributeElement( 'a', { href }, { priority: 5 } );
writer.setCustomProperty( 'link', true, linkElement );
return linkElement;
}
/**
* Returns a safe URL based on a given value.
*
* A URL is considered safe if it is safe for the user (does not contain any malicious code).
*
* If a URL is considered unsafe, a simple `"#"` is returned.
*
* @protected
* @param {*} url
* @returns {String} Safe URL.
*/
function ensureSafeUrl( url ) {
url = String( url );
return isSafeUrl( url ) ? url : '#';
}
// Checks whether the given URL is safe for the user (does not contain any malicious code).
//
// @param {String} url URL to check.
function isSafeUrl( url ) {
const normalizedUrl = url.replace( ATTRIBUTE_WHITESPACES, '' );
return normalizedUrl.match( SAFE_URL );
}
/**
* Returns the {@link module:link/link~LinkConfig#decorators `config.link.decorators`} configuration processed
* to respect the locale of the editor, i.e. to display the {@link module:link/link~LinkDecoratorManualDefinition label}
* in the correct language.
*
* **Note**: Only the few most commonly used labels are translated automatically. Other labels should be manually
* translated in the {@link module:link/link~LinkConfig#decorators `config.link.decorators`} configuration.
*
* @param {module:utils/locale~Locale#t} t shorthand for {@link module:utils/locale~Locale#t Locale#t}
* @param {Array.<module:link/link~LinkDecoratorDefinition>} The decorator reference
* where the label values should be localized.
* @returns {Array.<module:link/link~LinkDecoratorDefinition>}
*/
function getLocalizedDecorators( t, decorators ) {
const localizedDecoratorsLabels = {
'Open in a new tab': t( 'Open in a new tab' ),
'Downloadable': t( 'Downloadable' )
};
decorators.forEach( decorator => {
if ( decorator.label && localizedDecoratorsLabels[ decorator.label ] ) {
decorator.label = localizedDecoratorsLabels[ decorator.label ];
}
return decorator;
} );
return decorators;
}
/**
* Converts an object with defined decorators to a normalized array of decorators. The `id` key is added for each decorator and
* is used as the attribute's name in the model.
*
* @param {Object.<String, module:link/link~LinkDecoratorDefinition>} decorators
* @returns {Array.<module:link/link~LinkDecoratorDefinition>}
*/
function normalizeDecorators( decorators ) {
const retArray = [];
if ( decorators ) {
for ( const [ key, value ] of Object.entries( decorators ) ) {
const decorator = Object.assign(
{},
value,
{ id: `link${ (0,lodash_es__WEBPACK_IMPORTED_MODULE_0__["default"])( key ) }` }
);
retArray.push( decorator );
}
}
return retArray;
}
/**
* Returns `true` if the specified `element` can be linked (the element allows the `linkHref` attribute).
*
* @params {module:engine/model/element~Element|null} element
* @params {module:engine/model/schema~Schema} schema
* @returns {Boolean}
*/
function isLinkableElement( element, schema ) {
if ( !element ) {
return false;
}
return schema.checkAttribute( element.name, 'linkHref' );
}
/**
* Returns `true` if the specified `value` is an email.
*
* @params {String} value
* @returns {Boolean}
*/
function isEmail( value ) {
return EMAIL_REG_EXP.test( value );
}
/**
* Adds the protocol prefix to the specified `link` when:
*
* * it does not contain it already, and there is a {@link module:link/link~LinkConfig#defaultProtocol `defaultProtocol` }
* configuration value provided,
* * or the link is an email address.
*
*
* @params {String} link
* @params {String} defaultProtocol
* @returns {Boolean}
*/
function addLinkProtocolIfApplicable( link, defaultProtocol ) {
const protocol = isEmail( link ) ? 'mailto:' : defaultProtocol;
const isProtocolNeeded = !!protocol && !PROTOCOL_REG_EXP.test( link );
return link && isProtocolNeeded ? protocol + link : link;
}
/**
* Opens the link in a new browser tab.
*
* @param {String} link
*/
function openLink( link ) {
window.open( link, '_blank', 'noopener' );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-link/src/utils/automaticdecorators.js":
/*!********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-link/src/utils/automaticdecorators.js ***!
\********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ AutomaticDecorators)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md.
*/
/**
* @module link/utils
*/
/**
* Helper class that ties together all {@link module:link/link~LinkDecoratorAutomaticDefinition} and provides
* the {@link module:engine/conversion/downcasthelpers~DowncastHelpers#attributeToElement downcast dispatchers} for them.
*/
class AutomaticDecorators {
constructor() {
/**
* Stores the definition of {@link module:link/link~LinkDecoratorAutomaticDefinition automatic decorators}.
* This data is used as a source for a downcast dispatcher to create a proper conversion to output data.
*
* @private
* @type {Set}
*/
this._definitions = new Set();
}
/**
* Gives information about the number of decorators stored in the {@link module:link/utils~AutomaticDecorators} instance.
*
* @readonly
* @protected
* @type {Number}
*/
get length() {
return this._definitions.size;
}
/**
* Adds automatic decorator objects or an array with them to be used during downcasting.
*
* @param {module:link/link~LinkDecoratorAutomaticDefinition|Array.<module:link/link~LinkDecoratorAutomaticDefinition>} item
* A configuration object of automatic rules for decorating links. It might also be an array of such objects.
*/
add( item ) {
if ( Array.isArray( item ) ) {
item.forEach( item => this._definitions.add( item ) );
} else {
this._definitions.add( item );
}
}
/**
* Provides the conversion helper used in the {@link module:engine/conversion/downcasthelpers~DowncastHelpers#add} method.
*
* @returns {Function} A dispatcher function used as conversion helper
* in {@link module:engine/conversion/downcasthelpers~DowncastHelpers#add}.
*/
getDispatcher() {
return dispatcher => {
dispatcher.on( 'attribute:linkHref', ( evt, data, conversionApi ) => {
// There is only test as this behavior decorates links and
// it is run before dispatcher which actually consumes this node.
// This allows on writing own dispatcher with highest priority,
// which blocks both native converter and this additional decoration.
if ( !conversionApi.consumable.test( data.item, 'attribute:linkHref' ) ) {
return;
}
const viewWriter = conversionApi.writer;
const viewSelection = viewWriter.document.selection;
for ( const item of this._definitions ) {
const viewElement = viewWriter.createAttributeElement( 'a', item.attributes, {
priority: 5
} );
if ( item.classes ) {
viewWriter.addClass( item.classes, viewElement );
}
for ( const key in item.styles ) {
viewWriter.setStyle( key, item.styles[ key ], viewElement );
}
viewWriter.setCustomProperty( 'link', true, viewElement );
if ( item.callback( data.attributeNewValue ) ) {
if ( data.item.is( 'selection' ) ) {
viewWriter.wrap( viewSelection.getFirstRange(), viewElement );
} else {
viewWriter.wrap( conversionApi.mapper.toViewRange( data.range ), viewElement );
}
} else {
viewWriter.unwrap( conversionApi.mapper.toViewRange( data.range ), viewElement );
}
}
}, { priority: 'high' } );
};
}
/**
* Provides the conversion helper used in the {@link module:engine/conversion/downcasthelpers~DowncastHelpers#add} method
* when linking images.
*
* @returns {Function} A dispatcher function used as conversion helper
* in {@link module:engine/conversion/downcasthelpers~DowncastHelpers#add}.
*/
getDispatcherForLinkedImage() {
return dispatcher => {
dispatcher.on( 'attribute:linkHref:imageBlock', ( evt, data, { writer, mapper } ) => {
const viewFigure = mapper.toViewElement( data.item );
const linkInImage = Array.from( viewFigure.getChildren() ).find( child => child.name === 'a' );
for ( const item of this._definitions ) {
const attributes = (0,ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_0__.toMap)( item.attributes );
if ( item.callback( data.attributeNewValue ) ) {
for ( const [ key, val ] of attributes ) {
// Left for backward compatibility. Since v30 decorator should
// accept `classes` and `styles` separately from `attributes`.
if ( key === 'class' ) {
writer.addClass( val, linkInImage );
} else {
writer.setAttribute( key, val, linkInImage );
}
}
if ( item.classes ) {
writer.addClass( item.classes, linkInImage );
}
for ( const key in item.styles ) {
writer.setStyle( key, item.styles[ key ], linkInImage );
}
} else {
for ( const [ key, val ] of attributes ) {
if ( key === 'class' ) {
writer.removeClass( val, linkInImage );
} else {
writer.removeAttribute( key, linkInImage );
}
}
if ( item.classes ) {
writer.removeClass( item.classes, linkInImage );
}
for ( const key in item.styles ) {
writer.removeStyle( key, linkInImage );
}
}
}
} );
};
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-link/src/utils/manualdecorator.js":
/*!****************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-link/src/utils/manualdecorator.js ***!
\****************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ManualDecorator)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module link/utils
*/
/**
* Helper class that stores manual decorators with observable {@link module:link/utils~ManualDecorator#value}
* to support integration with the UI state. An instance of this class is a model with the state of individual manual decorators.
* These decorators are kept as collections in {@link module:link/linkcommand~LinkCommand#manualDecorators}.
*
* @mixes module:utils/observablemixin~ObservableMixin
*/
class ManualDecorator {
/**
* Creates a new instance of {@link module:link/utils~ManualDecorator}.
*
* @param {Object} config
* @param {String} config.id The name of the attribute used in the model that represents a given manual decorator.
* For example: `'linkIsExternal'`.
* @param {String} config.label The label used in the user interface to toggle the manual decorator.
* @param {Object} config.attributes A set of attributes added to output data when the decorator is active for a specific link.
* Attributes should keep the format of attributes defined in {@link module:engine/view/elementdefinition~ElementDefinition}.
* @param {Boolean} [config.defaultValue] Controls whether the decorator is "on" by default.
*/
constructor( { id, label, attributes, classes, styles, defaultValue } ) {
/**
* An ID of a manual decorator which is the name of the attribute in the model, for example: 'linkManualDecorator0'.
*
* @type {String}
*/
this.id = id;
/**
* The value of the current manual decorator. It reflects its state from the UI.
*
* @observable
* @member {Boolean} module:link/utils~ManualDecorator#value
*/
this.set( 'value' );
/**
* The default value of manual decorator.
*
* @type {Boolean}
*/
this.defaultValue = defaultValue;
/**
* The label used in the user interface to toggle the manual decorator.
*
* @type {String}
*/
this.label = label;
/**
* A set of attributes added to downcasted data when the decorator is activated for a specific link.
* Attributes should be added in a form of attributes defined in {@link module:engine/view/elementdefinition~ElementDefinition}.
*
* @type {Object}
*/
this.attributes = attributes;
/**
* A set of classes added to downcasted data when the decorator is activated for a specific link.
* Classes should be added in a form of classes defined in {@link module:engine/view/elementdefinition~ElementDefinition}.
*
* @type {Object}
*/
this.classes = classes;
/**
* A set of styles added to downcasted data when the decorator is activated for a specific link.
* Styles should be added in a form of styles defined in {@link module:engine/view/elementdefinition~ElementDefinition}.
*
* @type {Object}
*/
this.styles = styles;
}
/**
* Returns {@link module:engine/view/matcher~MatcherPattern} with decorator attributes.
*
* @protected
* @returns {module:engine/view/matcher~MatcherPattern}
*/
_createPattern() {
return {
attributes: this.attributes,
classes: this.classes,
styles: this.styles
};
}
}
(0,ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_0__.mix)( ManualDecorator, ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_0__.ObservableMixin );
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-list/src/list.js":
/*!***********************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-list/src/list.js ***!
\***********************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ List)
/* harmony export */ });
/* harmony import */ var _list_listediting__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./list/listediting */ "./node_modules/@ckeditor/ckeditor5-list/src/list/listediting.js");
/* harmony import */ var _list_listui__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./list/listui */ "./node_modules/@ckeditor/ckeditor5-list/src/list/listui.js");
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module list/list
*/
/**
* The list feature.
*
* This is a "glue" plugin that loads the {@link module:list/list/listediting~ListEditing list editing feature}
* and {@link module:list/list/listui~ListUI list UI feature}.
*
* @extends module:core/plugin~Plugin
*/
class List extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_2__.Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [ _list_listediting__WEBPACK_IMPORTED_MODULE_0__["default"], _list_listui__WEBPACK_IMPORTED_MODULE_1__["default"] ];
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'List';
}
}
/**
* The configuration of the {@link module:list/list~List list} feature.
*
* ClassicEditor
* .create( editorElement, {
* list: ... // The list feature configuration.
* } )
* .then( ... )
* .catch( ... );
*
* See {@link module:core/editor/editorconfig~EditorConfig all editor options}.
*
* @interface ListConfig
*/
/**
* The configuration of the {@link module:list/list~List} feature.
*
* Read more in {@link module:list/list~ListConfig}.
*
* @member {module:module:list/list~ListConfig} module:core/editor/editorconfig~EditorConfig#list
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-list/src/list/converters.js":
/*!**********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-list/src/list/converters.js ***!
\**********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "cleanList": () => (/* binding */ cleanList),
/* harmony export */ "cleanListItem": () => (/* binding */ cleanListItem),
/* harmony export */ "modelChangePostFixer": () => (/* binding */ modelChangePostFixer),
/* harmony export */ "modelIndentPasteFixer": () => (/* binding */ modelIndentPasteFixer),
/* harmony export */ "modelToViewPosition": () => (/* binding */ modelToViewPosition),
/* harmony export */ "modelViewChangeIndent": () => (/* binding */ modelViewChangeIndent),
/* harmony export */ "modelViewChangeType": () => (/* binding */ modelViewChangeType),
/* harmony export */ "modelViewInsertion": () => (/* binding */ modelViewInsertion),
/* harmony export */ "modelViewMergeAfter": () => (/* binding */ modelViewMergeAfter),
/* harmony export */ "modelViewMergeAfterChangeType": () => (/* binding */ modelViewMergeAfterChangeType),
/* harmony export */ "modelViewRemove": () => (/* binding */ modelViewRemove),
/* harmony export */ "modelViewSplitOnInsert": () => (/* binding */ modelViewSplitOnInsert),
/* harmony export */ "viewModelConverter": () => (/* binding */ viewModelConverter),
/* harmony export */ "viewToModelPosition": () => (/* binding */ viewToModelPosition)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/engine */ "./node_modules/ckeditor5/src/engine.js");
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./utils */ "./node_modules/@ckeditor/ckeditor5-list/src/list/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module list/list/converters
*/
/**
* A model-to-view converter for the `listItem` model element insertion.
*
* It creates a `<ul><li></li><ul>` (or `<ol>`) view structure out of a `listItem` model element, inserts it at the correct
* position, and merges the list with surrounding lists (if available).
*
* @see module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:insert
* @param {module:engine/model/model~Model} model Model instance.
* @returns {Function} Returns a conversion callback.
*/
function modelViewInsertion( model ) {
return ( evt, data, conversionApi ) => {
const consumable = conversionApi.consumable;
if ( !consumable.test( data.item, 'insert' ) ||
!consumable.test( data.item, 'attribute:listType' ) ||
!consumable.test( data.item, 'attribute:listIndent' )
) {
return;
}
consumable.consume( data.item, 'insert' );
consumable.consume( data.item, 'attribute:listType' );
consumable.consume( data.item, 'attribute:listIndent' );
const modelItem = data.item;
const viewItem = (0,_utils__WEBPACK_IMPORTED_MODULE_1__.generateLiInUl)( modelItem, conversionApi );
(0,_utils__WEBPACK_IMPORTED_MODULE_1__.injectViewList)( modelItem, viewItem, conversionApi, model );
};
}
/**
* A model-to-view converter for the `listItem` model element removal.
*
* @see module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:remove
* @param {module:engine/model/model~Model} model Model instance.
* @returns {Function} Returns a conversion callback.
*/
function modelViewRemove( model ) {
return ( evt, data, conversionApi ) => {
const viewPosition = conversionApi.mapper.toViewPosition( data.position );
const viewStart = viewPosition.getLastMatchingPosition( value => !value.item.is( 'element', 'li' ) );
const viewItem = viewStart.nodeAfter;
const viewWriter = conversionApi.writer;
// 1. Break the container after and before the list item.
// This will create a view list with one view list item - the one to remove.
viewWriter.breakContainer( viewWriter.createPositionBefore( viewItem ) );
viewWriter.breakContainer( viewWriter.createPositionAfter( viewItem ) );
// 2. Remove the list with the item to remove.
const viewList = viewItem.parent;
const viewListPrev = viewList.previousSibling;
const removeRange = viewWriter.createRangeOn( viewList );
const removed = viewWriter.remove( removeRange );
// 3. Merge the whole created by breaking and removing the list.
if ( viewListPrev && viewListPrev.nextSibling ) {
(0,_utils__WEBPACK_IMPORTED_MODULE_1__.mergeViewLists)( viewWriter, viewListPrev, viewListPrev.nextSibling );
}
// 4. Bring back nested list that was in the removed <li>.
const modelItem = conversionApi.mapper.toModelElement( viewItem );
hoistNestedLists( modelItem.getAttribute( 'listIndent' ) + 1, data.position, removeRange.start, viewItem, conversionApi, model );
// 5. Unbind removed view item and all children.
for ( const child of viewWriter.createRangeIn( removed ).getItems() ) {
conversionApi.mapper.unbindViewElement( child );
}
evt.stop();
};
}
/**
* A model-to-view converter for the `type` attribute change on the `listItem` model element.
*
* This change means that the `<li>` element parent changes from `<ul>` to `<ol>` (or vice versa). This is accomplished
* by breaking view elements and changing their name. The next {@link module:list/list/converters~modelViewMergeAfterChangeType}
* converter will attempt to merge split nodes.
*
* Splitting this conversion into 2 steps makes it possible to add an additional conversion in the middle.
* Check {@link module:list/todolist/todolistconverters~modelViewChangeType} to see an example of it.
*
* @see module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:attribute
* @param {module:utils/eventinfo~EventInfo} evt An object containing information about the fired event.
* @param {Object} data Additional information about the change.
* @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi Conversion interface.
*/
function modelViewChangeType( evt, data, conversionApi ) {
if ( !conversionApi.consumable.test( data.item, evt.name ) ) {
return;
}
const viewItem = conversionApi.mapper.toViewElement( data.item );
const viewWriter = conversionApi.writer;
// Break the container after and before the list item.
// This will create a view list with one view list item -- the one that changed type.
viewWriter.breakContainer( viewWriter.createPositionBefore( viewItem ) );
viewWriter.breakContainer( viewWriter.createPositionAfter( viewItem ) );
// Change name of the view list that holds the changed view item.
// We cannot just change name property, because that would not render properly.
const viewList = viewItem.parent;
const listName = data.attributeNewValue == 'numbered' ? 'ol' : 'ul';
viewWriter.rename( listName, viewList );
}
/**
* A model-to-view converter that attempts to merge nodes split by {@link module:list/list/converters~modelViewChangeType}.
*
* @see module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:attribute
* @param {module:utils/eventinfo~EventInfo} evt An object containing information about the fired event.
* @param {Object} data Additional information about the change.
* @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi Conversion interface.
*/
function modelViewMergeAfterChangeType( evt, data, conversionApi ) {
conversionApi.consumable.consume( data.item, evt.name );
const viewItem = conversionApi.mapper.toViewElement( data.item );
const viewList = viewItem.parent;
const viewWriter = conversionApi.writer;
// Merge the changed view list with other lists, if possible.
(0,_utils__WEBPACK_IMPORTED_MODULE_1__.mergeViewLists)( viewWriter, viewList, viewList.nextSibling );
(0,_utils__WEBPACK_IMPORTED_MODULE_1__.mergeViewLists)( viewWriter, viewList.previousSibling, viewList );
}
/**
* A model-to-view converter for the `listIndent` attribute change on the `listItem` model element.
*
* @see module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:attribute
* @param {module:engine/model/model~Model} model Model instance.
* @returns {Function} Returns a conversion callback.
*/
function modelViewChangeIndent( model ) {
return ( evt, data, conversionApi ) => {
if ( !conversionApi.consumable.consume( data.item, 'attribute:listIndent' ) ) {
return;
}
const viewItem = conversionApi.mapper.toViewElement( data.item );
const viewWriter = conversionApi.writer;
// 1. Break the container after and before the list item.
// This will create a view list with one view list item -- the one that changed type.
viewWriter.breakContainer( viewWriter.createPositionBefore( viewItem ) );
viewWriter.breakContainer( viewWriter.createPositionAfter( viewItem ) );
// 2. Extract view list with changed view list item and merge "hole" possibly created by breaking and removing elements.
const viewList = viewItem.parent;
const viewListPrev = viewList.previousSibling;
const removeRange = viewWriter.createRangeOn( viewList );
viewWriter.remove( removeRange );
if ( viewListPrev && viewListPrev.nextSibling ) {
(0,_utils__WEBPACK_IMPORTED_MODULE_1__.mergeViewLists)( viewWriter, viewListPrev, viewListPrev.nextSibling );
}
// 3. Bring back nested list that was in the removed <li>.
hoistNestedLists( data.attributeOldValue + 1, data.range.start, removeRange.start, viewItem, conversionApi, model );
// 4. Inject view list like it is newly inserted.
(0,_utils__WEBPACK_IMPORTED_MODULE_1__.injectViewList)( data.item, viewItem, conversionApi, model );
// 5. Consume insertion of children inside the item. They are already handled by re-building the item in view.
for ( const child of data.item.getChildren() ) {
conversionApi.consumable.consume( child, 'insert' );
}
};
}
/**
* A special model-to-view converter introduced by the {@link module:list/list~List list feature}. This converter is fired for
* insert change of every model item, and should be fired before the actual converter. The converter checks whether the inserted
* model item is a non-`listItem` element. If it is, and it is inserted inside a view list, the converter breaks the
* list so the model element is inserted to the view parent element corresponding to its model parent element.
*
* The converter prevents such situations:
*
* // Model: // View:
* <listItem>foo</listItem> <ul>
* <listItem>bar</listItem> <li>foo</li>
* <li>bar</li>
* </ul>
*
* // After change: // Correct view guaranteed by this converter:
* <listItem>foo</listItem> <ul><li>foo</li></ul><p>xxx</p><ul><li>bar</li></ul>
* <paragraph>xxx</paragraph> // Instead of this wrong view state:
* <listItem>bar</listItem> <ul><li>foo</li><p>xxx</p><li>bar</li></ul>
*
* @see module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:insert
* @param {module:utils/eventinfo~EventInfo} evt An object containing information about the fired event.
* @param {Object} data Additional information about the change.
* @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi Conversion interface.
*/
function modelViewSplitOnInsert( evt, data, conversionApi ) {
if ( data.item.name != 'listItem' ) {
let viewPosition = conversionApi.mapper.toViewPosition( data.range.start );
const viewWriter = conversionApi.writer;
const lists = [];
// Break multiple ULs/OLs if there are.
//
// Imagine following list:
//
// 1 --------
// 1.1 --------
// 1.1.1 --------
// 1.1.2 --------
// 1.1.3 --------
// 1.1.3.1 --------
// 1.2 --------
// 1.2.1 --------
// 2 --------
//
// Insert paragraph after item 1.1.1:
//
// 1 --------
// 1.1 --------
// 1.1.1 --------
//
// Lorem ipsum.
//
// 1.1.2 --------
// 1.1.3 --------
// 1.1.3.1 --------
// 1.2 --------
// 1.2.1 --------
// 2 --------
//
// In this case 1.1.2 has to become beginning of a new list.
// We need to break list before 1.1.2 (obvious), then we need to break list also before 1.2.
// Then we need to move those broken pieces one after another and merge:
//
// 1 --------
// 1.1 --------
// 1.1.1 --------
//
// Lorem ipsum.
//
// 1.1.2 --------
// 1.1.3 --------
// 1.1.3.1 --------
// 1.2 --------
// 1.2.1 --------
// 2 --------
//
while ( viewPosition.parent.name == 'ul' || viewPosition.parent.name == 'ol' ) {
viewPosition = viewWriter.breakContainer( viewPosition );
if ( viewPosition.parent.name != 'li' ) {
break;
}
// Remove lists that are after inserted element.
// They will be brought back later, below the inserted element.
const removeStart = viewPosition;
const removeEnd = viewWriter.createPositionAt( viewPosition.parent, 'end' );
// Don't remove if there is nothing to remove.
if ( !removeStart.isEqual( removeEnd ) ) {
const removed = viewWriter.remove( viewWriter.createRange( removeStart, removeEnd ) );
lists.push( removed );
}
viewPosition = viewWriter.createPositionAfter( viewPosition.parent );
}
// Bring back removed lists.
if ( lists.length > 0 ) {
for ( let i = 0; i < lists.length; i++ ) {
const previousList = viewPosition.nodeBefore;
const insertedRange = viewWriter.insert( viewPosition, lists[ i ] );
viewPosition = insertedRange.end;
// Don't merge first list! We want a split in that place (this is why this converter is introduced).
if ( i > 0 ) {
const mergePos = (0,_utils__WEBPACK_IMPORTED_MODULE_1__.mergeViewLists)( viewWriter, previousList, previousList.nextSibling );
// If `mergePos` is in `previousList` it means that the lists got merged.
// In this case, we need to fix insert position.
if ( mergePos && mergePos.parent == previousList ) {
viewPosition.offset--;
}
}
}
// Merge last inserted list with element after it.
(0,_utils__WEBPACK_IMPORTED_MODULE_1__.mergeViewLists)( viewWriter, viewPosition.nodeBefore, viewPosition.nodeAfter );
}
}
}
/**
* A special model-to-view converter introduced by the {@link module:list/list~List list feature}. This converter takes care of
* merging view lists after something is removed or moved from near them.
*
* Example:
*
* // Model: // View:
* <listItem>foo</listItem> <ul><li>foo</li></ul>
* <paragraph>xxx</paragraph> <p>xxx</p>
* <listItem>bar</listItem> <ul><li>bar</li></ul>
*
* // After change: // Correct view guaranteed by this converter:
* <listItem>foo</listItem> <ul>
* <listItem>bar</listItem> <li>foo</li>
* <li>bar</li>
* </ul>
*
* @see module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:remove
* @param {module:utils/eventinfo~EventInfo} evt An object containing information about the fired event.
* @param {Object} data Additional information about the change.
* @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi Conversion interface.
*/
function modelViewMergeAfter( evt, data, conversionApi ) {
const viewPosition = conversionApi.mapper.toViewPosition( data.position );
const viewItemPrev = viewPosition.nodeBefore;
const viewItemNext = viewPosition.nodeAfter;
// Merge lists if something (remove, move) was done from inside of list.
// Merging will be done only if both items are view lists of the same type.
// The check is done inside the helper function.
(0,_utils__WEBPACK_IMPORTED_MODULE_1__.mergeViewLists)( conversionApi.writer, viewItemPrev, viewItemNext );
}
/**
* A view-to-model converter that converts the `<li>` view elements into the `listItem` model elements.
*
* To set correct values of the `listType` and `listIndent` attributes the converter:
* * checks `<li>`'s parent,
* * stores and increases the `conversionApi.store.indent` value when `<li>`'s sub-items are converted.
*
* @see module:engine/conversion/upcastdispatcher~UpcastDispatcher#event:element
* @param {module:utils/eventinfo~EventInfo} evt An object containing information about the fired event.
* @param {Object} data An object containing conversion input and a placeholder for conversion output and possibly other values.
* @param {module:engine/conversion/upcastdispatcher~UpcastConversionApi} conversionApi Conversion interface to be used by the callback.
*/
function viewModelConverter( evt, data, conversionApi ) {
if ( conversionApi.consumable.consume( data.viewItem, { name: true } ) ) {
const writer = conversionApi.writer;
// 1. Create `listItem` model element.
const listItem = writer.createElement( 'listItem' );
// 2. Handle `listItem` model element attributes.
const indent = getIndent( data.viewItem );
writer.setAttribute( 'listIndent', indent, listItem );
// Set 'bulleted' as default. If this item is pasted into a context,
const type = data.viewItem.parent && data.viewItem.parent.name == 'ol' ? 'numbered' : 'bulleted';
writer.setAttribute( 'listType', type, listItem );
if ( !conversionApi.safeInsert( listItem, data.modelCursor ) ) {
return;
}
const nextPosition = viewToModelListItemChildrenConverter( listItem, data.viewItem.getChildren(), conversionApi );
// Result range starts before the first item and ends after the last.
data.modelRange = writer.createRange( data.modelCursor, nextPosition );
conversionApi.updateConversionResult( listItem, data );
}
}
/**
* A view-to-model converter for the `<ul>` and `<ol>` view elements that cleans the input view of garbage.
* This is mostly to clean whitespaces from between the `<li>` view elements inside the view list element, however, also
* incorrect data can be cleared if the view was incorrect.
*
* @see module:engine/conversion/upcastdispatcher~UpcastDispatcher#event:element
* @param {module:utils/eventinfo~EventInfo} evt An object containing information about the fired event.
* @param {Object} data An object containing conversion input and a placeholder for conversion output and possibly other values.
* @param {module:engine/conversion/upcastdispatcher~UpcastConversionApi} conversionApi Conversion interface to be used by the callback.
*/
function cleanList( evt, data, conversionApi ) {
if ( conversionApi.consumable.test( data.viewItem, { name: true } ) ) {
// Caching children because when we start removing them iterating fails.
const children = Array.from( data.viewItem.getChildren() );
for ( const child of children ) {
const isWrongElement = !( child.is( 'element', 'li' ) || isList( child ) );
if ( isWrongElement ) {
child._remove();
}
}
}
}
/**
* A view-to-model converter for the `<li>` elements that cleans whitespace formatting from the input view.
*
* @see module:engine/conversion/upcastdispatcher~UpcastDispatcher#event:element
* @param {module:utils/eventinfo~EventInfo} evt An object containing information about the fired event.
* @param {Object} data An object containing conversion input and a placeholder for conversion output and possibly other values.
* @param {module:engine/conversion/upcastdispatcher~UpcastConversionApi} conversionApi Conversion interface to be used by the callback.
*/
function cleanListItem( evt, data, conversionApi ) {
if ( conversionApi.consumable.test( data.viewItem, { name: true } ) ) {
if ( data.viewItem.childCount === 0 ) {
return;
}
const children = [ ...data.viewItem.getChildren() ];
let foundList = false;
for ( const child of children ) {
if ( foundList && !isList( child ) ) {
child._remove();
}
if ( isList( child ) ) {
// If this is a <ul> or <ol>, do not process it, just mark that we already visited list element.
foundList = true;
}
}
}
}
/**
* Returns a callback for model position to view position mapping for {@link module:engine/conversion/mapper~Mapper}. The callback fixes
* positions between the `listItem` elements that would be incorrectly mapped because of how list items are represented in the model
* and in the view.
*
* @see module:engine/conversion/mapper~Mapper#event:modelToViewPosition
* @param {module:engine/view/view~View} view A view instance.
* @returns {Function}
*/
function modelToViewPosition( view ) {
return ( evt, data ) => {
if ( data.isPhantom ) {
return;
}
const modelItem = data.modelPosition.nodeBefore;
if ( modelItem && modelItem.is( 'element', 'listItem' ) ) {
const viewItem = data.mapper.toViewElement( modelItem );
const topmostViewList = viewItem.getAncestors().find( isList );
const walker = view.createPositionAt( viewItem, 0 ).getWalker();
for ( const value of walker ) {
if ( value.type == 'elementStart' && value.item.is( 'element', 'li' ) ) {
data.viewPosition = value.previousPosition;
break;
} else if ( value.type == 'elementEnd' && value.item == topmostViewList ) {
data.viewPosition = value.nextPosition;
break;
}
}
}
};
}
/**
* The callback for view position to model position mapping for {@link module:engine/conversion/mapper~Mapper}. The callback fixes
* positions between the `<li>` elements that would be incorrectly mapped because of how list items are represented in the model
* and in the view.
*
* @see module:engine/conversion/mapper~Mapper#event:viewToModelPosition
* @param {module:engine/model/model~Model} model Model instance.
* @returns {Function} Returns a conversion callback.
*/
function viewToModelPosition( model ) {
return ( evt, data ) => {
const viewPos = data.viewPosition;
const viewParent = viewPos.parent;
const mapper = data.mapper;
if ( viewParent.name == 'ul' || viewParent.name == 'ol' ) {
// Position is directly in <ul> or <ol>.
if ( !viewPos.isAtEnd ) {
// If position is not at the end, it must be before <li>.
// Get that <li>, map it to `listItem` and set model position before that `listItem`.
const modelNode = mapper.toModelElement( viewPos.nodeAfter );
data.modelPosition = model.createPositionBefore( modelNode );
} else {
// Position is at the end of <ul> or <ol>, so there is no <li> after it to be mapped.
// There is <li> before the position, but we cannot just map it to `listItem` and set model position after it,
// because that <li> may contain nested items.
// We will check "model length" of that <li>, in other words - how many `listItem`s are in that <li>.
const modelNode = mapper.toModelElement( viewPos.nodeBefore );
const modelLength = mapper.getModelLength( viewPos.nodeBefore );
// Then we get model position before mapped `listItem` and shift it accordingly.
data.modelPosition = model.createPositionBefore( modelNode ).getShiftedBy( modelLength );
}
evt.stop();
} else if (
viewParent.name == 'li' &&
viewPos.nodeBefore &&
( viewPos.nodeBefore.name == 'ul' || viewPos.nodeBefore.name == 'ol' )
) {
// In most cases when view position is in <li> it is in text and this is a correct position.
// However, if position is after <ul> or <ol> we have to fix it -- because in model <ul>/<ol> are not in the `listItem`.
const modelNode = mapper.toModelElement( viewParent );
// Check all <ul>s and <ol>s that are in the <li> but before mapped position.
// Get model length of those elements and then add it to the offset of `listItem` mapped to the original <li>.
let modelLength = 1; // Starts from 1 because the original <li> has to be counted in too.
let viewList = viewPos.nodeBefore;
while ( viewList && isList( viewList ) ) {
modelLength += mapper.getModelLength( viewList );
viewList = viewList.previousSibling;
}
data.modelPosition = model.createPositionBefore( modelNode ).getShiftedBy( modelLength );
evt.stop();
}
};
}
/**
* Post-fixer that reacts to changes on document and fixes incorrect model states.
*
* In the example below, there is a correct list structure.
* Then the middle element is removed so the list structure will become incorrect:
*
* <listItem listType="bulleted" listIndent=0>Item 1</listItem>
* <listItem listType="bulleted" listIndent=1>Item 2</listItem> <--- this is removed.
* <listItem listType="bulleted" listIndent=2>Item 3</listItem>
*
* The list structure after the middle element is removed:
*
* <listItem listType="bulleted" listIndent=0>Item 1</listItem>
* <listItem listType="bulleted" listIndent=2>Item 3</listItem>
*
* Should become:
*
* <listItem listType="bulleted" listIndent=0>Item 1</listItem>
* <listItem listType="bulleted" listIndent=1>Item 3</listItem> <--- note that indent got post-fixed.
*
* @param {module:engine/model/model~Model} model The data model.
* @param {module:engine/model/writer~Writer} writer The writer to do changes with.
* @returns {Boolean} `true` if any change has been applied, `false` otherwise.
*/
function modelChangePostFixer( model, writer ) {
const changes = model.document.differ.getChanges();
const itemToListHead = new Map();
let applied = false;
for ( const entry of changes ) {
if ( entry.type == 'insert' && entry.name == 'listItem' ) {
_addListToFix( entry.position );
} else if ( entry.type == 'insert' && entry.name != 'listItem' ) {
if ( entry.name != '$text' ) {
// In case of renamed element.
const item = entry.position.nodeAfter;
if ( item.hasAttribute( 'listIndent' ) ) {
writer.removeAttribute( 'listIndent', item );
applied = true;
}
if ( item.hasAttribute( 'listType' ) ) {
writer.removeAttribute( 'listType', item );
applied = true;
}
if ( item.hasAttribute( 'listStyle' ) ) {
writer.removeAttribute( 'listStyle', item );
applied = true;
}
if ( item.hasAttribute( 'listReversed' ) ) {
writer.removeAttribute( 'listReversed', item );
applied = true;
}
if ( item.hasAttribute( 'listStart' ) ) {
writer.removeAttribute( 'listStart', item );
applied = true;
}
for ( const innerItem of Array.from( model.createRangeIn( item ) ).filter( e => e.item.is( 'element', 'listItem' ) ) ) {
_addListToFix( innerItem.previousPosition );
}
}
const posAfter = entry.position.getShiftedBy( entry.length );
_addListToFix( posAfter );
} else if ( entry.type == 'remove' && entry.name == 'listItem' ) {
_addListToFix( entry.position );
} else if ( entry.type == 'attribute' && entry.attributeKey == 'listIndent' ) {
_addListToFix( entry.range.start );
} else if ( entry.type == 'attribute' && entry.attributeKey == 'listType' ) {
_addListToFix( entry.range.start );
}
}
for ( const listHead of itemToListHead.values() ) {
_fixListIndents( listHead );
_fixListTypes( listHead );
}
return applied;
function _addListToFix( position ) {
const previousNode = position.nodeBefore;
if ( !previousNode || !previousNode.is( 'element', 'listItem' ) ) {
const item = position.nodeAfter;
if ( item && item.is( 'element', 'listItem' ) ) {
itemToListHead.set( item, item );
}
} else {
let listHead = previousNode;
if ( itemToListHead.has( listHead ) ) {
return;
}
for (
// Cache previousSibling and reuse for performance reasons. See #6581.
let previousSibling = listHead.previousSibling;
previousSibling && previousSibling.is( 'element', 'listItem' );
previousSibling = listHead.previousSibling
) {
listHead = previousSibling;
if ( itemToListHead.has( listHead ) ) {
return;
}
}
itemToListHead.set( previousNode, listHead );
}
}
function _fixListIndents( item ) {
let maxIndent = 0;
let fixBy = null;
while ( item && item.is( 'element', 'listItem' ) ) {
const itemIndent = item.getAttribute( 'listIndent' );
if ( itemIndent > maxIndent ) {
let newIndent;
if ( fixBy === null ) {
fixBy = itemIndent - maxIndent;
newIndent = maxIndent;
} else {
if ( fixBy > itemIndent ) {
fixBy = itemIndent;
}
newIndent = itemIndent - fixBy;
}
writer.setAttribute( 'listIndent', newIndent, item );
applied = true;
} else {
fixBy = null;
maxIndent = item.getAttribute( 'listIndent' ) + 1;
}
item = item.nextSibling;
}
}
function _fixListTypes( item ) {
let typesStack = [];
let prev = null;
while ( item && item.is( 'element', 'listItem' ) ) {
const itemIndent = item.getAttribute( 'listIndent' );
if ( prev && prev.getAttribute( 'listIndent' ) > itemIndent ) {
typesStack = typesStack.slice( 0, itemIndent + 1 );
}
if ( itemIndent != 0 ) {
if ( typesStack[ itemIndent ] ) {
const type = typesStack[ itemIndent ];
if ( item.getAttribute( 'listType' ) != type ) {
writer.setAttribute( 'listType', type, item );
applied = true;
}
} else {
typesStack[ itemIndent ] = item.getAttribute( 'listType' );
}
}
prev = item;
item = item.nextSibling;
}
}
}
/**
* A fixer for pasted content that includes list items.
*
* It fixes indentation of pasted list items so the pasted items match correctly to the context they are pasted into.
*
* Example:
*
* <listItem listType="bulleted" listIndent=0>A</listItem>
* <listItem listType="bulleted" listIndent=1>B^</listItem>
* // At ^ paste: <listItem listType="bulleted" listIndent=4>X</listItem>
* // <listItem listType="bulleted" listIndent=5>Y</listItem>
* <listItem listType="bulleted" listIndent=2>C</listItem>
*
* Should become:
*
* <listItem listType="bulleted" listIndent=0>A</listItem>
* <listItem listType="bulleted" listIndent=1>BX</listItem>
* <listItem listType="bulleted" listIndent=2>Y/listItem>
* <listItem listType="bulleted" listIndent=2>C</listItem>
*
* @param {module:utils/eventinfo~EventInfo} evt An object containing information about the fired event.
* @param {Array} args Arguments of {@link module:engine/model/model~Model#insertContent}.
*/
function modelIndentPasteFixer( evt, [ content, selectable ] ) {
// Check whether inserted content starts from a `listItem`. If it does not, it means that there are some other
// elements before it and there is no need to fix indents, because even if we insert that content into a list,
// that list will be broken.
// Note: we also need to handle singular elements because inserting item with indent 0 into 0,1,[],2
// would create incorrect model.
let item = content.is( 'documentFragment' ) ? content.getChild( 0 ) : content;
let selection;
if ( !selectable ) {
selection = this.document.selection;
} else {
selection = this.createSelection( selectable );
}
if ( item && item.is( 'element', 'listItem' ) ) {
// Get a reference list item. Inserted list items will be fixed according to that item.
const pos = selection.getFirstPosition();
let refItem = null;
if ( pos.parent.is( 'element', 'listItem' ) ) {
refItem = pos.parent;
} else if ( pos.nodeBefore && pos.nodeBefore.is( 'element', 'listItem' ) ) {
refItem = pos.nodeBefore;
}
// If there is `refItem` it means that we do insert list items into an existing list.
if ( refItem ) {
// First list item in `data` has indent equal to 0 (it is a first list item). It should have indent equal
// to the indent of reference item. We have to fix the first item and all of it's children and following siblings.
// Indent of all those items has to be adjusted to reference item.
const indentChange = refItem.getAttribute( 'listIndent' );
// Fix only if there is anything to fix.
if ( indentChange > 0 ) {
// Adjust indent of all "first" list items in inserted data.
while ( item && item.is( 'element', 'listItem' ) ) {
item._setAttribute( 'listIndent', item.getAttribute( 'listIndent' ) + indentChange );
item = item.nextSibling;
}
}
}
}
}
// Helper function that converts children of a given `<li>` view element into corresponding model elements.
// The function maintains proper order of elements if model `listItem` is split during the conversion
// due to block children conversion.
//
// @param {module:engine/model/element~Element} listItemModel List item model element to which converted children will be inserted.
// @param {Iterable.<module:engine/view/node~Node>} viewChildren View elements which will be converted.
// @param {module:engine/conversion/upcastdispatcher~UpcastConversionApi} conversionApi Conversion interface to be used by the callback.
// @returns {module:engine/model/position~Position} Position on which next elements should be inserted after children conversion.
function viewToModelListItemChildrenConverter( listItemModel, viewChildren, conversionApi ) {
const { writer, schema } = conversionApi;
// A position after the last inserted `listItem`.
let nextPosition = writer.createPositionAfter( listItemModel );
// Check all children of the converted `<li>`. At this point we assume there are no "whitespace" view text nodes
// in view list, between view list items. This should be handled by `<ul>` and `<ol>` converters.
for ( const child of viewChildren ) {
if ( child.name == 'ul' || child.name == 'ol' ) {
// If the children is a list, we will insert its conversion result after currently handled `listItem`.
// Then, next insertion position will be set after all the new list items (and maybe other elements if
// something split list item).
//
// If this is a list, we expect that some `listItem`s and possibly other blocks will be inserted, however `.modelCursor`
// should be set after last `listItem` (or block). This is why it feels safe to use it as `nextPosition`
nextPosition = conversionApi.convertItem( child, nextPosition ).modelCursor;
} else {
// If this is not a list, try inserting content at the end of the currently handled `listItem`.
const result = conversionApi.convertItem( child, writer.createPositionAt( listItemModel, 'end' ) );
// It may end up that the current `listItem` becomes split (if that content cannot be inside `listItem`). For example:
//
// <li><p>Foo</p></li>
//
// will be converted to:
//
// <listItem></listItem><paragraph>Foo</paragraph><listItem></listItem>
//
const convertedChild = result.modelRange.start.nodeAfter;
const wasSplit = convertedChild && convertedChild.is( 'element' ) && !schema.checkChild( listItemModel, convertedChild.name );
if ( wasSplit ) {
// As `lastListItem` got split, we need to update it to the second part of the split `listItem` element.
//
// `modelCursor` should be set to a position where the conversion should continue. There are multiple possible scenarios
// that may happen. Usually, `modelCursor` (marked as `#` below) would point to the second list item after conversion:
//
// `<li><p>Foo</p></li>` -> `<listItem></listItem><paragraph>Foo</paragraph><listItem>#</listItem>`
//
// However, in some cases, like auto-paragraphing, the position is placed at the end of the block element:
//
// `<li><div>Foo</div></li>` -> `<listItem></listItem><paragraph>Foo#</paragraph><listItem></listItem>`
//
// or after an element if another element broken auto-paragraphed element:
//
// `<li><div><h2>Foo</h2></div></li>` -> `<listItem></listItem><heading1>Foo</heading1>#<listItem></listItem>`
//
// We need to check for such cases and use proper list item and position based on it.
//
if ( result.modelCursor.parent.is( 'element', 'listItem' ) ) {
// (1).
listItemModel = result.modelCursor.parent;
} else {
// (2), (3).
listItemModel = findNextListItem( result.modelCursor );
}
nextPosition = writer.createPositionAfter( listItemModel );
}
}
}
return nextPosition;
}
// Helper function that seeks for a next list item starting from given `startPosition`.
function findNextListItem( startPosition ) {
const treeWalker = new ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_0__.TreeWalker( { startPosition } );
let value;
do {
value = treeWalker.next();
} while ( !value.value.item.is( 'element', 'listItem' ) );
return value.value.item;
}
// Helper function that takes all children of given `viewRemovedItem` and moves them in a correct place, according
// to other given parameters.
function hoistNestedLists( nextIndent, modelRemoveStartPosition, viewRemoveStartPosition, viewRemovedItem, conversionApi, model ) {
// Find correct previous model list item element.
// The element has to have either same or smaller indent than given reference indent.
// This will be the model element which will get nested items (if it has smaller indent) or sibling items (if it has same indent).
// Keep in mind that such element might not be found, if removed item was the first item.
const prevModelItem = (0,_utils__WEBPACK_IMPORTED_MODULE_1__.getSiblingListItem)( modelRemoveStartPosition.nodeBefore, {
sameIndent: true,
smallerIndent: true,
listIndent: nextIndent,
foo: 'b'
} );
const mapper = conversionApi.mapper;
const viewWriter = conversionApi.writer;
// Indent of found element or `null` if the element has not been found.
const prevIndent = prevModelItem ? prevModelItem.getAttribute( 'listIndent' ) : null;
let insertPosition;
if ( !prevModelItem ) {
// If element has not been found, simply insert lists at the position where the removed item was:
//
// Lorem ipsum.
// 1 -------- <--- this is removed, no previous list item, put nested items in place of removed item.
// 1.1 -------- <--- this is reference indent.
// 1.1.1 --------
// 1.1.2 --------
// 1.2 --------
//
// Becomes:
//
// Lorem ipsum.
// 1.1 --------
// 1.1.1 --------
// 1.1.2 --------
// 1.2 --------
insertPosition = viewRemoveStartPosition;
} else if ( prevIndent == nextIndent ) {
// If element has been found and has same indent as reference indent it means that nested items should
// become siblings of found element:
//
// 1 --------
// 1.1 --------
// 1.2 -------- <--- this is `prevModelItem`.
// 2 -------- <--- this is removed, previous list item has indent same as reference indent.
// 2.1 -------- <--- this is reference indent, this and 2.2 should become siblings of 1.2.
// 2.2 --------
//
// Becomes:
//
// 1 --------
// 1.1 --------
// 1.2 --------
// 2.1 --------
// 2.2 --------
const prevViewList = mapper.toViewElement( prevModelItem ).parent;
insertPosition = viewWriter.createPositionAfter( prevViewList );
} else {
// If element has been found and has smaller indent as reference indent it means that nested items
// should become nested items of found item:
//
// 1 -------- <--- this is `prevModelItem`.
// 1.1 -------- <--- this is removed, previous list item has indent smaller than reference indent.
// 1.1.1 -------- <--- this is reference indent, this and 1.1.1 should become nested items of 1.
// 1.1.2 --------
// 1.2 --------
//
// Becomes:
//
// 1 --------
// 1.1.1 --------
// 1.1.2 --------
// 1.2 --------
//
// Note: in this case 1.1.1 have indent 2 while 1 have indent 0. In model that should not be possible,
// because following item may have indent bigger only by one. But this is fixed by postfixer.
const modelPosition = model.createPositionAt( prevModelItem, 'end' );
insertPosition = mapper.toViewPosition( modelPosition );
}
insertPosition = (0,_utils__WEBPACK_IMPORTED_MODULE_1__.positionAfterUiElements)( insertPosition );
// Handle multiple lists. This happens if list item has nested numbered and bulleted lists. Following lists
// are inserted after the first list (no need to recalculate insertion position for them).
for ( const child of [ ...viewRemovedItem.getChildren() ] ) {
if ( isList( child ) ) {
insertPosition = viewWriter.move( viewWriter.createRangeOn( child ), insertPosition ).end;
(0,_utils__WEBPACK_IMPORTED_MODULE_1__.mergeViewLists)( viewWriter, child, child.nextSibling );
(0,_utils__WEBPACK_IMPORTED_MODULE_1__.mergeViewLists)( viewWriter, child.previousSibling, child );
}
}
}
// Checks if view element is a list type (ul or ol).
//
// @param {module:engine/view/element~Element} viewElement
// @returns {Boolean}
function isList( viewElement ) {
return viewElement.is( 'element', 'ol' ) || viewElement.is( 'element', 'ul' );
}
// Calculates the indent value for a list item. Handles HTML compliant and non-compliant lists.
//
// Also, fixes non HTML compliant lists indents:
//
// before: fixed list:
// OL OL
// |-> LI (parent LIs: 0) |-> LI (indent: 0)
// |-> OL |-> OL
// |-> OL |
// | |-> OL |
// | |-> OL |
// | |-> LI (parent LIs: 1) |-> LI (indent: 1)
// |-> LI (parent LIs: 1) |-> LI (indent: 1)
//
// before: fixed list:
// OL OL
// |-> OL |
// |-> OL |
// |-> OL |
// |-> LI (parent LIs: 0) |-> LI (indent: 0)
//
// before: fixed list:
// OL OL
// |-> LI (parent LIs: 0) |-> LI (indent: 0)
// |-> OL |-> OL
// |-> LI (parent LIs: 0) |-> LI (indent: 1)
//
// @param {module:engine/view/element~Element} listItem
// @param {Object} conversionStore
// @returns {Number}
function getIndent( listItem ) {
let indent = 0;
let parent = listItem.parent;
while ( parent ) {
// Each LI in the tree will result in an increased indent for HTML compliant lists.
if ( parent.is( 'element', 'li' ) ) {
indent++;
} else {
// If however the list is nested in other list we should check previous sibling of any of the list elements...
const previousSibling = parent.previousSibling;
// ...because the we might need increase its indent:
// before: fixed list:
// OL OL
// |-> LI (parent LIs: 0) |-> LI (indent: 0)
// |-> OL |-> OL
// |-> LI (parent LIs: 0) |-> LI (indent: 1)
if ( previousSibling && previousSibling.is( 'element', 'li' ) ) {
indent++;
}
}
parent = parent.parent;
}
return indent;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-list/src/list/indentcommand.js":
/*!*************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-list/src/list/indentcommand.js ***!
\*************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ IndentCommand)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module list/list/indentcommand
*/
/**
* The list indent command. It is used by the {@link module:list/list~List list feature}.
*
* @extends module:core/command~Command
*/
class IndentCommand extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Command {
/**
* Creates an instance of the command.
*
* @param {module:core/editor/editor~Editor} editor The editor instance.
* @param {'forward'|'backward'} indentDirection The direction of indent. If it is equal to `backward`, the command
* will outdent a list item.
*/
constructor( editor, indentDirection ) {
super( editor );
/**
* Determines by how much the command will change the list item's indent attribute.
*
* @readonly
* @private
* @member {Number}
*/
this._indentBy = indentDirection == 'forward' ? 1 : -1;
}
/**
* @inheritDoc
*/
refresh() {
this.isEnabled = this._checkEnabled();
}
/**
* Indents or outdents (depending on the {@link #constructor}'s `indentDirection` parameter) selected list items.
*
* @fires execute
* @fires _executeCleanup
*/
execute() {
const model = this.editor.model;
const doc = model.document;
let itemsToChange = Array.from( doc.selection.getSelectedBlocks() );
model.change( writer => {
const lastItem = itemsToChange[ itemsToChange.length - 1 ];
// Indenting a list item should also indent all the items that are already sub-items of indented item.
let next = lastItem.nextSibling;
// Check all items after last indented item, as long as their indent is bigger than indent of that item.
while ( next && next.name == 'listItem' && next.getAttribute( 'listIndent' ) > lastItem.getAttribute( 'listIndent' ) ) {
itemsToChange.push( next );
next = next.nextSibling;
}
// We need to be sure to keep model in correct state after each small change, because converters
// bases on that state and assumes that model is correct.
// Because of that, if the command outdents items, we will outdent them starting from the last item, as
// it is safer.
if ( this._indentBy < 0 ) {
itemsToChange = itemsToChange.reverse();
}
for ( const item of itemsToChange ) {
const indent = item.getAttribute( 'listIndent' ) + this._indentBy;
// If indent is lower than 0, it means that the item got outdented when it was not indented.
// This means that we need to convert that list item to paragraph.
if ( indent < 0 ) {
// To keep the model as correct as possible, first rename listItem, then remove attributes,
// as listItem without attributes is very incorrect and will cause problems in converters.
// No need to remove attributes, will be removed by post fixer.
writer.rename( item, 'paragraph' );
}
// If indent is >= 0, change the attribute value.
else {
writer.setAttribute( 'listIndent', indent, item );
}
}
/**
* Event fired by the {@link #execute} method.
*
* It allows to execute an action after executing the {@link ~IndentCommand#execute} method, for example adjusting
* attributes of changed list items.
*
* @protected
* @event _executeCleanup
*/
this.fire( '_executeCleanup', itemsToChange );
} );
}
/**
* Checks whether the command can be enabled in the current context.
*
* @private
* @returns {Boolean} Whether the command should be enabled.
*/
_checkEnabled() {
// Check whether any of position's ancestor is a list item.
const listItem = (0,ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__.first)( this.editor.model.document.selection.getSelectedBlocks() );
// If selection is not in a list item, the command is disabled.
if ( !listItem || !listItem.is( 'element', 'listItem' ) ) {
return false;
}
if ( this._indentBy > 0 ) {
// Cannot indent first item in it's list. Check if before `listItem` is a list item that is in same list.
// To be in the same list, the item has to have same attributes and cannot be "split" by an item with lower indent.
const indent = listItem.getAttribute( 'listIndent' );
const type = listItem.getAttribute( 'listType' );
let prev = listItem.previousSibling;
while ( prev && prev.is( 'element', 'listItem' ) && prev.getAttribute( 'listIndent' ) >= indent ) {
if ( prev.getAttribute( 'listIndent' ) == indent ) {
// The item is on the same level.
// If it has same type, it means that we found a preceding sibling from the same list.
// If it does not have same type, it means that `listItem` is on different list (this can happen only
// on top level lists, though).
return prev.getAttribute( 'listType' ) == type;
}
prev = prev.previousSibling;
}
// Could not find similar list item, this means that `listItem` is first in its list.
return false;
}
// If we are outdenting it is enough to be in list item. Every list item can always be outdented.
return true;
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-list/src/list/listcommand.js":
/*!***********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-list/src/list/listcommand.js ***!
\***********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ListCommand)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module list/list/listcommand
*/
/**
* The list command. It is used by the {@link module:list/list~List list feature}.
*
* @extends module:core/command~Command
*/
class ListCommand extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Command {
/**
* Creates an instance of the command.
*
* @param {module:core/editor/editor~Editor} editor The editor instance.
* @param {'numbered'|'bulleted'} type List type that will be handled by this command.
*/
constructor( editor, type ) {
super( editor );
/**
* The type of the list created by the command.
*
* @readonly
* @member {'numbered'|'bulleted'|'todo'}
*/
this.type = type;
/**
* A flag indicating whether the command is active, which means that the selection starts in a list of the same type.
*
* @observable
* @readonly
* @member {Boolean} #value
*/
}
/**
* @inheritDoc
*/
refresh() {
this.value = this._getValue();
this.isEnabled = this._checkEnabled();
}
/**
* Executes the list command.
*
* @fires execute
* @param {Object} [options] Command options.
* @param {Boolean} [options.forceValue] If set, it will force the command behavior. If `true`, the command will try to convert the
* selected items and potentially the neighbor elements to the proper list items. If set to `false`, it will convert selected elements
* to paragraphs. If not set, the command will toggle selected elements to list items or paragraphs, depending on the selection.
*/
execute( options = {} ) {
const model = this.editor.model;
const document = model.document;
const blocks = Array.from( document.selection.getSelectedBlocks() )
.filter( block => checkCanBecomeListItem( block, model.schema ) );
// Whether we are turning off some items.
const turnOff = options.forceValue !== undefined ? !options.forceValue : this.value;
// If we are turning off items, we are going to rename them to paragraphs.
model.change( writer => {
// If part of a list got turned off, we need to handle (outdent) all of sub-items of the last turned-off item.
// To be sure that model is all the time in a good state, we first fix items below turned-off item.
if ( turnOff ) {
// Start from the model item that is just after the last turned-off item.
let next = blocks[ blocks.length - 1 ].nextSibling;
let currentIndent = Number.POSITIVE_INFINITY;
let changes = [];
// Correct indent of all items after the last turned off item.
// Rules that should be followed:
// 1. All direct sub-items of turned-off item should become indent 0, because the first item after it
// will be the first item of a new list. Other items are at the same level, so should have same 0 index.
// 2. All items with indent lower than indent of turned-off item should become indent 0, because they
// should not end up as a child of any of list items that they were not children of before.
// 3. All other items should have their indent changed relatively to it's parent.
//
// For example:
// 1 * --------
// 2 * --------
// 3 * -------- <-- this is turned off.
// 4 * -------- <-- this has to become indent = 0, because it will be first item on a new list.
// 5 * -------- <-- this should be still be a child of item above, so indent = 1.
// 6 * -------- <-- this has to become indent = 0, because it should not be a child of any of items above.
// 7 * -------- <-- this should be still be a child of item above, so indent = 1.
// 8 * -------- <-- this has to become indent = 0.
// 9 * -------- <-- this should still be a child of item above, so indent = 1.
// 10 * -------- <-- this should still be a child of item above, so indent = 2.
// 11 * -------- <-- this should still be at the same level as item above, so indent = 2.
// 12 * -------- <-- this and all below are left unchanged.
// 13 * --------
// 14 * --------
//
// After turning off 3 the list becomes:
//
// 1 * --------
// 2 * --------
//
// 3 --------
//
// 4 * --------
// 5 * --------
// 6 * --------
// 7 * --------
// 8 * --------
// 9 * --------
// 10 * --------
// 11 * --------
// 12 * --------
// 13 * --------
// 14 * --------
//
// Thanks to this algorithm no lists are mismatched and no items get unexpected children/parent, while
// those parent-child connection which are possible to maintain are still maintained. It's worth noting
// that this is the same effect that we would be get by multiple use of outdent command. However doing
// it like this is much more efficient because it's less operation (less memory usage, easier OT) and
// less conversion (faster).
while ( next && next.name == 'listItem' && next.getAttribute( 'listIndent' ) !== 0 ) {
// Check each next list item, as long as its indent is bigger than 0.
// If the indent is 0 we are not going to change anything anyway.
const indent = next.getAttribute( 'listIndent' );
// We check if that's item indent is lower as current relative indent.
if ( indent < currentIndent ) {
// If it is, current relative indent becomes that indent.
currentIndent = indent;
}
// Fix indent relatively to current relative indent.
// Note, that if we just changed the current relative indent, the newIndent will be equal to 0.
const newIndent = indent - currentIndent;
// Save the entry in changes array. We do not apply it at the moment, because we will need to
// reverse the changes so the last item is changed first.
// This is to keep model in correct state all the time.
changes.push( { element: next, listIndent: newIndent } );
// Find next item.
next = next.nextSibling;
}
changes = changes.reverse();
for ( const item of changes ) {
writer.setAttribute( 'listIndent', item.listIndent, item.element );
}
}
// If we are turning on, we might change some items that are already `listItem`s but with different type.
// Changing one nested list item to other type should also trigger changing all its siblings so the
// whole nested list is of the same type.
// Example (assume changing to numbered list):
// * ------ <-- do not fix, top level item
// * ------ <-- fix, because latter list item of this item's list is changed
// * ------ <-- do not fix, item is not affected (different list)
// * ------ <-- fix, because latter list item of this item's list is changed
// * ------ <-- fix, because latter list item of this item's list is changed
// * ---[-- <-- already in selection
// * ------ <-- already in selection
// * ------ <-- already in selection
// * ------ <-- already in selection, but does not cause other list items to change because is top-level
// * ---]-- <-- already in selection
// * ------ <-- fix, because preceding list item of this item's list is changed
// * ------ <-- do not fix, item is not affected (different list)
// * ------ <-- do not fix, top level item
if ( !turnOff ) {
// Find lowest indent among selected items. This will be indicator what is the indent of
// top-most list affected by the command.
let lowestIndent = Number.POSITIVE_INFINITY;
for ( const item of blocks ) {
if ( item.is( 'element', 'listItem' ) && item.getAttribute( 'listIndent' ) < lowestIndent ) {
lowestIndent = item.getAttribute( 'listIndent' );
}
}
// Do not execute the fix for top-level lists.
lowestIndent = lowestIndent === 0 ? 1 : lowestIndent;
// Fix types of list items that are "before" the selected blocks.
_fixType( blocks, true, lowestIndent );
// Fix types of list items that are "after" the selected blocks.
_fixType( blocks, false, lowestIndent );
}
// Phew! Now it will be easier :).
// For each block element that was in the selection, we will either: turn it to list item,
// turn it to paragraph, or change it's type. Or leave it as it is.
// Do it in reverse as there might be multiple blocks (same as with changing indents).
for ( const element of blocks.reverse() ) {
if ( turnOff && element.name == 'listItem' ) {
// We are turning off and the element is a `listItem` - it should be converted to `paragraph`.
// List item specific attributes are removed by post fixer.
writer.rename( element, 'paragraph' );
} else if ( !turnOff && element.name != 'listItem' ) {
// We are turning on and the element is not a `listItem` - it should be converted to `listItem`.
// The order of operations is important to keep model in correct state.
writer.setAttributes( { listType: this.type, listIndent: 0 }, element );
writer.rename( element, 'listItem' );
} else if ( !turnOff && element.name == 'listItem' && element.getAttribute( 'listType' ) != this.type ) {
// We are turning on and the element is a `listItem` but has different type - change it's type and
// type of it's all siblings that have same indent.
writer.setAttribute( 'listType', this.type, element );
}
}
/**
* Event fired by the {@link #execute} method.
*
* It allows to execute an action after executing the {@link ~ListCommand#execute} method, for example adjusting
* attributes of changed blocks.
*
* @protected
* @event _executeCleanup
*/
this.fire( '_executeCleanup', blocks );
} );
}
/**
* Checks the command's {@link #value}.
*
* @private
* @returns {Boolean} The current value.
*/
_getValue() {
// Check whether closest `listItem` ancestor of the position has a correct type.
const listItem = (0,ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__.first)( this.editor.model.document.selection.getSelectedBlocks() );
return !!listItem && listItem.is( 'element', 'listItem' ) && listItem.getAttribute( 'listType' ) == this.type;
}
/**
* Checks whether the command can be enabled in the current context.
*
* @private
* @returns {Boolean} Whether the command should be enabled.
*/
_checkEnabled() {
// If command value is true it means that we are in list item, so the command should be enabled.
if ( this.value ) {
return true;
}
const selection = this.editor.model.document.selection;
const schema = this.editor.model.schema;
const firstBlock = (0,ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__.first)( selection.getSelectedBlocks() );
if ( !firstBlock ) {
return false;
}
// Otherwise, check if list item can be inserted at the position start.
return checkCanBecomeListItem( firstBlock, schema );
}
}
// Helper function used when one or more list item have their type changed. Fixes type of other list items
// that are affected by the change (are in same lists) but are not directly in selection. The function got extracted
// not to duplicated code, as same fix has to be performed before and after selection.
//
// @param {Array.<module:engine/model/node~Node>} blocks Blocks that are in selection.
// @param {Boolean} isBackward Specified whether fix will be applied for blocks before first selected block (`true`)
// or blocks after last selected block (`false`).
// @param {Number} lowestIndent Lowest indent among selected blocks.
function _fixType( blocks, isBackward, lowestIndent ) {
// We need to check previous sibling of first changed item and next siblings of last changed item.
const startingItem = isBackward ? blocks[ 0 ] : blocks[ blocks.length - 1 ];
if ( startingItem.is( 'element', 'listItem' ) ) {
let item = startingItem[ isBackward ? 'previousSibling' : 'nextSibling' ];
// During processing items, keeps the lowest indent of already processed items.
// This saves us from changing too many items.
// Following example is for going forward as it is easier to read, however same applies to going backward.
// * ------
// * ------
// * --[---
// * ------ <-- `lowestIndent` should be 1
// * --]--- <-- `startingItem`, `currentIndent` = 2, `lowestIndent` == 1
// * ------ <-- should be fixed, `indent` == 2 == `currentIndent`
// * ------ <-- should be fixed, set `currentIndent` to 1, `indent` == 1 == `currentIndent`
// * ------ <-- should not be fixed, item is in different list, `indent` = 2, `indent` != `currentIndent`
// * ------ <-- should be fixed, `indent` == 1 == `currentIndent`
// * ------ <-- break loop (`indent` < `lowestIndent`)
let currentIndent = startingItem.getAttribute( 'listIndent' );
// Look back until a list item with indent lower than reference `lowestIndent`.
// That would be the parent of nested sublist which contains item having `lowestIndent`.
while ( item && item.is( 'element', 'listItem' ) && item.getAttribute( 'listIndent' ) >= lowestIndent ) {
if ( currentIndent > item.getAttribute( 'listIndent' ) ) {
currentIndent = item.getAttribute( 'listIndent' );
}
// Found an item that is in the same nested sublist.
if ( item.getAttribute( 'listIndent' ) == currentIndent ) {
// Just add the item to selected blocks like it was selected by the user.
blocks[ isBackward ? 'unshift' : 'push' ]( item );
}
item = item[ isBackward ? 'previousSibling' : 'nextSibling' ];
}
}
}
// Checks whether the given block can be replaced by a listItem.
//
// @private
// @param {module:engine/model/element~Element} block A block to be tested.
// @param {module:engine/model/schema~Schema} schema The schema of the document.
// @returns {Boolean}
function checkCanBecomeListItem( block, schema ) {
return schema.checkChild( block.parent, 'listItem' ) && !schema.isObject( block );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-list/src/list/listediting.js":
/*!***********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-list/src/list/listediting.js ***!
\***********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ListEditing)
/* harmony export */ });
/* harmony import */ var _listcommand__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./listcommand */ "./node_modules/@ckeditor/ckeditor5-list/src/list/listcommand.js");
/* harmony import */ var _indentcommand__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./indentcommand */ "./node_modules/@ckeditor/ckeditor5-list/src/list/indentcommand.js");
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_enter__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ckeditor5/src/enter */ "./node_modules/ckeditor5/src/enter.js");
/* harmony import */ var ckeditor5_src_typing__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ckeditor5/src/typing */ "./node_modules/ckeditor5/src/typing.js");
/* harmony import */ var _converters__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./converters */ "./node_modules/@ckeditor/ckeditor5-list/src/list/converters.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module list/list/listediting
*/
/**
* The engine of the list feature. It handles creating, editing and removing lists and list items.
*
* It registers the `'numberedList'`, `'bulletedList'`, `'indentList'` and `'outdentList'` commands.
*
* @extends module:core/plugin~Plugin
*/
class ListEditing extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_2__.Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'ListEditing';
}
/**
* @inheritDoc
*/
static get requires() {
return [ ckeditor5_src_enter__WEBPACK_IMPORTED_MODULE_3__.Enter, ckeditor5_src_typing__WEBPACK_IMPORTED_MODULE_4__.Delete ];
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
// Schema.
// Note: in case `$block` will ever be allowed in `listItem`, keep in mind that this feature
// uses `Selection#getSelectedBlocks()` without any additional processing to obtain all selected list items.
// If there are blocks allowed inside list item, algorithms using `getSelectedBlocks()` will have to be modified.
editor.model.schema.register( 'listItem', {
inheritAllFrom: '$block',
allowAttributes: [ 'listType', 'listIndent' ]
} );
// Converters.
const data = editor.data;
const editing = editor.editing;
editor.model.document.registerPostFixer( writer => (0,_converters__WEBPACK_IMPORTED_MODULE_5__.modelChangePostFixer)( editor.model, writer ) );
editing.mapper.registerViewToModelLength( 'li', getViewListItemLength );
data.mapper.registerViewToModelLength( 'li', getViewListItemLength );
editing.mapper.on( 'modelToViewPosition', (0,_converters__WEBPACK_IMPORTED_MODULE_5__.modelToViewPosition)( editing.view ) );
editing.mapper.on( 'viewToModelPosition', (0,_converters__WEBPACK_IMPORTED_MODULE_5__.viewToModelPosition)( editor.model ) );
data.mapper.on( 'modelToViewPosition', (0,_converters__WEBPACK_IMPORTED_MODULE_5__.modelToViewPosition)( editing.view ) );
editor.conversion.for( 'editingDowncast' )
.add( dispatcher => {
dispatcher.on( 'insert', _converters__WEBPACK_IMPORTED_MODULE_5__.modelViewSplitOnInsert, { priority: 'high' } );
dispatcher.on( 'insert:listItem', (0,_converters__WEBPACK_IMPORTED_MODULE_5__.modelViewInsertion)( editor.model ) );
dispatcher.on( 'attribute:listType:listItem', _converters__WEBPACK_IMPORTED_MODULE_5__.modelViewChangeType, { priority: 'high' } );
dispatcher.on( 'attribute:listType:listItem', _converters__WEBPACK_IMPORTED_MODULE_5__.modelViewMergeAfterChangeType, { priority: 'low' } );
dispatcher.on( 'attribute:listIndent:listItem', (0,_converters__WEBPACK_IMPORTED_MODULE_5__.modelViewChangeIndent)( editor.model ) );
dispatcher.on( 'remove:listItem', (0,_converters__WEBPACK_IMPORTED_MODULE_5__.modelViewRemove)( editor.model ) );
dispatcher.on( 'remove', _converters__WEBPACK_IMPORTED_MODULE_5__.modelViewMergeAfter, { priority: 'low' } );
} );
editor.conversion.for( 'dataDowncast' )
.add( dispatcher => {
dispatcher.on( 'insert', _converters__WEBPACK_IMPORTED_MODULE_5__.modelViewSplitOnInsert, { priority: 'high' } );
dispatcher.on( 'insert:listItem', (0,_converters__WEBPACK_IMPORTED_MODULE_5__.modelViewInsertion)( editor.model ) );
} );
editor.conversion.for( 'upcast' )
.add( dispatcher => {
dispatcher.on( 'element:ul', _converters__WEBPACK_IMPORTED_MODULE_5__.cleanList, { priority: 'high' } );
dispatcher.on( 'element:ol', _converters__WEBPACK_IMPORTED_MODULE_5__.cleanList, { priority: 'high' } );
dispatcher.on( 'element:li', _converters__WEBPACK_IMPORTED_MODULE_5__.cleanListItem, { priority: 'high' } );
dispatcher.on( 'element:li', _converters__WEBPACK_IMPORTED_MODULE_5__.viewModelConverter );
} );
// Fix indentation of pasted items.
editor.model.on( 'insertContent', _converters__WEBPACK_IMPORTED_MODULE_5__.modelIndentPasteFixer, { priority: 'high' } );
// Register commands for numbered and bulleted list.
editor.commands.add( 'numberedList', new _listcommand__WEBPACK_IMPORTED_MODULE_0__["default"]( editor, 'numbered' ) );
editor.commands.add( 'bulletedList', new _listcommand__WEBPACK_IMPORTED_MODULE_0__["default"]( editor, 'bulleted' ) );
// Register commands for indenting.
editor.commands.add( 'indentList', new _indentcommand__WEBPACK_IMPORTED_MODULE_1__["default"]( editor, 'forward' ) );
editor.commands.add( 'outdentList', new _indentcommand__WEBPACK_IMPORTED_MODULE_1__["default"]( editor, 'backward' ) );
const viewDocument = editing.view.document;
// Overwrite default Enter key behavior.
// If Enter key is pressed with selection collapsed in empty list item, outdent it instead of breaking it.
this.listenTo( viewDocument, 'enter', ( evt, data ) => {
const doc = this.editor.model.document;
const positionParent = doc.selection.getLastPosition().parent;
if ( doc.selection.isCollapsed && positionParent.name == 'listItem' && positionParent.isEmpty ) {
this.editor.execute( 'outdentList' );
data.preventDefault();
evt.stop();
}
}, { context: 'li' } );
// Overwrite default Backspace key behavior.
// If Backspace key is pressed with selection collapsed on first position in first list item, outdent it. #83
this.listenTo( viewDocument, 'delete', ( evt, data ) => {
// Check conditions from those that require less computations like those immediately available.
if ( data.direction !== 'backward' ) {
return;
}
const selection = this.editor.model.document.selection;
if ( !selection.isCollapsed ) {
return;
}
const firstPosition = selection.getFirstPosition();
if ( !firstPosition.isAtStart ) {
return;
}
const positionParent = firstPosition.parent;
if ( positionParent.name !== 'listItem' ) {
return;
}
const previousIsAListItem = positionParent.previousSibling && positionParent.previousSibling.name === 'listItem';
if ( previousIsAListItem ) {
return;
}
this.editor.execute( 'outdentList' );
data.preventDefault();
evt.stop();
}, { context: 'li' } );
const getCommandExecuter = commandName => {
return ( data, cancel ) => {
const command = this.editor.commands.get( commandName );
if ( command.isEnabled ) {
this.editor.execute( commandName );
cancel();
}
};
};
editor.keystrokes.set( 'Tab', getCommandExecuter( 'indentList' ) );
editor.keystrokes.set( 'Shift+Tab', getCommandExecuter( 'outdentList' ) );
}
/**
* @inheritDoc
*/
afterInit() {
const commands = this.editor.commands;
const indent = commands.get( 'indent' );
const outdent = commands.get( 'outdent' );
if ( indent ) {
indent.registerChildCommand( commands.get( 'indentList' ) );
}
if ( outdent ) {
outdent.registerChildCommand( commands.get( 'outdentList' ) );
}
}
}
function getViewListItemLength( element ) {
let length = 1;
for ( const child of element.getChildren() ) {
if ( child.name == 'ul' || child.name == 'ol' ) {
for ( const item of child.getChildren() ) {
length += getViewListItemLength( item );
}
}
}
return length;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-list/src/list/listui.js":
/*!******************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-list/src/list/listui.js ***!
\******************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ListUI)
/* harmony export */ });
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./utils */ "./node_modules/@ckeditor/ckeditor5-list/src/list/utils.js");
/* harmony import */ var _theme_icons_numberedlist_svg__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../../theme/icons/numberedlist.svg */ "./node_modules/@ckeditor/ckeditor5-list/theme/icons/numberedlist.svg");
/* harmony import */ var _theme_icons_bulletedlist_svg__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../theme/icons/bulletedlist.svg */ "./node_modules/@ckeditor/ckeditor5-list/theme/icons/bulletedlist.svg");
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module list/list/listui
*/
/**
* The list UI feature. It introduces the `'numberedList'` and `'bulletedList'` buttons that
* allow to convert paragraphs to and from list items and indent or outdent them.
*
* @extends module:core/plugin~Plugin
*/
class ListUI extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_3__.Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'ListUI';
}
/**
* @inheritDoc
*/
init() {
const t = this.editor.t;
// Create two buttons and link them with numberedList and bulletedList commands.
(0,_utils__WEBPACK_IMPORTED_MODULE_0__.createUIComponent)( this.editor, 'numberedList', t( 'Numbered List' ), _theme_icons_numberedlist_svg__WEBPACK_IMPORTED_MODULE_1__["default"] );
(0,_utils__WEBPACK_IMPORTED_MODULE_0__.createUIComponent)( this.editor, 'bulletedList', t( 'Bulleted List' ), _theme_icons_bulletedlist_svg__WEBPACK_IMPORTED_MODULE_2__["default"] );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-list/src/list/utils.js":
/*!*****************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-list/src/list/utils.js ***!
\*****************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "createUIComponent": () => (/* binding */ createUIComponent),
/* harmony export */ "createViewListItemElement": () => (/* binding */ createViewListItemElement),
/* harmony export */ "findNestedList": () => (/* binding */ findNestedList),
/* harmony export */ "generateLiInUl": () => (/* binding */ generateLiInUl),
/* harmony export */ "getListTypeFromListStyleType": () => (/* binding */ getListTypeFromListStyleType),
/* harmony export */ "getSelectedListItems": () => (/* binding */ getSelectedListItems),
/* harmony export */ "getSiblingListItem": () => (/* binding */ getSiblingListItem),
/* harmony export */ "getSiblingNodes": () => (/* binding */ getSiblingNodes),
/* harmony export */ "injectViewList": () => (/* binding */ injectViewList),
/* harmony export */ "mergeViewLists": () => (/* binding */ mergeViewLists),
/* harmony export */ "positionAfterUiElements": () => (/* binding */ positionAfterUiElements)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/engine */ "./node_modules/ckeditor5/src/engine.js");
/* harmony import */ var ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/ui */ "./node_modules/ckeditor5/src/ui.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module list/list/utils
*/
/**
* Creates a list item {@link module:engine/view/containerelement~ContainerElement}.
*
* @param {module:engine/view/downcastwriter~DowncastWriter} writer The writer instance.
* @returns {module:engine/view/containerelement~ContainerElement}
*/
function createViewListItemElement( writer ) {
const viewItem = writer.createContainerElement( 'li' );
viewItem.getFillerOffset = getListItemFillerOffset;
return viewItem;
}
/**
* Helper function that creates a `<ul><li></li></ul>` or (`<ol>`) structure out of the given `modelItem` model `listItem` element.
* Then, it binds the created view list item (`<li>`) with the model `listItem` element.
* The function then returns the created view list item (`<li>`).
*
* @param {module:engine/model/item~Item} modelItem Model list item.
* @param {module:engine/conversion/upcastdispatcher~UpcastConversionApi} conversionApi Conversion interface.
* @returns {module:engine/view/containerelement~ContainerElement} View list element.
*/
function generateLiInUl( modelItem, conversionApi ) {
const mapper = conversionApi.mapper;
const viewWriter = conversionApi.writer;
const listType = modelItem.getAttribute( 'listType' ) == 'numbered' ? 'ol' : 'ul';
const viewItem = createViewListItemElement( viewWriter );
const viewList = viewWriter.createContainerElement( listType, null );
viewWriter.insert( viewWriter.createPositionAt( viewList, 0 ), viewItem );
mapper.bindElements( modelItem, viewItem );
return viewItem;
}
/**
* Helper function that inserts a view list at a correct place and merges it with its siblings.
* It takes a model list item element (`modelItem`) and a corresponding view list item element (`injectedItem`). The view list item
* should be in a view list element (`<ul>` or `<ol>`) and should be its only child.
* See comments below to better understand the algorithm.
*
* @param {module:engine/view/item~Item} modelItem Model list item.
* @param {module:engine/view/containerelement~ContainerElement} injectedItem
* @param {module:engine/conversion/upcastdispatcher~UpcastConversionApi} conversionApi Conversion interface.
* @param {module:engine/model/model~Model} model The model instance.
*/
function injectViewList( modelItem, injectedItem, conversionApi, model ) {
const injectedList = injectedItem.parent;
const mapper = conversionApi.mapper;
const viewWriter = conversionApi.writer;
// The position where the view list will be inserted.
let insertPosition = mapper.toViewPosition( model.createPositionBefore( modelItem ) );
// 1. Find the previous list item that has the same or smaller indent. Basically we are looking for the first model item
// that is a "parent" or "sibling" of the injected model item.
// If there is no such list item, it means that the injected list item is the first item in "its list".
const refItem = getSiblingListItem( modelItem.previousSibling, {
sameIndent: true,
smallerIndent: true,
listIndent: modelItem.getAttribute( 'listIndent' )
} );
const prevItem = modelItem.previousSibling;
if ( refItem && refItem.getAttribute( 'listIndent' ) == modelItem.getAttribute( 'listIndent' ) ) {
// There is a list item with the same indent - we found the same-level sibling.
// Break the list after it. The inserted view item will be added in the broken space.
const viewItem = mapper.toViewElement( refItem );
insertPosition = viewWriter.breakContainer( viewWriter.createPositionAfter( viewItem ) );
} else {
// There is no list item with the same indent. Check the previous model item.
if ( prevItem && prevItem.name == 'listItem' ) {
// If it is a list item, it has to have a lower indent.
// It means that the inserted item should be added to it as its nested item.
insertPosition = mapper.toViewPosition( model.createPositionAt( prevItem, 'end' ) );
// There could be some not mapped elements (eg. span in to-do list) but we need to insert
// a nested list directly inside the li element.
const mappedViewAncestor = mapper.findMappedViewAncestor( insertPosition );
const nestedList = findNestedList( mappedViewAncestor );
// If there already is some nested list, then use it's position.
if ( nestedList ) {
insertPosition = viewWriter.createPositionBefore( nestedList );
} else {
// Else just put new list on the end of list item content.
insertPosition = viewWriter.createPositionAt( mappedViewAncestor, 'end' );
}
} else {
// The previous item is not a list item (or does not exist at all).
// Just map the position and insert the view item at the mapped position.
insertPosition = mapper.toViewPosition( model.createPositionBefore( modelItem ) );
}
}
insertPosition = positionAfterUiElements( insertPosition );
// Insert the view item.
viewWriter.insert( insertPosition, injectedList );
// 2. Handle possible children of the injected model item.
if ( prevItem && prevItem.name == 'listItem' ) {
const prevView = mapper.toViewElement( prevItem );
const walkerBoundaries = viewWriter.createRange( viewWriter.createPositionAt( prevView, 0 ), insertPosition );
const walker = walkerBoundaries.getWalker( { ignoreElementEnd: true } );
for ( const value of walker ) {
if ( value.item.is( 'element', 'li' ) ) {
const breakPosition = viewWriter.breakContainer( viewWriter.createPositionBefore( value.item ) );
const viewList = value.item.parent;
const targetPosition = viewWriter.createPositionAt( injectedItem, 'end' );
mergeViewLists( viewWriter, targetPosition.nodeBefore, targetPosition.nodeAfter );
viewWriter.move( viewWriter.createRangeOn( viewList ), targetPosition );
walker.position = breakPosition;
}
}
} else {
const nextViewList = injectedList.nextSibling;
if ( nextViewList && ( nextViewList.is( 'element', 'ul' ) || nextViewList.is( 'element', 'ol' ) ) ) {
let lastSubChild = null;
for ( const child of nextViewList.getChildren() ) {
const modelChild = mapper.toModelElement( child );
if ( modelChild && modelChild.getAttribute( 'listIndent' ) > modelItem.getAttribute( 'listIndent' ) ) {
lastSubChild = child;
} else {
break;
}
}
if ( lastSubChild ) {
viewWriter.breakContainer( viewWriter.createPositionAfter( lastSubChild ) );
viewWriter.move( viewWriter.createRangeOn( lastSubChild.parent ), viewWriter.createPositionAt( injectedItem, 'end' ) );
}
}
}
// Merge the inserted view list with its possible neighbor lists.
mergeViewLists( viewWriter, injectedList, injectedList.nextSibling );
mergeViewLists( viewWriter, injectedList.previousSibling, injectedList );
}
/**
* Helper function that takes two parameters that are expected to be view list elements, and merges them.
* The merge happens only if both parameters are list elements of the same type (the same element name and the same class attributes).
*
* @param {module:engine/view/downcastwriter~DowncastWriter} viewWriter The writer instance.
* @param {module:engine/view/item~Item} firstList The first element to compare.
* @param {module:engine/view/item~Item} secondList The second element to compare.
* @returns {module:engine/view/position~Position|null} The position after merge or `null` when there was no merge.
*/
function mergeViewLists( viewWriter, firstList, secondList ) {
// Check if two lists are going to be merged.
if ( !firstList || !secondList || ( firstList.name != 'ul' && firstList.name != 'ol' ) ) {
return null;
}
// Both parameters are list elements, so compare types now.
if ( firstList.name != secondList.name || firstList.getAttribute( 'class' ) !== secondList.getAttribute( 'class' ) ) {
return null;
}
return viewWriter.mergeContainers( viewWriter.createPositionAfter( firstList ) );
}
/**
* Helper function that for a given `view.Position`, returns a `view.Position` that is after all `view.UIElement`s that
* are after the given position.
*
* For example:
* `<container:p>foo^<ui:span></ui:span><ui:span></ui:span>bar</container:p>`
* For position ^, the position before "bar" will be returned.
*
* @param {module:engine/view/position~Position} viewPosition
* @returns {module:engine/view/position~Position}
*/
function positionAfterUiElements( viewPosition ) {
return viewPosition.getLastMatchingPosition( value => value.item.is( 'uiElement' ) );
}
/**
* Helper function that searches for a previous list item sibling of a given model item that meets the given criteria
* passed by the options object.
*
* @param {module:engine/model/item~Item} modelItem
* @param {Object} options Search criteria.
* @param {Boolean} [options.sameIndent=false] Whether the sought sibling should have the same indentation.
* @param {Boolean} [options.smallerIndent=false] Whether the sought sibling should have a smaller indentation.
* @param {Number} [options.listIndent] The reference indentation.
* @param {'forward'|'backward'} [options.direction='backward'] Walking direction.
* @returns {module:engine/model/item~Item|null}
*/
function getSiblingListItem( modelItem, options ) {
const sameIndent = !!options.sameIndent;
const smallerIndent = !!options.smallerIndent;
const indent = options.listIndent;
let item = modelItem;
while ( item && item.name == 'listItem' ) {
const itemIndent = item.getAttribute( 'listIndent' );
if ( ( sameIndent && indent == itemIndent ) || ( smallerIndent && indent > itemIndent ) ) {
return item;
}
if ( options.direction === 'forward' ) {
item = item.nextSibling;
} else {
item = item.previousSibling;
}
}
return null;
}
/**
* Helper method for creating a UI button and linking it with an appropriate command.
*
* @private
* @param {module:core/editor/editor~Editor} editor The editor instance to which the UI component will be added.
* @param {String} commandName The name of the command.
* @param {String} label The button label.
* @param {String} icon The source of the icon.
*/
function createUIComponent( editor, commandName, label, icon ) {
editor.ui.componentFactory.add( commandName, locale => {
const command = editor.commands.get( commandName );
const buttonView = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.ButtonView( locale );
buttonView.set( {
label,
icon,
tooltip: true,
isToggleable: true
} );
// Bind button model to command.
buttonView.bind( 'isOn', 'isEnabled' ).to( command, 'value', 'isEnabled' );
// Execute command.
buttonView.on( 'execute', () => {
editor.execute( commandName );
editor.editing.view.focus();
} );
return buttonView;
} );
}
/**
* Returns a first list view element that is direct child of the given view element.
*
* @param {module:engine/view/element~Element} viewElement
* @return {module:engine/view/element~Element|null}
*/
function findNestedList( viewElement ) {
for ( const node of viewElement.getChildren() ) {
if ( node.name == 'ul' || node.name == 'ol' ) {
return node;
}
}
return null;
}
/**
* Returns an array with all `listItem` elements that represent the same list.
*
* It means that values of `listIndent`, `listType`, `listStyle`, `listReversed` and `listStart` for all items are equal.
*
* @param {module:engine/model/position~Position} position Starting position.
* @param {'forward'|'backward'} direction Walking direction.
* @returns {Array.<module:engine/model/element~Element>}
*/
function getSiblingNodes( position, direction ) {
const items = [];
const listItem = position.parent;
const walkerOptions = {
ignoreElementEnd: true,
startPosition: position,
shallow: true,
direction
};
const limitIndent = listItem.getAttribute( 'listIndent' );
const nodes = [ ...new ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_0__.TreeWalker( walkerOptions ) ]
.filter( value => value.item.is( 'element' ) )
.map( value => value.item );
for ( const element of nodes ) {
// If found something else than `listItem`, we're out of the list scope.
if ( !element.is( 'element', 'listItem' ) ) {
break;
}
// If current parsed item has lower indent that element that the element that was a starting point,
// it means we left a nested list. Abort searching items.
//
// ■ List item 1. [listIndent=0]
// ○ List item 2.[] [listIndent=1], limitIndent = 1,
// ○ List item 3. [listIndent=1]
// ■ List item 4. [listIndent=0]
//
// Abort searching when leave nested list.
if ( element.getAttribute( 'listIndent' ) < limitIndent ) {
break;
}
// ■ List item 1.[] [listIndent=0] limitIndent = 0,
// ○ List item 2. [listIndent=1]
// ○ List item 3. [listIndent=1]
// ■ List item 4. [listIndent=0]
//
// Ignore nested lists.
if ( element.getAttribute( 'listIndent' ) > limitIndent ) {
continue;
}
// ■ List item 1.[] [listType=bulleted]
// 1. List item 2. [listType=numbered]
// 2.List item 3. [listType=numbered]
//
// Abort searching when found a different kind of a list.
if ( element.getAttribute( 'listType' ) !== listItem.getAttribute( 'listType' ) ) {
break;
}
// ■ List item 1.[] [listType=bulleted]
// ■ List item 2. [listType=bulleted]
// ○ List item 3. [listType=bulleted]
// ○ List item 4. [listType=bulleted]
//
// Abort searching when found a different list style,
if ( element.getAttribute( 'listStyle' ) !== listItem.getAttribute( 'listStyle' ) ) {
break;
}
// ... different direction
if ( element.getAttribute( 'listReversed' ) !== listItem.getAttribute( 'listReversed' ) ) {
break;
}
// ... and different start index
if ( element.getAttribute( 'listStart' ) !== listItem.getAttribute( 'listStart' ) ) {
break;
}
if ( direction === 'backward' ) {
items.unshift( element );
} else {
items.push( element );
}
}
return items;
}
/**
* Returns an array with all `listItem` elements in the model selection.
*
* It returns all the items even if only a part of the list is selected, including items that belong to nested lists.
* If no list is selected, it returns an empty array.
* The order of the elements is not specified.
*
* @protected
* @param {module:engine/model/model~Model} model
* @returns {Array.<module:engine/model/element~Element>}
*/
function getSelectedListItems( model ) {
const document = model.document;
// For all selected blocks find all list items that are being selected
// and update the `listStyle` attribute in those lists.
let listItems = [ ...document.selection.getSelectedBlocks() ]
.filter( element => element.is( 'element', 'listItem' ) )
.map( element => {
const position = model.change( writer => writer.createPositionAt( element, 0 ) );
return [
...getSiblingNodes( position, 'backward' ),
...getSiblingNodes( position, 'forward' )
];
} )
.flat();
// Since `getSelectedBlocks()` can return items that belong to the same list, and
// `getSiblingNodes()` returns the entire list, we need to remove duplicated items.
listItems = [ ...new Set( listItems ) ];
return listItems;
}
const BULLETED_LIST_STYLE_TYPES = [ 'disc', 'circle', 'square' ];
// There's a lot of them (https://www.w3.org/TR/css-counter-styles-3/#typedef-counter-style).
// Let's support only those that can be selected by ListPropertiesUI.
const NUMBERED_LIST_STYLE_TYPES = [
'decimal',
'decimal-leading-zero',
'lower-roman',
'upper-roman',
'lower-latin',
'upper-latin'
];
/**
* Checks whether the given list-style-type is supported by numbered or bulleted list.
*
* @param {String} listStyleType
* @returns {'bulleted'|'numbered'|null}
*/
function getListTypeFromListStyleType( listStyleType ) {
if ( BULLETED_LIST_STYLE_TYPES.includes( listStyleType ) ) {
return 'bulleted';
}
if ( NUMBERED_LIST_STYLE_TYPES.includes( listStyleType ) ) {
return 'numbered';
}
return null;
}
// Implementation of getFillerOffset for view list item element.
//
// @returns {Number|null} Block filler offset or `null` if block filler is not needed.
function getListItemFillerOffset() {
const hasOnlyLists = !this.isEmpty && ( this.getChild( 0 ).name == 'ul' || this.getChild( 0 ).name == 'ol' );
if ( this.isEmpty || hasOnlyLists ) {
return 0;
}
return ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_0__.getFillerOffset.call( this );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-media-embed/src/automediaembed.js":
/*!****************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-media-embed/src/automediaembed.js ***!
\****************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ AutoMediaEmbed)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/engine */ "./node_modules/ckeditor5/src/engine.js");
/* harmony import */ var ckeditor5_src_clipboard__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ckeditor5/src/clipboard */ "./node_modules/ckeditor5/src/clipboard.js");
/* harmony import */ var ckeditor5_src_typing__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ckeditor5/src/typing */ "./node_modules/ckeditor5/src/typing.js");
/* harmony import */ var ckeditor5_src_undo__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ckeditor5/src/undo */ "./node_modules/ckeditor5/src/undo.js");
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/* harmony import */ var _mediaembedediting__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./mediaembedediting */ "./node_modules/@ckeditor/ckeditor5-media-embed/src/mediaembedediting.js");
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./utils */ "./node_modules/@ckeditor/ckeditor5-media-embed/src/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module media-embed/automediaembed
*/
const URL_REGEXP = /^(?:http(s)?:\/\/)?[\w-]+\.[\w-.~:/?#[\]@!$&'()*+,;=%]+$/;
/**
* The auto-media embed plugin. It recognizes media links in the pasted content and embeds
* them shortly after they are injected into the document.
*
* @extends module:core/plugin~Plugin
*/
class AutoMediaEmbed extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [ ckeditor5_src_clipboard__WEBPACK_IMPORTED_MODULE_2__.Clipboard, ckeditor5_src_typing__WEBPACK_IMPORTED_MODULE_3__.Delete, ckeditor5_src_undo__WEBPACK_IMPORTED_MODULE_4__.Undo ];
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'AutoMediaEmbed';
}
/**
* @inheritDoc
*/
constructor( editor ) {
super( editor );
/**
* The paste–to–embed `setTimeout` ID. Stored as a property to allow
* cleaning of the timeout.
*
* @private
* @member {Number} #_timeoutId
*/
this._timeoutId = null;
/**
* The position where the `<media>` element will be inserted after the timeout,
* determined each time the new content is pasted into the document.
*
* @private
* @member {module:engine/model/liveposition~LivePosition} #_positionToInsert
*/
this._positionToInsert = null;
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const modelDocument = editor.model.document;
// We need to listen on `Clipboard#inputTransformation` because we need to save positions of selection.
// After pasting, the content between those positions will be checked for a URL that could be transformed
// into media.
this.listenTo( editor.plugins.get( 'ClipboardPipeline' ), 'inputTransformation', () => {
const firstRange = modelDocument.selection.getFirstRange();
const leftLivePosition = ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_1__.LivePosition.fromPosition( firstRange.start );
leftLivePosition.stickiness = 'toPrevious';
const rightLivePosition = ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_1__.LivePosition.fromPosition( firstRange.end );
rightLivePosition.stickiness = 'toNext';
modelDocument.once( 'change:data', () => {
this._embedMediaBetweenPositions( leftLivePosition, rightLivePosition );
leftLivePosition.detach();
rightLivePosition.detach();
}, { priority: 'high' } );
} );
editor.commands.get( 'undo' ).on( 'execute', () => {
if ( this._timeoutId ) {
ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_5__.global.window.clearTimeout( this._timeoutId );
this._positionToInsert.detach();
this._timeoutId = null;
this._positionToInsert = null;
}
}, { priority: 'high' } );
}
/**
* Analyzes the part of the document between provided positions in search for a URL representing media.
* When the URL is found, it is automatically converted into media.
*
* @protected
* @param {module:engine/model/liveposition~LivePosition} leftPosition Left position of the selection.
* @param {module:engine/model/liveposition~LivePosition} rightPosition Right position of the selection.
*/
_embedMediaBetweenPositions( leftPosition, rightPosition ) {
const editor = this.editor;
const mediaRegistry = editor.plugins.get( _mediaembedediting__WEBPACK_IMPORTED_MODULE_6__["default"] ).registry;
// TODO: Use marker instead of LiveRange & LivePositions.
const urlRange = new ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_1__.LiveRange( leftPosition, rightPosition );
const walker = urlRange.getWalker( { ignoreElementEnd: true } );
let url = '';
for ( const node of walker ) {
if ( node.item.is( '$textProxy' ) ) {
url += node.item.data;
}
}
url = url.trim();
// If the URL does not match to universal URL regexp, let's skip that.
if ( !url.match( URL_REGEXP ) ) {
urlRange.detach();
return;
}
// If the URL represents a media, let's use it.
if ( !mediaRegistry.hasMedia( url ) ) {
urlRange.detach();
return;
}
const mediaEmbedCommand = editor.commands.get( 'mediaEmbed' );
// Do not anything if media element cannot be inserted at the current position (#47).
if ( !mediaEmbedCommand.isEnabled ) {
urlRange.detach();
return;
}
// Position won't be available in the `setTimeout` function so let's clone it.
this._positionToInsert = ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_1__.LivePosition.fromPosition( leftPosition );
// This action mustn't be executed if undo was called between pasting and auto-embedding.
this._timeoutId = ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_5__.global.window.setTimeout( () => {
editor.model.change( writer => {
this._timeoutId = null;
writer.remove( urlRange );
urlRange.detach();
let insertionPosition;
// Check if position where the media element should be inserted is still valid.
// Otherwise leave it as undefined to use document.selection - default behavior of model.insertContent().
if ( this._positionToInsert.root.rootName !== '$graveyard' ) {
insertionPosition = this._positionToInsert;
}
(0,_utils__WEBPACK_IMPORTED_MODULE_7__.insertMedia)( editor.model, url, insertionPosition );
this._positionToInsert.detach();
this._positionToInsert = null;
} );
editor.plugins.get( 'Delete' ).requestUndoOnBackspace();
}, 100 );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-media-embed/src/converters.js":
/*!************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-media-embed/src/converters.js ***!
\************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "modelToViewUrlAttributeConverter": () => (/* binding */ modelToViewUrlAttributeConverter)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module media-embed/converters
*/
/**
* Returns a function that converts the model "url" attribute to the view representation.
*
* Depending on the configuration, the view representation can be "semantic" (for the data pipeline):
*
* <figure class="media">
* <oembed url="foo"></oembed>
* </figure>
*
* or "non-semantic" (for the editing view pipeline):
*
* <figure class="media">
* <div data-oembed-url="foo">[ non-semantic media preview for "foo" ]</div>
* </figure>
*
* **Note:** Changing the model "url" attribute replaces the entire content of the
* `<figure>` in the view.
*
* @param {module:media-embed/mediaregistry~MediaRegistry} registry The registry providing
* the media and their content.
* @param {Object} options
* @param {String} [options.elementName] When set, overrides the default element name for semantic media embeds.
* @param {String} [options.renderMediaPreview] When `true`, the converter will create the view in the non-semantic form.
* @param {String} [options.renderForEditingView] When `true`, the converter will create a view specific for the
* editing pipeline (e.g. including CSS classes, content placeholders).
* @returns {Function}
*/
function modelToViewUrlAttributeConverter( registry, options ) {
return dispatcher => {
dispatcher.on( 'attribute:url:media', converter );
};
function converter( evt, data, conversionApi ) {
if ( !conversionApi.consumable.consume( data.item, evt.name ) ) {
return;
}
const url = data.attributeNewValue;
const viewWriter = conversionApi.writer;
const figure = conversionApi.mapper.toViewElement( data.item );
const mediaContentElement = [ ...figure.getChildren() ]
.find( child => child.getCustomProperty( 'media-content' ) );
// TODO: removing the wrapper and creating it from scratch is a hack. We can do better than that.
viewWriter.remove( mediaContentElement );
const mediaViewElement = registry.getMediaViewElement( viewWriter, url, options );
viewWriter.insert( viewWriter.createPositionAt( figure, 0 ), mediaViewElement );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-media-embed/src/mediaembed.js":
/*!************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-media-embed/src/mediaembed.js ***!
\************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ MediaEmbed)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_widget__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/widget */ "./node_modules/ckeditor5/src/widget.js");
/* harmony import */ var _mediaembedediting__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./mediaembedediting */ "./node_modules/@ckeditor/ckeditor5-media-embed/src/mediaembedediting.js");
/* harmony import */ var _automediaembed__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./automediaembed */ "./node_modules/@ckeditor/ckeditor5-media-embed/src/automediaembed.js");
/* harmony import */ var _mediaembedui__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./mediaembedui */ "./node_modules/@ckeditor/ckeditor5-media-embed/src/mediaembedui.js");
/* harmony import */ var _theme_mediaembed_css__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../theme/mediaembed.css */ "./node_modules/@ckeditor/ckeditor5-media-embed/theme/mediaembed.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module media-embed/mediaembed
*/
/**
* The media embed plugin.
*
* For a detailed overview, check the {@glink features/media-embed Media Embed feature documentation}.
*
* This is a "glue" plugin which loads the following plugins:
*
* * The {@link module:media-embed/mediaembedediting~MediaEmbedEditing media embed editing feature},
* * The {@link module:media-embed/mediaembedui~MediaEmbedUI media embed UI feature} and
* * The {@link module:media-embed/automediaembed~AutoMediaEmbed auto-media embed feature}.
*
* @extends module:core/plugin~Plugin
*/
class MediaEmbed extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [ _mediaembedediting__WEBPACK_IMPORTED_MODULE_2__["default"], _mediaembedui__WEBPACK_IMPORTED_MODULE_4__["default"], _automediaembed__WEBPACK_IMPORTED_MODULE_3__["default"], ckeditor5_src_widget__WEBPACK_IMPORTED_MODULE_1__.Widget ];
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'MediaEmbed';
}
}
/**
* The media embed provider descriptor. Used in
* {@link module:media-embed/mediaembed~MediaEmbedConfig#providers `config.mediaEmbed.providers`} and
* {@link module:media-embed/mediaembed~MediaEmbedConfig#extraProviders `config.mediaEmbed.extraProviders`}.
*
* See {@link module:media-embed/mediaembed~MediaEmbedConfig} to learn more.
*
* {
* name: 'example',
*
* // The following RegExp matches https://www.example.com/media/{media id},
* // (either with "http(s)://" and "www" or without), so the valid URLs are:
* //
* // * https://www.example.com/media/{media id},
* // * http://www.example.com/media/{media id},
* // * www.example.com/media/{media id},
* // * example.com/media/{media id}
* url: /^example\.com\/media\/(\w+)/,
*
* // The rendering function of the provider.
* // Used to represent the media when editing the content (i.e. in the view)
* // and also in the data output of the editor if semantic data output is disabled.
* html: match => `The HTML representing the media with ID=${ match[ 1 ] }.`
* }
*
* You can allow any sort of media in the editor using the "allow–all" `RegExp`.
* But mind that, since URLs are processed in the order of configuration, if one of the previous
* `RegExps` matches the URL, it will have a precedence over this one.
*
* {
* name: 'allow-all',
* url: /^.+/
* }
*
* To implement responsive media, you can use the following HTML structure:
*
* {
* ...
* html: match =>
* '<div style="position:relative; padding-bottom:100%; height:0">' +
* '<iframe src="..." frameborder="0" ' +
* 'style="position:absolute; width:100%; height:100%; top:0; left:0">' +
* '</iframe>' +
* '</div>'
* }
*
* @typedef {Object} module:media-embed/mediaembed~MediaEmbedProvider
* @property {String} name The name of the provider. Used e.g. when
* {@link module:media-embed/mediaembed~MediaEmbedConfig#removeProviders removing providers}.
* @property {RegExp|Array.<RegExp>} url The `RegExp` object (or array of objects) defining the URL of the media.
* If any URL matches the `RegExp`, it becomes the media in the editor model, as defined by the provider. The result
* of matching (output of `String.prototype.match()`) is passed to the `html` rendering function of the media.
*
* **Note:** You do not need to include the protocol (`http://`, `https://`) and `www` subdomain in your `RegExps`,
* they are stripped from the URLs before matching anyway.
* @property {Function} [html] (optional) The rendering function of the media. The function receives the entire matching
* array from the corresponding `url` `RegExp` as an argument, allowing rendering a dedicated
* preview of the media identified by a certain ID or a hash. When not defined, the media embed feature
* will use a generic media representation in the view and output data.
* Note that when
* {@link module:media-embed/mediaembed~MediaEmbedConfig#previewsInData `config.mediaEmbed.previewsInData`}
* is `true`, the rendering function **will always** be used for the media in the editor data output.
*/
/**
* The configuration of the {@link module:media-embed/mediaembed~MediaEmbed} feature.
*
* Read more in {@link module:media-embed/mediaembed~MediaEmbedConfig}.
*
* @member {module:media-embed/mediaembed~MediaEmbedConfig} module:core/editor/editorconfig~EditorConfig#mediaEmbed
*/
/**
* The configuration of the media embed features.
*
* Read more about {@glink features/media-embed#configuration configuring the media embed feature}.
*
* ClassicEditor
* .create( editorElement, {
* mediaEmbed: ... // Media embed feature options.
* } )
* .then( ... )
* .catch( ... );
*
* See {@link module:core/editor/editorconfig~EditorConfig all editor options}.
*
* @interface MediaEmbedConfig
*/
/**
* The default media providers supported by the editor.
*
* The names of providers with rendering functions (previews):
*
* * "dailymotion",
* * "spotify",
* * "youtube",
* * "vimeo"
*
* The names of providers without rendering functions:
*
* * "instagram",
* * "twitter",
* * "googleMaps",
* * "flickr",
* * "facebook"
*
* See the {@link module:media-embed/mediaembed~MediaEmbedProvider provider syntax} to learn more about
* different kinds of media and media providers.
*
* **Note**: The default media provider configuration may not support all possible media URLs,
* only the most common are included.
*
* Media without rendering functions are always represented in the data using the "semantic" markup. See
* {@link module:media-embed/mediaembed~MediaEmbedConfig#previewsInData `config.mediaEmbed.previewsInData`} to
* learn more about possible data outputs.
*
* The priority of media providers corresponds to the order of configuration. The first provider
* to match the URL is always used, even if there are other providers that support a particular URL.
* The URL is never matched against the remaining providers.
*
* To discard **all** default media providers, simply override this configuration with your own
* {@link module:media-embed/mediaembed~MediaEmbedProvider definitions}:
*
* ClassicEditor
* .create( editorElement, {
* plugins: [ MediaEmbed, ... ],
* mediaEmbed: {
* providers: [
* {
* name: 'myProvider',
* url: /^example\.com\/media\/(\w+)/,
* html: match => '...'
* },
* ...
* ]
* }
* } )
* .then( ... )
* .catch( ... );
*
* You can take inspiration from the default configuration of this feature which you can find in:
* https://github.com/ckeditor/ckeditor5-media-embed/blob/master/src/mediaembedediting.js
*
* To **extend** the list of default providers, use
* {@link module:media-embed/mediaembed~MediaEmbedConfig#extraProviders `config.mediaEmbed.extraProviders`}.
*
* To **remove** certain providers, use
* {@link module:media-embed/mediaembed~MediaEmbedConfig#removeProviders `config.mediaEmbed.removeProviders`}.
*
* @member {Array.<module:media-embed/mediaembed~MediaEmbedProvider>} module:media-embed/mediaembed~MediaEmbedConfig#providers
*/
/**
* The additional media providers supported by the editor. This configuration helps extend the default
* {@link module:media-embed/mediaembed~MediaEmbedConfig#providers}.
*
* ClassicEditor
* .create( editorElement, {
* plugins: [ MediaEmbed, ... ],
* mediaEmbed: {
* extraProviders: [
* {
* name: 'extraProvider',
* url: /^example\.com\/media\/(\w+)/,
* html: match => '...'
* },
* ...
* ]
* }
* } )
* .then( ... )
* .catch( ... );
*
* See the {@link module:media-embed/mediaembed~MediaEmbedProvider provider syntax} to learn more.
*
* @member {Array.<module:media-embed/mediaembed~MediaEmbedProvider>} module:media-embed/mediaembed~MediaEmbedConfig#extraProviders
*/
/**
* The list of media providers that should not be used despite being available in
* {@link module:media-embed/mediaembed~MediaEmbedConfig#providers `config.mediaEmbed.providers`} and
* {@link module:media-embed/mediaembed~MediaEmbedConfig#extraProviders `config.mediaEmbed.extraProviders`}
*
* mediaEmbed: {
* removeProviders: [ 'youtube', 'twitter' ]
* }
*
* @member {Array.<String>} module:media-embed/mediaembed~MediaEmbedConfig#removeProviders
*/
/**
* Overrides the element name used for "semantic" data.
*
* This is not relevant if {@link module:media-embed/mediaembed~MediaEmbedConfig#previewsInData `config.mediaEmbed.previewsInData`}
* is set to `true`.
*
* When not set, the feature produces the `<oembed>` tag:
*
* <figure class="media">
* <oembed url="https://url"></oembed>
* </figure>
*
* To override the element name with, for instance, the `o-embed` name:
*
* mediaEmbed: {
* elementName: 'o-embed'
* }
*
* This will produce semantic data with the `<o-embed>` tag:
*
* <figure class="media">
* <o-embed url="https://url"></o-embed>
* </figure>
*
* @default 'oembed'
* @member {String} [module:media-embed/mediaembed~MediaEmbedConfig#elementName]
*/
/**
* Controls the data format produced by the feature.
*
* When `false` (default), the feature produces "semantic" data, i.e. it does not include the preview of
* the media, just the `<oembed>` tag with the `url` attribute:
*
* <figure class="media">
* <oembed url="https://url"></oembed>
* </figure>
*
* When `true`, the media is represented in the output in the same way it looks in the editor,
* i.e. the media preview is saved to the database:
*
* <figure class="media">
* <div data-oembed-url="https://url">
* <iframe src="https://preview"></iframe>
* </div>
* </figure>
*
* **Note:** Media without preview are always represented in the data using the "semantic" markup
* regardless of the value of the `previewsInData`. Learn more about different kinds of media
* in the {@link module:media-embed/mediaembed~MediaEmbedConfig#providers `config.mediaEmbed.providers`}
* configuration description.
*
* @member {Boolean} [module:media-embed/mediaembed~MediaEmbedConfig#previewsInData=false]
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-media-embed/src/mediaembedcommand.js":
/*!*******************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-media-embed/src/mediaembedcommand.js ***!
\*******************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ MediaEmbedCommand)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_widget__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/widget */ "./node_modules/ckeditor5/src/widget.js");
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./utils */ "./node_modules/@ckeditor/ckeditor5-media-embed/src/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module media-embed/mediaembedcommand
*/
/**
* The insert media command.
*
* The command is registered by the {@link module:media-embed/mediaembedediting~MediaEmbedEditing} as `'mediaEmbed'`.
*
* To insert media at the current selection, execute the command and specify the URL:
*
* editor.execute( 'mediaEmbed', 'http://url.to.the/media' );
*
* @extends module:core/command~Command
*/
class MediaEmbedCommand extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Command {
/**
* @inheritDoc
*/
refresh() {
const model = this.editor.model;
const selection = model.document.selection;
const selectedMedia = (0,_utils__WEBPACK_IMPORTED_MODULE_2__.getSelectedMediaModelWidget)( selection );
this.value = selectedMedia ? selectedMedia.getAttribute( 'url' ) : null;
this.isEnabled = isMediaSelected( selection ) || isAllowedInParent( selection, model );
}
/**
* Executes the command, which either:
*
* * updates the URL of the selected media,
* * inserts the new media into the editor and puts the selection around it.
*
* @fires execute
* @param {String} url The URL of the media.
*/
execute( url ) {
const model = this.editor.model;
const selection = model.document.selection;
const selectedMedia = (0,_utils__WEBPACK_IMPORTED_MODULE_2__.getSelectedMediaModelWidget)( selection );
if ( selectedMedia ) {
model.change( writer => {
writer.setAttribute( 'url', url, selectedMedia );
} );
} else {
(0,_utils__WEBPACK_IMPORTED_MODULE_2__.insertMedia)( model, url, (0,ckeditor5_src_widget__WEBPACK_IMPORTED_MODULE_1__.findOptimalInsertionRange)( selection, model ) );
}
}
}
// Checks if the table is allowed in the parent.
//
// @param {module:engine/model/selection~Selection|module:engine/model/documentselection~DocumentSelection} selection
// @param {module:engine/model/model~Model} model
// @returns {Boolean}
function isAllowedInParent( selection, model ) {
const insertionRange = (0,ckeditor5_src_widget__WEBPACK_IMPORTED_MODULE_1__.findOptimalInsertionRange)( selection, model );
let parent = insertionRange.start.parent;
// The model.insertContent() will remove empty parent (unless it is a $root or a limit).
if ( parent.isEmpty && !model.schema.isLimit( parent ) ) {
parent = parent.parent;
}
return model.schema.checkChild( parent, 'media' );
}
// Checks if the media object is selected.
//
// @param {module:engine/model/selection~Selection|module:engine/model/documentselection~DocumentSelection} selection
// @returns {Boolean}
function isMediaSelected( selection ) {
const element = selection.getSelectedElement();
return !!element && element.name === 'media';
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-media-embed/src/mediaembedediting.js":
/*!*******************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-media-embed/src/mediaembedediting.js ***!
\*******************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ MediaEmbedEditing)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/* harmony import */ var _converters__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./converters */ "./node_modules/@ckeditor/ckeditor5-media-embed/src/converters.js");
/* harmony import */ var _mediaembedcommand__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./mediaembedcommand */ "./node_modules/@ckeditor/ckeditor5-media-embed/src/mediaembedcommand.js");
/* harmony import */ var _mediaregistry__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./mediaregistry */ "./node_modules/@ckeditor/ckeditor5-media-embed/src/mediaregistry.js");
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./utils */ "./node_modules/@ckeditor/ckeditor5-media-embed/src/utils.js");
/* harmony import */ var _theme_mediaembedediting_css__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ../theme/mediaembedediting.css */ "./node_modules/@ckeditor/ckeditor5-media-embed/theme/mediaembedediting.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module media-embed/mediaembedediting
*/
/**
* The media embed editing feature.
*
* @extends module:core/plugin~Plugin
*/
class MediaEmbedEditing extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'MediaEmbedEditing';
}
/**
* @inheritDoc
*/
constructor( editor ) {
super( editor );
editor.config.define( 'mediaEmbed', {
elementName: 'oembed',
providers: [
{
name: 'dailymotion',
url: /^dailymotion\.com\/video\/(\w+)/,
html: match => {
const id = match[ 1 ];
return (
'<div style="position: relative; padding-bottom: 100%; height: 0; ">' +
`<iframe src="https://www.dailymotion.com/embed/video/${ id }" ` +
'style="position: absolute; width: 100%; height: 100%; top: 0; left: 0;" ' +
'frameborder="0" width="480" height="270" allowfullscreen allow="autoplay">' +
'</iframe>' +
'</div>'
);
}
},
{
name: 'spotify',
url: [
/^open\.spotify\.com\/(artist\/\w+)/,
/^open\.spotify\.com\/(album\/\w+)/,
/^open\.spotify\.com\/(track\/\w+)/
],
html: match => {
const id = match[ 1 ];
return (
'<div style="position: relative; padding-bottom: 100%; height: 0; padding-bottom: 126%;">' +
`<iframe src="https://open.spotify.com/embed/${ id }" ` +
'style="position: absolute; width: 100%; height: 100%; top: 0; left: 0;" ' +
'frameborder="0" allowtransparency="true" allow="encrypted-media">' +
'</iframe>' +
'</div>'
);
}
},
{
name: 'youtube',
url: [
/^(?:m\.)?youtube\.com\/watch\?v=([\w-]+)/,
/^(?:m\.)?youtube\.com\/v\/([\w-]+)/,
/^youtube\.com\/embed\/([\w-]+)/,
/^youtu\.be\/([\w-]+)/
],
html: match => {
const id = match[ 1 ];
return (
'<div style="position: relative; padding-bottom: 100%; height: 0; padding-bottom: 56.2493%;">' +
`<iframe src="https://www.youtube.com/embed/${ id }" ` +
'style="position: absolute; width: 100%; height: 100%; top: 0; left: 0;" ' +
'frameborder="0" allow="autoplay; encrypted-media" allowfullscreen>' +
'</iframe>' +
'</div>'
);
}
},
{
name: 'vimeo',
url: [
/^vimeo\.com\/(\d+)/,
/^vimeo\.com\/[^/]+\/[^/]+\/video\/(\d+)/,
/^vimeo\.com\/album\/[^/]+\/video\/(\d+)/,
/^vimeo\.com\/channels\/[^/]+\/(\d+)/,
/^vimeo\.com\/groups\/[^/]+\/videos\/(\d+)/,
/^vimeo\.com\/ondemand\/[^/]+\/(\d+)/,
/^player\.vimeo\.com\/video\/(\d+)/
],
html: match => {
const id = match[ 1 ];
return (
'<div style="position: relative; padding-bottom: 100%; height: 0; padding-bottom: 56.2493%;">' +
`<iframe src="https://player.vimeo.com/video/${ id }" ` +
'style="position: absolute; width: 100%; height: 100%; top: 0; left: 0;" ' +
'frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen>' +
'</iframe>' +
'</div>'
);
}
},
{
name: 'instagram',
url: /^instagram\.com\/p\/(\w+)/
},
{
name: 'twitter',
url: /^twitter\.com/
},
{
name: 'googleMaps',
url: [
/^google\.com\/maps/,
/^goo\.gl\/maps/,
/^maps\.google\.com/,
/^maps\.app\.goo\.gl/
]
},
{
name: 'flickr',
url: /^flickr\.com/
},
{
name: 'facebook',
url: /^facebook\.com/
}
]
} );
/**
* The media registry managing the media providers in the editor.
*
* @member {module:media-embed/mediaregistry~MediaRegistry} #registry
*/
this.registry = new _mediaregistry__WEBPACK_IMPORTED_MODULE_4__["default"]( editor.locale, editor.config.get( 'mediaEmbed' ) );
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const schema = editor.model.schema;
const t = editor.t;
const conversion = editor.conversion;
const renderMediaPreview = editor.config.get( 'mediaEmbed.previewsInData' );
const elementName = editor.config.get( 'mediaEmbed.elementName' );
const registry = this.registry;
editor.commands.add( 'mediaEmbed', new _mediaembedcommand__WEBPACK_IMPORTED_MODULE_3__["default"]( editor ) );
// Configure the schema.
schema.register( 'media', {
isObject: true,
isBlock: true,
allowWhere: '$block',
allowAttributes: [ 'url' ]
} );
// Model -> Data
conversion.for( 'dataDowncast' ).elementToStructure( {
model: 'media',
view: ( modelElement, { writer } ) => {
const url = modelElement.getAttribute( 'url' );
return (0,_utils__WEBPACK_IMPORTED_MODULE_5__.createMediaFigureElement)( writer, registry, url, {
elementName,
renderMediaPreview: url && renderMediaPreview
} );
}
} );
// Model -> Data (url -> data-oembed-url)
conversion.for( 'dataDowncast' ).add(
(0,_converters__WEBPACK_IMPORTED_MODULE_2__.modelToViewUrlAttributeConverter)( registry, {
elementName,
renderMediaPreview
} ) );
// Model -> View (element)
conversion.for( 'editingDowncast' ).elementToStructure( {
model: 'media',
view: ( modelElement, { writer } ) => {
const url = modelElement.getAttribute( 'url' );
const figure = (0,_utils__WEBPACK_IMPORTED_MODULE_5__.createMediaFigureElement)( writer, registry, url, {
elementName,
renderForEditingView: true
} );
return (0,_utils__WEBPACK_IMPORTED_MODULE_5__.toMediaWidget)( figure, writer, t( 'media widget' ) );
}
} );
// Model -> View (url -> data-oembed-url)
conversion.for( 'editingDowncast' ).add(
(0,_converters__WEBPACK_IMPORTED_MODULE_2__.modelToViewUrlAttributeConverter)( registry, {
elementName,
renderForEditingView: true
} ) );
// View -> Model (data-oembed-url -> url)
conversion.for( 'upcast' )
// Upcast semantic media.
.elementToElement( {
view: element => [ 'oembed', elementName ].includes( element.name ) && element.getAttribute( 'url' ) ?
{ name: true } :
null,
model: ( viewMedia, { writer } ) => {
const url = viewMedia.getAttribute( 'url' );
if ( registry.hasMedia( url ) ) {
return writer.createElement( 'media', { url } );
}
}
} )
// Upcast non-semantic media.
.elementToElement( {
view: {
name: 'div',
attributes: {
'data-oembed-url': true
}
},
model: ( viewMedia, { writer } ) => {
const url = viewMedia.getAttribute( 'data-oembed-url' );
if ( registry.hasMedia( url ) ) {
return writer.createElement( 'media', { url } );
}
}
} )
// Consume `<figure class="media">` elements, that were left after upcast.
.add( dispatcher => {
dispatcher.on( 'element:figure', converter );
function converter( evt, data, conversionApi ) {
if ( !conversionApi.consumable.consume( data.viewItem, { name: true, classes: 'media' } ) ) {
return;
}
const { modelRange, modelCursor } = conversionApi.convertChildren( data.viewItem, data.modelCursor );
data.modelRange = modelRange;
data.modelCursor = modelCursor;
const modelElement = (0,ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__.first)( modelRange.getItems() );
if ( !modelElement ) {
// Revert consumed figure so other features can convert it.
conversionApi.consumable.revert( data.viewItem, { name: true, classes: 'media' } );
}
}
} );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-media-embed/src/mediaembedui.js":
/*!**************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-media-embed/src/mediaembedui.js ***!
\**************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ MediaEmbedUI)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/ui */ "./node_modules/ckeditor5/src/ui.js");
/* harmony import */ var _ui_mediaformview__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./ui/mediaformview */ "./node_modules/@ckeditor/ckeditor5-media-embed/src/ui/mediaformview.js");
/* harmony import */ var _mediaembedediting__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./mediaembedediting */ "./node_modules/@ckeditor/ckeditor5-media-embed/src/mediaembedediting.js");
/* harmony import */ var _theme_icons_media_svg__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../theme/icons/media.svg */ "./node_modules/@ckeditor/ckeditor5-media-embed/theme/icons/media.svg");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module media-embed/mediaembedui
*/
/**
* The media embed UI plugin.
*
* @extends module:core/plugin~Plugin
*/
class MediaEmbedUI extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [ _mediaembedediting__WEBPACK_IMPORTED_MODULE_3__["default"] ];
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'MediaEmbedUI';
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const command = editor.commands.get( 'mediaEmbed' );
const registry = editor.plugins.get( _mediaembedediting__WEBPACK_IMPORTED_MODULE_3__["default"] ).registry;
editor.ui.componentFactory.add( 'mediaEmbed', locale => {
const dropdown = (0,ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.createDropdown)( locale );
const mediaForm = new _ui_mediaformview__WEBPACK_IMPORTED_MODULE_2__["default"]( getFormValidators( editor.t, registry ), editor.locale );
this._setUpDropdown( dropdown, mediaForm, command, editor );
this._setUpForm( dropdown, mediaForm, command );
return dropdown;
} );
}
/**
* @private
* @param {module:ui/dropdown/dropdownview~DropdownView} dropdown
* @param {module:ui/view~View} form
* @param {module:media-embed/mediaembedcommand~MediaEmbedCommand} command
*/
_setUpDropdown( dropdown, form, command ) {
const editor = this.editor;
const t = editor.t;
const button = dropdown.buttonView;
dropdown.bind( 'isEnabled' ).to( command );
dropdown.panelView.children.add( form );
button.set( {
label: t( 'Insert media' ),
icon: _theme_icons_media_svg__WEBPACK_IMPORTED_MODULE_4__["default"],
tooltip: true
} );
// Note: Use the low priority to make sure the following listener starts working after the
// default action of the drop-down is executed (i.e. the panel showed up). Otherwise, the
// invisible form/input cannot be focused/selected.
button.on( 'open', () => {
form.disableCssTransitions();
// Make sure that each time the panel shows up, the URL field remains in sync with the value of
// the command. If the user typed in the input, then canceled (`urlInputView#fieldView#value` stays
// unaltered) and re-opened it without changing the value of the media command (e.g. because they
// didn't change the selection), they would see the old value instead of the actual value of the
// command.
form.url = command.value || '';
form.urlInputView.fieldView.select();
form.focus();
form.enableCssTransitions();
}, { priority: 'low' } );
dropdown.on( 'submit', () => {
if ( form.isValid() ) {
editor.execute( 'mediaEmbed', form.url );
closeUI();
}
} );
dropdown.on( 'change:isOpen', () => form.resetFormStatus() );
dropdown.on( 'cancel', () => closeUI() );
function closeUI() {
editor.editing.view.focus();
dropdown.isOpen = false;
}
}
/**
* @private
* @param {module:ui/dropdown/dropdownview~DropdownView} dropdown
* @param {module:ui/view~View} form
* @param {module:media-embed/mediaembedcommand~MediaEmbedCommand} command
*/
_setUpForm( dropdown, form, command ) {
form.delegate( 'submit', 'cancel' ).to( dropdown );
form.urlInputView.bind( 'value' ).to( command, 'value' );
// Form elements should be read-only when corresponding commands are disabled.
form.urlInputView.bind( 'isReadOnly' ).to( command, 'isEnabled', value => !value );
}
}
function getFormValidators( t, registry ) {
return [
form => {
if ( !form.url.length ) {
return t( 'The URL must not be empty.' );
}
},
form => {
if ( !registry.hasMedia( form.url ) ) {
return t( 'This media URL is not supported.' );
}
}
];
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-media-embed/src/mediaregistry.js":
/*!***************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-media-embed/src/mediaregistry.js ***!
\***************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ MediaRegistry)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/ui */ "./node_modules/ckeditor5/src/ui.js");
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/* harmony import */ var _theme_icons_media_placeholder_svg__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../theme/icons/media-placeholder.svg */ "./node_modules/@ckeditor/ckeditor5-media-embed/theme/icons/media-placeholder.svg");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module media-embed/mediaregistry
*/
const mediaPlaceholderIconViewBox = '0 0 64 42';
/**
* A bridge between the raw media content provider definitions and the editor view content.
*
* It helps translating media URLs to corresponding {@link module:engine/view/element~Element view elements}.
*
* Mostly used by the {@link module:media-embed/mediaembedediting~MediaEmbedEditing} plugin.
*/
class MediaRegistry {
/**
* Creates an instance of the {@link module:media-embed/mediaregistry~MediaRegistry} class.
*
* @param {module:utils/locale~Locale} locale The localization services instance.
* @param {module:media-embed/mediaembed~MediaEmbedConfig} config The configuration of the media embed feature.
*/
constructor( locale, config ) {
const providers = config.providers;
const extraProviders = config.extraProviders || [];
const removedProviders = new Set( config.removeProviders );
const providerDefinitions = providers
.concat( extraProviders )
.filter( provider => {
const name = provider.name;
if ( !name ) {
/**
* One of the providers (or extra providers) specified in the media embed configuration
* has no name and will not be used by the editor. In order to get this media
* provider working, double check your editor configuration.
*
* @error media-embed-no-provider-name
*/
(0,ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__.logWarning)( 'media-embed-no-provider-name', { provider } );
return false;
}
return !removedProviders.has( name );
} );
/**
* The {@link module:utils/locale~Locale} instance.
*
* @member {module:utils/locale~Locale}
*/
this.locale = locale;
/**
* The media provider definitions available for the registry. Usually corresponding with the
* {@link module:media-embed/mediaembed~MediaEmbedConfig media configuration}.
*
* @member {Array}
*/
this.providerDefinitions = providerDefinitions;
}
/**
* Checks whether the passed URL is representing a certain media type allowed in the editor.
*
* @param {String} url The URL to be checked
* @returns {Boolean}
*/
hasMedia( url ) {
return !!this._getMedia( url );
}
/**
* For the given media URL string and options, it returns the {@link module:engine/view/element~Element view element}
* representing that media.
*
* **Note:** If no URL is specified, an empty view element is returned.
*
* @param {module:engine/view/downcastwriter~DowncastWriter} writer The view writer used to produce a view element.
* @param {String} url The URL to be translated into a view element.
* @param {Object} options
* @param {String} [options.elementName]
* @param {Boolean} [options.renderMediaPreview]
* @param {Boolean} [options.renderForEditingView]
* @returns {module:engine/view/element~Element}
*/
getMediaViewElement( writer, url, options ) {
return this._getMedia( url ).getViewElement( writer, options );
}
/**
* Returns a `Media` instance for the given URL.
*
* @protected
* @param {String} url The URL of the media.
* @returns {module:media-embed/mediaregistry~Media|null} The `Media` instance or `null` when there is none.
*/
_getMedia( url ) {
if ( !url ) {
return new Media( this.locale );
}
url = url.trim();
for ( const definition of this.providerDefinitions ) {
const previewRenderer = definition.html;
const pattern = (0,ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__.toArray)( definition.url );
for ( const subPattern of pattern ) {
const match = this._getUrlMatches( url, subPattern );
if ( match ) {
return new Media( this.locale, url, match, previewRenderer );
}
}
}
return null;
}
/**
* Tries to match `url` to `pattern`.
*
* @private
* @param {String} url The URL of the media.
* @param {RegExp} pattern The pattern that should accept the media URL.
* @returns {Array|null}
*/
_getUrlMatches( url, pattern ) {
// 1. Try to match without stripping the protocol and "www" subdomain.
let match = url.match( pattern );
if ( match ) {
return match;
}
// 2. Try to match after stripping the protocol.
let rawUrl = url.replace( /^https?:\/\//, '' );
match = rawUrl.match( pattern );
if ( match ) {
return match;
}
// 3. Try to match after stripping the "www" subdomain.
rawUrl = rawUrl.replace( /^www\./, '' );
match = rawUrl.match( pattern );
if ( match ) {
return match;
}
return null;
}
}
/**
* Represents media defined by the provider configuration.
*
* It can be rendered to the {@link module:engine/view/element~Element view element} and used in the editing or data pipeline.
*
* @private
*/
class Media {
constructor( locale, url, match, previewRenderer ) {
/**
* The URL this Media instance represents.
*
* @member {String}
*/
this.url = this._getValidUrl( url );
/**
* Shorthand for {@link module:utils/locale~Locale#t}.
*
* @see module:utils/locale~Locale#t
* @method
*/
this._t = locale.t;
/**
* The output of the `RegExp.match` which validated the {@link #url} of this media.
*
* @member {Object}
*/
this._match = match;
/**
* The function returning the HTML string preview of this media.
*
* @member {Function}
*/
this._previewRenderer = previewRenderer;
}
/**
* Returns the view element representation of the media.
*
* @param {module:engine/view/downcastwriter~DowncastWriter} writer The view writer used to produce a view element.
* @param {Object} options
* @param {String} [options.elementName]
* @param {Boolean} [options.renderMediaPreview]
* @param {Boolean} [options.renderForEditingView]
* @returns {module:engine/view/element~Element}
*/
getViewElement( writer, options ) {
const attributes = {};
let viewElement;
if ( options.renderForEditingView || ( options.renderMediaPreview && this.url && this._previewRenderer ) ) {
if ( this.url ) {
attributes[ 'data-oembed-url' ] = this.url;
}
if ( options.renderForEditingView ) {
attributes.class = 'ck-media__wrapper';
}
const mediaHtml = this._getPreviewHtml( options );
viewElement = writer.createRawElement( 'div', attributes, ( domElement, domConverter ) => {
domConverter.setContentOf( domElement, mediaHtml );
} );
} else {
if ( this.url ) {
attributes.url = this.url;
}
viewElement = writer.createEmptyElement( options.elementName, attributes );
}
writer.setCustomProperty( 'media-content', true, viewElement );
return viewElement;
}
/**
* Returns the HTML string of the media content preview.
*
* @param {module:engine/view/downcastwriter~DowncastWriter} writer The view writer used to produce a view element.
* @param {Object} options
* @param {Boolean} [options.renderForEditingView]
* @returns {String}
*/
_getPreviewHtml( options ) {
if ( this._previewRenderer ) {
return this._previewRenderer( this._match );
} else {
// The placeholder only makes sense for editing view and media which have URLs.
// Placeholder is never displayed in data and URL-less media have no content.
if ( this.url && options.renderForEditingView ) {
return this._getPlaceholderHtml();
}
return '';
}
}
/**
* Returns the placeholder HTML when the media has no content preview.
*
* @returns {String}
*/
_getPlaceholderHtml() {
const tooltip = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.TooltipView();
const icon = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.IconView();
tooltip.text = this._t( 'Open media in new tab' );
icon.content = _theme_icons_media_placeholder_svg__WEBPACK_IMPORTED_MODULE_2__["default"];
icon.viewBox = mediaPlaceholderIconViewBox;
const placeholder = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.Template( {
tag: 'div',
attributes: {
class: 'ck ck-reset_all ck-media__placeholder'
},
children: [
{
tag: 'div',
attributes: {
class: 'ck-media__placeholder__icon'
},
children: [ icon ]
},
{
tag: 'a',
attributes: {
class: 'ck-media__placeholder__url',
target: '_blank',
rel: 'noopener noreferrer',
href: this.url
},
children: [
{
tag: 'span',
attributes: {
class: 'ck-media__placeholder__url__text'
},
children: [ this.url ]
},
tooltip
]
}
]
} ).render();
return placeholder.outerHTML;
}
/**
* Returns the full URL to the specified media.
*
* @param {String} url The URL of the media.
* @returns {String|null}
*/
_getValidUrl( url ) {
if ( !url ) {
return null;
}
if ( url.match( /^https?/ ) ) {
return url;
}
return 'https://' + url;
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-media-embed/src/ui/mediaformview.js":
/*!******************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-media-embed/src/ui/mediaformview.js ***!
\******************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ MediaFormView)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/ui */ "./node_modules/ckeditor5/src/ui.js");
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var _ckeditor_ckeditor5_ui_theme_components_responsive_form_responsiveform_css__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @ckeditor/ckeditor5-ui/theme/components/responsive-form/responsiveform.css */ "./node_modules/@ckeditor/ckeditor5-ui/theme/components/responsive-form/responsiveform.css");
/* harmony import */ var _theme_mediaform_css__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../../theme/mediaform.css */ "./node_modules/@ckeditor/ckeditor5-media-embed/theme/mediaform.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module media-embed/ui/mediaformview
*/
// See: #8833.
// eslint-disable-next-line ckeditor5-rules/ckeditor-imports
/**
* The media form view controller class.
*
* See {@link module:media-embed/ui/mediaformview~MediaFormView}.
*
* @extends module:ui/view~View
*/
class MediaFormView extends ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.View {
/**
* @param {Array.<Function>} validators Form validators used by {@link #isValid}.
* @param {module:utils/locale~Locale} [locale] The localization services instance.
*/
constructor( validators, locale ) {
super( locale );
const t = locale.t;
/**
* Tracks information about the DOM focus in the form.
*
* @readonly
* @member {module:utils/focustracker~FocusTracker}
*/
this.focusTracker = new ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__.FocusTracker();
/**
* An instance of the {@link module:utils/keystrokehandler~KeystrokeHandler}.
*
* @readonly
* @member {module:utils/keystrokehandler~KeystrokeHandler}
*/
this.keystrokes = new ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__.KeystrokeHandler();
/**
* The value of the URL input.
*
* @member {String} #mediaURLInputValue
* @observable
*/
this.set( 'mediaURLInputValue', '' );
/**
* The URL input view.
*
* @member {module:ui/labeledfield/labeledfieldview~LabeledFieldView}
*/
this.urlInputView = this._createUrlInput();
/**
* The Save button view.
*
* @member {module:ui/button/buttonview~ButtonView}
*/
this.saveButtonView = this._createButton( t( 'Save' ), ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_2__.icons.check, 'ck-button-save' );
this.saveButtonView.type = 'submit';
this.saveButtonView.bind( 'isEnabled' ).to( this, 'mediaURLInputValue', value => !!value );
/**
* The Cancel button view.
*
* @member {module:ui/button/buttonview~ButtonView}
*/
this.cancelButtonView = this._createButton( t( 'Cancel' ), ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_2__.icons.cancel, 'ck-button-cancel', 'cancel' );
/**
* A collection of views that can be focused in the form.
*
* @readonly
* @protected
* @member {module:ui/viewcollection~ViewCollection}
*/
this._focusables = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.ViewCollection();
/**
* Helps cycling over {@link #_focusables} in the form.
*
* @readonly
* @protected
* @member {module:ui/focuscycler~FocusCycler}
*/
this._focusCycler = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.FocusCycler( {
focusables: this._focusables,
focusTracker: this.focusTracker,
keystrokeHandler: this.keystrokes,
actions: {
// Navigate form fields backwards using the <kbd>Shift</kbd> + <kbd>Tab</kbd> keystroke.
focusPrevious: 'shift + tab',
// Navigate form fields forwards using the <kbd>Tab</kbd> key.
focusNext: 'tab'
}
} );
/**
* An array of form validators used by {@link #isValid}.
*
* @readonly
* @protected
* @member {Array.<Function>}
*/
this._validators = validators;
this.setTemplate( {
tag: 'form',
attributes: {
class: [
'ck',
'ck-media-form',
'ck-responsive-form'
],
tabindex: '-1'
},
children: [
this.urlInputView,
this.saveButtonView,
this.cancelButtonView
]
} );
(0,ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.injectCssTransitionDisabler)( this );
/**
* The default info text for the {@link #urlInputView}.
*
* @private
* @member {String} #_urlInputViewInfoDefault
*/
/**
* The info text with an additional tip for the {@link #urlInputView},
* displayed when the input has some value.
*
* @private
* @member {String} #_urlInputViewInfoTip
*/
}
/**
* @inheritDoc
*/
render() {
super.render();
(0,ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.submitHandler)( {
view: this
} );
const childViews = [
this.urlInputView,
this.saveButtonView,
this.cancelButtonView
];
childViews.forEach( v => {
// Register the view as focusable.
this._focusables.add( v );
// Register the view in the focus tracker.
this.focusTracker.add( v.element );
} );
// Start listening for the keystrokes coming from #element.
this.keystrokes.listenTo( this.element );
const stopPropagation = data => data.stopPropagation();
// Since the form is in the dropdown panel which is a child of the toolbar, the toolbar's
// keystroke handler would take over the key management in the URL input. We need to prevent
// this ASAP. Otherwise, the basic caret movement using the arrow keys will be impossible.
this.keystrokes.set( 'arrowright', stopPropagation );
this.keystrokes.set( 'arrowleft', stopPropagation );
this.keystrokes.set( 'arrowup', stopPropagation );
this.keystrokes.set( 'arrowdown', stopPropagation );
// Intercept the `selectstart` event, which is blocked by default because of the default behavior
// of the DropdownView#panelView.
// TODO: blocking `selectstart` in the #panelView should be configurable per–drop–down instance.
this.listenTo( this.urlInputView.element, 'selectstart', ( evt, domEvt ) => {
domEvt.stopPropagation();
}, { priority: 'high' } );
}
/**
* @inheritDoc
*/
destroy() {
super.destroy();
this.focusTracker.destroy();
this.keystrokes.destroy();
}
/**
* Focuses the fist {@link #_focusables} in the form.
*/
focus() {
this._focusCycler.focusFirst();
}
/**
* The native DOM `value` of the {@link #urlInputView} element.
*
* **Note**: Do not confuse it with the {@link module:ui/inputtext/inputtextview~InputTextView#value}
* which works one way only and may not represent the actual state of the component in the DOM.
*
* @type {String}
*/
get url() {
return this.urlInputView.fieldView.element.value.trim();
}
set url( url ) {
this.urlInputView.fieldView.element.value = url.trim();
}
/**
* Validates the form and returns `false` when some fields are invalid.
*
* @returns {Boolean}
*/
isValid() {
this.resetFormStatus();
for ( const validator of this._validators ) {
const errorText = validator( this );
// One error per field is enough.
if ( errorText ) {
// Apply updated error.
this.urlInputView.errorText = errorText;
return false;
}
}
return true;
}
/**
* Cleans up the supplementary error and information text of the {@link #urlInputView}
* bringing them back to the state when the form has been displayed for the first time.
*
* See {@link #isValid}.
*/
resetFormStatus() {
this.urlInputView.errorText = null;
this.urlInputView.infoText = this._urlInputViewInfoDefault;
}
/**
* Creates a labeled input view.
*
* @private
* @returns {module:ui/labeledfield/labeledfieldview~LabeledFieldView} Labeled input view instance.
*/
_createUrlInput() {
const t = this.locale.t;
const labeledInput = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.LabeledFieldView( this.locale, ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.createLabeledInputText );
const inputField = labeledInput.fieldView;
this._urlInputViewInfoDefault = t( 'Paste the media URL in the input.' );
this._urlInputViewInfoTip = t( 'Tip: Paste the URL into the content to embed faster.' );
labeledInput.label = t( 'Media URL' );
labeledInput.infoText = this._urlInputViewInfoDefault;
inputField.on( 'input', () => {
// Display the tip text only when there is some value. Otherwise fall back to the default info text.
labeledInput.infoText = inputField.element.value ? this._urlInputViewInfoTip : this._urlInputViewInfoDefault;
this.mediaURLInputValue = inputField.element.value.trim();
} );
return labeledInput;
}
/**
* Creates a button view.
*
* @private
* @param {String} label The button label.
* @param {String} icon The button icon.
* @param {String} className The additional button CSS class name.
* @param {String} [eventName] An event name that the `ButtonView#execute` event will be delegated to.
* @returns {module:ui/button/buttonview~ButtonView} The button view instance.
*/
_createButton( label, icon, className, eventName ) {
const button = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.ButtonView( this.locale );
button.set( {
label,
icon,
tooltip: true
} );
button.extendTemplate( {
attributes: {
class: className
}
} );
if ( eventName ) {
button.delegate( 'execute' ).to( this, eventName );
}
return button;
}
}
/**
* Fired when the form view is submitted (when one of the children triggered the submit event),
* e.g. click on {@link #saveButtonView}.
*
* @event submit
*/
/**
* Fired when the form view is canceled, e.g. by a click on {@link #cancelButtonView}.
*
* @event cancel
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-media-embed/src/utils.js":
/*!*******************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-media-embed/src/utils.js ***!
\*******************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "createMediaFigureElement": () => (/* binding */ createMediaFigureElement),
/* harmony export */ "getSelectedMediaModelWidget": () => (/* binding */ getSelectedMediaModelWidget),
/* harmony export */ "getSelectedMediaViewWidget": () => (/* binding */ getSelectedMediaViewWidget),
/* harmony export */ "insertMedia": () => (/* binding */ insertMedia),
/* harmony export */ "isMediaWidget": () => (/* binding */ isMediaWidget),
/* harmony export */ "toMediaWidget": () => (/* binding */ toMediaWidget)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_widget__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/widget */ "./node_modules/ckeditor5/src/widget.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module media-embed/utils
*/
/**
* Converts a given {@link module:engine/view/element~Element} to a media embed widget:
* * Adds a {@link module:engine/view/element~Element#_setCustomProperty custom property} allowing to recognize the media widget element.
* * Calls the {@link module:widget/utils~toWidget} function with the proper element's label creator.
*
* @param {module:engine/view/element~Element} viewElement
* @param {module:engine/view/downcastwriter~DowncastWriter} writer An instance of the view writer.
* @param {String} label The element's label.
* @returns {module:engine/view/element~Element}
*/
function toMediaWidget( viewElement, writer, label ) {
writer.setCustomProperty( 'media', true, viewElement );
return (0,ckeditor5_src_widget__WEBPACK_IMPORTED_MODULE_0__.toWidget)( viewElement, writer, { label } );
}
/**
* Returns a media widget editing view element if one is selected.
*
* @param {module:engine/view/selection~Selection|module:engine/view/documentselection~DocumentSelection} selection
* @returns {module:engine/view/element~Element|null}
*/
function getSelectedMediaViewWidget( selection ) {
const viewElement = selection.getSelectedElement();
if ( viewElement && isMediaWidget( viewElement ) ) {
return viewElement;
}
return null;
}
/**
* Checks if a given view element is a media widget.
*
* @param {module:engine/view/element~Element} viewElement
* @returns {Boolean}
*/
function isMediaWidget( viewElement ) {
return !!viewElement.getCustomProperty( 'media' ) && (0,ckeditor5_src_widget__WEBPACK_IMPORTED_MODULE_0__.isWidget)( viewElement );
}
/**
* Creates a view element representing the media. Either a "semantic" one for the data pipeline:
*
* <figure class="media">
* <oembed url="foo"></oembed>
* </figure>
*
* or a "non-semantic" (for the editing view pipeline):
*
* <figure class="media">
* <div data-oembed-url="foo">[ non-semantic media preview for "foo" ]</div>
* </figure>
*
* @param {module:engine/view/downcastwriter~DowncastWriter} writer
* @param {module:media-embed/mediaregistry~MediaRegistry} registry
* @param {String} url
* @param {Object} options
* @param {String} [options.elementName]
* @param {Boolean} [options.useSemanticWrapper]
* @param {Boolean} [options.renderForEditingView]
* @returns {module:engine/view/containerelement~ContainerElement}
*/
function createMediaFigureElement( writer, registry, url, options ) {
return writer.createContainerElement( 'figure', { class: 'media' }, [
registry.getMediaViewElement( writer, url, options ),
writer.createSlot()
] );
}
/**
* Returns a selected media element in the model, if any.
*
* @param {module:engine/model/selection~Selection} selection
* @returns {module:engine/model/element~Element|null}
*/
function getSelectedMediaModelWidget( selection ) {
const selectedElement = selection.getSelectedElement();
if ( selectedElement && selectedElement.is( 'element', 'media' ) ) {
return selectedElement;
}
return null;
}
/**
* Creates a media element and inserts it into the model.
*
* **Note**: This method will use {@link module:engine/model/model~Model#insertContent `model.insertContent()`} logic of inserting content
* if no `insertPosition` is passed.
*
* @param {module:engine/model/model~Model} model
* @param {String} url An URL of an embeddable media.
* @param {module:engine/model/range~Range} [insertRange] The range to insert the media. If not specified,
* the default behavior of {@link module:engine/model/model~Model#insertContent `model.insertContent()`} will
* be applied.
*/
function insertMedia( model, url, insertRange ) {
model.change( writer => {
const mediaElement = writer.createElement( 'media', { url } );
model.insertContent( mediaElement, insertRange );
writer.setSelection( mediaElement, 'on' );
} );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-paragraph/src/index.js":
/*!*****************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-paragraph/src/index.js ***!
\*****************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "Paragraph": () => (/* reexport safe */ _paragraph__WEBPACK_IMPORTED_MODULE_0__["default"]),
/* harmony export */ "ParagraphButtonUI": () => (/* reexport safe */ _paragraphbuttonui__WEBPACK_IMPORTED_MODULE_1__["default"])
/* harmony export */ });
/* harmony import */ var _paragraph__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./paragraph */ "./node_modules/@ckeditor/ckeditor5-paragraph/src/paragraph.js");
/* harmony import */ var _paragraphbuttonui__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./paragraphbuttonui */ "./node_modules/@ckeditor/ckeditor5-paragraph/src/paragraphbuttonui.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module paragraph
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-paragraph/src/insertparagraphcommand.js":
/*!**********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-paragraph/src/insertparagraphcommand.js ***!
\**********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ InsertParagraphCommand)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_core_src_command__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-core/src/command */ "./node_modules/@ckeditor/ckeditor5-core/src/command.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module paragraph/insertparagraphcommand
*/
/**
* The insert paragraph command. It inserts a new paragraph at a specific
* {@link module:engine/model/position~Position document position}.
*
* // Insert a new paragraph before an element in the document.
* editor.execute( 'insertParagraph', {
* position: editor.model.createPositionBefore( element )
* } );
*
* If a paragraph is disallowed in the context of the specific position, the command
* will attempt to split position ancestors to find a place where it is possible
* to insert a paragraph.
*
* **Note**: This command moves the selection to the inserted paragraph.
*
* @extends module:core/command~Command
*/
class InsertParagraphCommand extends _ckeditor_ckeditor5_core_src_command__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* Executes the command.
*
* @param {Object} options Options for the executed command.
* @param {module:engine/model/position~Position} options.position The model position at which
* the new paragraph will be inserted.
* @fires execute
*/
execute( options ) {
const model = this.editor.model;
let position = options.position;
model.change( writer => {
const paragraph = writer.createElement( 'paragraph' );
if ( !model.schema.checkChild( position.parent, paragraph ) ) {
const allowedParent = model.schema.findAllowedParent( position, paragraph );
// It could be there's no ancestor limit that would allow paragraph.
// In theory, "paragraph" could be disallowed even in the "$root".
if ( !allowedParent ) {
return;
}
position = writer.split( position, allowedParent ).position;
}
model.insertContent( paragraph, position );
writer.setSelection( paragraph, 'in' );
} );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-paragraph/src/paragraph.js":
/*!*********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-paragraph/src/paragraph.js ***!
\*********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ Paragraph)
/* harmony export */ });
/* harmony import */ var _paragraphcommand__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./paragraphcommand */ "./node_modules/@ckeditor/ckeditor5-paragraph/src/paragraphcommand.js");
/* harmony import */ var _insertparagraphcommand__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./insertparagraphcommand */ "./node_modules/@ckeditor/ckeditor5-paragraph/src/insertparagraphcommand.js");
/* harmony import */ var _ckeditor_ckeditor5_core_src_plugin__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @ckeditor/ckeditor5-core/src/plugin */ "./node_modules/@ckeditor/ckeditor5-core/src/plugin.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module paragraph/paragraph
*/
/**
* The paragraph feature for the editor.
*
* It introduces the `<paragraph>` element in the model which renders as a `<p>` element in the DOM and data.
*
* It also brings two editors commands:
*
* * The {@link module:paragraph/paragraphcommand~ParagraphCommand `'paragraph'`} command that converts all
* blocks in the model selection into paragraphs.
* * The {@link module:paragraph/insertparagraphcommand~InsertParagraphCommand `'insertParagraph'`} command
* that inserts a new paragraph at a specified location in the model.
*
* @extends module:core/plugin~Plugin
*/
class Paragraph extends _ckeditor_ckeditor5_core_src_plugin__WEBPACK_IMPORTED_MODULE_2__["default"] {
/**
* @inheritDoc
*/
static get pluginName() {
return 'Paragraph';
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const model = editor.model;
editor.commands.add( 'paragraph', new _paragraphcommand__WEBPACK_IMPORTED_MODULE_0__["default"]( editor ) );
editor.commands.add( 'insertParagraph', new _insertparagraphcommand__WEBPACK_IMPORTED_MODULE_1__["default"]( editor ) );
// Schema.
model.schema.register( 'paragraph', { inheritAllFrom: '$block' } );
editor.conversion.elementToElement( { model: 'paragraph', view: 'p' } );
// Conversion for paragraph-like elements which has not been converted by any plugin.
editor.conversion.for( 'upcast' ).elementToElement( {
model: ( viewElement, { writer } ) => {
if ( !Paragraph.paragraphLikeElements.has( viewElement.name ) ) {
return null;
}
// Do not auto-paragraph empty elements.
if ( viewElement.isEmpty ) {
return null;
}
return writer.createElement( 'paragraph' );
},
view: /.+/,
converterPriority: 'low'
} );
}
}
/**
* A list of element names which should be treated by the autoparagraphing algorithms as
* paragraph-like. This means that e.g. the following content:
*
* <h1>Foo</h1>
* <table>
* <tr>
* <td>X</td>
* <td>
* <ul>
* <li>Y</li>
* <li>Z</li>
* </ul>
* </td>
* </tr>
* </table>
*
* contains five paragraph-like elements: `<h1>`, two `<td>`s and two `<li>`s.
* Hence, if none of the features is going to convert those elements the above content will be automatically handled
* by the paragraph feature and converted to:
*
* <p>Foo</p>
* <p>X</p>
* <p>Y</p>
* <p>Z</p>
*
* Note: The `<td>` containing two `<li>` elements was ignored as the innermost paragraph-like elements
* have a priority upon conversion.
*
* @member {Set.<String>} module:paragraph/paragraph~Paragraph.paragraphLikeElements
*/
Paragraph.paragraphLikeElements = new Set( [
'blockquote',
'dd',
'div',
'dt',
'h1',
'h2',
'h3',
'h4',
'h5',
'h6',
'li',
'p',
'td',
'th'
] );
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-paragraph/src/paragraphbuttonui.js":
/*!*****************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-paragraph/src/paragraphbuttonui.js ***!
\*****************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ParagraphButtonUI)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_core_src_plugin__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-core/src/plugin */ "./node_modules/@ckeditor/ckeditor5-core/src/plugin.js");
/* harmony import */ var _ckeditor_ckeditor5_ui_src_button_buttonview__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-ui/src/button/buttonview */ "./node_modules/@ckeditor/ckeditor5-ui/src/button/buttonview.js");
/* harmony import */ var _theme_icons_paragraph_svg__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../theme/icons/paragraph.svg */ "./node_modules/@ckeditor/ckeditor5-paragraph/theme/icons/paragraph.svg");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module paragraph/paragraphbuttonui
*/
/**
* This plugin defines the `'paragraph'` button. It can be used together with
* {@link module:heading/headingbuttonsui~HeadingButtonsUI} to replace the standard heading dropdown.
*
* This plugin is not loaded automatically by the {@link module:paragraph/paragraph~Paragraph} plugin. It must
* be added manually.
*
* ClassicEditor
* .create( {
* plugins: [ ..., Heading, Paragraph, HeadingButtonsUI, ParagraphButtonUI ]
* toolbar: [ 'paragraph', 'heading1', 'heading2', 'heading3' ]
* } )
* .then( ... )
* .catch( ... );
*
* @extends module:core/plugin~Plugin
*/
class ParagraphButtonUI extends _ckeditor_ckeditor5_core_src_plugin__WEBPACK_IMPORTED_MODULE_0__["default"] {
init() {
const editor = this.editor;
const t = editor.t;
editor.ui.componentFactory.add( 'paragraph', locale => {
const view = new _ckeditor_ckeditor5_ui_src_button_buttonview__WEBPACK_IMPORTED_MODULE_1__["default"]( locale );
const command = editor.commands.get( 'paragraph' );
view.label = t( 'Paragraph' );
view.icon = _theme_icons_paragraph_svg__WEBPACK_IMPORTED_MODULE_2__["default"];
view.tooltip = true;
view.isToggleable = true;
view.bind( 'isEnabled' ).to( command );
view.bind( 'isOn' ).to( command, 'value' );
view.on( 'execute', () => {
editor.execute( 'paragraph' );
} );
return view;
} );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-paragraph/src/paragraphcommand.js":
/*!****************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-paragraph/src/paragraphcommand.js ***!
\****************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ParagraphCommand)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_core_src_command__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-core/src/command */ "./node_modules/@ckeditor/ckeditor5-core/src/command.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_first__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/first */ "./node_modules/@ckeditor/ckeditor5-utils/src/first.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module paragraph/paragraphcommand
*/
/**
* The paragraph command.
*
* @extends module:core/command~Command
*/
class ParagraphCommand extends _ckeditor_ckeditor5_core_src_command__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* The value of the command. Indicates whether the selection start is placed in a paragraph.
*
* @readonly
* @observable
* @member {Boolean} #value
*/
/**
* @inheritDoc
*/
refresh() {
const model = this.editor.model;
const document = model.document;
const block = (0,_ckeditor_ckeditor5_utils_src_first__WEBPACK_IMPORTED_MODULE_1__["default"])( document.selection.getSelectedBlocks() );
this.value = !!block && block.is( 'element', 'paragraph' );
this.isEnabled = !!block && checkCanBecomeParagraph( block, model.schema );
}
/**
* Executes the command. All the blocks (see {@link module:engine/model/schema~Schema}) in the selection
* will be turned to paragraphs.
*
* @fires execute
* @param {Object} [options] Options for the executed command.
* @param {module:engine/model/selection~Selection|module:engine/model/documentselection~DocumentSelection} [options.selection]
* The selection that the command should be applied to.
* By default, if not provided, the command is applied to the {@link module:engine/model/document~Document#selection}.
*/
execute( options = {} ) {
const model = this.editor.model;
const document = model.document;
model.change( writer => {
const blocks = ( options.selection || document.selection ).getSelectedBlocks();
for ( const block of blocks ) {
if ( !block.is( 'element', 'paragraph' ) && checkCanBecomeParagraph( block, model.schema ) ) {
writer.rename( block, 'paragraph' );
}
}
} );
}
}
// Checks whether the given block can be replaced by a paragraph.
//
// @private
// @param {module:engine/model/element~Element} block A block to be tested.
// @param {module:engine/model/schema~Schema} schema The schema of the document.
// @returns {Boolean}
function checkCanBecomeParagraph( block, schema ) {
return schema.checkChild( block.parent, 'paragraph' ) && !schema.isObject( block );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-paste-from-office/src/filters/image.js":
/*!*********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-paste-from-office/src/filters/image.js ***!
\*********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "_convertHexToBase64": () => (/* binding */ _convertHexToBase64),
/* harmony export */ "replaceImagesSourceWithBase64": () => (/* binding */ replaceImagesSourceWithBase64)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/engine */ "./node_modules/ckeditor5/src/engine.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module paste-from-office/filters/image
*/
/* globals btoa */
/**
* Replaces source attribute of all `<img>` elements representing regular
* images (not the Word shapes) with inlined base64 image representation extracted from RTF or Blob data.
*
* @param {module:engine/view/documentfragment~DocumentFragment} documentFragment Document fragment on which transform images.
* @param {String} rtfData The RTF data from which images representation will be used.
*/
function replaceImagesSourceWithBase64( documentFragment, rtfData ) {
if ( !documentFragment.childCount ) {
return;
}
const upcastWriter = new ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_0__.UpcastWriter();
const shapesIds = findAllShapesIds( documentFragment, upcastWriter );
removeAllImgElementsRepresentingShapes( shapesIds, documentFragment, upcastWriter );
removeAllShapeElements( documentFragment, upcastWriter );
const images = findAllImageElementsWithLocalSource( documentFragment, upcastWriter );
if ( images.length ) {
replaceImagesFileSourceWithInlineRepresentation( images, extractImageDataFromRtf( rtfData ), upcastWriter );
}
}
/**
* Converts given HEX string to base64 representation.
*
* @protected
* @param {String} hexString The HEX string to be converted.
* @returns {String} Base64 representation of a given HEX string.
*/
function _convertHexToBase64( hexString ) {
return btoa( hexString.match( /\w{2}/g ).map( char => {
return String.fromCharCode( parseInt( char, 16 ) );
} ).join( '' ) );
}
// Finds all shapes (`<v:*>...</v:*>`) ids. Shapes can represent images (canvas)
// or Word shapes (which does not have RTF or Blob representation).
//
// @param {module:engine/view/documentfragment~DocumentFragment} documentFragment Document fragment
// from which to extract shape ids.
// @param {module:engine/view/upcastwriter~UpcastWriter} writer
// @returns {Array.<String>} Array of shape ids.
function findAllShapesIds( documentFragment, writer ) {
const range = writer.createRangeIn( documentFragment );
const shapeElementsMatcher = new ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_0__.Matcher( {
name: /v:(.+)/
} );
const shapesIds = [];
for ( const value of range ) {
if ( value.type != 'elementStart' ) {
continue;
}
const el = value.item;
const prevSiblingName = el.previousSibling && el.previousSibling.name || null;
// If shape element have 'o:gfxdata' attribute and is not directly before `<v:shapetype>` element it means it represent Word shape.
if ( shapeElementsMatcher.match( el ) && el.getAttribute( 'o:gfxdata' ) && prevSiblingName !== 'v:shapetype' ) {
shapesIds.push( value.item.getAttribute( 'id' ) );
}
}
return shapesIds;
}
// Removes all `<img>` elements which represents Word shapes and not regular images.
//
// @param {Array.<String>} shapesIds Shape ids which will be checked against `<img>` elements.
// @param {module:engine/view/documentfragment~DocumentFragment} documentFragment Document fragment from which to remove `<img>` elements.
// @param {module:engine/view/upcastwriter~UpcastWriter} writer
function removeAllImgElementsRepresentingShapes( shapesIds, documentFragment, writer ) {
const range = writer.createRangeIn( documentFragment );
const imageElementsMatcher = new ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_0__.Matcher( {
name: 'img'
} );
const imgs = [];
for ( const value of range ) {
if ( imageElementsMatcher.match( value.item ) ) {
const el = value.item;
const shapes = el.getAttribute( 'v:shapes' ) ? el.getAttribute( 'v:shapes' ).split( ' ' ) : [];
if ( shapes.length && shapes.every( shape => shapesIds.indexOf( shape ) > -1 ) ) {
imgs.push( el );
// Shapes may also have empty source while content is paste in some browsers (Safari).
} else if ( !el.getAttribute( 'src' ) ) {
imgs.push( el );
}
}
}
for ( const img of imgs ) {
writer.remove( img );
}
}
// Removes all shape elements (`<v:*>...</v:*>`) so they do not pollute the output structure.
//
// @param {module:engine/view/documentfragment~DocumentFragment} documentFragment Document fragment from which to remove shape elements.
// @param {module:engine/view/upcastwriter~UpcastWriter} writer
function removeAllShapeElements( documentFragment, writer ) {
const range = writer.createRangeIn( documentFragment );
const shapeElementsMatcher = new ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_0__.Matcher( {
name: /v:(.+)/
} );
const shapes = [];
for ( const value of range ) {
if ( value.type == 'elementStart' && shapeElementsMatcher.match( value.item ) ) {
shapes.push( value.item );
}
}
for ( const shape of shapes ) {
writer.remove( shape );
}
}
// Finds all `<img>` elements in a given document fragment which have source pointing to local `file://` resource.
//
// @param {module:engine/view/documentfragment~DocumentFragment} documentFragment Document fragment in which to look for `<img>` elements.
// @param {module:engine/view/upcastwriter~UpcastWriter} writer
// @returns {Object} result All found images grouped by source type.
// @returns {Array.<module:engine/view/element~Element>} result.file Array of found `<img>` elements with `file://` source.
// @returns {Array.<module:engine/view/element~Element>} result.blob Array of found `<img>` elements with `blob:` source.
function findAllImageElementsWithLocalSource( documentFragment, writer ) {
const range = writer.createRangeIn( documentFragment );
const imageElementsMatcher = new ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_0__.Matcher( {
name: 'img'
} );
const imgs = [];
for ( const value of range ) {
if ( imageElementsMatcher.match( value.item ) ) {
if ( value.item.getAttribute( 'src' ).startsWith( 'file://' ) ) {
imgs.push( value.item );
}
}
}
return imgs;
}
// Extracts all images HEX representations from a given RTF data.
//
// @param {String} rtfData The RTF data from which to extract images HEX representation.
// @returns {Array.<Object>} Array of found HEX representations. Each array item is an object containing:
//
// * {String} hex Image representation in HEX format.
// * {string} type Type of image, `image/png` or `image/jpeg`.
function extractImageDataFromRtf( rtfData ) {
if ( !rtfData ) {
return [];
}
const regexPictureHeader = /{\\pict[\s\S]+?\\bliptag-?\d+(\\blipupi-?\d+)?({\\\*\\blipuid\s?[\da-fA-F]+)?[\s}]*?/;
const regexPicture = new RegExp( '(?:(' + regexPictureHeader.source + '))([\\da-fA-F\\s]+)\\}', 'g' );
const images = rtfData.match( regexPicture );
const result = [];
if ( images ) {
for ( const image of images ) {
let imageType = false;
if ( image.includes( '\\pngblip' ) ) {
imageType = 'image/png';
} else if ( image.includes( '\\jpegblip' ) ) {
imageType = 'image/jpeg';
}
if ( imageType ) {
result.push( {
hex: image.replace( regexPictureHeader, '' ).replace( /[^\da-fA-F]/g, '' ),
type: imageType
} );
}
}
}
return result;
}
// Replaces `src` attribute value of all given images with the corresponding base64 image representation.
//
// @param {Array.<module:engine/view/element~Element>} imageElements Array of image elements which will have its source replaced.
// @param {Array.<Object>} imagesHexSources Array of images hex sources (usually the result of `extractImageDataFromRtf()` function).
// The array should be the same length as `imageElements` parameter.
// @param {module:engine/view/upcastwriter~UpcastWriter} writer
function replaceImagesFileSourceWithInlineRepresentation( imageElements, imagesHexSources, writer ) {
// Assume there is an equal amount of image elements and images HEX sources so they can be matched accordingly based on existing order.
if ( imageElements.length === imagesHexSources.length ) {
for ( let i = 0; i < imageElements.length; i++ ) {
const newSrc = `data:${ imagesHexSources[ i ].type };base64,${ _convertHexToBase64( imagesHexSources[ i ].hex ) }`;
writer.setAttribute( 'src', newSrc, imageElements[ i ] );
}
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-paste-from-office/src/filters/list.js":
/*!********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-paste-from-office/src/filters/list.js ***!
\********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "transformListItemLikeElementsIntoLists": () => (/* binding */ transformListItemLikeElementsIntoLists),
/* harmony export */ "unwrapParagraphInListItem": () => (/* binding */ unwrapParagraphInListItem)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/engine */ "./node_modules/ckeditor5/src/engine.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module paste-from-office/filters/list
*/
/**
* Transforms Word specific list-like elements to the semantic HTML lists.
*
* Lists in Word are represented by block elements with special attributes like:
*
* <p class=MsoListParagraphCxSpFirst style='mso-list:l1 level1 lfo1'>...</p> // Paragraph based list.
* <h1 style='mso-list:l0 level1 lfo1'>...</h1> // Heading 1 based list.
*
* @param {module:engine/view/documentfragment~DocumentFragment} documentFragment The view structure to be transformed.
* @param {String} stylesString Styles from which list-like elements styling will be extracted.
*/
function transformListItemLikeElementsIntoLists( documentFragment, stylesString ) {
if ( !documentFragment.childCount ) {
return;
}
const writer = new ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_0__.UpcastWriter( documentFragment.document );
const itemLikeElements = findAllItemLikeElements( documentFragment, writer );
if ( !itemLikeElements.length ) {
return;
}
let currentList = null;
let currentIndentation = 1;
itemLikeElements.forEach( ( itemLikeElement, i ) => {
const isDifferentList = isNewListNeeded( itemLikeElements[ i - 1 ], itemLikeElement );
const previousItemLikeElement = isDifferentList ? null : itemLikeElements[ i - 1 ];
const indentationDifference = getIndentationDifference( previousItemLikeElement, itemLikeElement );
if ( isDifferentList ) {
currentList = null;
currentIndentation = 1;
}
if ( !currentList || indentationDifference !== 0 ) {
const listStyle = detectListStyle( itemLikeElement, stylesString );
if ( !currentList ) {
currentList = insertNewEmptyList( listStyle, itemLikeElement.element, writer );
} else if ( itemLikeElement.indent > currentIndentation ) {
const lastListItem = currentList.getChild( currentList.childCount - 1 );
const lastListItemChild = lastListItem.getChild( lastListItem.childCount - 1 );
currentList = insertNewEmptyList( listStyle, lastListItemChild, writer );
currentIndentation += 1;
} else if ( itemLikeElement.indent < currentIndentation ) {
const differentIndentation = currentIndentation - itemLikeElement.indent;
currentList = findParentListAtLevel( currentList, differentIndentation );
currentIndentation = parseInt( itemLikeElement.indent );
}
if ( itemLikeElement.indent <= currentIndentation ) {
if ( !currentList.is( 'element', listStyle.type ) ) {
currentList = writer.rename( listStyle.type, currentList );
}
}
}
const listItem = transformElementIntoListItem( itemLikeElement.element, writer );
writer.appendChild( listItem, currentList );
} );
}
/**
* Removes paragraph wrapping content inside a list item.
*
* @param {module:engine/view/documentfragment~DocumentFragment} documentFragment
* @param {module:engine/view/upcastwriter~UpcastWriter} writer
*/
function unwrapParagraphInListItem( documentFragment, writer ) {
for ( const value of writer.createRangeIn( documentFragment ) ) {
const element = value.item;
if ( element.is( 'element', 'li' ) ) {
// Google Docs allows for single paragraph inside LI.
const firstChild = element.getChild( 0 );
if ( firstChild && firstChild.is( 'element', 'p' ) ) {
writer.unwrapElement( firstChild );
}
}
}
}
// Finds all list-like elements in a given document fragment.
//
// @param {module:engine/view/documentfragment~DocumentFragment} documentFragment Document fragment
// in which to look for list-like nodes.
// @param {module:engine/view/upcastwriter~UpcastWriter} writer
// @returns {Array.<Object>} Array of found list-like items. Each item is an object containing:
//
// * {module:engine/src/view/element~Element} element List-like element.
// * {Number} id List item id parsed from `mso-list` style (see `getListItemData()` function).
// * {Number} order List item creation order parsed from `mso-list` style (see `getListItemData()` function).
// * {Number} indent List item indentation level parsed from `mso-list` style (see `getListItemData()` function).
function findAllItemLikeElements( documentFragment, writer ) {
const range = writer.createRangeIn( documentFragment );
// Matcher for finding list-like elements.
const itemLikeElementsMatcher = new ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_0__.Matcher( {
name: /^p|h\d+$/,
styles: {
'mso-list': /.*/
}
} );
const itemLikeElements = [];
for ( const value of range ) {
if ( value.type === 'elementStart' && itemLikeElementsMatcher.match( value.item ) ) {
const itemData = getListItemData( value.item );
itemLikeElements.push( {
element: value.item,
id: itemData.id,
order: itemData.order,
indent: itemData.indent
} );
}
}
return itemLikeElements;
}
// Extracts list item style from the provided CSS.
//
// List item style is extracted from the CSS stylesheet. Each list with its specific style attribute
// value (`mso-list:l1 level1 lfo1`) has its dedicated properties in a CSS stylesheet defined with a selector like:
//
// @list l1:level1 { ... }
//
// It contains `mso-level-number-format` property which defines list numbering/bullet style. If this property
// is not defined it means default `decimal` numbering.
//
// Here CSS string representation is used as `mso-level-number-format` property is an invalid CSS property
// and will be removed during CSS parsing.
//
// @param {Object} listLikeItem List-like item for which list style will be searched for. Usually
// a result of `findAllItemLikeElements()` function.
// @param {String} stylesString CSS stylesheet.
// @returns {Object} result
// @returns {String} result.type List type, could be `ul` or `ol`.
// @returns {Number} result.startIndex List start index, valid only for ordered lists.
// @returns {String|null} result.style List style, for example: `decimal`, `lower-roman`, etc. It is extracted
// directly from Word stylesheet and adjusted to represent proper values for the CSS `list-style-type` property.
// If it cannot be adjusted, the `null` value is returned.
function detectListStyle( listLikeItem, stylesString ) {
const listStyleRegexp = new RegExp( `@list l${ listLikeItem.id }:level${ listLikeItem.indent }\\s*({[^}]*)`, 'gi' );
const listStyleTypeRegex = /mso-level-number-format:([^;]{0,100});/gi;
const listStartIndexRegex = /mso-level-start-at:\s{0,100}([0-9]{0,10})\s{0,100};/gi;
const listStyleMatch = listStyleRegexp.exec( stylesString );
let listStyleType = 'decimal'; // Decimal is default one.
let type = 'ol'; // <ol> is default list.
let startIndex = null;
if ( listStyleMatch && listStyleMatch[ 1 ] ) {
const listStyleTypeMatch = listStyleTypeRegex.exec( listStyleMatch[ 1 ] );
if ( listStyleTypeMatch && listStyleTypeMatch[ 1 ] ) {
listStyleType = listStyleTypeMatch[ 1 ].trim();
type = listStyleType !== 'bullet' && listStyleType !== 'image' ? 'ol' : 'ul';
}
// Styles for the numbered lists are always defined in the Word CSS stylesheet.
// Unordered lists MAY contain a value for the Word CSS definition `mso-level-text` but sometimes
// this tag is missing. And because of that, we cannot depend on that. We need to predict the list style value
// based on the list style marker element.
if ( listStyleType === 'bullet' ) {
const bulletedStyle = findBulletedListStyle( listLikeItem.element );
if ( bulletedStyle ) {
listStyleType = bulletedStyle;
}
} else {
const listStartIndexMatch = listStartIndexRegex.exec( listStyleMatch[ 1 ] );
if ( listStartIndexMatch && listStartIndexMatch[ 1 ] ) {
startIndex = parseInt( listStartIndexMatch[ 1 ] );
}
}
}
return {
type,
startIndex,
style: mapListStyleDefinition( listStyleType )
};
}
// Tries to extract the `list-style-type` value based on the marker element for bulleted list.
//
// @param {module:engine/view/element~Element} element
// @returns {String|null}
function findBulletedListStyle( element ) {
const listMarkerElement = findListMarkerNode( element );
if ( !listMarkerElement ) {
return null;
}
const listMarker = listMarkerElement._data;
if ( listMarker === 'o' ) {
return 'circle';
} else if ( listMarker === '·' ) {
return 'disc';
}
// Word returns '§' instead of '■' for the square list style.
else if ( listMarker === '§' ) {
return 'square';
}
return null;
}
// Tries to find a text node that represents the marker element (list-style-type).
//
// @param {module:engine/view/element~Element} element
// @returns {module:engine/view/text~Text|null}
function findListMarkerNode( element ) {
// If the first child is a text node, it is the data for the element.
// The list-style marker is not present here.
if ( element.getChild( 0 ).is( '$text' ) ) {
return null;
}
for ( const childNode of element.getChildren() ) {
// The list-style marker will be inside the `<span>` element. Let's ignore all non-span elements.
// It may happen that the `<a>` element is added as the first child. Most probably, it's an anchor element.
if ( !childNode.is( 'element', 'span' ) ) {
continue;
}
const textNodeOrElement = childNode.getChild( 0 );
// If already found the marker element, use it.
if ( textNodeOrElement.is( '$text' ) ) {
return textNodeOrElement;
}
return textNodeOrElement.getChild( 0 );
}
}
// Parses the `list-style-type` value extracted directly from the Word CSS stylesheet and returns proper CSS definition.
//
// @param {String|null} value
// @returns {String|null}
function mapListStyleDefinition( value ) {
if ( value.startsWith( 'arabic-leading-zero' ) ) {
return 'decimal-leading-zero';
}
switch ( value ) {
case 'alpha-upper':
return 'upper-alpha';
case 'alpha-lower':
return 'lower-alpha';
case 'roman-upper':
return 'upper-roman';
case 'roman-lower':
return 'lower-roman';
case 'circle':
case 'disc':
case 'square':
return value;
default:
return null;
}
}
// Creates an empty list of a given type and inserts it after a specified element.
//
// @param {Object} listStyle List style object which determines the type of newly created list.
// Usually a result of `detectListStyle()` function.
// @param {module:engine/view/element~Element} element Element after which list is inserted.
// @param {module:engine/view/upcastwriter~UpcastWriter} writer
// @returns {module:engine/view/element~Element} Newly created list element.
function insertNewEmptyList( listStyle, element, writer ) {
const parent = element.parent;
const list = writer.createElement( listStyle.type );
const position = parent.getChildIndex( element ) + 1;
writer.insertChild( position, list, parent );
// We do not support modifying the marker for a particular list item.
// Set the value for the `list-style-type` property directly to the list container.
if ( listStyle.style ) {
writer.setStyle( 'list-style-type', listStyle.style, list );
}
if ( listStyle.startIndex && listStyle.startIndex > 1 ) {
writer.setAttribute( 'start', listStyle.startIndex, list );
}
return list;
}
// Transforms a given element into a semantic list item. As the function operates on a provided
// {module:engine/src/view/element~Element element} it will modify the view structure to which this element belongs.
//
// @param {module:engine/view/element~Element} element Element which will be transformed into a list item.
// @param {module:engine/view/upcastwriter~UpcastWriter} writer
// @returns {module:engine/view/element~Element} New element to which the given one was transformed. It is
// inserted in place of the old element (the reference to the old element is lost due to renaming).
function transformElementIntoListItem( element, writer ) {
removeBulletElement( element, writer );
return writer.rename( 'li', element );
}
// Extracts list item information from Word specific list-like element style:
//
// `style="mso-list:l1 level1 lfo1"`
//
// where:
//
// * `l1` is a list id (however it does not mean this is a continuous list - see #43),
// * `level1` is a list item indentation level,
// * `lfo1` is a list insertion order in a document.
//
// @param {module:engine/view/element~Element} element Element from which style data is extracted.
// @returns {Object} result
// @returns {Number} result.id Parent list id.
// @returns {Number} result.order List item creation order.
// @returns {Number} result.indent List item indentation level.
function getListItemData( element ) {
const data = {};
const listStyle = element.getStyle( 'mso-list' );
if ( listStyle ) {
const idMatch = listStyle.match( /(^|\s{1,100})l(\d+)/i );
const orderMatch = listStyle.match( /\s{0,100}lfo(\d+)/i );
const indentMatch = listStyle.match( /\s{0,100}level(\d+)/i );
if ( idMatch && orderMatch && indentMatch ) {
data.id = idMatch[ 2 ];
data.order = orderMatch[ 1 ];
data.indent = indentMatch[ 1 ];
}
}
return data;
}
// Removes span with a numbering/bullet from a given element.
//
// @param {module:engine/view/element~Element} element
// @param {module:engine/view/upcastwriter~UpcastWriter} writer
function removeBulletElement( element, writer ) {
// Matcher for finding `span` elements holding lists numbering/bullets.
const bulletMatcher = new ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_0__.Matcher( {
name: 'span',
styles: {
'mso-list': 'Ignore'
}
} );
const range = writer.createRangeIn( element );
for ( const value of range ) {
if ( value.type === 'elementStart' && bulletMatcher.match( value.item ) ) {
writer.remove( value.item );
}
}
}
// Whether the previous and current items belong to the same list. It is determined based on `item.id`
// (extracted from `mso-list` style, see #getListItemData) and a previous sibling of the current item.
//
// However, it's quite easy to change the `id` attribute for nested lists in Word. It will break the list feature while pasting.
// Let's check also the `indent` attribute. If the difference between those two elements is equal to 1, we can assume that
// the `currentItem` is a beginning of the nested list because lists in CKEditor 5 always start with the `indent=0` attribute.
// See: https://github.com/ckeditor/ckeditor5/issues/7805.
//
// @param {Object} previousItem
// @param {Object} currentItem
// @returns {Boolean}
function isNewListNeeded( previousItem, currentItem ) {
if ( !previousItem ) {
return true;
}
if ( previousItem.id !== currentItem.id ) {
// See: https://github.com/ckeditor/ckeditor5/issues/7805.
//
// * List item 1.
// - Nested list item 1.
if ( currentItem.indent - previousItem.indent === 1 ) {
return false;
}
return true;
}
const previousSibling = currentItem.element.previousSibling;
if ( !previousSibling ) {
return true;
}
// Even with the same id the list does not have to be continuous (#43).
return !isList( previousSibling );
}
function isList( element ) {
return element.is( 'element', 'ol' ) || element.is( 'element', 'ul' );
}
// Calculates the indentation difference between two given list items (based on the indent attribute
// extracted from the `mso-list` style, see #getListItemData).
//
// @param {Object} previousItem
// @param {Object} currentItem
// @returns {Number}
function getIndentationDifference( previousItem, currentItem ) {
return previousItem ? currentItem.indent - previousItem.indent : currentItem.indent - 1;
}
// Finds the parent list element (ul/ol) of a given list element with indentation level lower by a given value.
//
// @param {module:engine/view/element~Element} listElement List element from which to start looking for a parent list.
// @param {Number} indentationDifference Indentation difference between lists.
// @returns {module:engine/view/element~Element} Found list element with indentation level lower by a given value.
function findParentListAtLevel( listElement, indentationDifference ) {
const ancestors = listElement.getAncestors( { parentFirst: true } );
let parentList = null;
let levelChange = 0;
for ( const ancestor of ancestors ) {
if ( ancestor.name === 'ul' || ancestor.name === 'ol' ) {
levelChange++;
}
if ( levelChange === indentationDifference ) {
parentList = ancestor;
break;
}
}
return parentList;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-paste-from-office/src/filters/parse.js":
/*!*********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-paste-from-office/src/filters/parse.js ***!
\*********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "parseHtml": () => (/* binding */ parseHtml)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/engine */ "./node_modules/ckeditor5/src/engine.js");
/* harmony import */ var _space__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./space */ "./node_modules/@ckeditor/ckeditor5-paste-from-office/src/filters/space.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module paste-from-office/filters/parse
*/
/* globals DOMParser */
/**
* Parses provided HTML extracting contents of `<body>` and `<style>` tags.
*
* @param {String} htmlString HTML string to be parsed.
* @param {module:engine/view/stylesmap~StylesProcessor} stylesProcessor
* @returns {Object} result
* @returns {module:engine/view/documentfragment~DocumentFragment} result.body Parsed body
* content as a traversable structure.
* @returns {String} result.bodyString Entire body content as a string.
* @returns {Array.<CSSStyleSheet>} result.styles Array of native `CSSStyleSheet` objects, each representing
* separate `style` tag from the source HTML.
* @returns {String} result.stylesString All `style` tags contents combined in the order of occurrence into one string.
*/
function parseHtml( htmlString, stylesProcessor ) {
const domParser = new DOMParser();
// Remove Word specific "if comments" so content inside is not omitted by the parser.
htmlString = htmlString.replace( /<!--\[if gte vml 1]>/g, '' );
const normalizedHtml = (0,_space__WEBPACK_IMPORTED_MODULE_1__.normalizeSpacing)( cleanContentAfterBody( htmlString ) );
// Parse htmlString as native Document object.
const htmlDocument = domParser.parseFromString( normalizedHtml, 'text/html' );
(0,_space__WEBPACK_IMPORTED_MODULE_1__.normalizeSpacerunSpans)( htmlDocument );
// Get `innerHTML` first as transforming to View modifies the source document.
const bodyString = htmlDocument.body.innerHTML;
// Transform document.body to View.
const bodyView = documentToView( htmlDocument, stylesProcessor );
// Extract stylesheets.
const stylesObject = extractStyles( htmlDocument );
return {
body: bodyView,
bodyString,
styles: stylesObject.styles,
stylesString: stylesObject.stylesString
};
}
// Transforms native `Document` object into {@link module:engine/view/documentfragment~DocumentFragment}. Comments are skipped.
//
// @param {Document} htmlDocument Native `Document` object to be transformed.
// @param {module:engine/view/stylesmap~StylesProcessor} stylesProcessor
// @returns {module:engine/view/documentfragment~DocumentFragment}
function documentToView( htmlDocument, stylesProcessor ) {
const viewDocument = new ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_0__.ViewDocument( stylesProcessor );
const domConverter = new ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_0__.DomConverter( viewDocument, { renderingMode: 'data' } );
const fragment = htmlDocument.createDocumentFragment();
const nodes = htmlDocument.body.childNodes;
while ( nodes.length > 0 ) {
fragment.appendChild( nodes[ 0 ] );
}
return domConverter.domToView( fragment, { skipComments: true } );
}
// Extracts both `CSSStyleSheet` and string representation from all `style` elements available in a provided `htmlDocument`.
//
// @param {Document} htmlDocument Native `Document` object from which styles will be extracted.
// @returns {Object} result
// @returns {Array.<CSSStyleSheet>} result.styles Array of native `CSSStyleSheet` object, each representing
// separate `style` tag from the source object.
// @returns {String} result.stylesString All `style` tags contents combined in the order of occurrence as one string.
function extractStyles( htmlDocument ) {
const styles = [];
const stylesString = [];
const styleTags = Array.from( htmlDocument.getElementsByTagName( 'style' ) );
for ( const style of styleTags ) {
if ( style.sheet && style.sheet.cssRules && style.sheet.cssRules.length ) {
styles.push( style.sheet );
stylesString.push( style.innerHTML );
}
}
return {
styles,
stylesString: stylesString.join( ' ' )
};
}
// Removes leftover content from between closing </body> and closing </html> tag:
//
// <html><body><p>Foo Bar</p></body><span>Fo</span></html> -> <html><body><p>Foo Bar</p></body></html>
//
// This function is used as specific browsers (Edge) add some random content after `body` tag when pasting from Word.
// @param {String} htmlString The HTML string to be cleaned.
// @returns {String} The HTML string with leftover content removed.
function cleanContentAfterBody( htmlString ) {
const bodyCloseTag = '</body>';
const htmlCloseTag = '</html>';
const bodyCloseIndex = htmlString.indexOf( bodyCloseTag );
if ( bodyCloseIndex < 0 ) {
return htmlString;
}
const htmlCloseIndex = htmlString.indexOf( htmlCloseTag, bodyCloseIndex + bodyCloseTag.length );
return htmlString.substring( 0, bodyCloseIndex + bodyCloseTag.length ) +
( htmlCloseIndex >= 0 ? htmlString.substring( htmlCloseIndex ) : '' );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-paste-from-office/src/filters/removeboldwrapper.js":
/*!*********************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-paste-from-office/src/filters/removeboldwrapper.js ***!
\*********************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ removeBoldWrapper)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module paste-from-office/filters/removeboldwrapper
*/
/**
* Removes `<b>` tag wrapper added by Google Docs to a copied content.
*
* @param {module:engine/view/documentfragment~DocumentFragment} documentFragment element `data.content` obtained from clipboard
* @param {module:engine/view/upcastwriter~UpcastWriter} writer
*/
function removeBoldWrapper( documentFragment, writer ) {
for ( const child of documentFragment.getChildren() ) {
if ( child.is( 'element', 'b' ) && child.getStyle( 'font-weight' ) === 'normal' ) {
const childIndex = documentFragment.getChildIndex( child );
writer.remove( child );
writer.insertChild( childIndex, child.getChildren(), documentFragment );
}
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-paste-from-office/src/filters/space.js":
/*!*********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-paste-from-office/src/filters/space.js ***!
\*********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "normalizeSpacerunSpans": () => (/* binding */ normalizeSpacerunSpans),
/* harmony export */ "normalizeSpacing": () => (/* binding */ normalizeSpacing)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module paste-from-office/filters/space
*/
/**
* Replaces last space preceding elements closing tag with ` `. Such operation prevents spaces from being removed
* during further DOM/View processing (see especially {@link module:engine/view/domconverter~DomConverter#_processDataFromDomText}).
* This method also takes into account Word specific `<o:p></o:p>` empty tags.
* Additionally multiline sequences of spaces and new lines between tags are removed (see #39 and #40).
*
* @param {String} htmlString HTML string in which spacing should be normalized.
* @returns {String} Input HTML with spaces normalized.
*/
function normalizeSpacing( htmlString ) {
// Run normalizeSafariSpaceSpans() two times to cover nested spans.
return normalizeSafariSpaceSpans( normalizeSafariSpaceSpans( htmlString ) )
// Remove all \r\n from "spacerun spans" so the last replace line doesn't strip all whitespaces.
.replace( /(<span\s+style=['"]mso-spacerun:yes['"]>[^\S\r\n]*?)[\r\n]+([^\S\r\n]*<\/span>)/g, '$1$2' )
.replace( /<span\s+style=['"]mso-spacerun:yes['"]><\/span>/g, '' )
.replace( / <\//g, '\u00A0</' )
.replace( / <o:p><\/o:p>/g, '\u00A0<o:p></o:p>' )
// Remove <o:p> block filler from empty paragraph. Safari uses \u00A0 instead of .
.replace( /<o:p>( |\u00A0)<\/o:p>/g, '' )
// Remove all whitespaces when they contain any \r or \n.
.replace( />([^\S\r\n]*[\r\n]\s*)</g, '><' );
}
/**
* Normalizes spacing in special Word `spacerun spans` (`<span style='mso-spacerun:yes'>\s+</span>`) by replacing
* all spaces with ` ` pairs. This prevents spaces from being removed during further DOM/View processing
* (see especially {@link module:engine/view/domconverter~DomConverter#_processDataFromDomText}).
*
* @param {Document} htmlDocument Native `Document` object in which spacing should be normalized.
*/
function normalizeSpacerunSpans( htmlDocument ) {
htmlDocument.querySelectorAll( 'span[style*=spacerun]' ).forEach( el => {
const innerTextLength = el.innerText.length || 0;
el.innerHTML = Array( innerTextLength + 1 ).join( '\u00A0 ' ).substr( 0, innerTextLength );
} );
}
// Normalizes specific spacing generated by Safari when content pasted from Word (`<span class="Apple-converted-space"> </span>`)
// by replacing all spaces sequences longer than 1 space with ` ` pairs. This prevents spaces from being removed during
// further DOM/View processing (see especially {@link module:engine/view/domconverter~DomConverter#_processDataFromDomText}).
//
// This function is similar to {@link module:clipboard/utils/normalizeclipboarddata normalizeClipboardData util} but uses
// regular spaces / sequence for replacement.
//
// @param {String} htmlString HTML string in which spacing should be normalized
// @returns {String} Input HTML with spaces normalized.
function normalizeSafariSpaceSpans( htmlString ) {
return htmlString.replace( /<span(?: class="Apple-converted-space"|)>(\s+)<\/span>/g, ( fullMatch, spaces ) => {
return spaces.length === 1 ? ' ' : Array( spaces.length + 1 ).join( '\u00A0 ' ).substr( 0, spaces.length );
} );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-paste-from-office/src/normalizers/googledocsnormalizer.js":
/*!****************************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-paste-from-office/src/normalizers/googledocsnormalizer.js ***!
\****************************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ GoogleDocsNormalizer)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/engine */ "./node_modules/ckeditor5/src/engine.js");
/* harmony import */ var _filters_removeboldwrapper__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../filters/removeboldwrapper */ "./node_modules/@ckeditor/ckeditor5-paste-from-office/src/filters/removeboldwrapper.js");
/* harmony import */ var _filters_list__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../filters/list */ "./node_modules/@ckeditor/ckeditor5-paste-from-office/src/filters/list.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module paste-from-office/normalizers/googledocsnormalizer
*/
const googleDocsMatch = /id=("|')docs-internal-guid-[-0-9a-f]+("|')/i;
/**
* Normalizer for the content pasted from Google Docs.
*
* @implements module:paste-from-office/normalizer~Normalizer
*/
class GoogleDocsNormalizer {
/**
* Creates a new `GoogleDocsNormalizer` instance.
*
* @param {module:engine/view/document~Document} document View document.
*/
constructor( document ) {
/**
* @readonly
* @type {module:engine/view/document~Document}
*/
this.document = document;
}
/**
* @inheritDoc
*/
isActive( htmlString ) {
return googleDocsMatch.test( htmlString );
}
/**
* @inheritDoc
*/
execute( data ) {
const writer = new ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_0__.UpcastWriter( this.document );
const { body: documentFragment } = data._parsedData;
(0,_filters_removeboldwrapper__WEBPACK_IMPORTED_MODULE_1__["default"])( documentFragment, writer );
(0,_filters_list__WEBPACK_IMPORTED_MODULE_2__.unwrapParagraphInListItem)( documentFragment, writer );
data.content = documentFragment;
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-paste-from-office/src/normalizers/mswordnormalizer.js":
/*!************************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-paste-from-office/src/normalizers/mswordnormalizer.js ***!
\************************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ MSWordNormalizer)
/* harmony export */ });
/* harmony import */ var _filters_list__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../filters/list */ "./node_modules/@ckeditor/ckeditor5-paste-from-office/src/filters/list.js");
/* harmony import */ var _filters_image__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../filters/image */ "./node_modules/@ckeditor/ckeditor5-paste-from-office/src/filters/image.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module paste-from-office/normalizers/mswordnormalizer
*/
const msWordMatch1 = /<meta\s*name="?generator"?\s*content="?microsoft\s*word\s*\d+"?\/?>/i;
const msWordMatch2 = /xmlns:o="urn:schemas-microsoft-com/i;
/**
* Normalizer for the content pasted from Microsoft Word.
*
* @implements module:paste-from-office/normalizer~Normalizer
*/
class MSWordNormalizer {
/**
* Creates a new `MSWordNormalizer` instance.
*
* @param {module:engine/view/document~Document} document View document.
*/
constructor( document ) {
/**
* @readonly
* @type {module:engine/view/document~Document}
*/
this.document = document;
}
/**
* @inheritDoc
*/
isActive( htmlString ) {
return msWordMatch1.test( htmlString ) || msWordMatch2.test( htmlString );
}
/**
* @inheritDoc
*/
execute( data ) {
const { body: documentFragment, stylesString } = data._parsedData;
(0,_filters_list__WEBPACK_IMPORTED_MODULE_0__.transformListItemLikeElementsIntoLists)( documentFragment, stylesString );
(0,_filters_image__WEBPACK_IMPORTED_MODULE_1__.replaceImagesSourceWithBase64)( documentFragment, data.dataTransfer.getData( 'text/rtf' ) );
data.content = documentFragment;
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-paste-from-office/src/pastefromoffice.js":
/*!***********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-paste-from-office/src/pastefromoffice.js ***!
\***********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ PasteFromOffice)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_clipboard__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/clipboard */ "./node_modules/ckeditor5/src/clipboard.js");
/* harmony import */ var _normalizers_googledocsnormalizer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./normalizers/googledocsnormalizer */ "./node_modules/@ckeditor/ckeditor5-paste-from-office/src/normalizers/googledocsnormalizer.js");
/* harmony import */ var _normalizers_mswordnormalizer__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./normalizers/mswordnormalizer */ "./node_modules/@ckeditor/ckeditor5-paste-from-office/src/normalizers/mswordnormalizer.js");
/* harmony import */ var _filters_parse__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./filters/parse */ "./node_modules/@ckeditor/ckeditor5-paste-from-office/src/filters/parse.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module paste-from-office/pastefromoffice
*/
/**
* The Paste from Office plugin.
*
* This plugin handles content pasted from Office apps and transforms it (if necessary)
* to a valid structure which can then be understood by the editor features.
*
* Transformation is made by a set of predefined {@link module:paste-from-office/normalizer~Normalizer normalizers}.
* This plugin includes following normalizers:
* * {@link module:paste-from-office/normalizers/mswordnormalizer~MSWordNormalizer Microsoft Word normalizer}
* * {@link module:paste-from-office/normalizers/googledocsnormalizer~GoogleDocsNormalizer Google Docs normalizer}
*
* For more information about this feature check the {@glink api/paste-from-office package page}.
*
* @extends module:core/plugin~Plugin
*/
class PasteFromOffice extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'PasteFromOffice';
}
/**
* @inheritDoc
*/
static get requires() {
return [ ckeditor5_src_clipboard__WEBPACK_IMPORTED_MODULE_1__.ClipboardPipeline ];
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const viewDocument = editor.editing.view.document;
const normalizers = [];
normalizers.push( new _normalizers_mswordnormalizer__WEBPACK_IMPORTED_MODULE_3__["default"]( viewDocument ) );
normalizers.push( new _normalizers_googledocsnormalizer__WEBPACK_IMPORTED_MODULE_2__["default"]( viewDocument ) );
editor.plugins.get( 'ClipboardPipeline' ).on(
'inputTransformation',
( evt, data ) => {
if ( data._isTransformedWithPasteFromOffice ) {
return;
}
const htmlString = data.dataTransfer.getData( 'text/html' );
const activeNormalizer = normalizers.find( normalizer => normalizer.isActive( htmlString ) );
if ( activeNormalizer ) {
data._parsedData = (0,_filters_parse__WEBPACK_IMPORTED_MODULE_4__.parseHtml)( htmlString, viewDocument.stylesProcessor );
activeNormalizer.execute( data );
data._isTransformedWithPasteFromOffice = true;
}
},
{ priority: 'high' }
);
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-select-all/src/index.js":
/*!******************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-select-all/src/index.js ***!
\******************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "SelectAll": () => (/* reexport safe */ _selectall__WEBPACK_IMPORTED_MODULE_0__["default"]),
/* harmony export */ "SelectAllEditing": () => (/* reexport safe */ _selectallediting__WEBPACK_IMPORTED_MODULE_1__["default"]),
/* harmony export */ "SelectAllUI": () => (/* reexport safe */ _selectallui__WEBPACK_IMPORTED_MODULE_2__["default"])
/* harmony export */ });
/* harmony import */ var _selectall__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./selectall */ "./node_modules/@ckeditor/ckeditor5-select-all/src/selectall.js");
/* harmony import */ var _selectallediting__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./selectallediting */ "./node_modules/@ckeditor/ckeditor5-select-all/src/selectallediting.js");
/* harmony import */ var _selectallui__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./selectallui */ "./node_modules/@ckeditor/ckeditor5-select-all/src/selectallui.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module select-all
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-select-all/src/selectall.js":
/*!**********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-select-all/src/selectall.js ***!
\**********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ SelectAll)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_core_src_plugin__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-core/src/plugin */ "./node_modules/@ckeditor/ckeditor5-core/src/plugin.js");
/* harmony import */ var _selectallediting__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./selectallediting */ "./node_modules/@ckeditor/ckeditor5-select-all/src/selectallediting.js");
/* harmony import */ var _selectallui__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./selectallui */ "./node_modules/@ckeditor/ckeditor5-select-all/src/selectallui.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module select-all/selectall
*/
/**
* The select all feature.
*
* This is a "glue" plugin which loads the {@link module:select-all/selectallediting~SelectAllEditing select all editing feature}
* and the {@link module:select-all/selectallui~SelectAllUI select all UI feature}.
*
* Please refer to the documentation of individual features to learn more.
*
* @extends module:core/plugin~Plugin
*/
class SelectAll extends _ckeditor_ckeditor5_core_src_plugin__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* @inheritDoc
*/
static get requires() {
return [ _selectallediting__WEBPACK_IMPORTED_MODULE_1__["default"], _selectallui__WEBPACK_IMPORTED_MODULE_2__["default"] ];
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'SelectAll';
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-select-all/src/selectallcommand.js":
/*!*****************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-select-all/src/selectallcommand.js ***!
\*****************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ SelectAllCommand)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_core_src_command__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-core/src/command */ "./node_modules/@ckeditor/ckeditor5-core/src/command.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module select-all/selectallcommand
*/
/**
* The select all command.
*
* It is used by the {@link module:select-all/selectallediting~SelectAllEditing select all editing feature} to handle
* the <kbd>Ctrl/⌘</kbd>+<kbd>A</kbd> keystroke.
*
* Executing this command changes the {@glink framework/guides/architecture/editing-engine#model model}
* selection so it contains the entire content of the editable root of the editor the selection is
* {@link module:engine/model/selection~Selection#anchor anchored} in.
*
* If the selection was anchored in a {@glink framework/guides/tutorials/implementing-a-block-widget nested editable}
* (e.g. a caption of an image), the new selection will contain its entire content. Successive executions of this command
* will expand the selection to encompass more and more content up to the entire editable root of the editor.
*
* @extends module:core/command~Command
*/
class SelectAllCommand extends _ckeditor_ckeditor5_core_src_command__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* @inheritDoc
*/
constructor( editor ) {
super( editor );
// It does not affect data so should be enabled in read-only mode.
this.affectsData = false;
}
/**
* @inheritDoc
*/
execute() {
const model = this.editor.model;
const selection = model.document.selection;
let scopeElement = model.schema.getLimitElement( selection );
// If an entire scope is selected, or the selection's ancestor is not a scope yet,
// browse through ancestors to find the enclosing parent scope.
if ( selection.containsEntireContent( scopeElement ) || !isSelectAllScope( model.schema, scopeElement ) ) {
do {
scopeElement = scopeElement.parent;
// Do nothing, if the entire `root` is already selected.
if ( !scopeElement ) {
return;
}
} while ( !isSelectAllScope( model.schema, scopeElement ) );
}
model.change( writer => {
writer.setSelection( scopeElement, 'in' );
} );
}
}
// Checks whether the element is a valid select-all scope.
// Returns true, if the element is a {@link module:engine/model/schema~Schema#isLimit limit},
// and can contain any text or paragraph.
//
// @param {module:engine/model/schema~Schema} schema The schema to check against.
// @param {module:engine/model/element~Element} element
// @return {Boolean}
function isSelectAllScope( schema, element ) {
return schema.isLimit( element ) && ( schema.checkChild( element, '$text' ) || schema.checkChild( element, 'paragraph' ) );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-select-all/src/selectallediting.js":
/*!*****************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-select-all/src/selectallediting.js ***!
\*****************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ SelectAllEditing)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_core_src_plugin__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-core/src/plugin */ "./node_modules/@ckeditor/ckeditor5-core/src/plugin.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_keyboard__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/keyboard */ "./node_modules/@ckeditor/ckeditor5-utils/src/keyboard.js");
/* harmony import */ var _selectallcommand__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./selectallcommand */ "./node_modules/@ckeditor/ckeditor5-select-all/src/selectallcommand.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module select-all/selectallediting
*/
const SELECT_ALL_KEYSTROKE = (0,_ckeditor_ckeditor5_utils_src_keyboard__WEBPACK_IMPORTED_MODULE_1__.parseKeystroke)( 'Ctrl+A' );
/**
* The select all editing feature.
*
* It registers the `'selectAll'` {@link module:select-all/selectallcommand~SelectAllCommand command}
* and the <kbd>Ctrl/⌘</kbd>+<kbd>A</kbd> keystroke listener which executes it.
*
* @extends module:core/plugin~Plugin
*/
class SelectAllEditing extends _ckeditor_ckeditor5_core_src_plugin__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* @inheritDoc
*/
static get pluginName() {
return 'SelectAllEditing';
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const view = editor.editing.view;
const viewDocument = view.document;
editor.commands.add( 'selectAll', new _selectallcommand__WEBPACK_IMPORTED_MODULE_2__["default"]( editor ) );
this.listenTo( viewDocument, 'keydown', ( eventInfo, domEventData ) => {
if ( (0,_ckeditor_ckeditor5_utils_src_keyboard__WEBPACK_IMPORTED_MODULE_1__.getCode)( domEventData ) === SELECT_ALL_KEYSTROKE ) {
editor.execute( 'selectAll' );
domEventData.preventDefault();
}
} );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-select-all/src/selectallui.js":
/*!************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-select-all/src/selectallui.js ***!
\************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ SelectAllUI)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_core_src_plugin__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-core/src/plugin */ "./node_modules/@ckeditor/ckeditor5-core/src/plugin.js");
/* harmony import */ var _ckeditor_ckeditor5_ui_src_button_buttonview__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-ui/src/button/buttonview */ "./node_modules/@ckeditor/ckeditor5-ui/src/button/buttonview.js");
/* harmony import */ var _theme_icons_select_all_svg__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../theme/icons/select-all.svg */ "./node_modules/@ckeditor/ckeditor5-select-all/theme/icons/select-all.svg");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module select-all/selectallui
*/
/**
* The select all UI feature.
*
* It registers the `'selectAll'` UI button in the editor's
* {@link module:ui/componentfactory~ComponentFactory component factory}. When clicked, the button
* executes the {@link module:select-all/selectallcommand~SelectAllCommand select all command}.
*
* @extends module:core/plugin~Plugin
*/
class SelectAllUI extends _ckeditor_ckeditor5_core_src_plugin__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* @inheritDoc
*/
static get pluginName() {
return 'SelectAllUI';
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
editor.ui.componentFactory.add( 'selectAll', locale => {
const command = editor.commands.get( 'selectAll' );
const view = new _ckeditor_ckeditor5_ui_src_button_buttonview__WEBPACK_IMPORTED_MODULE_1__["default"]( locale );
const t = locale.t;
view.set( {
label: t( 'Select all' ),
icon: _theme_icons_select_all_svg__WEBPACK_IMPORTED_MODULE_2__["default"],
keystroke: 'Ctrl+A',
tooltip: true
} );
view.bind( 'isOn', 'isEnabled' ).to( command, 'value', 'isEnabled' );
// Execute the command.
this.listenTo( view, 'execute', () => {
editor.execute( 'selectAll' );
editor.editing.view.focus();
} );
return view;
} );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-special-characters/src/specialcharacters.js":
/*!**************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-special-characters/src/specialcharacters.js ***!
\**************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ SpecialCharacters)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_typing__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/typing */ "./node_modules/ckeditor5/src/typing.js");
/* harmony import */ var ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ckeditor5/src/ui */ "./node_modules/ckeditor5/src/ui.js");
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/* harmony import */ var _ui_specialcharactersnavigationview__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./ui/specialcharactersnavigationview */ "./node_modules/@ckeditor/ckeditor5-special-characters/src/ui/specialcharactersnavigationview.js");
/* harmony import */ var _ui_charactergridview__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./ui/charactergridview */ "./node_modules/@ckeditor/ckeditor5-special-characters/src/ui/charactergridview.js");
/* harmony import */ var _ui_characterinfoview__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./ui/characterinfoview */ "./node_modules/@ckeditor/ckeditor5-special-characters/src/ui/characterinfoview.js");
/* harmony import */ var _theme_icons_specialcharacters_svg__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ../theme/icons/specialcharacters.svg */ "./node_modules/@ckeditor/ckeditor5-special-characters/theme/icons/specialcharacters.svg");
/* harmony import */ var _theme_specialcharacters_css__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ../theme/specialcharacters.css */ "./node_modules/@ckeditor/ckeditor5-special-characters/theme/specialcharacters.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module special-characters/specialcharacters
*/
const ALL_SPECIAL_CHARACTERS_GROUP = 'All';
/**
* The special characters feature.
*
* Introduces the `'specialCharacters'` dropdown.
*
* @extends module:core/plugin~Plugin
*/
class SpecialCharacters extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [ ckeditor5_src_typing__WEBPACK_IMPORTED_MODULE_1__.Typing ];
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'SpecialCharacters';
}
/**
* @inheritDoc
*/
constructor( editor ) {
super( editor );
/**
* Registered characters. A pair of a character name and its symbol.
*
* @private
* @member {Map.<String, String>} #_characters
*/
this._characters = new Map();
/**
* Registered groups. Each group contains a collection with symbol names.
*
* @private
* @member {Map.<String, Set.<String>>} #_groups
*/
this._groups = new Map();
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const t = editor.t;
const inputCommand = editor.commands.get( 'input' );
// Add the `specialCharacters` dropdown button to feature components.
editor.ui.componentFactory.add( 'specialCharacters', locale => {
const dropdownView = (0,ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_2__.createDropdown)( locale );
let dropdownPanelContent;
dropdownView.buttonView.set( {
label: t( 'Special characters' ),
icon: _theme_icons_specialcharacters_svg__WEBPACK_IMPORTED_MODULE_7__["default"],
tooltip: true
} );
dropdownView.bind( 'isEnabled' ).to( inputCommand );
// Insert a special character when a tile was clicked.
dropdownView.on( 'execute', ( evt, data ) => {
editor.execute( 'input', { text: data.character } );
editor.editing.view.focus();
} );
dropdownView.on( 'change:isOpen', () => {
if ( !dropdownPanelContent ) {
dropdownPanelContent = this._createDropdownPanelContent( locale, dropdownView );
dropdownView.panelView.children.add( dropdownPanelContent.navigationView );
dropdownView.panelView.children.add( dropdownPanelContent.gridView );
dropdownView.panelView.children.add( dropdownPanelContent.infoView );
}
dropdownPanelContent.infoView.set( {
character: null,
name: null
} );
} );
return dropdownView;
} );
}
/**
* Adds a collection of special characters to the specified group. The title of a special character must be unique.
*
* **Note:** The "All" category name is reserved by the plugin and cannot be used as a new name for a special
* characters category.
*
* @param {String} groupName
* @param {Array.<module:special-characters/specialcharacters~SpecialCharacterDefinition>} items
*/
addItems( groupName, items ) {
if ( groupName === ALL_SPECIAL_CHARACTERS_GROUP ) {
/**
* The name "All" for a special category group cannot be used because it is a special category that displays all
* available special characters.
*
* @error special-character-invalid-group-name
*/
throw new ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_3__.CKEditorError(
`special-character-invalid-group-name: The name "${ ALL_SPECIAL_CHARACTERS_GROUP }" is reserved and cannot be used.`
);
}
const group = this._getGroup( groupName );
for ( const item of items ) {
group.add( item.title );
this._characters.set( item.title, item.character );
}
}
/**
* Returns an iterator of special characters groups.
*
* @returns {Iterable.<String>}
*/
getGroups() {
return this._groups.keys();
}
/**
* Returns a collection of special characters symbol names (titles).
*
* @param {String} groupName
* @returns {Set.<String>|undefined}
*/
getCharactersForGroup( groupName ) {
if ( groupName === ALL_SPECIAL_CHARACTERS_GROUP ) {
return new Set( this._characters.keys() );
}
return this._groups.get( groupName );
}
/**
* Returns the symbol of a special character for the specified name. If the special character could not be found, `undefined`
* is returned.
*
* @param {String} title The title of a special character.
* @returns {String|undefined}
*/
getCharacter( title ) {
return this._characters.get( title );
}
/**
* Returns a group of special characters. If the group with the specified name does not exist, it will be created.
*
* @private
* @param {String} groupName The name of the group to create.
*/
_getGroup( groupName ) {
if ( !this._groups.has( groupName ) ) {
this._groups.set( groupName, new Set() );
}
return this._groups.get( groupName );
}
/**
* Updates the symbol grid depending on the currently selected character group.
*
* @private
* @param {String} currentGroupName
* @param {module:special-characters/ui/charactergridview~CharacterGridView} gridView
*/
_updateGrid( currentGroupName, gridView ) {
// Updating the grid starts with removing all tiles belonging to the old group.
gridView.tiles.clear();
const characterTitles = this.getCharactersForGroup( currentGroupName );
for ( const title of characterTitles ) {
const character = this.getCharacter( title );
gridView.tiles.add( gridView.createTile( character, title ) );
}
}
/**
* Initializes the dropdown, used for lazy loading.
*
* @private
* @param {module:utils/locale~Locale} locale
* @param {module:ui/dropdown/dropdownview~DropdownView} dropdownView
* @returns {Object} Returns an object with `navigationView`, `gridView` and `infoView` properties, containing UI parts.
*/
_createDropdownPanelContent( locale, dropdownView ) {
const specialCharsGroups = [ ...this.getGroups() ];
// Add a special group that shows all available special characters.
specialCharsGroups.unshift( ALL_SPECIAL_CHARACTERS_GROUP );
const navigationView = new _ui_specialcharactersnavigationview__WEBPACK_IMPORTED_MODULE_4__["default"]( locale, specialCharsGroups );
const gridView = new _ui_charactergridview__WEBPACK_IMPORTED_MODULE_5__["default"]( locale );
const infoView = new _ui_characterinfoview__WEBPACK_IMPORTED_MODULE_6__["default"]( locale );
gridView.delegate( 'execute' ).to( dropdownView );
gridView.on( 'tileHover', ( evt, data ) => {
infoView.set( data );
} );
// Update the grid of special characters when a user changed the character group.
navigationView.on( 'execute', () => {
this._updateGrid( navigationView.currentGroupName, gridView );
} );
// Set the initial content of the special characters grid.
this._updateGrid( navigationView.currentGroupName, gridView );
return { navigationView, gridView, infoView };
}
}
/**
* @typedef {Object} module:special-characters/specialcharacters~SpecialCharacterDefinition
*
* @property {String} title A unique name of the character (e.g. "greek small letter epsilon").
* @property {String} character A human-readable character displayed as the label (e.g. "ε").
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-special-characters/src/specialcharactersarrows.js":
/*!********************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-special-characters/src/specialcharactersarrows.js ***!
\********************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ SpecialCharactersArrows)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module special-characters/specialcharactersarrows
*/
/**
* A plugin that provides special characters for the "Arrows" category.
*
* ClassicEditor
* .create( {
* plugins: [ ..., SpecialCharacters, SpecialCharactersArrows ],
* } )
* .then( ... )
* .catch( ... );
*
* @extends module:core/plugin~Plugin
*/
class SpecialCharactersArrows extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'SpecialCharactersArrows';
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const t = editor.t;
editor.plugins.get( 'SpecialCharacters' ).addItems( 'Arrows', [
{ title: t( 'leftwards double arrow' ), character: '⇐' },
{ title: t( 'rightwards double arrow' ), character: '⇒' },
{ title: t( 'upwards double arrow' ), character: '⇑' },
{ title: t( 'downwards double arrow' ), character: '⇓' },
{ title: t( 'leftwards dashed arrow' ), character: '⇠' },
{ title: t( 'rightwards dashed arrow' ), character: '⇢' },
{ title: t( 'upwards dashed arrow' ), character: '⇡' },
{ title: t( 'downwards dashed arrow' ), character: '⇣' },
{ title: t( 'leftwards arrow to bar' ), character: '⇤' },
{ title: t( 'rightwards arrow to bar' ), character: '⇥' },
{ title: t( 'upwards arrow to bar' ), character: '⤒' },
{ title: t( 'downwards arrow to bar' ), character: '⤓' },
{ title: t( 'up down arrow with base' ), character: '↨' },
{ title: t( 'back with leftwards arrow above' ), character: '🔙' },
{ title: t( 'end with leftwards arrow above' ), character: '🔚' },
{ title: t( 'on with exclamation mark with left right arrow above' ), character: '🔛' },
{ title: t( 'soon with rightwards arrow above' ), character: '🔜' },
{ title: t( 'top with upwards arrow above' ), character: '🔝' }
] );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-special-characters/src/specialcharacterscurrency.js":
/*!**********************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-special-characters/src/specialcharacterscurrency.js ***!
\**********************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ SpecialCharactersCurrency)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module special-characters/specialcharacterscurrency
*/
/**
* A plugin that provides special characters for the "Currency" category.
*
* ClassicEditor
* .create( {
* plugins: [ ..., SpecialCharacters, SpecialCharactersCurrency ],
* } )
* .then( ... )
* .catch( ... );
*
* @extends module:core/plugin~Plugin
*/
class SpecialCharactersCurrency extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'SpecialCharactersCurrency';
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const t = editor.t;
editor.plugins.get( 'SpecialCharacters' ).addItems( 'Currency', [
{ character: '$', title: t( 'Dollar sign' ) },
{ character: '€', title: t( 'Euro sign' ) },
{ character: '¥', title: t( 'Yen sign' ) },
{ character: '£', title: t( 'Pound sign' ) },
{ character: '¢', title: t( 'Cent sign' ) },
{ character: '₠', title: t( 'Euro-currency sign' ) },
{ character: '₡', title: t( 'Colon sign' ) },
{ character: '₢', title: t( 'Cruzeiro sign' ) },
{ character: '₣', title: t( 'French franc sign' ) },
{ character: '₤', title: t( 'Lira sign' ) },
{ character: '¤', title: t( 'Currency sign' ) },
{ character: '₿', title: t( 'Bitcoin sign' ) },
{ character: '₥', title: t( 'Mill sign' ) },
{ character: '₦', title: t( 'Naira sign' ) },
{ character: '₧', title: t( 'Peseta sign' ) },
{ character: '₨', title: t( 'Rupee sign' ) },
{ character: '₩', title: t( 'Won sign' ) },
{ character: '₪', title: t( 'New sheqel sign' ) },
{ character: '₫', title: t( 'Dong sign' ) },
{ character: '₭', title: t( 'Kip sign' ) },
{ character: '₮', title: t( 'Tugrik sign' ) },
{ character: '₯', title: t( 'Drachma sign' ) },
{ character: '₰', title: t( 'German penny sign' ) },
{ character: '₱', title: t( 'Peso sign' ) },
{ character: '₲', title: t( 'Guarani sign' ) },
{ character: '₳', title: t( 'Austral sign' ) },
{ character: '₴', title: t( 'Hryvnia sign' ) },
{ character: '₵', title: t( 'Cedi sign' ) },
{ character: '₶', title: t( 'Livre tournois sign' ) },
{ character: '₷', title: t( 'Spesmilo sign' ) },
{ character: '₸', title: t( 'Tenge sign' ) },
{ character: '₹', title: t( 'Indian rupee sign' ) },
{ character: '₺', title: t( 'Turkish lira sign' ) },
{ character: '₻', title: t( 'Nordic mark sign' ) },
{ character: '₼', title: t( 'Manat sign' ) },
{ character: '₽', title: t( 'Ruble sign' ) }
] );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-special-characters/src/ui/charactergridview.js":
/*!*****************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-special-characters/src/ui/charactergridview.js ***!
\*****************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ CharacterGridView)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/ui */ "./node_modules/ckeditor5/src/ui.js");
/* harmony import */ var _theme_charactergrid_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../../theme/charactergrid.css */ "./node_modules/@ckeditor/ckeditor5-special-characters/theme/charactergrid.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module special-characters/ui/charactergridview
*/
/**
* A grid of character tiles. It allows browsing special characters and selecting the character to
* be inserted into the content.
*
* @extends module:ui/view~View
*/
class CharacterGridView extends ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.View {
/**
* Creates an instance of a character grid containing tiles representing special characters.
*
* @param {module:utils/locale~Locale} locale The localization services instance.
*/
constructor( locale ) {
super( locale );
/**
* A collection of the child tile views. Each tile represents a particular character.
*
* @readonly
* @member {module:ui/viewcollection~ViewCollection}
*/
this.tiles = this.createCollection();
this.setTemplate( {
tag: 'div',
children: [
{
tag: 'div',
attributes: {
class: [
'ck',
'ck-character-grid__tiles'
]
},
children: this.tiles
}
],
attributes: {
class: [
'ck',
'ck-character-grid'
]
}
} );
/**
* Fired when any of {@link #tiles grid tiles} is clicked.
*
* @event execute
* @param {Object} data Additional information about the event.
* @param {String} data.name The name of the tile that caused the event (e.g. "greek small letter epsilon").
* @param {String} data.character A human-readable character displayed as the label (e.g. "ε").
*/
/**
* Fired when a mouse or another pointing device caused the cursor to move onto any {@link #tiles grid tile}
* (similar to the native `mouseover` DOM event).
*
* @event tileHover
* @param {Object} data Additional information about the event.
* @param {String} data.name The name of the tile that caused the event (e.g. "greek small letter epsilon").
* @param {String} data.character A human-readable character displayed as the label (e.g. "ε").
*/
}
/**
* Creates a new tile for the grid.
*
* @param {String} character A human-readable character displayed as the label (e.g. "ε").
* @param {String} name The name of the character (e.g. "greek small letter epsilon").
* @returns {module:ui/button/buttonview~ButtonView}
*/
createTile( character, name ) {
const tile = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.ButtonView( this.locale );
tile.set( {
label: character,
withText: true,
class: 'ck-character-grid__tile'
} );
// Labels are vital for the users to understand what character they're looking at.
// For now we're using native title attribute for that, see #5817.
tile.extendTemplate( {
attributes: {
title: name
},
on: {
mouseover: tile.bindTemplate.to( 'mouseover' )
}
} );
tile.on( 'mouseover', () => {
this.fire( 'tileHover', { name, character } );
} );
tile.on( 'execute', () => {
this.fire( 'execute', { name, character } );
} );
return tile;
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-special-characters/src/ui/characterinfoview.js":
/*!*****************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-special-characters/src/ui/characterinfoview.js ***!
\*****************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ CharacterInfoView)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/ui */ "./node_modules/ckeditor5/src/ui.js");
/* harmony import */ var _theme_characterinfo_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../../theme/characterinfo.css */ "./node_modules/@ckeditor/ckeditor5-special-characters/theme/characterinfo.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module special-characters/ui/characterinfoview
*/
/**
* The view displaying detailed information about a special character glyph, e.g. upon
* hovering it with a mouse.
*
* @extends module:ui/view~View
*/
class CharacterInfoView extends ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.View {
constructor( locale ) {
super( locale );
const bind = this.bindTemplate;
/**
* The character whose information is displayed by the view. For instance,
* "∑" or "¿".
*
* @observable
* @member {String|null} #character
*/
this.set( 'character', null );
/**
* The name of the {@link #character}. For instance,
* "N-ary summation" or "Inverted question mark".
*
* @observable
* @member {String|null} #name
*/
this.set( 'name', null );
/**
* The "Unicode string" of the {@link #character}. For instance,
* "U+0061".
*
* @observable
* @readonly
* @member {String} #code
*/
this.bind( 'code' ).to( this, 'character', characterToUnicodeString );
this.setTemplate( {
tag: 'div',
children: [
{
tag: 'span',
attributes: {
class: [
'ck-character-info__name'
]
},
children: [
{
// Note: ZWSP to prevent vertical collapsing.
text: bind.to( 'name', name => name ? name : '\u200B' )
}
]
},
{
tag: 'span',
attributes: {
class: [
'ck-character-info__code'
]
},
children: [
{
text: bind.to( 'code' )
}
]
}
],
attributes: {
class: [
'ck',
'ck-character-info'
]
}
} );
}
}
// Converts a character into a "Unicode string", for instance:
//
// "$" -> "U+0024"
//
// Returns an empty string when the character is `null`.
//
// @param {String} character
// @returns {String}
function characterToUnicodeString( character ) {
if ( character === null ) {
return '';
}
const hexCode = character.codePointAt( 0 ).toString( 16 );
return 'U+' + ( '0000' + hexCode ).slice( -4 );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-special-characters/src/ui/specialcharactersnavigationview.js":
/*!*******************************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-special-characters/src/ui/specialcharactersnavigationview.js ***!
\*******************************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ SpecialCharactersNavigationView)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/* harmony import */ var ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/ui */ "./node_modules/ckeditor5/src/ui.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module special-characters/ui/specialcharactersnavigationview
*/
/**
* A class representing the navigation part of the special characters UI. It is responsible
* for describing the feature and allowing the user to select a particular character group.
*
* @extends module:ui/formheader/formheaderview~FormHeaderView
*/
class SpecialCharactersNavigationView extends ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.FormHeaderView {
/**
* Creates an instance of the {@link module:special-characters/ui/specialcharactersnavigationview~SpecialCharactersNavigationView}
* class.
*
* @param {module:utils/locale~Locale} locale The localization services instance.
* @param {Iterable.<String>} groupNames The names of the character groups.
*/
constructor( locale, groupNames ) {
super( locale );
const t = locale.t;
this.set( 'class', 'ck-special-characters-navigation' );
/**
* A dropdown that allows selecting a group of special characters to be displayed.
*
* @member {module:ui/dropdown/dropdownview~DropdownView}
*/
this.groupDropdownView = this._createGroupDropdown( groupNames );
this.groupDropdownView.panelPosition = locale.uiLanguageDirection === 'rtl' ? 'se' : 'sw';
/**
* @inheritDoc
*/
this.label = t( 'Special characters' );
/**
* @inheritDoc
*/
this.children.add( this.groupDropdownView );
}
/**
* Returns the name of the character group currently selected in the {@link #groupDropdownView}.
*
* @type {String}
*/
get currentGroupName() {
return this.groupDropdownView.value;
}
/**
* Returns a dropdown that allows selecting character groups.
*
* @private
* @param {Iterable.<String>} groupNames The names of the character groups.
* @returns {module:ui/dropdown/dropdownview~DropdownView}
*/
_createGroupDropdown( groupNames ) {
const locale = this.locale;
const t = locale.t;
const dropdown = (0,ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.createDropdown)( locale );
const groupDefinitions = this._getCharacterGroupListItemDefinitions( dropdown, groupNames );
dropdown.set( 'value', groupDefinitions.first.model.label );
dropdown.buttonView.bind( 'label' ).to( dropdown, 'value' );
dropdown.buttonView.set( {
isOn: false,
withText: true,
tooltip: t( 'Character categories' ),
class: [ 'ck-dropdown__button_label-width_auto' ]
} );
dropdown.on( 'execute', evt => {
dropdown.value = evt.source.label;
} );
dropdown.delegate( 'execute' ).to( this );
(0,ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.addListToDropdown)( dropdown, groupDefinitions );
return dropdown;
}
/**
* Returns list item definitions to be used in the character group dropdown
* representing specific character groups.
*
* @private
* @param {module:ui/dropdown/dropdownview~DropdownView} dropdown
* @param {Iterable.<String>} groupNames The names of the character groups.
* @returns {Iterable.<module:ui/dropdown/utils~ListDropdownItemDefinition>}
*/
_getCharacterGroupListItemDefinitions( dropdown, groupNames ) {
const groupDefs = new ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_0__.Collection();
for ( const name of groupNames ) {
const definition = {
type: 'button',
model: new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.Model( {
label: name,
withText: true
} )
};
definition.model.bind( 'isOn' ).to( dropdown, 'value', value => {
return value === definition.model.label;
} );
groupDefs.add( definition );
}
return groupDefs;
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/commands/insertcolumncommand.js":
/*!************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/commands/insertcolumncommand.js ***!
\************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ InsertColumnCommand)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/commands/insertcolumncommand
*/
/**
* The insert column command.
*
* The command is registered by {@link module:table/tableediting~TableEditing} as the `'insertTableColumnLeft'` and
* `'insertTableColumnRight'` editor commands.
*
* To insert a column to the left of the selected cell, execute the following command:
*
* editor.execute( 'insertTableColumnLeft' );
*
* To insert a column to the right of the selected cell, execute the following command:
*
* editor.execute( 'insertTableColumnRight' );
*
* @extends module:core/command~Command
*/
class InsertColumnCommand extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Command {
/**
* Creates a new `InsertColumnCommand` instance.
*
* @param {module:core/editor/editor~Editor} editor An editor on which this command will be used.
* @param {Object} options
* @param {String} [options.order="right"] The order of insertion relative to the column in which the caret is located.
* Possible values: `"left"` and `"right"`.
*/
constructor( editor, options = {} ) {
super( editor );
/**
* The order of insertion relative to the column in which the caret is located.
*
* @readonly
* @member {String} module:table/commands/insertcolumncommand~InsertColumnCommand#order
*/
this.order = options.order || 'right';
}
/**
* @inheritDoc
*/
refresh() {
const selection = this.editor.model.document.selection;
const tableUtils = this.editor.plugins.get( 'TableUtils' );
const isAnyCellSelected = !!tableUtils.getSelectionAffectedTableCells( selection ).length;
this.isEnabled = isAnyCellSelected;
}
/**
* Executes the command.
*
* Depending on the command's {@link #order} value, it inserts a column to the `'left'` or `'right'` of the column
* in which the selection is set.
*
* @fires execute
*/
execute() {
const editor = this.editor;
const selection = editor.model.document.selection;
const tableUtils = editor.plugins.get( 'TableUtils' );
const insertBefore = this.order === 'left';
const affectedTableCells = tableUtils.getSelectionAffectedTableCells( selection );
const columnIndexes = tableUtils.getColumnIndexes( affectedTableCells );
const column = insertBefore ? columnIndexes.first : columnIndexes.last;
const table = affectedTableCells[ 0 ].findAncestor( 'table' );
tableUtils.insertColumns( table, { columns: 1, at: insertBefore ? column : column + 1 } );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/commands/insertrowcommand.js":
/*!*********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/commands/insertrowcommand.js ***!
\*********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ InsertRowCommand)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/commands/insertrowcommand
*/
/**
* The insert row command.
*
* The command is registered by {@link module:table/tableediting~TableEditing} as the `'insertTableRowBelow'` and
* `'insertTableRowAbove'` editor commands.
*
* To insert a row below the selected cell, execute the following command:
*
* editor.execute( 'insertTableRowBelow' );
*
* To insert a row above the selected cell, execute the following command:
*
* editor.execute( 'insertTableRowAbove' );
*
* @extends module:core/command~Command
*/
class InsertRowCommand extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Command {
/**
* Creates a new `InsertRowCommand` instance.
*
* @param {module:core/editor/editor~Editor} editor The editor on which this command will be used.
* @param {Object} options
* @param {String} [options.order="below"] The order of insertion relative to the row in which the caret is located.
* Possible values: `"above"` and `"below"`.
*/
constructor( editor, options = {} ) {
super( editor );
/**
* The order of insertion relative to the row in which the caret is located.
*
* @readonly
* @member {String} module:table/commands/insertrowcommand~InsertRowCommand#order
*/
this.order = options.order || 'below';
}
/**
* @inheritDoc
*/
refresh() {
const selection = this.editor.model.document.selection;
const tableUtils = this.editor.plugins.get( 'TableUtils' );
const isAnyCellSelected = !!tableUtils.getSelectionAffectedTableCells( selection ).length;
this.isEnabled = isAnyCellSelected;
}
/**
* Executes the command.
*
* Depending on the command's {@link #order} value, it inserts a row `'below'` or `'above'` the row in which selection is set.
*
* @fires execute
*/
execute() {
const editor = this.editor;
const selection = editor.model.document.selection;
const tableUtils = editor.plugins.get( 'TableUtils' );
const insertAbove = this.order === 'above';
const affectedTableCells = tableUtils.getSelectionAffectedTableCells( selection );
const rowIndexes = tableUtils.getRowIndexes( affectedTableCells );
const row = insertAbove ? rowIndexes.first : rowIndexes.last;
const table = affectedTableCells[ 0 ].findAncestor( 'table' );
tableUtils.insertRows( table, { at: insertAbove ? row : row + 1, copyStructureFromAbove: !insertAbove } );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/commands/inserttablecommand.js":
/*!***********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/commands/inserttablecommand.js ***!
\***********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ InsertTableCommand)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_widget__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/widget */ "./node_modules/ckeditor5/src/widget.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/commands/inserttablecommand
*/
/**
* The insert table command.
*
* The command is registered by {@link module:table/tableediting~TableEditing} as the `'insertTable'` editor command.
*
* To insert a table at the current selection, execute the command and specify the dimensions:
*
* editor.execute( 'insertTable', { rows: 20, columns: 5 } );
*
* @extends module:core/command~Command
*/
class InsertTableCommand extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Command {
/**
* @inheritDoc
*/
refresh() {
const model = this.editor.model;
const selection = model.document.selection;
const schema = model.schema;
this.isEnabled = isAllowedInParent( selection, schema );
}
/**
* Executes the command.
*
* Inserts a table with the given number of rows and columns into the editor.
*
* @param {Object} options
* @param {Number} [options.rows=2] The number of rows to create in the inserted table.
* @param {Number} [options.columns=2] The number of columns to create in the inserted table.
* @param {Number} [options.headingRows] The number of heading rows.
* If not provided it will default to {@link module:table/table~TableConfig#defaultHeadings `config.table.defaultHeadings.rows`}
* table config.
* @param {Number} [options.headingColumns] The number of heading columns.
* If not provided it will default to {@link module:table/table~TableConfig#defaultHeadings `config.table.defaultHeadings.columns`}
* table config.
* @fires execute
*/
execute( options = {} ) {
const model = this.editor.model;
const selection = model.document.selection;
const tableUtils = this.editor.plugins.get( 'TableUtils' );
const config = this.editor.config.get( 'table' );
const insertionRange = (0,ckeditor5_src_widget__WEBPACK_IMPORTED_MODULE_1__.findOptimalInsertionRange)( selection, model );
const defaultRows = config.defaultHeadings.rows;
const defaultColumns = config.defaultHeadings.columns;
if ( options.headingRows === undefined && defaultRows ) {
options.headingRows = defaultRows;
}
if ( options.headingColumns === undefined && defaultColumns ) {
options.headingColumns = defaultColumns;
}
model.change( writer => {
const table = tableUtils.createTable( writer, options );
model.insertContent( table, insertionRange );
writer.setSelection( writer.createPositionAt( table.getNodeByPath( [ 0, 0, 0 ] ), 0 ) );
} );
}
}
// Checks if the table is allowed in the parent.
//
// @param {module:engine/model/selection~Selection|module:engine/model/documentselection~DocumentSelection} selection
// @param {module:engine/model/schema~Schema} schema
// @returns {Boolean}
function isAllowedInParent( selection, schema ) {
const positionParent = selection.getFirstPosition().parent;
const validParent = positionParent === positionParent.root ? positionParent : positionParent.parent;
return schema.checkChild( validParent, 'table' );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/commands/mergecellcommand.js":
/*!*********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/commands/mergecellcommand.js ***!
\*********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ MergeCellCommand)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var _tablewalker__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../tablewalker */ "./node_modules/@ckeditor/ckeditor5-table/src/tablewalker.js");
/* harmony import */ var _utils_common__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../utils/common */ "./node_modules/@ckeditor/ckeditor5-table/src/utils/common.js");
/* harmony import */ var _utils_structure__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../utils/structure */ "./node_modules/@ckeditor/ckeditor5-table/src/utils/structure.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/commands/mergecellcommand
*/
/**
* The merge cell command.
*
* The command is registered by {@link module:table/tableediting~TableEditing} as the `'mergeTableCellRight'`, `'mergeTableCellLeft'`,
* `'mergeTableCellUp'` and `'mergeTableCellDown'` editor commands.
*
* To merge a table cell at the current selection with another cell, execute the command corresponding with the preferred direction.
*
* For example, to merge with a cell to the right:
*
* editor.execute( 'mergeTableCellRight' );
*
* **Note**: If a table cell has a different [`rowspan`](https://www.w3.org/TR/html50/tabular-data.html#attr-tdth-rowspan)
* (for `'mergeTableCellRight'` and `'mergeTableCellLeft'`) or [`colspan`](https://www.w3.org/TR/html50/tabular-data.html#attr-tdth-colspan)
* (for `'mergeTableCellUp'` and `'mergeTableCellDown'`), the command will be disabled.
*
* @extends module:core/command~Command
*/
class MergeCellCommand extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Command {
/**
* Creates a new `MergeCellCommand` instance.
*
* @param {module:core/editor/editor~Editor} editor The editor on which this command will be used.
* @param {Object} options
* @param {String} options.direction Indicates which cell to merge with the currently selected one.
* Possible values are: `'left'`, `'right'`, `'up'` and `'down'`.
*/
constructor( editor, options ) {
super( editor );
/**
* The direction that indicates which cell will be merged with the currently selected one.
*
* @readonly
* @member {String} #direction
*/
this.direction = options.direction;
/**
* Whether the merge is horizontal (left/right) or vertical (up/down).
*
* @readonly
* @member {Boolean} #isHorizontal
*/
this.isHorizontal = this.direction == 'right' || this.direction == 'left';
}
/**
* @inheritDoc
*/
refresh() {
const cellToMerge = this._getMergeableCell();
this.value = cellToMerge;
this.isEnabled = !!cellToMerge;
}
/**
* Executes the command.
*
* Depending on the command's {@link #direction} value, it will merge the cell that is to the `'left'`, `'right'`, `'up'` or `'down'`.
*
* @fires execute
*/
execute() {
const model = this.editor.model;
const doc = model.document;
const tableUtils = this.editor.plugins.get( 'TableUtils' );
const tableCell = tableUtils.getTableCellsContainingSelection( doc.selection )[ 0 ];
const cellToMerge = this.value;
const direction = this.direction;
model.change( writer => {
const isMergeNext = direction == 'right' || direction == 'down';
// The merge mechanism is always the same so sort cells to be merged.
const cellToExpand = isMergeNext ? tableCell : cellToMerge;
const cellToRemove = isMergeNext ? cellToMerge : tableCell;
// Cache the parent of cell to remove for later check.
const removedTableCellRow = cellToRemove.parent;
mergeTableCells( cellToRemove, cellToExpand, writer );
const spanAttribute = this.isHorizontal ? 'colspan' : 'rowspan';
const cellSpan = parseInt( tableCell.getAttribute( spanAttribute ) || 1 );
const cellToMergeSpan = parseInt( cellToMerge.getAttribute( spanAttribute ) || 1 );
// Update table cell span attribute and merge set selection on merged contents.
writer.setAttribute( spanAttribute, cellSpan + cellToMergeSpan, cellToExpand );
writer.setSelection( writer.createRangeIn( cellToExpand ) );
const tableUtils = this.editor.plugins.get( 'TableUtils' );
const table = removedTableCellRow.findAncestor( 'table' );
// Remove empty rows and columns after merging.
(0,_utils_structure__WEBPACK_IMPORTED_MODULE_3__.removeEmptyRowsColumns)( table, tableUtils );
} );
}
/**
* Returns a cell that can be merged with the current cell depending on the command's direction.
*
* @returns {module:engine/model/element~Element|undefined}
* @private
*/
_getMergeableCell() {
const model = this.editor.model;
const doc = model.document;
const tableUtils = this.editor.plugins.get( 'TableUtils' );
const tableCell = tableUtils.getTableCellsContainingSelection( doc.selection )[ 0 ];
if ( !tableCell ) {
return;
}
// First get the cell on proper direction.
const cellToMerge = this.isHorizontal ?
getHorizontalCell( tableCell, this.direction, tableUtils ) :
getVerticalCell( tableCell, this.direction, tableUtils );
if ( !cellToMerge ) {
return;
}
// If found check if the span perpendicular to merge direction is equal on both cells.
const spanAttribute = this.isHorizontal ? 'rowspan' : 'colspan';
const span = parseInt( tableCell.getAttribute( spanAttribute ) || 1 );
const cellToMergeSpan = parseInt( cellToMerge.getAttribute( spanAttribute ) || 1 );
if ( cellToMergeSpan === span ) {
return cellToMerge;
}
}
}
// Returns the cell that can be merged horizontally.
//
// @param {module:engine/model/element~Element} tableCell
// @param {String} direction
// @param {module:table/tableutils~TableUtils} tableUtils
// @returns {module:engine/model/node~Node|null}
function getHorizontalCell( tableCell, direction, tableUtils ) {
const tableRow = tableCell.parent;
const table = tableRow.parent;
const horizontalCell = direction == 'right' ? tableCell.nextSibling : tableCell.previousSibling;
const hasHeadingColumns = ( table.getAttribute( 'headingColumns' ) || 0 ) > 0;
if ( !horizontalCell ) {
return;
}
// Sort cells:
const cellOnLeft = direction == 'right' ? tableCell : horizontalCell;
const cellOnRight = direction == 'right' ? horizontalCell : tableCell;
// Get their column indexes:
const { column: leftCellColumn } = tableUtils.getCellLocation( cellOnLeft );
const { column: rightCellColumn } = tableUtils.getCellLocation( cellOnRight );
const leftCellSpan = parseInt( cellOnLeft.getAttribute( 'colspan' ) || 1 );
const isCellOnLeftInHeadingColumn = (0,_utils_common__WEBPACK_IMPORTED_MODULE_2__.isHeadingColumnCell)( tableUtils, cellOnLeft, table );
const isCellOnRightInHeadingColumn = (0,_utils_common__WEBPACK_IMPORTED_MODULE_2__.isHeadingColumnCell)( tableUtils, cellOnRight, table );
// We cannot merge heading columns cells with regular cells.
if ( hasHeadingColumns && isCellOnLeftInHeadingColumn != isCellOnRightInHeadingColumn ) {
return;
}
// The cell on the right must have index that is distant to the cell on the left by the left cell's width (colspan).
const cellsAreTouching = leftCellColumn + leftCellSpan === rightCellColumn;
// If the right cell's column index is different it means that there are rowspanned cells between them.
return cellsAreTouching ? horizontalCell : undefined;
}
// Returns the cell that can be merged vertically.
//
// @param {module:engine/model/element~Element} tableCell
// @param {String} direction
// @param {module:table/tableutils~TableUtils} tableUtils
// @returns {module:engine/model/node~Node|null}
function getVerticalCell( tableCell, direction, tableUtils ) {
const tableRow = tableCell.parent;
const table = tableRow.parent;
const rowIndex = table.getChildIndex( tableRow );
// Don't search for mergeable cell if direction points out of the table.
if ( ( direction == 'down' && rowIndex === tableUtils.getRows( table ) - 1 ) || ( direction == 'up' && rowIndex === 0 ) ) {
return;
}
const rowspan = parseInt( tableCell.getAttribute( 'rowspan' ) || 1 );
const headingRows = table.getAttribute( 'headingRows' ) || 0;
const isMergeWithBodyCell = direction == 'down' && ( rowIndex + rowspan ) === headingRows;
const isMergeWithHeadCell = direction == 'up' && rowIndex === headingRows;
// Don't search for mergeable cell if direction points out of the current table section.
if ( headingRows && ( isMergeWithBodyCell || isMergeWithHeadCell ) ) {
return;
}
const currentCellRowSpan = parseInt( tableCell.getAttribute( 'rowspan' ) || 1 );
const rowOfCellToMerge = direction == 'down' ? rowIndex + currentCellRowSpan : rowIndex;
const tableMap = [ ...new _tablewalker__WEBPACK_IMPORTED_MODULE_1__["default"]( table, { endRow: rowOfCellToMerge } ) ];
const currentCellData = tableMap.find( value => value.cell === tableCell );
const mergeColumn = currentCellData.column;
const cellToMergeData = tableMap.find( ( { row, cellHeight, column } ) => {
if ( column !== mergeColumn ) {
return false;
}
if ( direction == 'down' ) {
// If merging a cell below the mergeRow is already calculated.
return row === rowOfCellToMerge;
} else {
// If merging a cell above calculate if it spans to mergeRow.
return rowOfCellToMerge === row + cellHeight;
}
} );
return cellToMergeData && cellToMergeData.cell;
}
// Merges two table cells. It will ensure that after merging cells with an empty paragraph, the resulting table cell will only have one
// paragraph. If one of the merged table cells is empty, the merged table cell will have the contents of the non-empty table cell.
// If both are empty, the merged table cell will have only one empty paragraph.
//
// @param {module:engine/model/element~Element} cellToRemove
// @param {module:engine/model/element~Element} cellToExpand
// @param {module:engine/model/writer~Writer} writer
function mergeTableCells( cellToRemove, cellToExpand, writer ) {
if ( !isEmpty( cellToRemove ) ) {
if ( isEmpty( cellToExpand ) ) {
writer.remove( writer.createRangeIn( cellToExpand ) );
}
writer.move( writer.createRangeIn( cellToRemove ), writer.createPositionAt( cellToExpand, 'end' ) );
}
// Remove merged table cell.
writer.remove( cellToRemove );
}
// Checks if the passed table cell contains an empty paragraph.
//
// @param {module:engine/model/element~Element} tableCell
// @returns {Boolean}
function isEmpty( tableCell ) {
return tableCell.childCount == 1 && tableCell.getChild( 0 ).is( 'element', 'paragraph' ) && tableCell.getChild( 0 ).isEmpty;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/commands/mergecellscommand.js":
/*!**********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/commands/mergecellscommand.js ***!
\**********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ MergeCellsCommand)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var _tableutils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../tableutils */ "./node_modules/@ckeditor/ckeditor5-table/src/tableutils.js");
/* harmony import */ var _utils_common__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../utils/common */ "./node_modules/@ckeditor/ckeditor5-table/src/utils/common.js");
/* harmony import */ var _utils_structure__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../utils/structure */ "./node_modules/@ckeditor/ckeditor5-table/src/utils/structure.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/commands/mergecellscommand
*/
/**
* The merge cells command.
*
* The command is registered by {@link module:table/tableediting~TableEditing} as the `'mergeTableCells'` editor command.
*
* For example, to merge selected table cells:
*
* editor.execute( 'mergeTableCells' );
*
* @extends module:core/command~Command
*/
class MergeCellsCommand extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Command {
/**
* @inheritDoc
*/
refresh() {
const tableUtils = this.editor.plugins.get( _tableutils__WEBPACK_IMPORTED_MODULE_1__["default"] );
const selectedTableCells = tableUtils.getSelectedTableCells( this.editor.model.document.selection );
this.isEnabled = tableUtils.isSelectionRectangular( selectedTableCells, this.editor.plugins.get( _tableutils__WEBPACK_IMPORTED_MODULE_1__["default"] ) );
}
/**
* Executes the command.
*
* @fires execute
*/
execute() {
const model = this.editor.model;
const tableUtils = this.editor.plugins.get( _tableutils__WEBPACK_IMPORTED_MODULE_1__["default"] );
model.change( writer => {
const selectedTableCells = tableUtils.getSelectedTableCells( model.document.selection );
// All cells will be merged into the first one.
const firstTableCell = selectedTableCells.shift();
// Update target cell dimensions.
const { mergeWidth, mergeHeight } = getMergeDimensions( firstTableCell, selectedTableCells, tableUtils );
(0,_utils_common__WEBPACK_IMPORTED_MODULE_2__.updateNumericAttribute)( 'colspan', mergeWidth, firstTableCell, writer );
(0,_utils_common__WEBPACK_IMPORTED_MODULE_2__.updateNumericAttribute)( 'rowspan', mergeHeight, firstTableCell, writer );
for ( const tableCell of selectedTableCells ) {
mergeTableCells( tableCell, firstTableCell, writer );
}
const table = firstTableCell.findAncestor( 'table' );
// Remove rows and columns that become empty (have no anchored cells).
(0,_utils_structure__WEBPACK_IMPORTED_MODULE_3__.removeEmptyRowsColumns)( table, tableUtils );
writer.setSelection( firstTableCell, 'in' );
} );
}
}
// Merges two table cells. It will ensure that after merging cells with empty paragraphs the resulting table cell will only have one
// paragraph. If one of the merged table cells is empty, the merged table cell will have contents of the non-empty table cell.
// If both are empty, the merged table cell will have only one empty paragraph.
//
// @param {module:engine/model/element~Element} cellBeingMerged
// @param {module:engine/model/element~Element} targetCell
// @param {module:engine/model/writer~Writer} writer
function mergeTableCells( cellBeingMerged, targetCell, writer ) {
if ( !isEmpty( cellBeingMerged ) ) {
if ( isEmpty( targetCell ) ) {
writer.remove( writer.createRangeIn( targetCell ) );
}
writer.move( writer.createRangeIn( cellBeingMerged ), writer.createPositionAt( targetCell, 'end' ) );
}
// Remove merged table cell.
writer.remove( cellBeingMerged );
}
// Checks if the passed table cell contains an empty paragraph.
//
// @param {module:engine/model/element~Element} tableCell
// @returns {Boolean}
function isEmpty( tableCell ) {
return tableCell.childCount == 1 && tableCell.getChild( 0 ).is( 'element', 'paragraph' ) && tableCell.getChild( 0 ).isEmpty;
}
function getMergeDimensions( firstTableCell, selectedTableCells, tableUtils ) {
let maxWidthOffset = 0;
let maxHeightOffset = 0;
for ( const tableCell of selectedTableCells ) {
const { row, column } = tableUtils.getCellLocation( tableCell );
maxWidthOffset = getMaxOffset( tableCell, column, maxWidthOffset, 'colspan' );
maxHeightOffset = getMaxOffset( tableCell, row, maxHeightOffset, 'rowspan' );
}
// Update table cell span attribute and merge set selection on a merged contents.
const { row: firstCellRow, column: firstCellColumn } = tableUtils.getCellLocation( firstTableCell );
const mergeWidth = maxWidthOffset - firstCellColumn;
const mergeHeight = maxHeightOffset - firstCellRow;
return { mergeWidth, mergeHeight };
}
function getMaxOffset( tableCell, start, currentMaxOffset, which ) {
const dimensionValue = parseInt( tableCell.getAttribute( which ) || 1 );
return Math.max( currentMaxOffset, start + dimensionValue );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/commands/removecolumncommand.js":
/*!************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/commands/removecolumncommand.js ***!
\************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ RemoveColumnCommand)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var _tablewalker__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../tablewalker */ "./node_modules/@ckeditor/ckeditor5-table/src/tablewalker.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/commands/removecolumncommand
*/
/**
* The remove column command.
*
* The command is registered by {@link module:table/tableediting~TableEditing} as the `'removeTableColumn'` editor command.
*
* To remove the column containing the selected cell, execute the command:
*
* editor.execute( 'removeTableColumn' );
*
* @extends module:core/command~Command
*/
class RemoveColumnCommand extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Command {
/**
* @inheritDoc
*/
refresh() {
const tableUtils = this.editor.plugins.get( 'TableUtils' );
const selectedCells = tableUtils.getSelectionAffectedTableCells( this.editor.model.document.selection );
const firstCell = selectedCells[ 0 ];
if ( firstCell ) {
const table = firstCell.findAncestor( 'table' );
const tableColumnCount = tableUtils.getColumns( table );
const { first, last } = tableUtils.getColumnIndexes( selectedCells );
this.isEnabled = last - first < ( tableColumnCount - 1 );
} else {
this.isEnabled = false;
}
}
/**
* @inheritDoc
*/
execute() {
const tableUtils = this.editor.plugins.get( 'TableUtils' );
const [ firstCell, lastCell ] = getBoundaryCells( this.editor.model.document.selection, tableUtils );
const table = firstCell.parent.parent;
// Cache the table before removing or updating colspans.
const tableMap = [ ...new _tablewalker__WEBPACK_IMPORTED_MODULE_1__["default"]( table ) ];
// Store column indexes of removed columns.
const removedColumnIndexes = {
first: tableMap.find( value => value.cell === firstCell ).column,
last: tableMap.find( value => value.cell === lastCell ).column
};
const cellToFocus = getCellToFocus( tableMap, firstCell, lastCell, removedColumnIndexes );
this.editor.model.change( writer => {
const columnsToRemove = removedColumnIndexes.last - removedColumnIndexes.first + 1;
this.editor.plugins.get( 'TableUtils' ).removeColumns( table, {
at: removedColumnIndexes.first,
columns: columnsToRemove
} );
writer.setSelection( writer.createPositionAt( cellToFocus, 0 ) );
} );
}
}
// Returns a proper table cell to focus after removing a column.
// - selection is on last table cell it will return previous cell.
function getCellToFocus( tableMap, firstCell, lastCell, removedColumnIndexes ) {
const colspan = parseInt( lastCell.getAttribute( 'colspan' ) || 1 );
// If the table cell is spanned over 2+ columns - it will be truncated so the selection should
// stay in that cell.
if ( colspan > 1 ) {
return lastCell;
}
// Normally, look for the cell in the same row that precedes the first cell to put selection there ("column on the left").
// If the deleted column is the first column of the table, there will be no predecessor: use the cell
// from the column that follows then (also in the same row).
else if ( firstCell.previousSibling || lastCell.nextSibling ) {
return lastCell.nextSibling || firstCell.previousSibling;
}
// It can happen that table cells have no siblings in a row, for instance, when there are row spans
// in the table (in the previous row). Then just look for the closest cell that is in a column
// that will not be removed to put the selection there.
else {
// Look for any cell in a column that precedes the first removed column.
if ( removedColumnIndexes.first ) {
return tableMap.reverse().find( ( { column } ) => {
return column < removedColumnIndexes.first;
} ).cell;
}
// If the first removed column is the first column of the table, then
// look for any cell that is in a column that follows the last removed column.
else {
return tableMap.reverse().find( ( { column } ) => {
return column > removedColumnIndexes.last;
} ).cell;
}
}
}
// Returns helper object returning the first and the last cell contained in given selection, based on DOM order.
function getBoundaryCells( selection, tableUtils ) {
const referenceCells = tableUtils.getSelectionAffectedTableCells( selection );
const firstCell = referenceCells[ 0 ];
const lastCell = referenceCells.pop();
const returnValue = [ firstCell, lastCell ];
return firstCell.isBefore( lastCell ) ? returnValue : returnValue.reverse();
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/commands/removerowcommand.js":
/*!*********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/commands/removerowcommand.js ***!
\*********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ RemoveRowCommand)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/commands/removerowcommand
*/
/**
* The remove row command.
*
* The command is registered by {@link module:table/tableediting~TableEditing} as the `'removeTableRow'` editor command.
*
* To remove the row containing the selected cell, execute the command:
*
* editor.execute( 'removeTableRow' );
*
* @extends module:core/command~Command
*/
class RemoveRowCommand extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Command {
/**
* @inheritDoc
*/
refresh() {
const tableUtils = this.editor.plugins.get( 'TableUtils' );
const selectedCells = tableUtils.getSelectionAffectedTableCells( this.editor.model.document.selection );
const firstCell = selectedCells[ 0 ];
if ( firstCell ) {
const table = firstCell.findAncestor( 'table' );
const tableRowCount = this.editor.plugins.get( 'TableUtils' ).getRows( table );
const lastRowIndex = tableRowCount - 1;
const selectedRowIndexes = tableUtils.getRowIndexes( selectedCells );
const areAllRowsSelected = selectedRowIndexes.first === 0 && selectedRowIndexes.last === lastRowIndex;
// Disallow selecting whole table -> delete whole table should be used instead.
this.isEnabled = !areAllRowsSelected;
} else {
this.isEnabled = false;
}
}
/**
* @inheritDoc
*/
execute() {
const model = this.editor.model;
const tableUtils = this.editor.plugins.get( 'TableUtils' );
const referenceCells = tableUtils.getSelectionAffectedTableCells( model.document.selection );
const removedRowIndexes = tableUtils.getRowIndexes( referenceCells );
const firstCell = referenceCells[ 0 ];
const table = firstCell.findAncestor( 'table' );
const columnIndexToFocus = tableUtils.getCellLocation( firstCell ).column;
model.change( writer => {
const rowsToRemove = removedRowIndexes.last - removedRowIndexes.first + 1;
tableUtils.removeRows( table, {
at: removedRowIndexes.first,
rows: rowsToRemove
} );
const cellToFocus = getCellToFocus( table, removedRowIndexes.first, columnIndexToFocus, tableUtils.getRows( table ) );
writer.setSelection( writer.createPositionAt( cellToFocus, 0 ) );
} );
}
}
// Returns a cell that should be focused before removing the row, belonging to the same column as the currently focused cell.
// * If the row was not the last one, the cell to focus will be in the row that followed it (before removal).
// * If the row was the last one, the cell to focus will be in the row that preceded it (before removal).
function getCellToFocus( table, removedRowIndex, columnToFocus, tableRowCount ) {
// Don't go beyond last row's index.
const row = table.getChild( Math.min( removedRowIndex, tableRowCount - 1 ) );
// Default to first table cell.
let cellToFocus = row.getChild( 0 );
let column = 0;
for ( const tableCell of row.getChildren() ) {
if ( column > columnToFocus ) {
return cellToFocus;
}
cellToFocus = tableCell;
column += parseInt( tableCell.getAttribute( 'colspan' ) || 1 );
}
return cellToFocus;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/commands/selectcolumncommand.js":
/*!************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/commands/selectcolumncommand.js ***!
\************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ SelectColumnCommand)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var _tablewalker__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../tablewalker */ "./node_modules/@ckeditor/ckeditor5-table/src/tablewalker.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/commands/selectcolumncommand
*/
/**
* The select column command.
*
* The command is registered by {@link module:table/tableediting~TableEditing} as the `'selectTableColumn'` editor command.
*
* To select the columns containing the selected cells, execute the command:
*
* editor.execute( 'selectTableColumn' );
*
* @extends module:core/command~Command
*/
class SelectColumnCommand extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Command {
/**
* @inheritDoc
*/
constructor( editor ) {
super( editor );
// It does not affect data so should be enabled in read-only mode.
this.affectsData = false;
}
/**
* @inheritDoc
*/
refresh() {
const tableUtils = this.editor.plugins.get( 'TableUtils' );
const selectedCells = tableUtils.getSelectionAffectedTableCells( this.editor.model.document.selection );
this.isEnabled = selectedCells.length > 0;
}
/**
* @inheritDoc
*/
execute() {
const tableUtils = this.editor.plugins.get( 'TableUtils' );
const model = this.editor.model;
const referenceCells = tableUtils.getSelectionAffectedTableCells( model.document.selection );
const firstCell = referenceCells[ 0 ];
const lastCell = referenceCells.pop();
const table = firstCell.findAncestor( 'table' );
const startLocation = tableUtils.getCellLocation( firstCell );
const endLocation = tableUtils.getCellLocation( lastCell );
const startColumn = Math.min( startLocation.column, endLocation.column );
const endColumn = Math.max( startLocation.column, endLocation.column );
const rangesToSelect = [];
for ( const cellInfo of new _tablewalker__WEBPACK_IMPORTED_MODULE_1__["default"]( table, { startColumn, endColumn } ) ) {
rangesToSelect.push( model.createRangeOn( cellInfo.cell ) );
}
model.change( writer => {
writer.setSelection( rangesToSelect );
} );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/commands/selectrowcommand.js":
/*!*********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/commands/selectrowcommand.js ***!
\*********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ SelectRowCommand)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/commands/selectrowcommand
*/
/**
* The select row command.
*
* The command is registered by {@link module:table/tableediting~TableEditing} as the `'selectTableRow'` editor command.
*
* To select the rows containing the selected cells, execute the command:
*
* editor.execute( 'selectTableRow' );
*
* @extends module:core/command~Command
*/
class SelectRowCommand extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Command {
/**
* @inheritDoc
*/
constructor( editor ) {
super( editor );
// It does not affect data so should be enabled in read-only mode.
this.affectsData = false;
}
/**
* @inheritDoc
*/
refresh() {
const tableUtils = this.editor.plugins.get( 'TableUtils' );
const selectedCells = tableUtils.getSelectionAffectedTableCells( this.editor.model.document.selection );
this.isEnabled = selectedCells.length > 0;
}
/**
* @inheritDoc
*/
execute() {
const model = this.editor.model;
const tableUtils = this.editor.plugins.get( 'TableUtils' );
const referenceCells = tableUtils.getSelectionAffectedTableCells( model.document.selection );
const rowIndexes = tableUtils.getRowIndexes( referenceCells );
const table = referenceCells[ 0 ].findAncestor( 'table' );
const rangesToSelect = [];
for ( let rowIndex = rowIndexes.first; rowIndex <= rowIndexes.last; rowIndex++ ) {
for ( const cell of table.getChild( rowIndex ).getChildren() ) {
rangesToSelect.push( model.createRangeOn( cell ) );
}
}
model.change( writer => {
writer.setSelection( rangesToSelect );
} );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/commands/setheadercolumncommand.js":
/*!***************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/commands/setheadercolumncommand.js ***!
\***************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ SetHeaderColumnCommand)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var _utils_common__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../utils/common */ "./node_modules/@ckeditor/ckeditor5-table/src/utils/common.js");
/* harmony import */ var _utils_structure__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../utils/structure */ "./node_modules/@ckeditor/ckeditor5-table/src/utils/structure.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/commands/setheadercolumncommand
*/
/**
* The header column command.
*
* The command is registered by {@link module:table/tableediting~TableEditing} as the `'setTableColumnHeader'` editor command.
*
* You can make the column containing the selected cell a [header](https://www.w3.org/TR/html50/tabular-data.html#the-th-element)
* by executing:
*
* editor.execute( 'setTableColumnHeader' );
*
* **Note:** All preceding columns will also become headers. If the current column is already a header, executing this command
* will make it a regular column back again (including the following columns).
*
* @extends module:core/command~Command
*/
class SetHeaderColumnCommand extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Command {
/**
* @inheritDoc
*/
refresh() {
const model = this.editor.model;
const tableUtils = this.editor.plugins.get( 'TableUtils' );
const selectedCells = tableUtils.getSelectionAffectedTableCells( model.document.selection );
const isInTable = selectedCells.length > 0;
this.isEnabled = isInTable;
/**
* Flag indicating whether the command is active. The command is active when the
* {@link module:engine/model/selection~Selection} is in a header column.
*
* @observable
* @readonly
* @member {Boolean} #value
*/
this.value = isInTable && selectedCells.every( cell => (0,_utils_common__WEBPACK_IMPORTED_MODULE_1__.isHeadingColumnCell)( tableUtils, cell ) );
}
/**
* Executes the command.
*
* When the selection is in a non-header column, the command will set the `headingColumns` table attribute to cover that column.
*
* When the selection is already in a header column, it will set `headingColumns` so the heading section will end before that column.
*
* @fires execute
* @param {Object} [options]
* @param {Boolean} [options.forceValue] If set, the command will set (`true`) or unset (`false`) the header columns according to
* the `forceValue` parameter instead of the current model state.
*/
execute( options = {} ) {
if ( options.forceValue === this.value ) {
return;
}
const tableUtils = this.editor.plugins.get( 'TableUtils' );
const model = this.editor.model;
const selectedCells = tableUtils.getSelectionAffectedTableCells( model.document.selection );
const table = selectedCells[ 0 ].findAncestor( 'table' );
const { first, last } = tableUtils.getColumnIndexes( selectedCells );
const headingColumnsToSet = this.value ? first : last + 1;
model.change( writer => {
if ( headingColumnsToSet ) {
// Changing heading columns requires to check if any of a heading cell is overlapping horizontally the table head.
// Any table cell that has a colspan attribute > 1 will not exceed the table head so we need to fix it in columns before.
const overlappingCells = (0,_utils_structure__WEBPACK_IMPORTED_MODULE_2__.getHorizontallyOverlappingCells)( table, headingColumnsToSet );
for ( const { cell, column } of overlappingCells ) {
(0,_utils_structure__WEBPACK_IMPORTED_MODULE_2__.splitVertically)( cell, column, headingColumnsToSet, writer );
}
}
(0,_utils_common__WEBPACK_IMPORTED_MODULE_1__.updateNumericAttribute)( 'headingColumns', headingColumnsToSet, table, writer, 0 );
} );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/commands/setheaderrowcommand.js":
/*!************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/commands/setheaderrowcommand.js ***!
\************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ SetHeaderRowCommand)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var _utils_common__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../utils/common */ "./node_modules/@ckeditor/ckeditor5-table/src/utils/common.js");
/* harmony import */ var _utils_structure__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../utils/structure */ "./node_modules/@ckeditor/ckeditor5-table/src/utils/structure.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/commands/setheaderrowcommand
*/
/**
* The header row command.
*
* The command is registered by {@link module:table/tableediting~TableEditing} as the `'setTableColumnHeader'` editor command.
*
* You can make the row containing the selected cell a [header](https://www.w3.org/TR/html50/tabular-data.html#the-th-element) by executing:
*
* editor.execute( 'setTableRowHeader' );
*
* **Note:** All preceding rows will also become headers. If the current row is already a header, executing this command
* will make it a regular row back again (including the following rows).
*
* @extends module:core/command~Command
*/
class SetHeaderRowCommand extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Command {
/**
* @inheritDoc
*/
refresh() {
const tableUtils = this.editor.plugins.get( 'TableUtils' );
const model = this.editor.model;
const selectedCells = tableUtils.getSelectionAffectedTableCells( model.document.selection );
const isInTable = selectedCells.length > 0;
this.isEnabled = isInTable;
/**
* Flag indicating whether the command is active. The command is active when the
* {@link module:engine/model/selection~Selection} is in a header row.
*
* @observable
* @readonly
* @member {Boolean} #value
*/
this.value = isInTable && selectedCells.every( cell => this._isInHeading( cell, cell.parent.parent ) );
}
/**
* Executes the command.
*
* When the selection is in a non-header row, the command will set the `headingRows` table attribute to cover that row.
*
* When the selection is already in a header row, it will set `headingRows` so the heading section will end before that row.
*
* @fires execute
* @param {Object} options
* @param {Boolean} [options.forceValue] If set, the command will set (`true`) or unset (`false`) the header rows according to
* the `forceValue` parameter instead of the current model state.
*/
execute( options = {} ) {
if ( options.forceValue === this.value ) {
return;
}
const tableUtils = this.editor.plugins.get( 'TableUtils' );
const model = this.editor.model;
const selectedCells = tableUtils.getSelectionAffectedTableCells( model.document.selection );
const table = selectedCells[ 0 ].findAncestor( 'table' );
const { first, last } = tableUtils.getRowIndexes( selectedCells );
const headingRowsToSet = this.value ? first : last + 1;
const currentHeadingRows = table.getAttribute( 'headingRows' ) || 0;
model.change( writer => {
if ( headingRowsToSet ) {
// Changing heading rows requires to check if any of a heading cell is overlapping vertically the table head.
// Any table cell that has a rowspan attribute > 1 will not exceed the table head so we need to fix it in rows below.
const startRow = headingRowsToSet > currentHeadingRows ? currentHeadingRows : 0;
const overlappingCells = (0,_utils_structure__WEBPACK_IMPORTED_MODULE_2__.getVerticallyOverlappingCells)( table, headingRowsToSet, startRow );
for ( const { cell } of overlappingCells ) {
(0,_utils_structure__WEBPACK_IMPORTED_MODULE_2__.splitHorizontally)( cell, headingRowsToSet, writer );
}
}
(0,_utils_common__WEBPACK_IMPORTED_MODULE_1__.updateNumericAttribute)( 'headingRows', headingRowsToSet, table, writer, 0 );
} );
}
/**
* Checks if a table cell is in the heading section.
*
* @param {module:engine/model/element~Element} tableCell
* @param {module:engine/model/element~Element} table
* @returns {Boolean}
* @private
*/
_isInHeading( tableCell, table ) {
const headingRows = parseInt( table.getAttribute( 'headingRows' ) || 0 );
return !!headingRows && tableCell.parent.index < headingRows;
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/commands/splitcellcommand.js":
/*!*********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/commands/splitcellcommand.js ***!
\*********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ SplitCellCommand)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/commands/splitcellcommand
*/
/**
* The split cell command.
*
* The command is registered by {@link module:table/tableediting~TableEditing} as the `'splitTableCellVertically'`
* and `'splitTableCellHorizontally'` editor commands.
*
* You can split any cell vertically or horizontally by executing this command. For example, to split the selected table cell vertically:
*
* editor.execute( 'splitTableCellVertically' );
*
* @extends module:core/command~Command
*/
class SplitCellCommand extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Command {
/**
* Creates a new `SplitCellCommand` instance.
*
* @param {module:core/editor/editor~Editor} editor The editor on which this command will be used.
* @param {Object} options
* @param {String} options.direction Indicates whether the command should split cells `'horizontally'` or `'vertically'`.
*/
constructor( editor, options = {} ) {
super( editor );
/**
* The direction that indicates which cell will be split.
*
* @readonly
* @member {String} #direction
*/
this.direction = options.direction || 'horizontally';
}
/**
* @inheritDoc
*/
refresh() {
const tableUtils = this.editor.plugins.get( 'TableUtils' );
const selectedCells = tableUtils.getSelectionAffectedTableCells( this.editor.model.document.selection );
this.isEnabled = selectedCells.length === 1;
}
/**
* @inheritDoc
*/
execute() {
const tableUtils = this.editor.plugins.get( 'TableUtils' );
const tableCell = tableUtils.getSelectionAffectedTableCells( this.editor.model.document.selection )[ 0 ];
const isHorizontal = this.direction === 'horizontally';
if ( isHorizontal ) {
tableUtils.splitCellHorizontally( tableCell, 2 );
} else {
tableUtils.splitCellVertically( tableCell, 2 );
}
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/converters/downcast.js":
/*!***************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/converters/downcast.js ***!
\***************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "convertParagraphInTableCell": () => (/* binding */ convertParagraphInTableCell),
/* harmony export */ "downcastCell": () => (/* binding */ downcastCell),
/* harmony export */ "downcastRow": () => (/* binding */ downcastRow),
/* harmony export */ "downcastTable": () => (/* binding */ downcastTable),
/* harmony export */ "isSingleParagraphWithoutAttributes": () => (/* binding */ isSingleParagraphWithoutAttributes)
/* harmony export */ });
/* harmony import */ var _tablewalker__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./../tablewalker */ "./node_modules/@ckeditor/ckeditor5-table/src/tablewalker.js");
/* harmony import */ var ckeditor5_src_widget__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/widget */ "./node_modules/ckeditor5/src/widget.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/converters/downcast
*/
/**
* Model table element to view table element conversion helper.
*
* @param {module:table/tableutils~TableUtils} tableUtils The `TableUtils` plugin instance.
* @param {Object} [options]
* @param {Boolean} [options.asWidget] If set to `true`, the downcast conversion will produce a widget.
* @returns {Function} Element creator.
*/
function downcastTable( tableUtils, options = {} ) {
return ( table, { writer } ) => {
const headingRows = table.getAttribute( 'headingRows' ) || 0;
const tableSections = [];
// Table head slot.
if ( headingRows > 0 ) {
tableSections.push(
writer.createContainerElement( 'thead', null,
writer.createSlot( element => element.is( 'element', 'tableRow' ) && element.index < headingRows )
)
);
}
// Table body slot.
if ( headingRows < tableUtils.getRows( table ) ) {
tableSections.push(
writer.createContainerElement( 'tbody', null,
writer.createSlot( element => element.is( 'element', 'tableRow' ) && element.index >= headingRows )
)
);
}
const figureElement = writer.createContainerElement( 'figure', { class: 'table' }, [
// Table with proper sections (thead, tbody).
writer.createContainerElement( 'table', null, tableSections ),
// Slot for the rest (for example caption).
writer.createSlot( element => !element.is( 'element', 'tableRow' ) )
] );
return options.asWidget ? toTableWidget( figureElement, writer ) : figureElement;
};
}
/**
* Model table row element to view `<tr>` element conversion helper.
*
* @returns {Function} Element creator.
*/
function downcastRow() {
return ( tableRow, { writer } ) => {
return tableRow.isEmpty ?
writer.createEmptyElement( 'tr' ) :
writer.createContainerElement( 'tr' );
};
}
/**
* Model table cell element to view `<td>` or `<th>` element conversion helper.
*
* This conversion helper will create proper `<th>` elements for table cells that are in the heading section (heading row or column)
* and `<td>` otherwise.
*
* @param {Object} [options]
* @param {Boolean} [options.asWidget] If set to `true`, the downcast conversion will produce a widget.
* @returns {Function} Element creator.
*/
function downcastCell( options = {} ) {
return ( tableCell, { writer } ) => {
const tableRow = tableCell.parent;
const table = tableRow.parent;
const rowIndex = table.getChildIndex( tableRow );
const tableWalker = new _tablewalker__WEBPACK_IMPORTED_MODULE_0__["default"]( table, { row: rowIndex } );
const headingRows = table.getAttribute( 'headingRows' ) || 0;
const headingColumns = table.getAttribute( 'headingColumns' ) || 0;
// We need to iterate over a table in order to get proper row & column values from a walker.
for ( const tableSlot of tableWalker ) {
if ( tableSlot.cell == tableCell ) {
const isHeading = tableSlot.row < headingRows || tableSlot.column < headingColumns;
const cellElementName = isHeading ? 'th' : 'td';
return options.asWidget ?
(0,ckeditor5_src_widget__WEBPACK_IMPORTED_MODULE_1__.toWidgetEditable)( writer.createEditableElement( cellElementName ), writer ) :
writer.createContainerElement( cellElementName );
}
}
};
}
/**
* Overrides paragraph inside table cell conversion.
*
* This converter:
* * should be used to override default paragraph conversion.
* * It will only convert `<paragraph>` placed directly inside `<tableCell>`.
* * For a single paragraph without attributes it returns `<span>` to simulate data table.
* * For all other cases it returns `<p>` element.
*
* @param {Object} [options]
* @param {Boolean} [options.asWidget] If set to `true`, the downcast conversion will produce a widget.
* @returns {Function} Element creator.
*/
function convertParagraphInTableCell( options = {} ) {
return ( modelElement, { writer, consumable, mapper } ) => {
if ( !modelElement.parent.is( 'element', 'tableCell' ) ) {
return;
}
if ( !isSingleParagraphWithoutAttributes( modelElement ) ) {
return;
}
if ( options.asWidget ) {
return writer.createContainerElement( 'span', { class: 'ck-table-bogus-paragraph' } );
} else {
// Additional requirement for data pipeline to have backward compatible data tables.
consumable.consume( modelElement, 'insert' );
mapper.bindElements( modelElement, mapper.toViewElement( modelElement.parent ) );
}
};
}
/**
* Checks if given model `<paragraph>` is an only child of a parent (`<tableCell>`) and if it has any attribute set.
*
* The paragraph should be converted in the editing view to:
*
* * If returned `true` - to a `<span class="ck-table-bogus-paragraph">`
* * If returned `false` - to a `<p>`
*
* @param {module:engine/model/element~Element} modelElement
* @returns {Boolean}
*/
function isSingleParagraphWithoutAttributes( modelElement ) {
const tableCell = modelElement.parent;
const isSingleParagraph = tableCell.childCount == 1;
return isSingleParagraph && !hasAnyAttribute( modelElement );
}
// Converts a given {@link module:engine/view/element~Element} to a table widget:
// * Adds a {@link module:engine/view/element~Element#_setCustomProperty custom property} allowing to recognize the table widget element.
// * Calls the {@link module:widget/utils~toWidget} function with the proper element's label creator.
//
// @param {module:engine/view/element~Element} viewElement
// @param {module:engine/view/downcastwriter~DowncastWriter} writer An instance of the view writer.
// @param {String} label The element's label. It will be concatenated with the table `alt` attribute if one is present.
// @returns {module:engine/view/element~Element}
function toTableWidget( viewElement, writer ) {
writer.setCustomProperty( 'table', true, viewElement );
return (0,ckeditor5_src_widget__WEBPACK_IMPORTED_MODULE_1__.toWidget)( viewElement, writer, { hasSelectionHandle: true } );
}
// Checks if an element has any attributes set.
//
// @param {module:engine/model/element~Element element
// @returns {Boolean}
function hasAnyAttribute( element ) {
return !![ ...element.getAttributeKeys() ].length;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/converters/table-caption-post-fixer.js":
/*!*******************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/converters/table-caption-post-fixer.js ***!
\*******************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ injectTableCaptionPostFixer)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/converters/table-caption-post-fixer
*/
/**
* Injects a table caption post-fixer into the model.
*
* The role of the table caption post-fixer is to ensure that the table with caption have the correct structure
* after a {@link module:engine/model/model~Model#change `change()`} block was executed.
*
* The correct structure means that:
*
* * If there are many caption model element, they are merged into one model.
* * A final, merged caption model is placed at the end of the table.
*
* @param {module:engine/model/model~Model} model
*/
function injectTableCaptionPostFixer( model ) {
model.document.registerPostFixer( writer => tableCaptionPostFixer( writer, model ) );
}
// The table caption post-fixer.
//
// @param {module:engine/model/writer~Writer} writer
// @param {module:engine/model/model~Model} model
function tableCaptionPostFixer( writer, model ) {
const changes = model.document.differ.getChanges();
let wasFixed = false;
for ( const entry of changes ) {
if ( entry.type != 'insert' ) {
continue;
}
const positionParent = entry.position.parent;
if ( positionParent.is( 'element', 'table' ) || entry.name == 'table' ) {
const table = entry.name == 'table' ? entry.position.nodeAfter : entry.position.parent;
const captionsToMerge = Array.from( table.getChildren() ).filter( child => child.is( 'element', 'caption' ) );
const firstCaption = captionsToMerge.shift();
if ( !firstCaption ) {
continue;
}
// Move all the contents of the captions to the first one.
for ( const caption of captionsToMerge ) {
writer.move( writer.createRangeIn( caption ), firstCaption, 'end' );
writer.remove( caption );
}
// Make sure the final caption is at the end of the table.
if ( firstCaption.nextSibling ) {
writer.move( writer.createRangeOn( firstCaption ), table, 'end' );
wasFixed = true;
}
// Do we merged captions and/or moved the single caption to the end of the table?
wasFixed = !!captionsToMerge.length || wasFixed;
}
}
return wasFixed;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/converters/table-cell-paragraph-post-fixer.js":
/*!**************************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/converters/table-cell-paragraph-post-fixer.js ***!
\**************************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ injectTableCellParagraphPostFixer)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/converters/table-cell-paragraph-post-fixer
*/
/**
* Injects a table cell post-fixer into the model which inserts a `paragraph` element into empty table cells.
*
* A table cell must contain at least one block element as a child. An empty table cell will have an empty `paragraph` as a child.
*
* <table>
* <tableRow>
* <tableCell></tableCell>
* </tableRow>
* </table>
*
* Will be fixed to:
*
* <table>
* <tableRow>
* <tableCell><paragraph></paragraph></tableCell>
* </tableRow>
* </table>
*
* @param {module:engine/model/model~Model} model
*/
function injectTableCellParagraphPostFixer( model ) {
model.document.registerPostFixer( writer => tableCellContentsPostFixer( writer, model ) );
}
// The table cell contents post-fixer.
//
// @param {module:engine/model/writer~Writer} writer
// @param {module:engine/model/model~Model} model
function tableCellContentsPostFixer( writer, model ) {
const changes = model.document.differ.getChanges();
let wasFixed = false;
for ( const entry of changes ) {
if ( entry.type == 'insert' && entry.name == 'table' ) {
wasFixed = fixTable( entry.position.nodeAfter, writer ) || wasFixed;
}
if ( entry.type == 'insert' && entry.name == 'tableRow' ) {
wasFixed = fixTableRow( entry.position.nodeAfter, writer ) || wasFixed;
}
if ( entry.type == 'insert' && entry.name == 'tableCell' ) {
wasFixed = fixTableCellContent( entry.position.nodeAfter, writer ) || wasFixed;
}
if ( checkTableCellChange( entry ) ) {
wasFixed = fixTableCellContent( entry.position.parent, writer ) || wasFixed;
}
}
return wasFixed;
}
// Fixes all table cells in a table.
//
// @param {module:engine/model/element~Element} table
// @param {module:engine/model/writer~Writer} writer
function fixTable( table, writer ) {
let wasFixed = false;
for ( const row of table.getChildren() ) {
if ( row.is( 'element', 'tableRow' ) ) {
wasFixed = fixTableRow( row, writer ) || wasFixed;
}
}
return wasFixed;
}
// Fixes all table cells in a table row.
//
// @param {module:engine/model/element~Element} tableRow
// @param {module:engine/model/writer~Writer} writer
function fixTableRow( tableRow, writer ) {
let wasFixed = false;
for ( const tableCell of tableRow.getChildren() ) {
wasFixed = fixTableCellContent( tableCell, writer ) || wasFixed;
}
return wasFixed;
}
// Fixes all table cell content by:
// - Adding a paragraph to a table cell without any child.
// - Wrapping direct $text in a `<paragraph>`.
//
// @param {module:engine/model/element~Element} table
// @param {module:engine/model/writer~Writer} writer
// @returns {Boolean}
function fixTableCellContent( tableCell, writer ) {
// Insert paragraph to an empty table cell.
if ( tableCell.childCount == 0 ) {
// @if CK_DEBUG_TABLE // console.log( 'Post-fixing table: insert paragraph in empty cell.' );
writer.insertElement( 'paragraph', tableCell );
return true;
}
// Check table cell children for directly placed text nodes.
// Temporary solution. See https://github.com/ckeditor/ckeditor5/issues/1464.
const textNodes = Array.from( tableCell.getChildren() ).filter( child => child.is( '$text' ) );
// @if CK_DEBUG_TABLE // textNodes.length && console.log( 'Post-fixing table: wrap cell content with paragraph.' );
for ( const child of textNodes ) {
writer.wrap( writer.createRangeOn( child ), 'paragraph' );
}
// Return true when there were text nodes to fix.
return !!textNodes.length;
}
// Checks if a differ change should fix the table cell. This happens on:
// - Removing content from the table cell (i.e. `tableCell` can be left empty).
// - Adding a text node directly into a table cell.
//
// @param {Object} differ change entry
// @returns {Boolean}
function checkTableCellChange( entry ) {
if ( !entry.position || !entry.position.parent.is( 'element', 'tableCell' ) ) {
return false;
}
return entry.type == 'insert' && entry.name == '$text' || entry.type == 'remove';
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/converters/table-cell-refresh-handler.js":
/*!*********************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/converters/table-cell-refresh-handler.js ***!
\*********************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ tableCellRefreshHandler)
/* harmony export */ });
/* harmony import */ var _downcast__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./downcast */ "./node_modules/@ckeditor/ckeditor5-table/src/converters/downcast.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/converters/table-cell-refresh-post-fixer
*/
/**
* A table cell refresh handler which marks the table cell in the differ to have it re-rendered.
*
* Model `paragraph` inside a table cell can be rendered as `<span>` or `<p>`. It is rendered as `<span>` if this is the only block
* element in that table cell and it does not have any attributes. It is rendered as `<p>` otherwise.
*
* When table cell content changes, for example a second `paragraph` element is added, we need to ensure that the first `paragraph` is
* re-rendered so it changes from `<span>` to `<p>`. The easiest way to do it is to re-render the entire table cell.
*
* @param {module:engine/model/model~Model} model
* @param {module:engine/controller/editingcontroller~EditingController} editing
*/
function tableCellRefreshHandler( model, editing ) {
const differ = model.document.differ;
// Stores cells to be refreshed, so the table cell will be refreshed once for multiple changes.
const cellsToCheck = new Set();
for ( const change of differ.getChanges() ) {
const parent = change.type == 'attribute' ? change.range.start.parent : change.position.parent;
if ( parent.is( 'element', 'tableCell' ) ) {
cellsToCheck.add( parent );
}
}
for ( const tableCell of cellsToCheck.values() ) {
const paragraphsToRefresh = Array.from( tableCell.getChildren() ).filter( child => shouldRefresh( child, editing.mapper ) );
for ( const paragraph of paragraphsToRefresh ) {
editing.reconvertItem( paragraph );
}
}
}
// Check if given model element needs refreshing.
//
// @param {module:engine/model/element~Element} modelElement
// @param {module:engine/conversion/mapper~Mapper} mapper
// @returns {Boolean}
function shouldRefresh( child, mapper ) {
if ( !child.is( 'element', 'paragraph' ) ) {
return false;
}
const viewElement = mapper.toViewElement( child );
if ( !viewElement ) {
return false;
}
return (0,_downcast__WEBPACK_IMPORTED_MODULE_0__.isSingleParagraphWithoutAttributes)( child ) !== viewElement.is( 'element', 'span' );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/converters/table-headings-refresh-handler.js":
/*!*************************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/converters/table-headings-refresh-handler.js ***!
\*************************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ tableHeadingsRefreshHandler)
/* harmony export */ });
/* harmony import */ var _tablewalker__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../tablewalker */ "./node_modules/@ckeditor/ckeditor5-table/src/tablewalker.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/converters/table-heading-rows-refresh-post-fixer
*/
/**
* A table headings refresh handler which marks the table cells or rows in the differ to have it re-rendered
* if the headings attribute changed.
*
* Table heading rows and heading columns are represented in the model by a `headingRows` and `headingColumns` attributes.
*
* When table headings attribute changes, all the cells/rows are marked to re-render to change between `<td>` and `<th>`.
*
* @param {module:engine/model/model~Model} model
* @param {module:engine/controller/editingcontroller~EditingController} editing
*/
function tableHeadingsRefreshHandler( model, editing ) {
const differ = model.document.differ;
for ( const change of differ.getChanges() ) {
let table;
let isRowChange = false;
if ( change.type == 'attribute' ) {
const element = change.range.start.nodeAfter;
if ( !element || !element.is( 'element', 'table' ) ) {
continue;
}
if ( change.attributeKey != 'headingRows' && change.attributeKey != 'headingColumns' ) {
continue;
}
table = element;
isRowChange = change.attributeKey == 'headingRows';
} else if ( change.name == 'tableRow' || change.name == 'tableCell' ) {
table = change.position.findAncestor( 'table' );
isRowChange = change.name == 'tableRow';
}
if ( !table ) {
continue;
}
const headingRows = table.getAttribute( 'headingRows' ) || 0;
const headingColumns = table.getAttribute( 'headingColumns' ) || 0;
const tableWalker = new _tablewalker__WEBPACK_IMPORTED_MODULE_0__["default"]( table );
for ( const tableSlot of tableWalker ) {
const isHeading = tableSlot.row < headingRows || tableSlot.column < headingColumns;
const expectedElementName = isHeading ? 'th' : 'td';
const viewElement = editing.mapper.toViewElement( tableSlot.cell );
if ( viewElement && viewElement.is( 'element' ) && viewElement.name != expectedElementName ) {
editing.reconvertItem( isRowChange ? tableSlot.cell.parent : tableSlot.cell );
}
}
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/converters/table-layout-post-fixer.js":
/*!******************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/converters/table-layout-post-fixer.js ***!
\******************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ injectTableLayoutPostFixer)
/* harmony export */ });
/* harmony import */ var _tablewalker__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./../tablewalker */ "./node_modules/@ckeditor/ckeditor5-table/src/tablewalker.js");
/* harmony import */ var _utils_common__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../utils/common */ "./node_modules/@ckeditor/ckeditor5-table/src/utils/common.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/converters/table-layout-post-fixer
*/
/**
* Injects a table layout post-fixer into the model.
*
* The role of the table layout post-fixer is to ensure that the table rows have the correct structure
* after a {@link module:engine/model/model~Model#change `change()`} block was executed.
*
* The correct structure means that:
*
* * All table rows have the same size.
* * None of the table cells extend vertically beyond their section (either header or body).
* * A table cell has always at least one element as a child.
*
* If the table structure is not correct, the post-fixer will automatically correct it in two steps:
*
* 1. It will clip table cells that extend beyond their section.
* 2. It will add empty table cells to the rows that are narrower than the widest table row.
*
* ## Clipping overlapping table cells
*
* Such situation may occur when pasting a table (or a part of a table) to the editor from external sources.
*
* For example, see the following table which has a cell (FOO) with the rowspan attribute (2):
*
* <table headingRows="1">
* <tableRow>
* <tableCell rowspan="2"><paragraph>FOO</paragraph></tableCell>
* <tableCell colspan="2"><paragraph>BAR</paragraph></tableCell>
* </tableRow>
* <tableRow>
* <tableCell><paragraph>BAZ</paragraph></tableCell>
* <tableCell><paragraph>XYZ</paragraph></tableCell>
* </tableRow>
* </table>
*
* It will be rendered in the view as:
*
* <table>
* <thead>
* <tr>
* <td rowspan="2">FOO</td>
* <td colspan="2">BAR</td>
* </tr>
* </thead>
* <tbody>
* <tr>
* <td>BAZ</td>
* <td>XYZ</td>
* </tr>
* </tbody>
* </table>
*
* In the above example the table will be rendered as a table with two rows: one in the header and second one in the body.
* The table cell (FOO) cannot span over multiple rows as it would extend from the header to the body section.
* The `rowspan` attribute must be changed to (1). The value (1) is the default value of the `rowspan` attribute
* so the `rowspan` attribute will be removed from the model.
*
* The table cell with BAZ in the content will be in the first column of the table.
*
* ## Adding missing table cells
*
* The table post-fixer will insert empty table cells to equalize table row sizes (the number of columns).
* The size of a table row is calculated by counting column spans of table cells, both horizontal (from the same row) and
* vertical (from the rows above).
*
* In the above example, the table row in the body section of the table is narrower then the row from the header: it has two cells
* with the default colspan (1). The header row has one cell with colspan (1) and the second with colspan (2).
* The table cell (FOO) does not extend beyond the head section (and as such will be fixed in the first step of this post-fixer).
* The post-fixer will add a missing table cell to the row in the body section of the table.
*
* The table from the above example will be fixed and rendered to the view as below:
*
* <table>
* <thead>
* <tr>
* <td rowspan="2">FOO</td>
* <td colspan="2">BAR</td>
* </tr>
* </thead>
* <tbody>
* <tr>
* <td>BAZ</td>
* <td>XYZ</td>
* </tr>
* </tbody>
* </table>
*
* ## Collaboration and undo - Expectations vs post-fixer results
*
* The table post-fixer only ensures proper structure without a deeper analysis of the nature of the change. As such, it might lead
* to a structure which was not intended by the user. In particular, it will also fix undo steps (in conjunction with collaboration)
* in which the editor content might not return to the original state.
*
* This will usually happen when one or more users change the size of the table.
*
* As an example see the table below:
*
* <table>
* <tbody>
* <tr>
* <td>11</td>
* <td>12</td>
* </tr>
* <tr>
* <td>21</td>
* <td>22</td>
* </tr>
* </tbody>
* </table>
*
* and the user actions:
*
* 1. Both users have a table with two rows and two columns.
* 2. User A adds a column at the end of the table. This will insert empty table cells to two rows.
* 3. User B adds a row at the end of the table. This will insert a row with two empty table cells.
* 4. Both users will have a table as below:
*
*
* <table>
* <tbody>
* <tr>
* <td>11</td>
* <td>12</td>
* <td>(empty, inserted by A)</td>
* </tr>
* <tr>
* <td>21</td>
* <td>22</td>
* <td>(empty, inserted by A)</td>
* </tr>
* <tr>
* <td>(empty, inserted by B)</td>
* <td>(empty, inserted by B)</td>
* </tr>
* </tbody>
* </table>
*
* The last row is shorter then others so the table post-fixer will add an empty row to the last row:
*
* <table>
* <tbody>
* <tr>
* <td>11</td>
* <td>12</td>
* <td>(empty, inserted by A)</td>
* </tr>
* <tr>
* <td>21</td>
* <td>22</td>
* <td>(empty, inserted by A)</td>
* </tr>
* <tr>
* <td>(empty, inserted by B)</td>
* <td>(empty, inserted by B)</td>
* <td>(empty, inserted by the post-fixer)</td>
* </tr>
* </tbody>
* </table>
*
* Unfortunately undo does not know the nature of the changes and depending on which user applies the post-fixer changes, undoing them
* might lead to a broken table. If User B undoes inserting the column to the table, the undo engine will undo only the operations of
* inserting empty cells to rows from the initial table state (row 1 and 2) but the cell in the post-fixed row will remain:
*
* <table>
* <tbody>
* <tr>
* <td>11</td>
* <td>12</td>
* </tr>
* <tr>
* <td>21</td>
* <td>22</td>
* </tr>
* <tr>
* <td>(empty, inserted by B)</td>
* <td>(empty, inserted by B)</td>
* <td>(empty, inserted by a post-fixer)</td>
* </tr>
* </tbody>
* </table>
*
* After undo, the table post-fixer will detect that two rows are shorter than others and will fix the table to:
*
* <table>
* <tbody>
* <tr>
* <td>11</td>
* <td>12</td>
* <td>(empty, inserted by a post-fixer after undo)</td>
* </tr>
* <tr>
* <td>21</td>
* <td>22</td>
* <td>(empty, inserted by a post-fixer after undo)</td>
* </tr>
* <tr>
* <td>(empty, inserted by B)</td>
* <td>(empty, inserted by B)</td>
* <td>(empty, inserted by a post-fixer)</td>
* </tr>
* </tbody>
* </table>
* @param {module:engine/model/model~Model} model
*/
function injectTableLayoutPostFixer( model ) {
model.document.registerPostFixer( writer => tableLayoutPostFixer( writer, model ) );
}
// The table layout post-fixer.
//
// @param {module:engine/model/writer~Writer} writer
// @param {module:engine/model/model~Model} model
function tableLayoutPostFixer( writer, model ) {
const changes = model.document.differ.getChanges();
let wasFixed = false;
// Do not analyze the same table more then once - may happen for multiple changes in the same table.
const analyzedTables = new Set();
for ( const entry of changes ) {
let table;
if ( entry.name == 'table' && entry.type == 'insert' ) {
table = entry.position.nodeAfter;
}
// Fix table on adding/removing table cells and rows.
if ( entry.name == 'tableRow' || entry.name == 'tableCell' ) {
table = entry.position.findAncestor( 'table' );
}
// Fix table on any table's attribute change - including attributes of table cells.
if ( isTableAttributeEntry( entry ) ) {
table = entry.range.start.findAncestor( 'table' );
}
if ( table && !analyzedTables.has( table ) ) {
// Step 1: correct rowspans of table cells if necessary.
// The wasFixed flag should be true if any of tables in batch was fixed - might be more then one.
wasFixed = fixTableCellsRowspan( table, writer ) || wasFixed;
// Step 2: fix table rows sizes.
wasFixed = fixTableRowsSizes( table, writer ) || wasFixed;
analyzedTables.add( table );
}
}
return wasFixed;
}
// Fixes the invalid value of the `rowspan` attribute because a table cell cannot vertically extend beyond the table section it belongs to.
//
// @param {module:engine/model/element~Element} table
// @param {module:engine/model/writer~Writer} writer
// @returns {Boolean} Returns `true` if the table was fixed.
function fixTableCellsRowspan( table, writer ) {
let wasFixed = false;
const cellsToTrim = findCellsToTrim( table );
if ( cellsToTrim.length ) {
// @if CK_DEBUG_TABLE // console.log( `Post-fixing table: trimming cells row-spans (${ cellsToTrim.length }).` );
wasFixed = true;
for ( const data of cellsToTrim ) {
(0,_utils_common__WEBPACK_IMPORTED_MODULE_1__.updateNumericAttribute)( 'rowspan', data.rowspan, data.cell, writer, 1 );
}
}
return wasFixed;
}
// Makes all table rows in a table the same size.
//
// @param {module:engine/model/element~Element} table
// @param {module:engine/model/writer~Writer} writer
// @returns {Boolean} Returns `true` if the table was fixed.
function fixTableRowsSizes( table, writer ) {
let wasFixed = false;
const childrenLengths = getChildrenLengths( table );
const rowsToRemove = [];
// Find empty rows.
for ( const [ rowIndex, size ] of childrenLengths.entries() ) {
// Ignore all non-row models.
if ( !size && table.getChild( rowIndex ).is( 'element', 'tableRow' ) ) {
rowsToRemove.push( rowIndex );
}
}
// Remove empty rows.
if ( rowsToRemove.length ) {
// @if CK_DEBUG_TABLE // console.log( `Post-fixing table: remove empty rows (${ rowsToRemove.length }).` );
wasFixed = true;
for ( const rowIndex of rowsToRemove.reverse() ) {
writer.remove( table.getChild( rowIndex ) );
childrenLengths.splice( rowIndex, 1 );
}
}
// Filter out everything that's not a table row.
const rowsLengths = childrenLengths.filter( ( row, rowIndex ) => table.getChild( rowIndex ).is( 'element', 'tableRow' ) );
// Verify if all the rows have the same number of columns.
const tableSize = rowsLengths[ 0 ];
const isValid = rowsLengths.every( length => length === tableSize );
if ( !isValid ) {
// @if CK_DEBUG_TABLE // console.log( 'Post-fixing table: adding missing cells.' );
// Find the maximum number of columns.
const maxColumns = rowsLengths.reduce( ( prev, current ) => current > prev ? current : prev, 0 );
for ( const [ rowIndex, size ] of rowsLengths.entries() ) {
const columnsToInsert = maxColumns - size;
if ( columnsToInsert ) {
for ( let i = 0; i < columnsToInsert; i++ ) {
(0,_utils_common__WEBPACK_IMPORTED_MODULE_1__.createEmptyTableCell)( writer, writer.createPositionAt( table.getChild( rowIndex ), 'end' ) );
}
wasFixed = true;
}
}
}
return wasFixed;
}
// Searches for table cells that extend beyond the table section to which they belong to. It will return an array of objects
// that stores table cells to be trimmed and the correct value of the `rowspan` attribute to set.
//
// @param {module:engine/model/element~Element} table
// @returns {Array.<{{cell, rowspan}}>}
function findCellsToTrim( table ) {
const headingRows = parseInt( table.getAttribute( 'headingRows' ) || 0 );
const maxRows = Array.from( table.getChildren() )
.reduce( ( count, row ) => row.is( 'element', 'tableRow' ) ? count + 1 : count, 0 );
const cellsToTrim = [];
for ( const { row, cell, cellHeight } of new _tablewalker__WEBPACK_IMPORTED_MODULE_0__["default"]( table ) ) {
// Skip cells that do not expand over its row.
if ( cellHeight < 2 ) {
continue;
}
const isInHeader = row < headingRows;
// Row limit is either end of header section or whole table as table body is after the header.
const rowLimit = isInHeader ? headingRows : maxRows;
// If table cell expands over its limit reduce it height to proper value.
if ( row + cellHeight > rowLimit ) {
const newRowspan = rowLimit - row;
cellsToTrim.push( { cell, rowspan: newRowspan } );
}
}
return cellsToTrim;
}
// Returns an array with lengths of rows assigned to the corresponding row index.
//
// @param {module:engine/model/element~Element} table
// @returns {Array.<Number>}
function getChildrenLengths( table ) {
// TableWalker will not provide items for the empty rows, we need to pre-fill this array.
const lengths = new Array( table.childCount ).fill( 0 );
for ( const { rowIndex } of new _tablewalker__WEBPACK_IMPORTED_MODULE_0__["default"]( table, { includeAllSlots: true } ) ) {
lengths[ rowIndex ]++;
}
return lengths;
}
// Checks if the differ entry for an attribute change is one of the table's attributes.
//
// @param entry
// @returns {Boolean}
function isTableAttributeEntry( entry ) {
const isAttributeType = entry.type === 'attribute';
const key = entry.attributeKey;
return isAttributeType && ( key === 'headingRows' || key === 'colspan' || key === 'rowspan' );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/converters/tableproperties.js":
/*!**********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/converters/tableproperties.js ***!
\**********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "downcastAttributeToStyle": () => (/* binding */ downcastAttributeToStyle),
/* harmony export */ "downcastTableAttribute": () => (/* binding */ downcastTableAttribute),
/* harmony export */ "upcastBorderStyles": () => (/* binding */ upcastBorderStyles),
/* harmony export */ "upcastStyleToAttribute": () => (/* binding */ upcastStyleToAttribute)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/converters/tableproperites
*/
/**
* Conversion helper for upcasting attributes using normalized styles.
*
* @param {module:engine/conversion/conversion~Conversion} conversion
* @param {Object} options
* @param {String} options.modelAttribute The attribute to set.
* @param {String} options.styleName The style name to convert.
* @param {String} options.viewElement The view element name that should be converted.
* @param {String} options.defaultValue The default value for the specified `modelAttribute`.
* @param {Boolean} [options.reduceBoxSides=false]
*/
function upcastStyleToAttribute( conversion, options ) {
const { viewElement, defaultValue, modelAttribute, styleName, reduceBoxSides = false } = options;
conversion.for( 'upcast' ).attributeToAttribute( {
view: {
name: viewElement,
styles: {
[ styleName ]: /[\s\S]+/
}
},
model: {
key: modelAttribute,
value: viewElement => {
const normalized = viewElement.getNormalizedStyle( styleName );
const value = reduceBoxSides ? reduceBoxSidesValue( normalized ) : normalized;
if ( defaultValue !== value ) {
return value;
}
}
}
} );
}
/**
* Conversion helper for upcasting border styles for view elements.
*
* @param {module:engine/conversion/conversion~Conversion} conversion
* @param {String} viewElementName
* @param {Object} modelAttributes
* @param {Object} defaultBorder The default border values.
* @param {String} defaultBorder.color The default `borderColor` value.
* @param {String} defaultBorder.style The default `borderStyle` value.
* @param {String} defaultBorder.width The default `borderWidth` value.
*/
function upcastBorderStyles( conversion, viewElementName, modelAttributes, defaultBorder ) {
conversion.for( 'upcast' ).add( dispatcher => dispatcher.on( 'element:' + viewElementName, ( evt, data, conversionApi ) => {
// If the element was not converted by element-to-element converter,
// we should not try to convert the style. See #8393.
if ( !data.modelRange ) {
return;
}
// Check the most detailed properties. These will be always set directly or
// when using the "group" properties like: `border-(top|right|bottom|left)` or `border`.
const stylesToConsume = [
'border-top-width',
'border-top-color',
'border-top-style',
'border-bottom-width',
'border-bottom-color',
'border-bottom-style',
'border-right-width',
'border-right-color',
'border-right-style',
'border-left-width',
'border-left-color',
'border-left-style'
].filter( styleName => data.viewItem.hasStyle( styleName ) );
if ( !stylesToConsume.length ) {
return;
}
const matcherPattern = {
styles: stylesToConsume
};
// Try to consume appropriate values from consumable values list.
if ( !conversionApi.consumable.test( data.viewItem, matcherPattern ) ) {
return;
}
const modelElement = [ ...data.modelRange.getItems( { shallow: true } ) ].pop();
conversionApi.consumable.consume( data.viewItem, matcherPattern );
const normalizedBorder = {
style: data.viewItem.getNormalizedStyle( 'border-style' ),
color: data.viewItem.getNormalizedStyle( 'border-color' ),
width: data.viewItem.getNormalizedStyle( 'border-width' )
};
const reducedBorder = {
style: reduceBoxSidesValue( normalizedBorder.style ),
color: reduceBoxSidesValue( normalizedBorder.color ),
width: reduceBoxSidesValue( normalizedBorder.width )
};
if ( reducedBorder.style !== defaultBorder.style ) {
conversionApi.writer.setAttribute( modelAttributes.style, reducedBorder.style, modelElement );
}
if ( reducedBorder.color !== defaultBorder.color ) {
conversionApi.writer.setAttribute( modelAttributes.color, reducedBorder.color, modelElement );
}
if ( reducedBorder.width !== defaultBorder.width ) {
conversionApi.writer.setAttribute( modelAttributes.width, reducedBorder.width, modelElement );
}
} ) );
}
/**
* Conversion helper for downcasting an attribute to a style.
*
* @param {module:engine/conversion/conversion~Conversion} conversion
* @param {Object} options
* @param {String} options.modelElement
* @param {String} options.modelAttribute
* @param {String} options.styleName
*/
function downcastAttributeToStyle( conversion, { modelElement, modelAttribute, styleName } ) {
conversion.for( 'downcast' ).attributeToAttribute( {
model: {
name: modelElement,
key: modelAttribute
},
view: modelAttributeValue => ( {
key: 'style',
value: {
[ styleName ]: modelAttributeValue
}
} )
} );
}
/**
* Conversion helper for downcasting attributes from the model table to a view table (not to `<figure>`).
*
* @param {module:engine/conversion/conversion~Conversion} conversion
* @param {Object} options
* @param {String} options.modelAttribute
* @param {String} options.styleName
*/
function downcastTableAttribute( conversion, { modelAttribute, styleName } ) {
conversion.for( 'downcast' ).add( dispatcher => dispatcher.on( `attribute:${ modelAttribute }:table`, ( evt, data, conversionApi ) => {
const { item, attributeNewValue } = data;
const { mapper, writer } = conversionApi;
if ( !conversionApi.consumable.consume( data.item, evt.name ) ) {
return;
}
const table = [ ...mapper.toViewElement( item ).getChildren() ].find( child => child.is( 'element', 'table' ) );
if ( attributeNewValue ) {
writer.setStyle( styleName, attributeNewValue, table );
} else {
writer.removeStyle( styleName, table );
}
} ) );
}
// Reduces the full top, right, bottom, left object to a single string if all sides are equal.
function reduceBoxSidesValue( style ) {
if ( !style ) {
return;
}
const commonValue = [ 'top', 'right', 'bottom', 'left' ]
.map( side => style[ side ] )
.reduce( ( result, side ) => result == side ? result : null );
return commonValue || style;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/converters/upcasttable.js":
/*!******************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/converters/upcasttable.js ***!
\******************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ upcastTable),
/* harmony export */ "ensureParagraphInTableCell": () => (/* binding */ ensureParagraphInTableCell),
/* harmony export */ "skipEmptyTableRow": () => (/* binding */ skipEmptyTableRow),
/* harmony export */ "upcastTableFigure": () => (/* binding */ upcastTableFigure)
/* harmony export */ });
/* harmony import */ var _utils_common__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../utils/common */ "./node_modules/@ckeditor/ckeditor5-table/src/utils/common.js");
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/converters/upcasttable
*/
/**
* Returns a function that converts the table view representation:
*
* <figure class="table"><table>...</table></figure>
*
* to the model representation:
*
* <table></table>
*
* @returns {Function}
*/
function upcastTableFigure() {
return dispatcher => {
dispatcher.on( 'element:figure', ( evt, data, conversionApi ) => {
// Do not convert if this is not a "table figure".
if ( !conversionApi.consumable.test( data.viewItem, { name: true, classes: 'table' } ) ) {
return;
}
// Find an table element inside the figure element.
const viewTable = getViewTableFromFigure( data.viewItem );
// Do not convert if table element is absent or was already converted.
if ( !viewTable || !conversionApi.consumable.test( viewTable, { name: true } ) ) {
return;
}
// Consume the figure to prevent other converters from processing it again.
conversionApi.consumable.consume( data.viewItem, { name: true, classes: 'table' } );
// Convert view table to model table.
const conversionResult = conversionApi.convertItem( viewTable, data.modelCursor );
// Get table element from conversion result.
const modelTable = (0,ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__.first)( conversionResult.modelRange.getItems() );
// When table wasn't successfully converted then finish conversion.
if ( !modelTable ) {
// Revert consumed figure so other features can convert it.
conversionApi.consumable.revert( data.viewItem, { name: true, classes: 'table' } );
return;
}
conversionApi.convertChildren( data.viewItem, conversionApi.writer.createPositionAt( modelTable, 'end' ) );
conversionApi.updateConversionResult( modelTable, data );
} );
};
}
/**
* View table element to model table element conversion helper.
*
* This conversion helper converts the table element as well as table rows.
*
* @returns {Function} Conversion helper.
*/
function upcastTable() {
return dispatcher => {
dispatcher.on( 'element:table', ( evt, data, conversionApi ) => {
const viewTable = data.viewItem;
// When element was already consumed then skip it.
if ( !conversionApi.consumable.test( viewTable, { name: true } ) ) {
return;
}
const { rows, headingRows, headingColumns } = scanTable( viewTable );
// Only set attributes if values is greater then 0.
const attributes = {};
if ( headingColumns ) {
attributes.headingColumns = headingColumns;
}
if ( headingRows ) {
attributes.headingRows = headingRows;
}
const table = conversionApi.writer.createElement( 'table', attributes );
if ( !conversionApi.safeInsert( table, data.modelCursor ) ) {
return;
}
conversionApi.consumable.consume( viewTable, { name: true } );
// Upcast table rows in proper order (heading rows first).
rows.forEach( row => conversionApi.convertItem( row, conversionApi.writer.createPositionAt( table, 'end' ) ) );
// Convert everything else.
conversionApi.convertChildren( viewTable, conversionApi.writer.createPositionAt( table, 'end' ) );
// Create one row and one table cell for empty table.
if ( table.isEmpty ) {
const row = conversionApi.writer.createElement( 'tableRow' );
conversionApi.writer.insert( row, conversionApi.writer.createPositionAt( table, 'end' ) );
(0,_utils_common__WEBPACK_IMPORTED_MODULE_0__.createEmptyTableCell)( conversionApi.writer, conversionApi.writer.createPositionAt( row, 'end' ) );
}
conversionApi.updateConversionResult( table, data );
} );
};
}
/**
* A conversion helper that skips empty <tr> elements from upcasting at the beginning of the table.
*
* An empty row is considered a table model error but when handling clipboard data there could be rows that contain only row-spanned cells
* and empty TR-s are used to maintain the table structure (also {@link module:table/tablewalker~TableWalker} assumes that there are only
* rows that have related `tableRow` elements).
*
* *Note:* Only the first empty rows are removed because they have no meaning and it solves the issue
* of an improper table with all empty rows.
*
* @returns {Function} Conversion helper.
*/
function skipEmptyTableRow() {
return dispatcher => {
dispatcher.on( 'element:tr', ( evt, data ) => {
if ( data.viewItem.isEmpty && data.modelCursor.index == 0 ) {
evt.stop();
}
}, { priority: 'high' } );
};
}
/**
* A converter that ensures an empty paragraph is inserted in a table cell if no other content was converted.
*
* @returns {Function} Conversion helper.
*/
function ensureParagraphInTableCell( elementName ) {
return dispatcher => {
dispatcher.on( `element:${ elementName }`, ( evt, data, conversionApi ) => {
// The default converter will create a model range on converted table cell.
if ( !data.modelRange ) {
return;
}
// Ensure a paragraph in the model for empty table cells for converted table cells.
if ( data.viewItem.isEmpty ) {
const tableCell = data.modelRange.start.nodeAfter;
const modelCursor = conversionApi.writer.createPositionAt( tableCell, 0 );
conversionApi.writer.insertElement( 'paragraph', modelCursor );
}
}, { priority: 'low' } );
};
}
// Get view `<table>` element from the view widget (`<figure>`).
//
// @private
// @param {module:engine/view/element~Element} figureView
// @returns {module:engine/view/element~Element}
function getViewTableFromFigure( figureView ) {
for ( const figureChild of figureView.getChildren() ) {
if ( figureChild.is( 'element', 'table' ) ) {
return figureChild;
}
}
}
// Scans table rows and extracts required metadata from the table:
//
// headingRows - The number of rows that go as table headers.
// headingColumns - The maximum number of row headings.
// rows - Sorted `<tr>` elements as they should go into the model - ie. if `<thead>` is inserted after `<tbody>` in the view.
//
// @private
// @param {module:engine/view/element~Element} viewTable
// @returns {{headingRows, headingColumns, rows}}
function scanTable( viewTable ) {
const tableMeta = {
headingRows: 0,
headingColumns: 0
};
// The `<tbody>` and `<thead>` sections in the DOM do not have to be in order `<thead>` -> `<tbody>` and there might be more than one
// of them.
// As the model does not have these sections, rows from different sections must be sorted.
// For example, below is a valid HTML table:
//
// <table>
// <tbody><tr><td>2</td></tr></tbody>
// <thead><tr><td>1</td></tr></thead>
// <tbody><tr><td>3</td></tr></tbody>
// </table>
//
// But browsers will render rows in order as: 1 as the heading and 2 and 3 as the body.
const headRows = [];
const bodyRows = [];
// Currently the editor does not support more then one <thead> section.
// Only the first <thead> from the view will be used as a heading row and the others will be converted to body rows.
let firstTheadElement;
for ( const tableChild of Array.from( viewTable.getChildren() ) ) {
// Only `<thead>`, `<tbody>` & `<tfoot>` from allowed table children can have `<tr>`s.
// The else is for future purposes (mainly `<caption>`).
if ( tableChild.name === 'tbody' || tableChild.name === 'thead' || tableChild.name === 'tfoot' ) {
// Save the first `<thead>` in the table as table header - all other ones will be converted to table body rows.
if ( tableChild.name === 'thead' && !firstTheadElement ) {
firstTheadElement = tableChild;
}
// There might be some extra empty text nodes between the `<tr>`s.
// Make sure further code operates on `tr`s only. (#145)
const trs = Array.from( tableChild.getChildren() ).filter( el => el.is( 'element', 'tr' ) );
for ( const tr of trs ) {
// This <tr> is a child of a first <thead> element.
if ( tr.parent.name === 'thead' && tr.parent === firstTheadElement ) {
tableMeta.headingRows++;
headRows.push( tr );
} else {
bodyRows.push( tr );
// For other rows check how many column headings this row has.
const headingCols = scanRowForHeadingColumns( tr, tableMeta, firstTheadElement );
if ( headingCols > tableMeta.headingColumns ) {
tableMeta.headingColumns = headingCols;
}
}
}
}
}
tableMeta.rows = [ ...headRows, ...bodyRows ];
return tableMeta;
}
// Scans a `<tr>` element and its children for metadata:
// - For heading row:
// - Adds this row to either the heading or the body rows.
// - Updates the number of heading rows.
// - For body rows:
// - Calculates the number of column headings.
//
// @private
// @param {module:engine/view/element~Element} tr
// @returns {Number}
function scanRowForHeadingColumns( tr ) {
let headingColumns = 0;
let index = 0;
// Filter out empty text nodes from tr children.
const children = Array.from( tr.getChildren() )
.filter( child => child.name === 'th' || child.name === 'td' );
// Count starting adjacent <th> elements of a <tr>.
while ( index < children.length && children[ index ].name === 'th' ) {
const th = children[ index ];
// Adjust columns calculation by the number of spanned columns.
const colspan = parseInt( th.getAttribute( 'colspan' ) || 1 );
headingColumns = headingColumns + colspan;
index++;
}
return headingColumns;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/table.js":
/*!*************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/table.js ***!
\*************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ Table)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_widget__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/widget */ "./node_modules/ckeditor5/src/widget.js");
/* harmony import */ var _tableediting__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./tableediting */ "./node_modules/@ckeditor/ckeditor5-table/src/tableediting.js");
/* harmony import */ var _tableui__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./tableui */ "./node_modules/@ckeditor/ckeditor5-table/src/tableui.js");
/* harmony import */ var _tableselection__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./tableselection */ "./node_modules/@ckeditor/ckeditor5-table/src/tableselection.js");
/* harmony import */ var _tableclipboard__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./tableclipboard */ "./node_modules/@ckeditor/ckeditor5-table/src/tableclipboard.js");
/* harmony import */ var _tablekeyboard__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./tablekeyboard */ "./node_modules/@ckeditor/ckeditor5-table/src/tablekeyboard.js");
/* harmony import */ var _tablemouse__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./tablemouse */ "./node_modules/@ckeditor/ckeditor5-table/src/tablemouse.js");
/* harmony import */ var _theme_table_css__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ../theme/table.css */ "./node_modules/@ckeditor/ckeditor5-table/theme/table.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/table
*/
/**
* The table plugin.
*
* For a detailed overview, check the {@glink features/table Table feature documentation}.
*
* This is a "glue" plugin that loads the following table features:
*
* * {@link module:table/tableediting~TableEditing editing feature},
* * {@link module:table/tableselection~TableSelection selection feature},
* * {@link module:table/tablekeyboard~TableKeyboard keyboard navigation feature},
* * {@link module:table/tablemouse~TableMouse mouse selection feature},
* * {@link module:table/tableclipboard~TableClipboard clipboard feature},
* * {@link module:table/tableui~TableUI UI feature}.
*
* @extends module:core/plugin~Plugin
*/
class Table extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [ _tableediting__WEBPACK_IMPORTED_MODULE_2__["default"], _tableui__WEBPACK_IMPORTED_MODULE_3__["default"], _tableselection__WEBPACK_IMPORTED_MODULE_4__["default"], _tablemouse__WEBPACK_IMPORTED_MODULE_7__["default"], _tablekeyboard__WEBPACK_IMPORTED_MODULE_6__["default"], _tableclipboard__WEBPACK_IMPORTED_MODULE_5__["default"], ckeditor5_src_widget__WEBPACK_IMPORTED_MODULE_1__.Widget ];
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'Table';
}
}
/**
* The configuration of the table feature. Used by the table feature in the `@ckeditor/ckeditor5-table` package.
*
* ClassicEditor
* .create( editorElement, {
* table: ... // Table feature options.
* } )
* .then( ... )
* .catch( ... );
*
* See {@link module:core/editor/editorconfig~EditorConfig all editor options}.
*
* @interface TableConfig
*/
/**
* The configuration of the {@link module:table/table~Table} feature.
*
* Read more in {@link module:table/table~TableConfig}.
*
* @member {module:table/table~TableConfig} module:core/editor/editorconfig~EditorConfig#table
*/
/**
* Number of rows and columns to render by default as table heading when inserting new tables.
*
* You can configure it like this:
*
* const tableConfig = {
* defaultHeadings: {
* rows: 1,
* columns: 1
* }
* };
*
* Both rows and columns properties are optional defaulting to 0 (no heading).
*
* @member {Object} module:table/table~TableConfig#defaultHeadings
*/
/**
* An array of color definitions (either strings or objects).
*
* const colors = [
* {
* color: 'hsl(0, 0%, 60%)',
* label: 'Grey'
* },
* 'hsl(0, 0%, 80%)',
* {
* color: 'hsl(0, 0%, 90%)',
* label: 'Light grey'
* },
* {
* color: 'hsl(0, 0%, 100%)',
* label: 'White',
* hasBorder: true
* },
* '#FF0000'
* ]
*
* Usually used as a configuration parameter, for instance in
* {@link module:table/table~TableConfig#tableProperties `config.table.tableProperties`}
* or {@link module:table/table~TableConfig#tableCellProperties `config.table.tableCellProperties`}.
*
* @typedef {Array.<String|Object>} module:table/table~TableColorConfig
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/tablecaption.js":
/*!********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/tablecaption.js ***!
\********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ TableCaption)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var _tablecaption_tablecaptionediting__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./tablecaption/tablecaptionediting */ "./node_modules/@ckeditor/ckeditor5-table/src/tablecaption/tablecaptionediting.js");
/* harmony import */ var _tablecaption_tablecaptionui__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./tablecaption/tablecaptionui */ "./node_modules/@ckeditor/ckeditor5-table/src/tablecaption/tablecaptionui.js");
/* harmony import */ var _theme_tablecaption_css__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../theme/tablecaption.css */ "./node_modules/@ckeditor/ckeditor5-table/theme/tablecaption.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/tablecaption
*/
/**
* The table caption plugin.
*
* @extends module:core/plugin~Plugin
*/
class TableCaption extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'TableCaption';
}
/**
* @inheritDoc
*/
static get requires() {
return [ _tablecaption_tablecaptionediting__WEBPACK_IMPORTED_MODULE_1__["default"], _tablecaption_tablecaptionui__WEBPACK_IMPORTED_MODULE_2__["default"] ];
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/tablecaption/tablecaptionediting.js":
/*!****************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/tablecaption/tablecaptionediting.js ***!
\****************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ TableCaptionEditing)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/engine */ "./node_modules/ckeditor5/src/engine.js");
/* harmony import */ var ckeditor5_src_widget__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ckeditor5/src/widget */ "./node_modules/ckeditor5/src/widget.js");
/* harmony import */ var _converters_table_caption_post_fixer__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../converters/table-caption-post-fixer */ "./node_modules/@ckeditor/ckeditor5-table/src/converters/table-caption-post-fixer.js");
/* harmony import */ var _toggletablecaptioncommand__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./toggletablecaptioncommand */ "./node_modules/@ckeditor/ckeditor5-table/src/tablecaption/toggletablecaptioncommand.js");
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./utils */ "./node_modules/@ckeditor/ckeditor5-table/src/tablecaption/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/tablecaption/tablecaptionediting
*/
/**
* The table caption editing plugin.
*
* @extends module:core/plugin~Plugin
*/
class TableCaptionEditing extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'TableCaptionEditing';
}
/**
* @inheritDoc
*/
constructor( editor ) {
super( editor );
/**
* A map that keeps saved JSONified table captions and table model elements they are
* associated with.
*
* To learn more about this system, see {@link #_saveCaption}.
*
* @member {WeakMap.<module:engine/model/element~Element,Object>}
*/
this._savedCaptionsMap = new WeakMap();
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const schema = editor.model.schema;
const view = editor.editing.view;
const t = editor.t;
if ( !schema.isRegistered( 'caption' ) ) {
schema.register( 'caption', {
allowIn: 'table',
allowContentOf: '$block',
isLimit: true
} );
} else {
schema.extend( 'caption', {
allowIn: 'table'
} );
}
editor.commands.add( 'toggleTableCaption', new _toggletablecaptioncommand__WEBPACK_IMPORTED_MODULE_4__["default"]( this.editor ) );
// View -> model converter for the data pipeline.
editor.conversion.for( 'upcast' ).elementToElement( {
view: _utils__WEBPACK_IMPORTED_MODULE_5__.matchTableCaptionViewElement,
model: 'caption'
} );
// Model -> view converter for the data pipeline.
editor.conversion.for( 'dataDowncast' ).elementToElement( {
model: 'caption',
view: ( modelElement, { writer } ) => {
if ( !(0,_utils__WEBPACK_IMPORTED_MODULE_5__.isTable)( modelElement.parent ) ) {
return null;
}
return writer.createContainerElement( 'figcaption' );
}
} );
// Model -> view converter for the editing pipeline.
editor.conversion.for( 'editingDowncast' ).elementToElement( {
model: 'caption',
view: ( modelElement, { writer } ) => {
if ( !(0,_utils__WEBPACK_IMPORTED_MODULE_5__.isTable)( modelElement.parent ) ) {
return null;
}
const figcaptionElement = writer.createEditableElement( 'figcaption' );
writer.setCustomProperty( 'tableCaption', true, figcaptionElement );
(0,ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_1__.enablePlaceholder)( {
view,
element: figcaptionElement,
text: t( 'Enter table caption' ),
keepOnFocus: true
} );
return (0,ckeditor5_src_widget__WEBPACK_IMPORTED_MODULE_2__.toWidgetEditable)( figcaptionElement, writer );
}
} );
(0,_converters_table_caption_post_fixer__WEBPACK_IMPORTED_MODULE_3__["default"])( editor.model );
}
/**
* Returns the saved {@link module:engine/model/element~Element#toJSON JSONified} caption
* of a table model element.
*
* See {@link #_saveCaption}.
*
* @protected
* @param {module:engine/model/element~Element} tableModelElement The model element the
* caption should be returned for.
* @returns {module:engine/model/element~Element|null} The model caption element or `null` if there is none.
*/
_getSavedCaption( tableModelElement ) {
const jsonObject = this._savedCaptionsMap.get( tableModelElement );
return jsonObject ? ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_1__.Element.fromJSON( jsonObject ) : null;
}
/**
* Saves a {@link module:engine/model/element~Element#toJSON JSONified} caption for
* a table element to allow restoring it in the future.
*
* A caption is saved every time it gets hidden. The
* user should be able to restore it on demand.
*
* **Note**: The caption cannot be stored in the table model element attribute because,
* for instance, when the model state propagates to collaborators, the attribute would get
* lost (mainly because it does not convert to anything when the caption is hidden) and
* the states of collaborators' models would de-synchronize causing numerous issues.
*
* See {@link #_getSavedCaption}.
*
* @protected
* @param {module:engine/model/element~Element} tableModelElement The model element the
* caption is saved for.
* @param {module:engine/model/element~Element} caption The caption model element to be saved.
*/
_saveCaption( tableModelElement, caption ) {
this._savedCaptionsMap.set( tableModelElement, caption.toJSON() );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/tablecaption/tablecaptionui.js":
/*!***********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/tablecaption/tablecaptionui.js ***!
\***********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ TableCaptionUI)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/ui */ "./node_modules/ckeditor5/src/ui.js");
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./utils */ "./node_modules/@ckeditor/ckeditor5-table/src/tablecaption/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/tablecaption/tablecaptionui
*/
/**
* The table caption UI plugin. It introduces the `'toggleTableCaption'` UI button.
*
* @extends module:core/plugin~Plugin
*/
class TableCaptionUI extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'TableCaptionUI';
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const editingView = editor.editing.view;
const t = editor.t;
editor.ui.componentFactory.add( 'toggleTableCaption', locale => {
const command = editor.commands.get( 'toggleTableCaption' );
const view = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.ButtonView( locale );
view.set( {
icon: ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.icons.caption,
tooltip: true,
isToggleable: true
} );
view.bind( 'isOn', 'isEnabled' ).to( command, 'value', 'isEnabled' );
view.bind( 'label' ).to( command, 'value', value => value ? t( 'Toggle caption off' ) : t( 'Toggle caption on' ) );
this.listenTo( view, 'execute', () => {
editor.execute( 'toggleTableCaption', { focusCaptionOnShow: true } );
// Scroll to the selection and highlight the caption if the caption showed up.
if ( command.value ) {
const modelCaptionElement = (0,_utils__WEBPACK_IMPORTED_MODULE_2__.getCaptionFromModelSelection)( editor.model.document.selection );
const figcaptionElement = editor.editing.mapper.toViewElement( modelCaptionElement );
if ( !figcaptionElement ) {
return;
}
editingView.scrollToTheSelection();
editingView.change( writer => {
writer.addClass( 'table__caption_highlighted', figcaptionElement );
} );
}
} );
return view;
} );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/tablecaption/toggletablecaptioncommand.js":
/*!**********************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/tablecaption/toggletablecaptioncommand.js ***!
\**********************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ToggleTableCaptionCommand)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./utils */ "./node_modules/@ckeditor/ckeditor5-table/src/tablecaption/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/tablecaption/toggletablecaptioncommand
*/
/**
* The toggle table caption command.
*
* This command is registered by {@link module:table/tablecaption/tablecaptionediting~TableCaptionEditing} as the
* `'toggleTableCaption'` editor command.
*
* Executing this command:
*
* * either adds or removes the table caption of a selected table (depending on whether the caption is present or not),
* * removes the table caption if the selection is anchored in one.
*
* // Toggle the presence of the caption.
* editor.execute( 'toggleTableCaption' );
*
* **Note**: You can move the selection to the caption right away as it shows up upon executing this command by using
* the `focusCaptionOnShow` option:
*
* editor.execute( 'toggleTableCaption', { focusCaptionOnShow: true } );
*
* @extends module:core/command~Command
*/
class ToggleTableCaptionCommand extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Command {
/**
* @inheritDoc
*/
refresh() {
const editor = this.editor;
const tableElement = (0,_utils__WEBPACK_IMPORTED_MODULE_1__.getSelectionAffectedTable)( editor.model.document.selection );
this.isEnabled = !!tableElement;
if ( !this.isEnabled ) {
this.value = false;
} else {
this.value = !!(0,_utils__WEBPACK_IMPORTED_MODULE_1__.getCaptionFromTableModelElement)( tableElement );
}
}
/**
* Executes the command.
*
* editor.execute( 'toggleTableCaption' );
*
* @param {Object} [options] Options for the executed command.
* @param {String} [options.focusCaptionOnShow] When true and the caption shows up, the selection will be moved into it straight away.
* @fires execute
*/
execute( options = {} ) {
const { focusCaptionOnShow } = options;
this.editor.model.change( writer => {
if ( this.value ) {
this._hideTableCaption( writer );
} else {
this._showTableCaption( writer, focusCaptionOnShow );
}
} );
}
/**
* Shows the table caption. Also:
*
* * it attempts to restore the caption content from the `TableCaptionEditing` caption registry,
* * it moves the selection to the caption right away, it the `focusCaptionOnShow` option was set.
*
* @private
* @param {module:engine/model/writer~Writer} writer
* @param {Boolean} focusCaptionOnShow Default focus behavior when showing the caption.
*/
_showTableCaption( writer, focusCaptionOnShow ) {
const model = this.editor.model;
const tableElement = (0,_utils__WEBPACK_IMPORTED_MODULE_1__.getSelectionAffectedTable)( model.document.selection );
const tableCaptionEditing = this.editor.plugins.get( 'TableCaptionEditing' );
const savedCaptionElement = tableCaptionEditing._getSavedCaption( tableElement );
// Try restoring the caption from the TableCaptionEditing plugin storage.
const newCaptionElement = savedCaptionElement || writer.createElement( 'caption' );
writer.append( newCaptionElement, tableElement );
if ( focusCaptionOnShow ) {
writer.setSelection( newCaptionElement, 'in' );
}
}
/**
* Hides the caption of a selected table (or an table caption the selection is anchored to).
*
* The content of the caption is stored in the `TableCaptionEditing` caption registry to make this
* a reversible action.
*
* @private
* @param {module:engine/model/writer~Writer} writer
*/
_hideTableCaption( writer ) {
const model = this.editor.model;
const tableElement = (0,_utils__WEBPACK_IMPORTED_MODULE_1__.getSelectionAffectedTable)( model.document.selection );
const tableCaptionEditing = this.editor.plugins.get( 'TableCaptionEditing' );
const captionElement = (0,_utils__WEBPACK_IMPORTED_MODULE_1__.getCaptionFromTableModelElement)( tableElement );
// Store the caption content so it can be restored quickly if the user changes their mind.
tableCaptionEditing._saveCaption( tableElement, captionElement );
writer.setSelection( writer.createRangeIn( tableElement.getChild( 0 ).getChild( 0 ) ) );
writer.remove( captionElement );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/tablecaption/utils.js":
/*!**************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/tablecaption/utils.js ***!
\**************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "getCaptionFromModelSelection": () => (/* binding */ getCaptionFromModelSelection),
/* harmony export */ "getCaptionFromTableModelElement": () => (/* binding */ getCaptionFromTableModelElement),
/* harmony export */ "getSelectionAffectedTable": () => (/* binding */ getSelectionAffectedTable),
/* harmony export */ "isTable": () => (/* binding */ isTable),
/* harmony export */ "matchTableCaptionViewElement": () => (/* binding */ matchTableCaptionViewElement)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/tablecaption/utils
*/
/**
* Checks if the provided model element is a `table`.
*
* @param {module:engine/model/element~Element} modelElement Element to check if it is a table.
* @returns {Boolean}
*/
function isTable( modelElement ) {
return !!modelElement && modelElement.is( 'element', 'table' );
}
/**
* Returns the caption model element from a given table element. Returns `null` if no caption is found.
*
* @param {module:engine/model/element~Element} tableModelElement Table element in which we will try to find a caption element.
* @returns {module:engine/model/element~Element|null}
*/
function getCaptionFromTableModelElement( tableModelElement ) {
for ( const node of tableModelElement.getChildren() ) {
if ( node.is( 'element', 'caption' ) ) {
return node;
}
}
return null;
}
/**
* Returns the caption model element for a model selection. Returns `null` if the selection has no caption element ancestor.
*
* @param {module:engine/model/selection~Selection|module:engine/model/documentselection~DocumentSelection} selection
* The selection checked for caption presence.
* @returns {module:engine/model/element~Element|null}
*/
function getCaptionFromModelSelection( selection ) {
const tableElement = getSelectionAffectedTable( selection );
if ( !tableElement ) {
return null;
}
return getCaptionFromTableModelElement( tableElement );
}
/**
* {@link module:engine/view/matcher~Matcher} pattern. Checks if a given element is a caption.
*
* There are two possible forms of the valid caption:
* - A `<figcaption>` element inside a `<figure class="table">` element.
* - A `<caption>` inside a <table>.
*
* @param {module:engine/view/element~Element} element
* @returns {Object|null} Returns the object accepted by {@link module:engine/view/matcher~Matcher} or `null` if the element
* cannot be matched.
*/
function matchTableCaptionViewElement( element ) {
const parent = element.parent;
if ( element.name == 'figcaption' && parent && parent.name == 'figure' && parent.hasClass( 'table' ) ) {
return { name: true };
}
if ( element.name == 'caption' && parent && parent.name == 'table' ) {
return { name: true };
}
return null;
}
/**
* Depending on the position of the selection we either return the table under cursor or look for the table higher in the hierarchy.
*
* @param {module:engine/model/position~Position} position
* @returns {module:engine/model/element~Element}
*/
function getSelectionAffectedTable( selection ) {
const selectedElement = selection.getSelectedElement();
// Is the command triggered from the `tableToolbar`?
if ( selectedElement && selectedElement.is( 'element', 'table' ) ) {
return selectedElement;
}
return selection.getFirstPosition().findAncestor( 'table' );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/tablecellproperties.js":
/*!***************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/tablecellproperties.js ***!
\***************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ TableCellProperties)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var _tablecellproperties_tablecellpropertiesui__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./tablecellproperties/tablecellpropertiesui */ "./node_modules/@ckeditor/ckeditor5-table/src/tablecellproperties/tablecellpropertiesui.js");
/* harmony import */ var _tablecellproperties_tablecellpropertiesediting__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./tablecellproperties/tablecellpropertiesediting */ "./node_modules/@ckeditor/ckeditor5-table/src/tablecellproperties/tablecellpropertiesediting.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/tablecellproperties
*/
/**
* The table cell properties feature. Enables support for setting properties of table cells (size, border, background, etc.).
*
* Read more in the {@glink features/table#table-and-cell-styling-tools Table and cell styling tools} section.
* See also the {@link module:table/tableproperties~TableProperties} plugin.
*
* This is a "glue" plugin that loads the
* {@link module:table/tablecellproperties/tablecellpropertiesediting~TableCellPropertiesEditing table cell properties editing feature} and
* the {@link module:table/tablecellproperties/tablecellpropertiesui~TableCellPropertiesUI table cell properties UI feature}.
*
* @extends module:core/plugin~Plugin
*/
class TableCellProperties extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'TableCellProperties';
}
/**
* @inheritDoc
*/
static get requires() {
return [ _tablecellproperties_tablecellpropertiesediting__WEBPACK_IMPORTED_MODULE_2__["default"], _tablecellproperties_tablecellpropertiesui__WEBPACK_IMPORTED_MODULE_1__["default"] ];
}
}
/**
* The configuration of the table cell properties user interface (balloon). It allows to define:
*
* * The color palette for the cell border color style field (`tableCellProperties.borderColors`),
* * The color palette for the cell background style field (`tableCellProperties.backgroundColors`).
*
* const tableConfig = {
* tableCellProperties: {
* borderColors: [
* {
* color: 'hsl(0, 0%, 90%)',
* label: 'Light grey'
* },
* // ...
* ],
* backgroundColors: [
* {
* color: 'hsl(120, 75%, 60%)',
* label: 'Green'
* },
* // ...
* ]
* }
* };
*
* * The default styles for table cells (`tableCellProperties.defaultProperties`):
*
* const tableConfig = {
* tableCellProperties: {
* defaultProperties: {
* horizontalAlignment: 'right',
* verticalAlignment: 'bottom',
* padding: '5px'
* }
* }
* }
*
* {@link module:table/tableproperties~TablePropertiesOptions Read more about the supported properties.}
*
* **Note**: The `borderColors` and `backgroundColors` options do not impact the data loaded into the editor,
* i.e. they do not limit or filter the colors in the data. They are used only in the user interface
* allowing users to pick colors in a more convenient way. The `defaultProperties` option does impact the data.
* Default values will not be kept in the editor model.
*
* The default color palettes for the cell background and the cell border are the same
* ({@link module:table/utils/ui/table-properties~defaultColors check out their content}).
*
* Both color palette configurations must follow the
* {@link module:table/table~TableColorConfig table color configuration format}.
*
* Read more about configuring the table feature in {@link module:table/table~TableConfig}.
*
* @member {Object} module:table/table~TableConfig#tableCellProperties
*/
/**
* The configuration of the table cell default properties feature.
*
* @typedef {Object} module:table/tablecellproperties~TableCellPropertiesOptions
*
* @property {String} width The default `width` of the table cell.
*
* @property {String} height The default `height` of the table cell.
*
* @property {String} padding The default `padding` of the table cell.
*
* @property {String} backgroundColor The default `background-color` of the table cell.
*
* @property {String} borderColor The default `border-color` of the table cell.
*
* @property {String} borderWidth The default `border-width` of the table cell.
*
* @property {String} [borderStyle='none'] The default `border-style` of the table cell.
*
* @property {String} [horizontalAlignment='center'] The default `horizontalAlignment` of the table cell.
*
* @property {String} [verticalAlignment='middle'] The default `verticalAlignment` of the table cell.
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/tablecellproperties/commands/tablecellbackgroundcolorcommand.js":
/*!********************************************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/tablecellproperties/commands/tablecellbackgroundcolorcommand.js ***!
\********************************************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ TableCellBackgroundColorCommand)
/* harmony export */ });
/* harmony import */ var _tablecellpropertycommand__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./tablecellpropertycommand */ "./node_modules/@ckeditor/ckeditor5-table/src/tablecellproperties/commands/tablecellpropertycommand.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/tablecellproperties/commands/tablecellbackgroundcolorcommand
*/
/**
* The table cell background color command.
*
* The command is registered by the {@link module:table/tablecellproperties/tablecellpropertiesediting~TableCellPropertiesEditing} as
* the `'tableCellBackgroundColor'` editor command.
*
* To change the background color of selected cells, execute the command:
*
* editor.execute( 'tableCellBackgroundColor', {
* value: '#f00'
* } );
*
* @extends module:table/tablecellproperties/commands/tablecellpropertycommand~TableCellPropertyCommand
*/
class TableCellBackgroundColorCommand extends _tablecellpropertycommand__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* Creates a new `TableCellBackgroundColorCommand` instance.
*
* @param {module:core/editor/editor~Editor} editor An editor in which this command will be used.
* @param {String} defaultValue The default value of the attribute.
*/
constructor( editor, defaultValue ) {
super( editor, 'tableCellBackgroundColor', defaultValue );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/tablecellproperties/commands/tablecellbordercolorcommand.js":
/*!****************************************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/tablecellproperties/commands/tablecellbordercolorcommand.js ***!
\****************************************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ TableCellBorderColorCommand)
/* harmony export */ });
/* harmony import */ var _tablecellpropertycommand__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./tablecellpropertycommand */ "./node_modules/@ckeditor/ckeditor5-table/src/tablecellproperties/commands/tablecellpropertycommand.js");
/* harmony import */ var _utils_table_properties__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../../utils/table-properties */ "./node_modules/@ckeditor/ckeditor5-table/src/utils/table-properties.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/tablecellproperties/commands/tablecellbordercolorcommand
*/
/**
* The table cell border color command.
*
* The command is registered by the {@link module:table/tablecellproperties/tablecellpropertiesediting~TableCellPropertiesEditing} as
* the `'tableCellBorderColor'` editor command.
*
* To change the border color of selected cells, execute the command:
*
* editor.execute( 'tableCellBorderColor', {
* value: '#f00'
* } );
*
* @extends module:table/tablecellproperties/commands/tablecellpropertycommand~TableCellPropertyCommand
*/
class TableCellBorderColorCommand extends _tablecellpropertycommand__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* Creates a new `TableCellBorderColorCommand` instance.
*
* @param {module:core/editor/editor~Editor} editor An editor in which this command will be used.
* @param {String} defaultValue The default value of the attribute.
*/
constructor( editor, defaultValue ) {
super( editor, 'tableCellBorderColor', defaultValue );
}
/**
* @inheritDoc
*/
_getAttribute( tableCell ) {
if ( !tableCell ) {
return;
}
const value = (0,_utils_table_properties__WEBPACK_IMPORTED_MODULE_1__.getSingleValue)( tableCell.getAttribute( this.attributeName ) );
if ( value === this._defaultValue ) {
return;
}
return value;
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/tablecellproperties/commands/tablecellborderstylecommand.js":
/*!****************************************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/tablecellproperties/commands/tablecellborderstylecommand.js ***!
\****************************************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ TableCellBorderStyleCommand)
/* harmony export */ });
/* harmony import */ var _tablecellpropertycommand__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./tablecellpropertycommand */ "./node_modules/@ckeditor/ckeditor5-table/src/tablecellproperties/commands/tablecellpropertycommand.js");
/* harmony import */ var _utils_table_properties__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../../utils/table-properties */ "./node_modules/@ckeditor/ckeditor5-table/src/utils/table-properties.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/tablecellproperties/commands/tablecellborderstylecommand
*/
/**
* The table cell border style command.
*
* The command is registered by the {@link module:table/tablecellproperties/tablecellpropertiesediting~TableCellPropertiesEditing} as
* the `'tableCellBorderStyle'` editor command.
*
* To change the border style of selected cells, execute the command:
*
* editor.execute( 'tableCellBorderStyle', {
* value: 'dashed'
* } );
*
* @extends module:table/tablecellproperties/commands/tablecellpropertycommand~TableCellPropertyCommand
*/
class TableCellBorderStyleCommand extends _tablecellpropertycommand__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* Creates a new `TableCellBorderStyleCommand` instance.
*
* @param {module:core/editor/editor~Editor} editor An editor in which this command will be used.
* @param {String} defaultValue The default value of the attribute.
*/
constructor( editor, defaultValue ) {
super( editor, 'tableCellBorderStyle', defaultValue );
}
/**
* @inheritDoc
*/
_getAttribute( tableCell ) {
if ( !tableCell ) {
return;
}
const value = (0,_utils_table_properties__WEBPACK_IMPORTED_MODULE_1__.getSingleValue)( tableCell.getAttribute( this.attributeName ) );
if ( value === this._defaultValue ) {
return;
}
return value;
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/tablecellproperties/commands/tablecellborderwidthcommand.js":
/*!****************************************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/tablecellproperties/commands/tablecellborderwidthcommand.js ***!
\****************************************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ TableCellBorderWidthCommand)
/* harmony export */ });
/* harmony import */ var _tablecellpropertycommand__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./tablecellpropertycommand */ "./node_modules/@ckeditor/ckeditor5-table/src/tablecellproperties/commands/tablecellpropertycommand.js");
/* harmony import */ var _utils_table_properties__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../../utils/table-properties */ "./node_modules/@ckeditor/ckeditor5-table/src/utils/table-properties.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/tablecellproperties/commands/tablecellborderwidthcommand
*/
/**
* The table cell border width command.
*
* The command is registered by the {@link module:table/tablecellproperties/tablecellpropertiesediting~TableCellPropertiesEditing} as
* the `'tableCellBorderWidth'` editor command.
*
* To change the border width of selected cells, execute the command:
*
* editor.execute( 'tableCellBorderWidth', {
* value: '5px'
* } );
*
* **Note**: This command adds the default `'px'` unit to numeric values. Executing:
*
* editor.execute( 'tableCellBorderWidth', {
* value: '5'
* } );
*
* will set the `borderWidth` attribute to `'5px'` in the model.
*
* @extends module:table/tablecellproperties/commands/tablecellpropertycommand~TableCellPropertyCommand
*/
class TableCellBorderWidthCommand extends _tablecellpropertycommand__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* Creates a new `TableCellBorderWidthCommand` instance.
*
* @param {module:core/editor/editor~Editor} editor An editor in which this command will be used.
* @param {String} defaultValue The default value of the attribute.
*/
constructor( editor, defaultValue ) {
super( editor, 'tableCellBorderWidth', defaultValue );
}
/**
* @inheritDoc
*/
_getAttribute( tableCell ) {
if ( !tableCell ) {
return;
}
const value = (0,_utils_table_properties__WEBPACK_IMPORTED_MODULE_1__.getSingleValue)( tableCell.getAttribute( this.attributeName ) );
if ( value === this._defaultValue ) {
return;
}
return value;
}
/**
* @inheritDoc
*/
_getValueToSet( value ) {
value = (0,_utils_table_properties__WEBPACK_IMPORTED_MODULE_1__.addDefaultUnitToNumericValue)( value, 'px' );
if ( value === this._defaultValue ) {
return;
}
return value;
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/tablecellproperties/commands/tablecellheightcommand.js":
/*!***********************************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/tablecellproperties/commands/tablecellheightcommand.js ***!
\***********************************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ TableCellHeightCommand)
/* harmony export */ });
/* harmony import */ var _tablecellpropertycommand__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./tablecellpropertycommand */ "./node_modules/@ckeditor/ckeditor5-table/src/tablecellproperties/commands/tablecellpropertycommand.js");
/* harmony import */ var _utils_table_properties__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../../utils/table-properties */ "./node_modules/@ckeditor/ckeditor5-table/src/utils/table-properties.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/tablecellproperties/commands/tablecellheightcommand
*/
/**
* The table cell height command.
*
* The command is registered by the {@link module:table/tablecellproperties/tablecellpropertiesediting~TableCellPropertiesEditing} as
* the `'tableCellHeight'` editor command.
*
* To change the height of selected cells, execute the command:
*
* editor.execute( 'tableCellHeight', {
* value: '50px'
* } );
*
* **Note**: This command adds the default `'px'` unit to numeric values. Executing:
*
* editor.execute( 'tableCellHeight', {
* value: '50'
* } );
*
* will set the `height` attribute to `'50px'` in the model.
*
* @extends module:table/tablecellproperties/commands/tablecellpropertycommand~TableCellPropertyCommand
*/
class TableCellHeightCommand extends _tablecellpropertycommand__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* Creates a new `TableCellHeightCommand` instance.
*
* @param {module:core/editor/editor~Editor} editor An editor in which this command will be used.
* @param {String} defaultValue The default value of the attribute.
*/
constructor( editor, defaultValue ) {
super( editor, 'tableCellHeight', defaultValue );
}
/**
* @inheritDoc
*/
_getValueToSet( value ) {
value = (0,_utils_table_properties__WEBPACK_IMPORTED_MODULE_1__.addDefaultUnitToNumericValue)( value, 'px' );
if ( value === this._defaultValue ) {
return null;
}
return value;
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/tablecellproperties/commands/tablecellhorizontalalignmentcommand.js":
/*!************************************************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/tablecellproperties/commands/tablecellhorizontalalignmentcommand.js ***!
\************************************************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ TableCellHorizontalAlignmentCommand)
/* harmony export */ });
/* harmony import */ var _tablecellpropertycommand__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./tablecellpropertycommand */ "./node_modules/@ckeditor/ckeditor5-table/src/tablecellproperties/commands/tablecellpropertycommand.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/tablecellproperties/commands/tablecellhorizontalalignmentcommand
*/
/**
* The table cell horizontal alignment command.
*
* The command is registered by the {@link module:table/tablecellproperties/tablecellpropertiesediting~TableCellPropertiesEditing} as
* the `'tableCellHorizontalAlignment'` editor command.
*
* To change the horizontal text alignment of selected cells, execute the command:
*
* editor.execute( 'tableCellHorizontalAlignment', {
* value: 'right'
* } );
*
* @extends module:table/tablecellproperties/commands/tablecellpropertycommand~TableCellPropertyCommand
*/
class TableCellHorizontalAlignmentCommand extends _tablecellpropertycommand__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* Creates a new `TableCellHorizontalAlignmentCommand` instance.
*
* @param {module:core/editor/editor~Editor} editor An editor in which this command will be used.
* @param {String} defaultValue The default value for the "alignment" attribute.
*/
constructor( editor, defaultValue ) {
super( editor, 'tableCellHorizontalAlignment', defaultValue );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/tablecellproperties/commands/tablecellpaddingcommand.js":
/*!************************************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/tablecellproperties/commands/tablecellpaddingcommand.js ***!
\************************************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ TableCellPaddingCommand)
/* harmony export */ });
/* harmony import */ var _tablecellpropertycommand__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./tablecellpropertycommand */ "./node_modules/@ckeditor/ckeditor5-table/src/tablecellproperties/commands/tablecellpropertycommand.js");
/* harmony import */ var _utils_table_properties__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../../utils/table-properties */ "./node_modules/@ckeditor/ckeditor5-table/src/utils/table-properties.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/tablecellproperties/commands/tablecellpaddingcommand
*/
/**
* The table cell padding command.
*
* The command is registered by the {@link module:table/tablecellproperties/tablecellpropertiesediting~TableCellPropertiesEditing} as
* the `'tableCellPadding'` editor command.
*
* To change the padding of selected cells, execute the command:
*
* editor.execute( 'tableCellPadding', {
* value: '5px'
* } );
*
* **Note**: This command adds the default `'px'` unit to numeric values. Executing:
*
* editor.execute( 'tableCellPadding', {
* value: '5'
* } );
*
* will set the `padding` attribute to `'5px'` in the model.
*
* @extends module:table/tablecellproperties/commands/tablecellpropertycommand~TableCellPropertyCommand
*/
class TableCellPaddingCommand extends _tablecellpropertycommand__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* Creates a new `TableCellPaddingCommand` instance.
*
* @param {module:core/editor/editor~Editor} editor An editor in which this command will be used.
* @param {String} defaultValue The default value of the attribute.
*/
constructor( editor, defaultValue ) {
super( editor, 'tableCellPadding', defaultValue );
}
/**
* @inheritDoc
*/
_getAttribute( tableCell ) {
if ( !tableCell ) {
return;
}
const value = (0,_utils_table_properties__WEBPACK_IMPORTED_MODULE_1__.getSingleValue)( tableCell.getAttribute( this.attributeName ) );
if ( value === this._defaultValue ) {
return;
}
return value;
}
/**
* @inheritDoc
*/
_getValueToSet( value ) {
value = (0,_utils_table_properties__WEBPACK_IMPORTED_MODULE_1__.addDefaultUnitToNumericValue)( value, 'px' );
if ( value === this._defaultValue ) {
return;
}
return value;
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/tablecellproperties/commands/tablecellpropertycommand.js":
/*!*************************************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/tablecellproperties/commands/tablecellpropertycommand.js ***!
\*************************************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ TableCellPropertyCommand)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/tablecellproperties/commands/tablecellpropertycommand
*/
/**
* The table cell attribute command.
*
* The command is a base command for other table cell property commands.
*
* @extends module:core/command~Command
*/
class TableCellPropertyCommand extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Command {
/**
* Creates a new `TableCellPropertyCommand` instance.
*
* @param {module:core/editor/editor~Editor} editor An editor in which this command will be used.
* @param {String} attributeName Table cell attribute name.
* @param {String} defaultValue The default value of the attribute.
*/
constructor( editor, attributeName, defaultValue ) {
super( editor );
/**
* The attribute that will be set by the command.
*
* @readonly
* @member {String}
*/
this.attributeName = attributeName;
/**
* The default value for the attribute.
*
* @readonly
* @protected
* @member {String}
*/
this._defaultValue = defaultValue;
}
/**
* @inheritDoc
*/
refresh() {
const editor = this.editor;
const tableUtils = this.editor.plugins.get( 'TableUtils' );
const selectedTableCells = tableUtils.getSelectionAffectedTableCells( editor.model.document.selection );
this.isEnabled = !!selectedTableCells.length;
this.value = this._getSingleValue( selectedTableCells );
}
/**
* Executes the command.
*
* @fires execute
* @param {Object} [options]
* @param {*} [options.value] If set, the command will set the attribute on selected table cells.
* If it is not set, the command will remove the attribute from the selected table cells.
* @param {module:engine/model/batch~Batch} [options.batch] Pass the model batch instance to the command to aggregate changes,
* for example to allow a single undo step for multiple executions.
*/
execute( options = {} ) {
const { value, batch } = options;
const model = this.editor.model;
const tableUtils = this.editor.plugins.get( 'TableUtils' );
const tableCells = tableUtils.getSelectionAffectedTableCells( model.document.selection );
const valueToSet = this._getValueToSet( value );
model.enqueueChange( batch, writer => {
if ( valueToSet ) {
tableCells.forEach( tableCell => writer.setAttribute( this.attributeName, valueToSet, tableCell ) );
} else {
tableCells.forEach( tableCell => writer.removeAttribute( this.attributeName, tableCell ) );
}
} );
}
/**
* Returns the attribute value for a table cell.
*
* @param {module:engine/model/element~Element} tableCell
* @returns {String|undefined}
* @private
*/
_getAttribute( tableCell ) {
if ( !tableCell ) {
return;
}
const value = tableCell.getAttribute( this.attributeName );
if ( value === this._defaultValue ) {
return;
}
return value;
}
/**
* Returns the proper model value. It can be used to add a default unit to numeric values.
*
* @private
* @param {*} value
* @returns {*}
*/
_getValueToSet( value ) {
if ( value === this._defaultValue ) {
return;
}
return value;
}
/**
* Returns a single value for all selected table cells. If the value is the same for all cells,
* it will be returned (`undefined` otherwise).
*
* @param {Array.<module:engine/model/element~Element>} tableCell
* @returns {*}
* @private
*/
_getSingleValue( tableCell ) {
const firstCellValue = this._getAttribute( tableCell[ 0 ] );
const everyCellHasAttribute = tableCell.every( tableCell => this._getAttribute( tableCell ) === firstCellValue );
return everyCellHasAttribute ? firstCellValue : undefined;
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/tablecellproperties/commands/tablecellverticalalignmentcommand.js":
/*!**********************************************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/tablecellproperties/commands/tablecellverticalalignmentcommand.js ***!
\**********************************************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ TableCellVerticalAlignmentCommand)
/* harmony export */ });
/* harmony import */ var _tablecellpropertycommand__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./tablecellpropertycommand */ "./node_modules/@ckeditor/ckeditor5-table/src/tablecellproperties/commands/tablecellpropertycommand.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/tablecellproperties/commands/tablecellverticalalignmentcommand
*/
/**
* The table cell vertical alignment command.
*
* The command is registered by the {@link module:table/tablecellproperties/tablecellpropertiesediting~TableCellPropertiesEditing} as
* the `'tableCellVerticalAlignment'` editor command.
*
* To change the vertical text alignment of selected cells, execute the command:
*
* editor.execute( 'tableCellVerticalAlignment', {
* value: 'top'
* } );
*
* The following values, corresponding to the
* [`vertical-align` CSS attribute](https://developer.mozilla.org/en-US/docs/Web/CSS/vertical-align), are allowed:
*
* * `'top'`
* * `'bottom'`
*
* The `'middle'` value is the default one so there is no need to set it.
*
* @extends module:table/tablecellproperties/commands/tablecellpropertycommand~TableCellPropertyCommand
*/
class TableCellVerticalAlignmentCommand extends _tablecellpropertycommand__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* Creates a new `TableCellVerticalAlignmentCommand` instance.
*
* @param {module:core/editor/editor~Editor} editor An editor in which this command will be used.
* @param {String} defaultValue The default value for the "alignment" attribute.
*/
constructor( editor, defaultValue ) {
super( editor, 'tableCellVerticalAlignment', defaultValue );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/tablecellproperties/commands/tablecellwidthcommand.js":
/*!**********************************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/tablecellproperties/commands/tablecellwidthcommand.js ***!
\**********************************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ TableCellWidthCommand)
/* harmony export */ });
/* harmony import */ var _tablecellpropertycommand__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./tablecellpropertycommand */ "./node_modules/@ckeditor/ckeditor5-table/src/tablecellproperties/commands/tablecellpropertycommand.js");
/* harmony import */ var _utils_table_properties__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../../utils/table-properties */ "./node_modules/@ckeditor/ckeditor5-table/src/utils/table-properties.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/tablecellproperties/commands/tablecellwidthcommand
*/
/**
* The table cell width command.
*
* The command is registered by the {@link module:table/tablecellproperties/tablecellpropertiesediting~TableCellPropertiesEditing} as
* the `'tableCellWidth'` editor command.
*
* To change the width of selected cells, execute the command:
*
* editor.execute( 'tableCellWidth', {
* value: '50px'
* } );
*
* **Note**: This command adds a default `'px'` unit to numeric values. Executing:
*
* editor.execute( 'tableCellWidth', {
* value: '50'
* } );
*
* will set the `width` attribute to `'50px'` in the model.
*
* @extends module:table/tablecellproperties/commands/tablecellpropertycommand~TableCellPropertyCommand
*/
class TableCellWidthCommand extends _tablecellpropertycommand__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* Creates a new `TableCellWidthCommand` instance.
*
* @param {module:core/editor/editor~Editor} editor An editor in which this command will be used.
* @param {String} defaultValue The default value of the attribute.
*/
constructor( editor, defaultValue ) {
super( editor, 'tableCellWidth', defaultValue );
}
/**
* @inheritDoc
*/
_getValueToSet( value ) {
value = (0,_utils_table_properties__WEBPACK_IMPORTED_MODULE_1__.addDefaultUnitToNumericValue)( value, 'px' );
if ( value === this._defaultValue ) {
return;
}
return value;
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/tablecellproperties/tablecellpropertiesediting.js":
/*!******************************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/tablecellproperties/tablecellpropertiesediting.js ***!
\******************************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ TableCellPropertiesEditing)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/engine */ "./node_modules/ckeditor5/src/engine.js");
/* harmony import */ var _converters_tableproperties__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./../converters/tableproperties */ "./node_modules/@ckeditor/ckeditor5-table/src/converters/tableproperties.js");
/* harmony import */ var _tableediting__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./../tableediting */ "./node_modules/@ckeditor/ckeditor5-table/src/tableediting.js");
/* harmony import */ var _commands_tablecellpaddingcommand__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./commands/tablecellpaddingcommand */ "./node_modules/@ckeditor/ckeditor5-table/src/tablecellproperties/commands/tablecellpaddingcommand.js");
/* harmony import */ var _commands_tablecellwidthcommand__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./commands/tablecellwidthcommand */ "./node_modules/@ckeditor/ckeditor5-table/src/tablecellproperties/commands/tablecellwidthcommand.js");
/* harmony import */ var _commands_tablecellheightcommand__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./commands/tablecellheightcommand */ "./node_modules/@ckeditor/ckeditor5-table/src/tablecellproperties/commands/tablecellheightcommand.js");
/* harmony import */ var _commands_tablecellbackgroundcolorcommand__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./commands/tablecellbackgroundcolorcommand */ "./node_modules/@ckeditor/ckeditor5-table/src/tablecellproperties/commands/tablecellbackgroundcolorcommand.js");
/* harmony import */ var _commands_tablecellverticalalignmentcommand__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./commands/tablecellverticalalignmentcommand */ "./node_modules/@ckeditor/ckeditor5-table/src/tablecellproperties/commands/tablecellverticalalignmentcommand.js");
/* harmony import */ var _commands_tablecellhorizontalalignmentcommand__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./commands/tablecellhorizontalalignmentcommand */ "./node_modules/@ckeditor/ckeditor5-table/src/tablecellproperties/commands/tablecellhorizontalalignmentcommand.js");
/* harmony import */ var _commands_tablecellborderstylecommand__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./commands/tablecellborderstylecommand */ "./node_modules/@ckeditor/ckeditor5-table/src/tablecellproperties/commands/tablecellborderstylecommand.js");
/* harmony import */ var _commands_tablecellbordercolorcommand__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ./commands/tablecellbordercolorcommand */ "./node_modules/@ckeditor/ckeditor5-table/src/tablecellproperties/commands/tablecellbordercolorcommand.js");
/* harmony import */ var _commands_tablecellborderwidthcommand__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! ./commands/tablecellborderwidthcommand */ "./node_modules/@ckeditor/ckeditor5-table/src/tablecellproperties/commands/tablecellborderwidthcommand.js");
/* harmony import */ var _utils_table_properties__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(/*! ../utils/table-properties */ "./node_modules/@ckeditor/ckeditor5-table/src/utils/table-properties.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/tablecellproperties/tablecellpropertiesediting
*/
const VALIGN_VALUES_REG_EXP = /^(top|middle|bottom)$/;
const ALIGN_VALUES_REG_EXP = /^(left|center|right|justify)$/;
/**
* The table cell properties editing feature.
*
* Introduces table cell model attributes and their conversion:
*
* - border: `tableCellBorderStyle`, `tableCellBorderColor` and `tableCellBorderWidth`
* - background color: `tableCellBackgroundColor`
* - cell padding: `tableCellPadding`
* - horizontal and vertical alignment: `tableCellHorizontalAlignment`, `tableCellVerticalAlignment`
* - cell width and height: `tableCellWidth`, `tableCellHeight`
*
* It also registers commands used to manipulate the above attributes:
*
* - border: the `'tableCellBorderStyle'`, `'tableCellBorderColor'` and `'tableCellBorderWidth'` commands
* - background color: the `'tableCellBackgroundColor'` command
* - cell padding: the `'tableCellPadding'` command
* - horizontal and vertical alignment: the `'tableCellHorizontalAlignment'` and `'tableCellVerticalAlignment'` commands
* - width and height: the `'tableCellWidth'` and `'tableCellHeight'` commands
*
* @extends module:core/plugin~Plugin
*/
class TableCellPropertiesEditing extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'TableCellPropertiesEditing';
}
/**
* @inheritDoc
*/
static get requires() {
return [ _tableediting__WEBPACK_IMPORTED_MODULE_3__["default"] ];
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const schema = editor.model.schema;
const conversion = editor.conversion;
editor.config.define( 'table.tableCellProperties.defaultProperties', {} );
const defaultTableCellProperties = (0,_utils_table_properties__WEBPACK_IMPORTED_MODULE_13__.getNormalizedDefaultProperties)(
editor.config.get( 'table.tableCellProperties.defaultProperties' ),
{
includeVerticalAlignmentProperty: true,
includeHorizontalAlignmentProperty: true,
includePaddingProperty: true,
isRightToLeftContent: editor.locale.contentLanguageDirection === 'rtl'
}
);
editor.data.addStyleProcessorRules( ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_1__.addBorderRules );
enableBorderProperties( schema, conversion, {
color: defaultTableCellProperties.borderColor,
style: defaultTableCellProperties.borderStyle,
width: defaultTableCellProperties.borderWidth
} );
editor.commands.add( 'tableCellBorderStyle', new _commands_tablecellborderstylecommand__WEBPACK_IMPORTED_MODULE_10__["default"]( editor, defaultTableCellProperties.borderStyle ) );
editor.commands.add( 'tableCellBorderColor', new _commands_tablecellbordercolorcommand__WEBPACK_IMPORTED_MODULE_11__["default"]( editor, defaultTableCellProperties.borderColor ) );
editor.commands.add( 'tableCellBorderWidth', new _commands_tablecellborderwidthcommand__WEBPACK_IMPORTED_MODULE_12__["default"]( editor, defaultTableCellProperties.borderWidth ) );
enableProperty( schema, conversion, {
modelAttribute: 'tableCellWidth',
styleName: 'width',
defaultValue: defaultTableCellProperties.width
} );
editor.commands.add( 'tableCellWidth', new _commands_tablecellwidthcommand__WEBPACK_IMPORTED_MODULE_5__["default"]( editor, defaultTableCellProperties.width ) );
enableProperty( schema, conversion, {
modelAttribute: 'tableCellHeight',
styleName: 'height',
defaultValue: defaultTableCellProperties.height
} );
editor.commands.add( 'tableCellHeight', new _commands_tablecellheightcommand__WEBPACK_IMPORTED_MODULE_6__["default"]( editor, defaultTableCellProperties.height ) );
editor.data.addStyleProcessorRules( ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_1__.addPaddingRules );
enableProperty( schema, conversion, {
modelAttribute: 'tableCellPadding',
styleName: 'padding',
reduceBoxSides: true,
defaultValue: defaultTableCellProperties.padding
} );
editor.commands.add( 'tableCellPadding', new _commands_tablecellpaddingcommand__WEBPACK_IMPORTED_MODULE_4__["default"]( editor, defaultTableCellProperties.padding ) );
editor.data.addStyleProcessorRules( ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_1__.addBackgroundRules );
enableProperty( schema, conversion, {
modelAttribute: 'tableCellBackgroundColor',
styleName: 'background-color',
defaultValue: defaultTableCellProperties.backgroundColor
} );
editor.commands.add(
'tableCellBackgroundColor',
new _commands_tablecellbackgroundcolorcommand__WEBPACK_IMPORTED_MODULE_7__["default"]( editor, defaultTableCellProperties.backgroundColor )
);
enableHorizontalAlignmentProperty( schema, conversion, defaultTableCellProperties.horizontalAlignment );
editor.commands.add(
'tableCellHorizontalAlignment',
new _commands_tablecellhorizontalalignmentcommand__WEBPACK_IMPORTED_MODULE_9__["default"]( editor, defaultTableCellProperties.horizontalAlignment )
);
enableVerticalAlignmentProperty( schema, conversion, defaultTableCellProperties.verticalAlignment );
editor.commands.add(
'tableCellVerticalAlignment',
new _commands_tablecellverticalalignmentcommand__WEBPACK_IMPORTED_MODULE_8__["default"]( editor, defaultTableCellProperties.verticalAlignment )
);
}
}
// Enables the `'tableCellBorderStyle'`, `'tableCellBorderColor'` and `'tableCellBorderWidth'` attributes for table cells.
//
// @param {module:engine/model/schema~Schema} schema
// @param {module:engine/conversion/conversion~Conversion} conversion
// @param {Object} defaultBorder The default border values.
// @param {String} defaultBorder.color The default `tableCellBorderColor` value.
// @param {String} defaultBorder.style The default `tableCellBorderStyle` value.
// @param {String} defaultBorder.width The default `tableCellBorderWidth` value.
function enableBorderProperties( schema, conversion, defaultBorder ) {
const modelAttributes = {
width: 'tableCellBorderWidth',
color: 'tableCellBorderColor',
style: 'tableCellBorderStyle'
};
schema.extend( 'tableCell', {
allowAttributes: Object.values( modelAttributes )
} );
(0,_converters_tableproperties__WEBPACK_IMPORTED_MODULE_2__.upcastBorderStyles)( conversion, 'td', modelAttributes, defaultBorder );
(0,_converters_tableproperties__WEBPACK_IMPORTED_MODULE_2__.upcastBorderStyles)( conversion, 'th', modelAttributes, defaultBorder );
(0,_converters_tableproperties__WEBPACK_IMPORTED_MODULE_2__.downcastAttributeToStyle)( conversion, { modelElement: 'tableCell', modelAttribute: modelAttributes.style, styleName: 'border-style' } );
(0,_converters_tableproperties__WEBPACK_IMPORTED_MODULE_2__.downcastAttributeToStyle)( conversion, { modelElement: 'tableCell', modelAttribute: modelAttributes.color, styleName: 'border-color' } );
(0,_converters_tableproperties__WEBPACK_IMPORTED_MODULE_2__.downcastAttributeToStyle)( conversion, { modelElement: 'tableCell', modelAttribute: modelAttributes.width, styleName: 'border-width' } );
}
// Enables the `'tableCellHorizontalAlignment'` attribute for table cells.
//
// @param {module:engine/model/schema~Schema} schema
// @param {module:engine/conversion/conversion~Conversion} conversion
// @param {module:utils/locale~Locale} locale The {@link module:core/editor/editor~Editor#locale} instance.
// @param {String} defaultValue The default horizontal alignment value.
function enableHorizontalAlignmentProperty( schema, conversion, defaultValue ) {
schema.extend( 'tableCell', {
allowAttributes: [ 'tableCellHorizontalAlignment' ]
} );
conversion.for( 'downcast' )
.attributeToAttribute( {
model: {
name: 'tableCell',
key: 'tableCellHorizontalAlignment'
},
view: alignment => ( {
key: 'style',
value: {
'text-align': alignment
}
} )
} );
conversion.for( 'upcast' )
// Support for the `text-align:*;` CSS definition for the table cell alignment.
.attributeToAttribute( {
view: {
name: /^(td|th)$/,
styles: {
'text-align': ALIGN_VALUES_REG_EXP
}
},
model: {
key: 'tableCellHorizontalAlignment',
value: viewElement => {
const align = viewElement.getStyle( 'text-align' );
return align === defaultValue ? null : align;
}
}
} )
// Support for the `align` attribute as the backward compatibility while pasting from other sources.
.attributeToAttribute( {
view: {
name: /^(td|th)$/,
attributes: {
align: ALIGN_VALUES_REG_EXP
}
},
model: {
key: 'tableCellHorizontalAlignment',
value: viewElement => {
const align = viewElement.getAttribute( 'align' );
return align === defaultValue ? null : align;
}
}
} );
}
// Enables the `'verticalAlignment'` attribute for table cells.
//
// @param {module:engine/model/schema~Schema} schema
// @param {module:engine/conversion/conversion~Conversion} conversion
// @param {String} defaultValue The default vertical alignment value.
function enableVerticalAlignmentProperty( schema, conversion, defaultValue ) {
schema.extend( 'tableCell', {
allowAttributes: [ 'tableCellVerticalAlignment' ]
} );
conversion.for( 'downcast' )
.attributeToAttribute( {
model: {
name: 'tableCell',
key: 'tableCellVerticalAlignment'
},
view: alignment => ( {
key: 'style',
value: {
'vertical-align': alignment
}
} )
} );
conversion.for( 'upcast' )
// Support for the `vertical-align:*;` CSS definition for the table cell alignment.
.attributeToAttribute( {
view: {
name: /^(td|th)$/,
styles: {
'vertical-align': VALIGN_VALUES_REG_EXP
}
},
model: {
key: 'tableCellVerticalAlignment',
value: viewElement => {
const align = viewElement.getStyle( 'vertical-align' );
return align === defaultValue ? null : align;
}
}
} )
// Support for the `align` attribute as the backward compatibility while pasting from other sources.
.attributeToAttribute( {
view: {
name: /^(td|th)$/,
attributes: {
valign: VALIGN_VALUES_REG_EXP
}
},
model: {
key: 'tableCellVerticalAlignment',
value: viewElement => {
const valign = viewElement.getAttribute( 'valign' );
return valign === defaultValue ? null : valign;
}
}
} );
}
// Enables conversion for an attribute for simple view-model mappings.
//
// @param {module:engine/model/schema~Schema} schema
// @param {module:engine/conversion/conversion~Conversion} conversion
// @param {Object} options
// @param {String} options.modelAttribute
// @param {String} options.styleName
// @param {String} options.defaultValue The default value for the specified `modelAttribute`.
// @param {Boolean} [options.reduceBoxSides=false]
function enableProperty( schema, conversion, options ) {
const { modelAttribute } = options;
schema.extend( 'tableCell', {
allowAttributes: [ modelAttribute ]
} );
(0,_converters_tableproperties__WEBPACK_IMPORTED_MODULE_2__.upcastStyleToAttribute)( conversion, { viewElement: /^(td|th)$/, ...options } );
(0,_converters_tableproperties__WEBPACK_IMPORTED_MODULE_2__.downcastAttributeToStyle)( conversion, { modelElement: 'tableCell', ...options } );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/tablecellproperties/tablecellpropertiesui.js":
/*!*************************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/tablecellproperties/tablecellpropertiesui.js ***!
\*************************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ TableCellPropertiesUI)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/ui */ "./node_modules/ckeditor5/src/ui.js");
/* harmony import */ var _ui_tablecellpropertiesview__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./ui/tablecellpropertiesview */ "./node_modules/@ckeditor/ckeditor5-table/src/tablecellproperties/ui/tablecellpropertiesview.js");
/* harmony import */ var _utils_ui_table_properties__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../utils/ui/table-properties */ "./node_modules/@ckeditor/ckeditor5-table/src/utils/ui/table-properties.js");
/* harmony import */ var lodash_es__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! lodash-es */ "./node_modules/lodash-es/debounce.js");
/* harmony import */ var _utils_ui_widget__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../utils/ui/widget */ "./node_modules/@ckeditor/ckeditor5-table/src/utils/ui/widget.js");
/* harmony import */ var _utils_ui_contextualballoon__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../utils/ui/contextualballoon */ "./node_modules/@ckeditor/ckeditor5-table/src/utils/ui/contextualballoon.js");
/* harmony import */ var _theme_icons_table_cell_properties_svg__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./../../theme/icons/table-cell-properties.svg */ "./node_modules/@ckeditor/ckeditor5-table/theme/icons/table-cell-properties.svg");
/* harmony import */ var _utils_table_properties__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ../utils/table-properties */ "./node_modules/@ckeditor/ckeditor5-table/src/utils/table-properties.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/tablecellproperties/tablecellpropertiesui
*/
const ERROR_TEXT_TIMEOUT = 500;
// Map of view properties and related commands.
const propertyToCommandMap = {
borderStyle: 'tableCellBorderStyle',
borderColor: 'tableCellBorderColor',
borderWidth: 'tableCellBorderWidth',
width: 'tableCellWidth',
height: 'tableCellHeight',
padding: 'tableCellPadding',
backgroundColor: 'tableCellBackgroundColor',
horizontalAlignment: 'tableCellHorizontalAlignment',
verticalAlignment: 'tableCellVerticalAlignment'
};
/**
* The table cell properties UI plugin. It introduces the `'tableCellProperties'` button
* that opens a form allowing to specify the visual styling of a table cell.
*
* It uses the
* {@link module:ui/panel/balloon/contextualballoon~ContextualBalloon contextual balloon plugin}.
*
* @extends module:core/plugin~Plugin
*/
class TableCellPropertiesUI extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [ ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.ContextualBalloon ];
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'TableCellPropertiesUI';
}
/**
* @inheritDoc
*/
constructor( editor ) {
super( editor );
editor.config.define( 'table.tableCellProperties', {
borderColors: _utils_ui_table_properties__WEBPACK_IMPORTED_MODULE_3__.defaultColors,
backgroundColors: _utils_ui_table_properties__WEBPACK_IMPORTED_MODULE_3__.defaultColors
} );
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const t = editor.t;
/**
* The default table cell properties.
*
* @protected
* @member {module:table/tablecellproperties~TableCellPropertiesOptions}
*/
this._defaultTableCellProperties = (0,_utils_table_properties__WEBPACK_IMPORTED_MODULE_7__.getNormalizedDefaultProperties)(
editor.config.get( 'table.tableCellProperties.defaultProperties' ),
{
includeVerticalAlignmentProperty: true,
includeHorizontalAlignmentProperty: true,
includePaddingProperty: true,
isRightToLeftContent: editor.locale.contentLanguageDirection === 'rtl'
}
);
/**
* The contextual balloon plugin instance.
*
* @private
* @member {module:ui/panel/balloon/contextualballoon~ContextualBalloon}
*/
this._balloon = editor.plugins.get( ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.ContextualBalloon );
/**
* The cell properties form view displayed inside the balloon.
*
* @member {module:table/tablecellproperties/ui/tablecellpropertiesview~TableCellPropertiesView}
*/
this.view = this._createPropertiesView();
/**
* The batch used to undo all changes made by the form (which are live, as the user types)
* when "Cancel" was pressed. Each time the view is shown, a new batch is created.
*
* @protected
* @member {module:engine/model/batch~Batch}
*/
this._undoStepBatch = null;
editor.ui.componentFactory.add( 'tableCellProperties', locale => {
const view = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.ButtonView( locale );
view.set( {
label: t( 'Cell properties' ),
icon: _theme_icons_table_cell_properties_svg__WEBPACK_IMPORTED_MODULE_6__["default"],
tooltip: true
} );
this.listenTo( view, 'execute', () => this._showView() );
const commands = Object.values( propertyToCommandMap )
.map( commandName => editor.commands.get( commandName ) );
view.bind( 'isEnabled' ).toMany( commands, 'isEnabled', ( ...areEnabled ) => (
areEnabled.some( isCommandEnabled => isCommandEnabled )
) );
return view;
} );
}
/**
* @inheritDoc
*/
destroy() {
super.destroy();
// Destroy created UI components as they are not automatically destroyed.
// See https://github.com/ckeditor/ckeditor5/issues/1341.
this.view.destroy();
}
/**
* Creates the {@link module:table/tablecellproperties/ui/tablecellpropertiesview~TableCellPropertiesView} instance.
*
* @private
* @returns {module:table/tablecellproperties/ui/tablecellpropertiesview~TableCellPropertiesView} The cell
* properties form view instance.
*/
_createPropertiesView() {
const editor = this.editor;
const viewDocument = editor.editing.view.document;
const config = editor.config.get( 'table.tableCellProperties' );
const borderColorsConfig = (0,ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.normalizeColorOptions)( config.borderColors );
const localizedBorderColors = (0,ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.getLocalizedColorOptions)( editor.locale, borderColorsConfig );
const backgroundColorsConfig = (0,ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.normalizeColorOptions)( config.backgroundColors );
const localizedBackgroundColors = (0,ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.getLocalizedColorOptions)( editor.locale, backgroundColorsConfig );
const view = new _ui_tablecellpropertiesview__WEBPACK_IMPORTED_MODULE_2__["default"]( editor.locale, {
borderColors: localizedBorderColors,
backgroundColors: localizedBackgroundColors,
defaultTableCellProperties: this._defaultTableCellProperties
} );
const t = editor.t;
// Render the view so its #element is available for the clickOutsideHandler.
view.render();
this.listenTo( view, 'submit', () => {
this._hideView();
} );
this.listenTo( view, 'cancel', () => {
// https://github.com/ckeditor/ckeditor5/issues/6180
if ( this._undoStepBatch.operations.length ) {
editor.execute( 'undo', this._undoStepBatch );
}
this._hideView();
} );
// Close the balloon on Esc key press.
view.keystrokes.set( 'Esc', ( data, cancel ) => {
this._hideView();
cancel();
} );
// Reposition the balloon or hide the form if a table cell is no longer selected.
this.listenTo( editor.ui, 'update', () => {
if ( !(0,_utils_ui_widget__WEBPACK_IMPORTED_MODULE_4__.getTableWidgetAncestor)( viewDocument.selection ) ) {
this._hideView();
} else if ( this._isViewVisible ) {
(0,_utils_ui_contextualballoon__WEBPACK_IMPORTED_MODULE_5__.repositionContextualBalloon)( editor, 'cell' );
}
} );
// Close on click outside of balloon panel element.
(0,ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.clickOutsideHandler)( {
emitter: view,
activator: () => this._isViewInBalloon,
contextElements: [ this._balloon.view.element ],
callback: () => this._hideView()
} );
const colorErrorText = (0,_utils_ui_table_properties__WEBPACK_IMPORTED_MODULE_3__.getLocalizedColorErrorText)( t );
const lengthErrorText = (0,_utils_ui_table_properties__WEBPACK_IMPORTED_MODULE_3__.getLocalizedLengthErrorText)( t );
// Create the "UI -> editor data" binding.
// These listeners update the editor data (via table commands) when any observable
// property of the view has changed. They also validate the value and display errors in the UI
// when necessary. This makes the view live, which means the changes are
// visible in the editing as soon as the user types or changes fields' values.
view.on(
'change:borderStyle',
this._getPropertyChangeCallback( 'tableCellBorderStyle', this._defaultTableCellProperties.borderStyle )
);
view.on( 'change:borderColor', this._getValidatedPropertyChangeCallback( {
viewField: view.borderColorInput,
commandName: 'tableCellBorderColor',
errorText: colorErrorText,
validator: _utils_ui_table_properties__WEBPACK_IMPORTED_MODULE_3__.colorFieldValidator,
defaultValue: this._defaultTableCellProperties.borderColor
} ) );
view.on( 'change:borderWidth', this._getValidatedPropertyChangeCallback( {
viewField: view.borderWidthInput,
commandName: 'tableCellBorderWidth',
errorText: lengthErrorText,
validator: _utils_ui_table_properties__WEBPACK_IMPORTED_MODULE_3__.lineWidthFieldValidator,
defaultValue: this._defaultTableCellProperties.borderWidth
} ) );
view.on( 'change:padding', this._getValidatedPropertyChangeCallback( {
viewField: view.paddingInput,
commandName: 'tableCellPadding',
errorText: lengthErrorText,
validator: _utils_ui_table_properties__WEBPACK_IMPORTED_MODULE_3__.lengthFieldValidator,
defaultValue: this._defaultTableCellProperties.padding
} ) );
view.on( 'change:width', this._getValidatedPropertyChangeCallback( {
viewField: view.widthInput,
commandName: 'tableCellWidth',
errorText: lengthErrorText,
validator: _utils_ui_table_properties__WEBPACK_IMPORTED_MODULE_3__.lengthFieldValidator,
defaultValue: this._defaultTableCellProperties.width
} ) );
view.on( 'change:height', this._getValidatedPropertyChangeCallback( {
viewField: view.heightInput,
commandName: 'tableCellHeight',
errorText: lengthErrorText,
validator: _utils_ui_table_properties__WEBPACK_IMPORTED_MODULE_3__.lengthFieldValidator,
defaultValue: this._defaultTableCellProperties.height
} ) );
view.on( 'change:backgroundColor', this._getValidatedPropertyChangeCallback( {
viewField: view.backgroundInput,
commandName: 'tableCellBackgroundColor',
errorText: colorErrorText,
validator: _utils_ui_table_properties__WEBPACK_IMPORTED_MODULE_3__.colorFieldValidator,
defaultValue: this._defaultTableCellProperties.backgroundColor
} ) );
view.on(
'change:horizontalAlignment',
this._getPropertyChangeCallback( 'tableCellHorizontalAlignment', this._defaultTableCellProperties.horizontalAlignment )
);
view.on(
'change:verticalAlignment',
this._getPropertyChangeCallback( 'tableCellVerticalAlignment', this._defaultTableCellProperties.verticalAlignment )
);
return view;
}
/**
* In this method the "editor data -> UI" binding is happening.
*
* When executed, this method obtains selected cell property values from various table commands
* and passes them to the {@link #view}.
*
* This way, the UI stays up–to–date with the editor data.
*
* @private
*/
_fillViewFormFromCommandValues() {
const commands = this.editor.commands;
const borderStyleCommand = commands.get( 'tableCellBorderStyle' );
Object.entries( propertyToCommandMap )
.map( ( [ property, commandName ] ) => {
const defaultValue = this._defaultTableCellProperties[ property ] || '';
return [ property, commands.get( commandName ).value || defaultValue ];
} )
.forEach( ( [ property, value ] ) => {
// Do not set the `border-color` and `border-width` fields if `border-style:none`.
if ( ( property === 'borderColor' || property === 'borderWidth' ) && borderStyleCommand.value === 'none' ) {
return;
}
this.view.set( property, value );
} );
}
/**
* Shows the {@link #view} in the {@link #_balloon}.
*
* **Note**: Each time a view is shown, a new {@link #_undoStepBatch} is created. It contains
* all changes made to the document when the view is visible, allowing a single undo step
* for all of them.
*
* @protected
*/
_showView() {
const editor = this.editor;
// Update the view with the model values.
this._fillViewFormFromCommandValues();
this._balloon.add( {
view: this.view,
position: (0,_utils_ui_contextualballoon__WEBPACK_IMPORTED_MODULE_5__.getBalloonCellPositionData)( editor )
} );
// Create a new batch. Clicking "Cancel" will undo this batch.
this._undoStepBatch = editor.model.createBatch();
// Basic a11y.
this.view.focus();
}
/**
* Removes the {@link #view} from the {@link #_balloon}.
*
* @protected
*/
_hideView() {
if ( !this._isViewInBalloon ) {
return;
}
const editor = this.editor;
this.stopListening( editor.ui, 'update' );
// Blur any input element before removing it from DOM to prevent issues in some browsers.
// See https://github.com/ckeditor/ckeditor5/issues/1501.
this.view.saveButtonView.focus();
this._balloon.remove( this.view );
// Make sure the focus is not lost in the process by putting it directly
// into the editing view.
this.editor.editing.view.focus();
}
/**
* Returns `true` when the {@link #view} is visible in the {@link #_balloon}.
*
* @private
* @type {Boolean}
*/
get _isViewVisible() {
return this._balloon.visibleView === this.view;
}
/**
* Returns `true` when the {@link #view} is in the {@link #_balloon}.
*
* @private
* @type {Boolean}
*/
get _isViewInBalloon() {
return this._balloon.hasView( this.view );
}
/**
* Creates a callback that when executed upon the {@link #view view's} property change
* executes a related editor command with the new property value.
*
* @private
* @param {String} commandName
* @param {String} defaultValue The default value of the command.
* @returns {Function}
*/
_getPropertyChangeCallback( commandName, defaultValue ) {
return ( evt, propertyName, newValue, oldValue ) => {
// If the "oldValue" is missing and "newValue" is set to the default value, do not execute the command.
// It is an initial call (when opening the table properties view).
if ( !oldValue && defaultValue === newValue ) {
return;
}
this.editor.execute( commandName, {
value: newValue,
batch: this._undoStepBatch
} );
};
}
/**
* Creates a callback that when executed upon the {@link #view view's} property change:
* * Executes a related editor command with the new property value if the value is valid,
* * Or sets the error text next to the invalid field, if the value did not pass the validation.
*
* @private
* @param {Object} options
* @param {String} options.commandName
* @param {module:ui/view~View} options.viewField
* @param {Function} options.validator
* @param {String} options.errorText
* @param {String} options.defaultValue
* @returns {Function}
*/
_getValidatedPropertyChangeCallback( options ) {
const { commandName, viewField, validator, errorText, defaultValue } = options;
const setErrorTextDebounced = (0,lodash_es__WEBPACK_IMPORTED_MODULE_8__["default"])( () => {
viewField.errorText = errorText;
}, ERROR_TEXT_TIMEOUT );
return ( evt, propertyName, newValue, oldValue ) => {
setErrorTextDebounced.cancel();
// If the "oldValue" is missing and "newValue" is set to the default value, do not execute the command.
// It is an initial call (when opening the table properties view).
if ( !oldValue && defaultValue === newValue ) {
return;
}
if ( validator( newValue ) ) {
this.editor.execute( commandName, {
value: newValue,
batch: this._undoStepBatch
} );
viewField.errorText = null;
} else {
setErrorTextDebounced();
}
};
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/tablecellproperties/ui/tablecellpropertiesview.js":
/*!******************************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/tablecellproperties/ui/tablecellpropertiesview.js ***!
\******************************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ TableCellPropertiesView)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/ui */ "./node_modules/ckeditor5/src/ui.js");
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var _utils_ui_table_properties__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../../utils/ui/table-properties */ "./node_modules/@ckeditor/ckeditor5-table/src/utils/ui/table-properties.js");
/* harmony import */ var _ui_formrowview__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../../ui/formrowview */ "./node_modules/@ckeditor/ckeditor5-table/src/ui/formrowview.js");
/* harmony import */ var _theme_form_css__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../../../theme/form.css */ "./node_modules/@ckeditor/ckeditor5-table/theme/form.css");
/* harmony import */ var _theme_tableform_css__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ../../../theme/tableform.css */ "./node_modules/@ckeditor/ckeditor5-table/theme/tableform.css");
/* harmony import */ var _theme_tablecellproperties_css__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ../../../theme/tablecellproperties.css */ "./node_modules/@ckeditor/ckeditor5-table/theme/tablecellproperties.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/tablecellproperties/ui/tablecellpropertiesview
*/
const ALIGNMENT_ICONS = {
left: ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_2__.icons.alignLeft,
center: ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_2__.icons.alignCenter,
right: ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_2__.icons.alignRight,
justify: ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_2__.icons.alignJustify,
top: ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_2__.icons.alignTop,
middle: ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_2__.icons.alignMiddle,
bottom: ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_2__.icons.alignBottom
};
/**
* The class representing a table cell properties form, allowing users to customize
* certain style aspects of a table cell, for instance, border, padding, text alignment, etc..
*
* @extends module:ui/view~View
*/
class TableCellPropertiesView extends ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.View {
/**
* @param {module:utils/locale~Locale} locale The {@link module:core/editor/editor~Editor#locale} instance.
* @param {Object} options Additional configuration of the view.
* @param {module:table/table~TableColorConfig} options.borderColors A configuration of the border
* color palette used by the
* {@link module:table/tablecellproperties/ui/tablecellpropertiesview~TableCellPropertiesView#borderColorInput}.
* @param {module:table/table~TableColorConfig} options.backgroundColors A configuration of the background
* color palette used by the
* {@link module:table/tablecellproperties/ui/tablecellpropertiesview~TableCellPropertiesView#backgroundInput}.
* @param {module:table/tablecellproperties~TableCellPropertiesOptions} options.defaultTableCellProperties The default
* table cell properties.
*/
constructor( locale, options ) {
super( locale );
this.set( {
/**
* The value of the cell border style.
*
* @observable
* @default ''
* @member #borderStyle
*/
borderStyle: '',
/**
* The value of the cell border width style.
*
* @observable
* @default ''
* @member #borderWidth
*/
borderWidth: '',
/**
* The value of the cell border color style.
*
* @observable
* @default ''
* @member #borderColor
*/
borderColor: '',
/**
* The value of the cell padding style.
*
* @observable
* @default ''
* @member #padding
*/
padding: '',
/**
* The value of the cell background color style.
*
* @observable
* @default ''
* @member #backgroundColor
*/
backgroundColor: '',
/**
* The value of the table cell width style.
*
* @observable
* @default ''
* @member #width
*/
width: '',
/**
* The value of the table cell height style.
*
* @observable
* @default ''
* @member #height
*/
height: '',
/**
* The value of the horizontal text alignment style.
*
* @observable
* @default ''
* @member #horizontalAlignment
*/
horizontalAlignment: '',
/**
* The value of the vertical text alignment style.
*
* @observable
* @default ''
* @member #verticalAlignment
*/
verticalAlignment: ''
} );
/**
* Options passed to the view. See {@link #constructor} to learn more.
*
* @member {Object}
*/
this.options = options;
const { borderStyleDropdown, borderWidthInput, borderColorInput, borderRowLabel } = this._createBorderFields();
const { backgroundRowLabel, backgroundInput } = this._createBackgroundFields();
const { widthInput, operatorLabel, heightInput, dimensionsLabel } = this._createDimensionFields();
const { horizontalAlignmentToolbar, verticalAlignmentToolbar, alignmentLabel } = this._createAlignmentFields();
/**
* Tracks information about the DOM focus in the form.
*
* @readonly
* @member {module:utils/focustracker~FocusTracker}
*/
this.focusTracker = new ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__.FocusTracker();
/**
* An instance of the {@link module:utils/keystrokehandler~KeystrokeHandler}.
*
* @readonly
* @member {module:utils/keystrokehandler~KeystrokeHandler}
*/
this.keystrokes = new ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__.KeystrokeHandler();
/**
* A collection of child views in the form.
*
* @readonly
* @type {module:ui/viewcollection~ViewCollection}
*/
this.children = this.createCollection();
/**
* A dropdown that allows selecting the style of the table cell border.
*
* @readonly
* @member {module:ui/dropdown/dropdownview~DropdownView}
*/
this.borderStyleDropdown = borderStyleDropdown;
/**
* An input that allows specifying the width of the table cell border.
*
* @readonly
* @member {module:ui/inputtext/inputtextview~InputTextView}
*/
this.borderWidthInput = borderWidthInput;
/**
* An input that allows specifying the color of the table cell border.
*
* @readonly
* @member {module:table/ui/colorinputview~ColorInputView}
*/
this.borderColorInput = borderColorInput;
/**
* An input that allows specifying the table cell background color.
*
* @readonly
* @member {module:table/ui/colorinputview~ColorInputView}
*/
this.backgroundInput = backgroundInput;
/**
* An input that allows specifying the table cell padding.
*
* @readonly
* @member {module:ui/inputtext/inputtextview~InputTextView}
*/
this.paddingInput = this._createPaddingField();
/**
* An input that allows specifying the table cell width.
*
* @readonly
* @member {module:ui/inputtext/inputtextview~InputTextView}
*/
this.widthInput = widthInput;
/**
* An input that allows specifying the table cell height.
*
* @readonly
* @member {module:ui/inputtext/inputtextview~InputTextView}
*/
this.heightInput = heightInput;
/**
* A toolbar with buttons that allow changing the horizontal text alignment in a table cell.
*
* @readonly
* @member {module:ui/toolbar/toolbar~ToolbarView}
*/
this.horizontalAlignmentToolbar = horizontalAlignmentToolbar;
/**
* A toolbar with buttons that allow changing the vertical text alignment in a table cell.
*
* @readonly
* @member {module:ui/toolbar/toolbar~ToolbarView}
*/
this.verticalAlignmentToolbar = verticalAlignmentToolbar;
// Defer creating to make sure other fields are present and the Save button can
// bind its #isEnabled to their error messages so there's no way to save unless all
// fields are valid.
const { saveButtonView, cancelButtonView } = this._createActionButtons();
/**
* The "Save" button view.
*
* @member {module:ui/button/buttonview~ButtonView}
*/
this.saveButtonView = saveButtonView;
/**
* The "Cancel" button view.
*
* @member {module:ui/button/buttonview~ButtonView}
*/
this.cancelButtonView = cancelButtonView;
/**
* A collection of views that can be focused in the form.
*
* @readonly
* @protected
* @member {module:ui/viewcollection~ViewCollection}
*/
this._focusables = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.ViewCollection();
/**
* Helps cycling over {@link #_focusables} in the form.
*
* @readonly
* @protected
* @member {module:ui/focuscycler~FocusCycler}
*/
this._focusCycler = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.FocusCycler( {
focusables: this._focusables,
focusTracker: this.focusTracker,
keystrokeHandler: this.keystrokes,
actions: {
// Navigate form fields backwards using the Shift + Tab keystroke.
focusPrevious: 'shift + tab',
// Navigate form fields forwards using the Tab key.
focusNext: 'tab'
}
} );
// Form header.
this.children.add( new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.FormHeaderView( locale, {
label: this.t( 'Cell properties' )
} ) );
// Border row.
this.children.add( new _ui_formrowview__WEBPACK_IMPORTED_MODULE_4__["default"]( locale, {
labelView: borderRowLabel,
children: [
borderRowLabel,
borderStyleDropdown,
borderColorInput,
borderWidthInput
],
class: 'ck-table-form__border-row'
} ) );
// Background.
this.children.add( new _ui_formrowview__WEBPACK_IMPORTED_MODULE_4__["default"]( locale, {
labelView: backgroundRowLabel,
children: [
backgroundRowLabel,
backgroundInput
],
class: 'ck-table-form__background-row'
} ) );
// Dimensions row and padding.
this.children.add( new _ui_formrowview__WEBPACK_IMPORTED_MODULE_4__["default"]( locale, {
children: [
// Dimensions row.
new _ui_formrowview__WEBPACK_IMPORTED_MODULE_4__["default"]( locale, {
labelView: dimensionsLabel,
children: [
dimensionsLabel,
widthInput,
operatorLabel,
heightInput
],
class: 'ck-table-form__dimensions-row'
} ),
// Padding row.
new _ui_formrowview__WEBPACK_IMPORTED_MODULE_4__["default"]( locale, {
children: [
this.paddingInput
],
class: 'ck-table-cell-properties-form__padding-row'
} )
]
} ) );
// Text alignment row.
this.children.add( new _ui_formrowview__WEBPACK_IMPORTED_MODULE_4__["default"]( locale, {
labelView: alignmentLabel,
children: [
alignmentLabel,
horizontalAlignmentToolbar,
verticalAlignmentToolbar
],
class: 'ck-table-cell-properties-form__alignment-row'
} ) );
// Action row.
this.children.add( new _ui_formrowview__WEBPACK_IMPORTED_MODULE_4__["default"]( locale, {
children: [
this.saveButtonView,
this.cancelButtonView
],
class: 'ck-table-form__action-row'
} ) );
this.setTemplate( {
tag: 'form',
attributes: {
class: [
'ck',
'ck-form',
'ck-table-form',
'ck-table-cell-properties-form'
],
// https://github.com/ckeditor/ckeditor5-link/issues/90
tabindex: '-1'
},
children: this.children
} );
}
/**
* @inheritDoc
*/
render() {
super.render();
// Enable the "submit" event for this view. It can be triggered by the #saveButtonView
// which is of the "submit" DOM "type".
(0,ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.submitHandler)( {
view: this
} );
[
this.borderStyleDropdown,
this.borderColorInput,
this.borderWidthInput,
this.backgroundInput,
this.widthInput,
this.heightInput,
this.paddingInput,
this.horizontalAlignmentToolbar,
this.verticalAlignmentToolbar,
this.saveButtonView,
this.cancelButtonView
].forEach( view => {
// Register the view as focusable.
this._focusables.add( view );
// Register the view in the focus tracker.
this.focusTracker.add( view.element );
} );
// Mainly for closing using "Esc" and navigation using "Tab".
this.keystrokes.listenTo( this.element );
}
/**
* @inheritDoc
*/
destroy() {
super.destroy();
this.focusTracker.destroy();
this.keystrokes.destroy();
}
/**
* Focuses the fist focusable field in the form.
*/
focus() {
this._focusCycler.focusFirst();
}
/**
* Creates the following form fields:
*
* * {@link #borderStyleDropdown},
* * {@link #borderWidthInput},
* * {@link #borderColorInput}.
*
* @private
* @returns {Object.<String,module:ui/view~View>}
*/
_createBorderFields() {
const defaultTableCellProperties = this.options.defaultTableCellProperties;
const defaultBorder = {
style: defaultTableCellProperties.borderStyle,
width: defaultTableCellProperties.borderWidth,
color: defaultTableCellProperties.borderColor
};
const colorInputCreator = (0,_utils_ui_table_properties__WEBPACK_IMPORTED_MODULE_3__.getLabeledColorInputCreator)( {
colorConfig: this.options.borderColors,
columns: 5,
defaultColorValue: defaultBorder.color
} );
const locale = this.locale;
const t = this.t;
// -- Group label ---------------------------------------------
const borderRowLabel = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.LabelView( locale );
borderRowLabel.text = t( 'Border' );
// -- Style ---------------------------------------------------
const styleLabels = (0,_utils_ui_table_properties__WEBPACK_IMPORTED_MODULE_3__.getBorderStyleLabels)( t );
const borderStyleDropdown = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.LabeledFieldView( locale, ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.createLabeledDropdown );
borderStyleDropdown.set( {
label: t( 'Style' ),
class: 'ck-table-form__border-style'
} );
borderStyleDropdown.fieldView.buttonView.set( {
isOn: false,
withText: true,
tooltip: t( 'Style' )
} );
borderStyleDropdown.fieldView.buttonView.bind( 'label' ).to( this, 'borderStyle', value => {
return styleLabels[ value ? value : 'none' ];
} );
borderStyleDropdown.fieldView.on( 'execute', evt => {
this.borderStyle = evt.source._borderStyleValue;
} );
borderStyleDropdown.bind( 'isEmpty' ).to( this, 'borderStyle', value => !value );
(0,ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.addListToDropdown)( borderStyleDropdown.fieldView, (0,_utils_ui_table_properties__WEBPACK_IMPORTED_MODULE_3__.getBorderStyleDefinitions)( this, defaultBorder.style ) );
// -- Width ---------------------------------------------------
const borderWidthInput = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.LabeledFieldView( locale, ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.createLabeledInputText );
borderWidthInput.set( {
label: t( 'Width' ),
class: 'ck-table-form__border-width'
} );
borderWidthInput.fieldView.bind( 'value' ).to( this, 'borderWidth' );
borderWidthInput.bind( 'isEnabled' ).to( this, 'borderStyle', isBorderStyleSet );
borderWidthInput.fieldView.on( 'input', () => {
this.borderWidth = borderWidthInput.fieldView.element.value;
} );
// -- Color ---------------------------------------------------
const borderColorInput = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.LabeledFieldView( locale, colorInputCreator );
borderColorInput.set( {
label: t( 'Color' ),
class: 'ck-table-form__border-color'
} );
borderColorInput.fieldView.bind( 'value' ).to( this, 'borderColor' );
borderColorInput.bind( 'isEnabled' ).to( this, 'borderStyle', isBorderStyleSet );
borderColorInput.fieldView.on( 'input', () => {
this.borderColor = borderColorInput.fieldView.value;
} );
// Reset the border color and width fields depending on the `border-style` value.
this.on( 'change:borderStyle', ( evt, name, newValue, oldValue ) => {
// When removing the border (`border-style:none`), clear the remaining `border-*` properties.
// See: https://github.com/ckeditor/ckeditor5/issues/6227.
if ( !isBorderStyleSet( newValue ) ) {
this.borderColor = '';
this.borderWidth = '';
}
// When setting the `border-style` from `none`, set the default `border-color` and `border-width` properties.
if ( !isBorderStyleSet( oldValue ) ) {
this.borderColor = defaultBorder.color;
this.borderWidth = defaultBorder.width;
}
} );
return {
borderRowLabel,
borderStyleDropdown,
borderColorInput,
borderWidthInput
};
}
/**
* Creates the following form fields:
*
* * {@link #backgroundInput}.
*
* @private
* @returns {Object.<String,module:ui/view~View>}
*/
_createBackgroundFields() {
const locale = this.locale;
const t = this.t;
// -- Group label ---------------------------------------------
const backgroundRowLabel = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.LabelView( locale );
backgroundRowLabel.text = t( 'Background' );
// -- Background color input -----------------------------------
const colorInputCreator = (0,_utils_ui_table_properties__WEBPACK_IMPORTED_MODULE_3__.getLabeledColorInputCreator)( {
colorConfig: this.options.backgroundColors,
columns: 5,
defaultColorValue: this.options.defaultTableCellProperties.backgroundColor
} );
const backgroundInput = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.LabeledFieldView( locale, colorInputCreator );
backgroundInput.set( {
label: t( 'Color' ),
class: 'ck-table-cell-properties-form__background'
} );
backgroundInput.fieldView.bind( 'value' ).to( this, 'backgroundColor' );
backgroundInput.fieldView.on( 'input', () => {
this.backgroundColor = backgroundInput.fieldView.value;
} );
return {
backgroundRowLabel,
backgroundInput
};
}
/**
* Creates the following form fields:
*
* * {@link #widthInput}.
* * {@link #heightInput}.
*
* @private
* @returns {module:ui/labeledfield/labeledfieldview~LabeledFieldView}
*/
_createDimensionFields() {
const locale = this.locale;
const t = this.t;
// -- Label ---------------------------------------------------
const dimensionsLabel = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.LabelView( locale );
dimensionsLabel.text = t( 'Dimensions' );
// -- Width ---------------------------------------------------
const widthInput = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.LabeledFieldView( locale, ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.createLabeledInputText );
widthInput.set( {
label: t( 'Width' ),
class: 'ck-table-form__dimensions-row__width'
} );
widthInput.fieldView.bind( 'value' ).to( this, 'width' );
widthInput.fieldView.on( 'input', () => {
this.width = widthInput.fieldView.element.value;
} );
// -- Operator ---------------------------------------------------
const operatorLabel = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.View( locale );
operatorLabel.setTemplate( {
tag: 'span',
attributes: {
class: [
'ck-table-form__dimension-operator'
]
},
children: [
{ text: '×' }
]
} );
// -- Height ---------------------------------------------------
const heightInput = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.LabeledFieldView( locale, ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.createLabeledInputText );
heightInput.set( {
label: t( 'Height' ),
class: 'ck-table-form__dimensions-row__height'
} );
heightInput.fieldView.bind( 'value' ).to( this, 'height' );
heightInput.fieldView.on( 'input', () => {
this.height = heightInput.fieldView.element.value;
} );
return {
dimensionsLabel,
widthInput,
operatorLabel,
heightInput
};
}
/**
* Creates the following form fields:
*
* * {@link #paddingInput}.
*
* @private
* @returns {module:ui/labeledfield/labeledfieldview~LabeledFieldView}
*/
_createPaddingField() {
const locale = this.locale;
const t = this.t;
const paddingInput = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.LabeledFieldView( locale, ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.createLabeledInputText );
paddingInput.set( {
label: t( 'Padding' ),
class: 'ck-table-cell-properties-form__padding'
} );
paddingInput.fieldView.bind( 'value' ).to( this, 'padding' );
paddingInput.fieldView.on( 'input', () => {
this.padding = paddingInput.fieldView.element.value;
} );
return paddingInput;
}
/**
* Creates the following form fields:
*
* * {@link #horizontalAlignmentToolbar},
* * {@link #verticalAlignmentToolbar}.
*
* @private
* @returns {Object.<String,module:ui/view~View>}
*/
_createAlignmentFields() {
const locale = this.locale;
const t = this.t;
const alignmentLabel = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.LabelView( locale );
alignmentLabel.text = t( 'Table cell text alignment' );
// -- Horizontal ---------------------------------------------------
const horizontalAlignmentToolbar = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.ToolbarView( locale );
const isContentRTL = this.locale.contentLanguageDirection === 'rtl';
horizontalAlignmentToolbar.set( {
isCompact: true,
ariaLabel: t( 'Horizontal text alignment toolbar' )
} );
(0,_utils_ui_table_properties__WEBPACK_IMPORTED_MODULE_3__.fillToolbar)( {
view: this,
icons: ALIGNMENT_ICONS,
toolbar: horizontalAlignmentToolbar,
labels: this._horizontalAlignmentLabels,
propertyName: 'horizontalAlignment',
nameToValue: name => {
// For the RTL content, we want to swap the buttons "align to the left" and "align to the right".
if ( isContentRTL ) {
if ( name === 'left' ) {
return 'right';
} else if ( name === 'right' ) {
return 'left';
}
}
return name;
},
defaultValue: this.options.defaultTableCellProperties.horizontalAlignment
} );
// -- Vertical -----------------------------------------------------
const verticalAlignmentToolbar = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.ToolbarView( locale );
verticalAlignmentToolbar.set( {
isCompact: true,
ariaLabel: t( 'Vertical text alignment toolbar' )
} );
(0,_utils_ui_table_properties__WEBPACK_IMPORTED_MODULE_3__.fillToolbar)( {
view: this,
icons: ALIGNMENT_ICONS,
toolbar: verticalAlignmentToolbar,
labels: this._verticalAlignmentLabels,
propertyName: 'verticalAlignment',
defaultValue: this.options.defaultTableCellProperties.verticalAlignment
} );
return {
horizontalAlignmentToolbar,
verticalAlignmentToolbar,
alignmentLabel
};
}
/**
* Creates the following form controls:
*
* * {@link #saveButtonView},
* * {@link #cancelButtonView}.
*
* @private
* @returns {Object.<String,module:ui/view~View>}
*/
_createActionButtons() {
const locale = this.locale;
const t = this.t;
const saveButtonView = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.ButtonView( locale );
const cancelButtonView = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.ButtonView( locale );
const fieldsThatShouldValidateToSave = [
this.borderWidthInput,
this.borderColorInput,
this.backgroundInput,
this.paddingInput
];
saveButtonView.set( {
label: t( 'Save' ),
icon: ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_2__.icons.check,
class: 'ck-button-save',
type: 'submit',
withText: true
} );
saveButtonView.bind( 'isEnabled' ).toMany( fieldsThatShouldValidateToSave, 'errorText', ( ...errorTexts ) => {
return errorTexts.every( errorText => !errorText );
} );
cancelButtonView.set( {
label: t( 'Cancel' ),
icon: ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_2__.icons.cancel,
class: 'ck-button-cancel',
withText: true
} );
cancelButtonView.delegate( 'execute' ).to( this, 'cancel' );
return {
saveButtonView, cancelButtonView
};
}
/**
* Provides localized labels for {@link #horizontalAlignmentToolbar} buttons.
*
* @private
* @type {Object.<String,String>}
*/
get _horizontalAlignmentLabels() {
const locale = this.locale;
const t = this.t;
const left = t( 'Align cell text to the left' );
const center = t( 'Align cell text to the center' );
const right = t( 'Align cell text to the right' );
const justify = t( 'Justify cell text' );
// Returns object with a proper order of labels.
if ( locale.uiLanguageDirection === 'rtl' ) {
return { right, center, left, justify };
} else {
return { left, center, right, justify };
}
}
/**
* Provides localized labels for {@link #verticalAlignmentToolbar} buttons.
*
* @private
* @type {Object.<String,String>}
*/
get _verticalAlignmentLabels() {
const t = this.t;
return {
top: t( 'Align cell text to the top' ),
middle: t( 'Align cell text to the middle' ),
bottom: t( 'Align cell text to the bottom' )
};
}
}
function isBorderStyleSet( value ) {
return value !== 'none';
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/tableclipboard.js":
/*!**********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/tableclipboard.js ***!
\**********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ TableClipboard)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var _tableselection__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./tableselection */ "./node_modules/@ckeditor/ckeditor5-table/src/tableselection.js");
/* harmony import */ var _tablewalker__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./tablewalker */ "./node_modules/@ckeditor/ckeditor5-table/src/tablewalker.js");
/* harmony import */ var _tableutils__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./tableutils */ "./node_modules/@ckeditor/ckeditor5-table/src/tableutils.js");
/* harmony import */ var _utils_structure__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./utils/structure */ "./node_modules/@ckeditor/ckeditor5-table/src/utils/structure.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/tableclipboard
*/
/**
* This plugin adds support for copying/cutting/pasting fragments of tables.
* It is loaded automatically by the {@link module:table/table~Table} plugin.
*
* @extends module:core/plugin~Plugin
*/
class TableClipboard extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'TableClipboard';
}
/**
* @inheritDoc
*/
static get requires() {
return [ _tableselection__WEBPACK_IMPORTED_MODULE_1__["default"], _tableutils__WEBPACK_IMPORTED_MODULE_3__["default"] ];
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const viewDocument = editor.editing.view.document;
this.listenTo( viewDocument, 'copy', ( evt, data ) => this._onCopyCut( evt, data ) );
this.listenTo( viewDocument, 'cut', ( evt, data ) => this._onCopyCut( evt, data ) );
this.listenTo( editor.model, 'insertContent', ( evt, args ) => this._onInsertContent( evt, ...args ), { priority: 'high' } );
this.decorate( '_replaceTableSlotCell' );
}
/**
* Copies table content to a clipboard on "copy" & "cut" events.
*
* @private
* @param {module:utils/eventinfo~EventInfo} evt An object containing information about the handled event.
* @param {Object} data Clipboard event data.
*/
_onCopyCut( evt, data ) {
const tableSelection = this.editor.plugins.get( _tableselection__WEBPACK_IMPORTED_MODULE_1__["default"] );
if ( !tableSelection.getSelectedTableCells() ) {
return;
}
if ( evt.name == 'cut' && this.editor.isReadOnly ) {
return;
}
data.preventDefault();
evt.stop();
const dataController = this.editor.data;
const viewDocument = this.editor.editing.view.document;
const content = dataController.toView( tableSelection.getSelectionAsFragment() );
viewDocument.fire( 'clipboardOutput', {
dataTransfer: data.dataTransfer,
content,
method: evt.name
} );
}
/**
* Overrides default {@link module:engine/model/model~Model#insertContent `model.insertContent()`} method to handle pasting table inside
* selected table fragment.
*
* Depending on selected table fragment:
* - If a selected table fragment is smaller than paste table it will crop pasted table to match dimensions.
* - If dimensions are equal it will replace selected table fragment with a pasted table contents.
*
* @private
* @param evt
* @param {module:engine/model/documentfragment~DocumentFragment|module:engine/model/item~Item} content The content to insert.
* @param {module:engine/model/selection~Selectable} [selectable=model.document.selection]
* The selection into which the content should be inserted. If not provided the current model document selection will be used.
*/
_onInsertContent( evt, content, selectable ) {
if ( selectable && !selectable.is( 'documentSelection' ) ) {
return;
}
const model = this.editor.model;
const tableUtils = this.editor.plugins.get( _tableutils__WEBPACK_IMPORTED_MODULE_3__["default"] );
// We might need to crop table before inserting so reference might change.
let pastedTable = getTableIfOnlyTableInContent( content, model );
if ( !pastedTable ) {
return;
}
const selectedTableCells = tableUtils.getSelectionAffectedTableCells( model.document.selection );
if ( !selectedTableCells.length ) {
(0,_utils_structure__WEBPACK_IMPORTED_MODULE_4__.removeEmptyRowsColumns)( pastedTable, tableUtils );
return;
}
// Override default model.insertContent() handling at this point.
evt.stop();
model.change( writer => {
const pastedDimensions = {
width: tableUtils.getColumns( pastedTable ),
height: tableUtils.getRows( pastedTable )
};
// Prepare the table for pasting.
const selection = prepareTableForPasting( selectedTableCells, pastedDimensions, writer, tableUtils );
// Beyond this point we operate on a fixed content table with rectangular selection and proper last row/column values.
const selectionHeight = selection.lastRow - selection.firstRow + 1;
const selectionWidth = selection.lastColumn - selection.firstColumn + 1;
// Crop pasted table if:
// - Pasted table dimensions exceeds selection area.
// - Pasted table has broken layout (ie some cells sticks out by the table dimensions established by the first and last row).
//
// Note: The table dimensions are established by the width of the first row and the total number of rows.
// It is possible to programmatically create a table that has rows which would have cells anchored beyond first row width but
// such table will not be created by other editing solutions.
const cropDimensions = {
startRow: 0,
startColumn: 0,
endRow: Math.min( selectionHeight, pastedDimensions.height ) - 1,
endColumn: Math.min( selectionWidth, pastedDimensions.width ) - 1
};
pastedTable = (0,_utils_structure__WEBPACK_IMPORTED_MODULE_4__.cropTableToDimensions)( pastedTable, cropDimensions, writer );
// Content table to which we insert a pasted table.
const selectedTable = selectedTableCells[ 0 ].findAncestor( 'table' );
const cellsToSelect = this._replaceSelectedCellsWithPasted( pastedTable, pastedDimensions, selectedTable, selection, writer );
if ( this.editor.plugins.get( 'TableSelection' ).isEnabled ) {
// Selection ranges must be sorted because the first and last selection ranges are considered
// as anchor/focus cell ranges for multi-cell selection.
const selectionRanges = tableUtils.sortRanges( cellsToSelect.map( cell => writer.createRangeOn( cell ) ) );
writer.setSelection( selectionRanges );
} else {
// Set selection inside first cell if multi-cell selection is disabled.
writer.setSelection( cellsToSelect[ 0 ], 0 );
}
} );
}
/**
* Replaces the part of selectedTable with pastedTable.
*
* @private
* @param {module:engine/model/element~Element} pastedTable
* @param {Object} pastedDimensions
* @param {Number} pastedDimensions.height
* @param {Number} pastedDimensions.width
* @param {module:engine/model/element~Element} selectedTable
* @param {Object} selection
* @param {Number} selection.firstColumn
* @param {Number} selection.firstRow
* @param {Number} selection.lastColumn
* @param {Number} selection.lastRow
* @param {module:engine/model/writer~Writer} writer
* @returns {Array.<module:engine/model/element~Element>}
*/
_replaceSelectedCellsWithPasted( pastedTable, pastedDimensions, selectedTable, selection, writer ) {
const { width: pastedWidth, height: pastedHeight } = pastedDimensions;
// Holds two-dimensional array that is addressed by [ row ][ column ] that stores cells anchored at given location.
const pastedTableLocationMap = createLocationMap( pastedTable, pastedWidth, pastedHeight );
const selectedTableMap = [ ...new _tablewalker__WEBPACK_IMPORTED_MODULE_2__["default"]( selectedTable, {
startRow: selection.firstRow,
endRow: selection.lastRow,
startColumn: selection.firstColumn,
endColumn: selection.lastColumn,
includeAllSlots: true
} ) ];
// Selection must be set to pasted cells (some might be removed or new created).
const cellsToSelect = [];
// Store next cell insert position.
let insertPosition;
// Content table replace cells algorithm iterates over a selected table fragment and:
//
// - Removes existing table cells at current slot (location).
// - Inserts cell from a pasted table for a matched slots.
//
// This ensures proper table geometry after the paste
for ( const tableSlot of selectedTableMap ) {
const { row, column } = tableSlot;
// Save the insert position for current row start.
if ( column === selection.firstColumn ) {
insertPosition = tableSlot.getPositionBefore();
}
// Map current table slot location to an pasted table slot location.
const pastedRow = row - selection.firstRow;
const pastedColumn = column - selection.firstColumn;
const pastedCell = pastedTableLocationMap[ pastedRow % pastedHeight ][ pastedColumn % pastedWidth ];
// Clone cell to insert (to duplicate its attributes and children).
// Cloning is required to support repeating pasted table content when inserting to a bigger selection.
const cellToInsert = pastedCell ? writer.cloneElement( pastedCell ) : null;
// Replace the cell from the current slot with new table cell.
const newTableCell = this._replaceTableSlotCell( tableSlot, cellToInsert, insertPosition, writer );
// The cell was only removed.
if ( !newTableCell ) {
continue;
}
// Trim the cell if it's row/col-spans would exceed selection area.
(0,_utils_structure__WEBPACK_IMPORTED_MODULE_4__.trimTableCellIfNeeded)( newTableCell, row, column, selection.lastRow, selection.lastColumn, writer );
cellsToSelect.push( newTableCell );
insertPosition = writer.createPositionAfter( newTableCell );
}
// If there are any headings, all the cells that overlap from heading must be splitted.
const headingRows = parseInt( selectedTable.getAttribute( 'headingRows' ) || 0 );
const headingColumns = parseInt( selectedTable.getAttribute( 'headingColumns' ) || 0 );
const areHeadingRowsIntersectingSelection = selection.firstRow < headingRows && headingRows <= selection.lastRow;
const areHeadingColumnsIntersectingSelection = selection.firstColumn < headingColumns && headingColumns <= selection.lastColumn;
if ( areHeadingRowsIntersectingSelection ) {
const columnsLimit = { first: selection.firstColumn, last: selection.lastColumn };
const newCells = doHorizontalSplit( selectedTable, headingRows, columnsLimit, writer, selection.firstRow );
cellsToSelect.push( ...newCells );
}
if ( areHeadingColumnsIntersectingSelection ) {
const rowsLimit = { first: selection.firstRow, last: selection.lastRow };
const newCells = doVerticalSplit( selectedTable, headingColumns, rowsLimit, writer );
cellsToSelect.push( ...newCells );
}
return cellsToSelect;
}
/**
* Replaces a single table slot.
*
* @private
* @param {module:table/tablewalker~TableSlot} tableSlot
* @param {module:engine/model/element~Element} cellToInsert
* @param {module:engine/model/position~Position} insertPosition
* @param {module:engine/model/writer~Writer} writer
* @returns {module:engine/model/element~Element|null} Inserted table cell or null if slot should remain empty.
*/
_replaceTableSlotCell( tableSlot, cellToInsert, insertPosition, writer ) {
const { cell, isAnchor } = tableSlot;
// If the slot is occupied by a cell in a selected table - remove it.
// The slot of this cell will be either:
// - Replaced by a pasted table cell.
// - Spanned by a previously pasted table cell.
if ( isAnchor ) {
writer.remove( cell );
}
// There is no cell to insert (might be spanned by other cell in a pasted table) - advance to the next content table slot.
if ( !cellToInsert ) {
return null;
}
writer.insert( cellToInsert, insertPosition );
return cellToInsert;
}
/**
* Extracts the table for pasting into a table.
*
* @protected
* @param {module:engine/model/documentfragment~DocumentFragment|module:engine/model/item~Item} content The content to insert.
* @param {module:engine/model/model~Model} model The editor model.
* @returns {module:engine/model/element~Element|null}
*/
getTableIfOnlyTableInContent( content, model ) {
return getTableIfOnlyTableInContent( content, model );
}
}
function getTableIfOnlyTableInContent( content, model ) {
if ( !content.is( 'documentFragment' ) && !content.is( 'element' ) ) {
return null;
}
// Table passed directly.
if ( content.is( 'element', 'table' ) ) {
return content;
}
// We do not support mixed content when pasting table into table.
// See: https://github.com/ckeditor/ckeditor5/issues/6817.
if ( content.childCount == 1 && content.getChild( 0 ).is( 'element', 'table' ) ) {
return content.getChild( 0 );
}
// If there are only whitespaces around a table then use that table for pasting.
const contentRange = model.createRangeIn( content );
for ( const element of contentRange.getItems() ) {
if ( element.is( 'element', 'table' ) ) {
// Stop checking if there is some content before table.
const rangeBefore = model.createRange( contentRange.start, model.createPositionBefore( element ) );
if ( model.hasContent( rangeBefore, { ignoreWhitespaces: true } ) ) {
return null;
}
// Stop checking if there is some content after table.
const rangeAfter = model.createRange( model.createPositionAfter( element ), contentRange.end );
if ( model.hasContent( rangeAfter, { ignoreWhitespaces: true } ) ) {
return null;
}
// There wasn't any content neither before nor after.
return element;
}
}
return null;
}
// Prepares a table for pasting and returns adjusted selection dimensions.
//
// @param {Array.<module:engine/model/element~Element>} selectedTableCells
// @param {Object} pastedDimensions
// @param {Number} pastedDimensions.height
// @param {Number} pastedDimensions.width
// @param {module:engine/model/writer~Writer} writer
// @param {module:table/tableutils~TableUtils} tableUtils
// @returns {Object} selection
// @returns {Number} selection.firstColumn
// @returns {Number} selection.firstRow
// @returns {Number} selection.lastColumn
// @returns {Number} selection.lastRow
function prepareTableForPasting( selectedTableCells, pastedDimensions, writer, tableUtils ) {
const selectedTable = selectedTableCells[ 0 ].findAncestor( 'table' );
const columnIndexes = tableUtils.getColumnIndexes( selectedTableCells );
const rowIndexes = tableUtils.getRowIndexes( selectedTableCells );
const selection = {
firstColumn: columnIndexes.first,
lastColumn: columnIndexes.last,
firstRow: rowIndexes.first,
lastRow: rowIndexes.last
};
// Single cell selected - expand selection to pasted table dimensions.
const shouldExpandSelection = selectedTableCells.length === 1;
if ( shouldExpandSelection ) {
selection.lastRow += pastedDimensions.height - 1;
selection.lastColumn += pastedDimensions.width - 1;
expandTableSize( selectedTable, selection.lastRow + 1, selection.lastColumn + 1, tableUtils );
}
// In case of expanding selection we do not reset the selection so in this case we will always try to fix selection
// like in the case of a non-rectangular area. This might be fixed by re-setting selected cells array but this shortcut is safe.
if ( shouldExpandSelection || !tableUtils.isSelectionRectangular( selectedTableCells ) ) {
// For a non-rectangular selection (ie in which some cells sticks out from a virtual selection rectangle) we need to create
// a table layout that has a rectangular selection. This will split cells so the selection become rectangular.
// Beyond this point we will operate on fixed content table.
splitCellsToRectangularSelection( selectedTable, selection, writer );
}
// However a selected table fragment might be invalid if examined alone. Ie such table fragment:
//
// +---+---+---+---+
// 0 | a | b | c | d |
// + + +---+---+
// 1 | | e | f | g |
// + +---+ +---+
// 2 | | h | | i | <- last row, each cell has rowspan = 2,
// + + + + + so we need to return 3, not 2
// 3 | | | | |
// +---+---+---+---+
//
// is invalid as the cells "h" and "i" have rowspans.
// This case needs only adjusting the selection dimension as the rest of the algorithm operates on empty slots also.
else {
selection.lastRow = (0,_utils_structure__WEBPACK_IMPORTED_MODULE_4__.adjustLastRowIndex)( selectedTable, selection );
selection.lastColumn = (0,_utils_structure__WEBPACK_IMPORTED_MODULE_4__.adjustLastColumnIndex)( selectedTable, selection );
}
return selection;
}
// Expand table (in place) to expected size.
function expandTableSize( table, expectedHeight, expectedWidth, tableUtils ) {
const tableWidth = tableUtils.getColumns( table );
const tableHeight = tableUtils.getRows( table );
if ( expectedWidth > tableWidth ) {
tableUtils.insertColumns( table, {
at: tableWidth,
columns: expectedWidth - tableWidth
} );
}
if ( expectedHeight > tableHeight ) {
tableUtils.insertRows( table, {
at: tableHeight,
rows: expectedHeight - tableHeight
} );
}
}
// Returns two-dimensional array that is addressed by [ row ][ column ] that stores cells anchored at given location.
//
// At given row & column location it might be one of:
//
// * cell - cell from pasted table anchored at this location.
// * null - if no cell is anchored at this location.
//
// For instance, from a table below:
//
// +----+----+----+----+
// | 00 | 01 | 02 | 03 |
// + +----+----+----+
// | | 11 | 13 |
// +----+ +----+
// | 20 | | 23 |
// +----+----+----+----+
//
// The method will return an array (numbers represents cell element):
//
// const map = [
// [ '00', '01', '02', '03' ],
// [ null, '11', null, '13' ],
// [ '20', null, null, '23' ]
// ]
//
// This allows for a quick access to table at give row & column. For instance to access table cell "13" from pasted table call:
//
// const cell = map[ 1 ][ 3 ]
//
function createLocationMap( table, width, height ) {
// Create height x width (row x column) two-dimensional table to store cells.
const map = new Array( height ).fill( null )
.map( () => new Array( width ).fill( null ) );
for ( const { column, row, cell } of new _tablewalker__WEBPACK_IMPORTED_MODULE_2__["default"]( table ) ) {
map[ row ][ column ] = cell;
}
return map;
}
// Make selected cells rectangular by splitting the cells that stand out from a rectangular selection.
//
// In the table below a selection is shown with "::" and slots with anchor cells are named.
//
// +----+----+----+----+----+ +----+----+----+----+----+
// | 00 | 01 | 02 | 03 | | 00 | 01 | 02 | 03 |
// + +----+ +----+----+ | ::::::::::::::::----+
// | | 11 | | 13 | 14 | | ::11 | | 13:: 14 | <- first row
// +----+----+ + +----+ +----::---| | ::----+
// | 20 | 21 | | | 24 | select cells: | 20 ::21 | | :: 24 |
// +----+----+ +----+----+ 11 -> 33 +----::---| |---::----+
// | 30 | | 33 | 34 | | 30 :: | | 33:: 34 | <- last row
// + + +----+ + | :::::::::::::::: +
// | | | 43 | | | | | 43 | |
// +----+----+----+----+----+ +----+----+----+----+----+
// ^ ^
// first & last columns
//
// Will update table to:
//
// +----+----+----+----+----+
// | 00 | 01 | 02 | 03 |
// + +----+----+----+----+
// | | 11 | | 13 | 14 |
// +----+----+ + +----+
// | 20 | 21 | | | 24 |
// +----+----+ +----+----+
// | 30 | | | 33 | 34 |
// + +----+----+----+ +
// | | | | 43 | |
// +----+----+----+----+----+
//
// In th example above:
// - Cell "02" which have `rowspan = 4` must be trimmed at first and at after last row.
// - Cell "03" which have `rowspan = 2` and `colspan = 2` must be trimmed at first column and after last row.
// - Cells "00", "03" & "30" which cannot be cut by this algorithm as they are outside the trimmed area.
// - Cell "13" cannot be cut as it is inside the trimmed area.
function splitCellsToRectangularSelection( table, dimensions, writer ) {
const { firstRow, lastRow, firstColumn, lastColumn } = dimensions;
const rowIndexes = { first: firstRow, last: lastRow };
const columnIndexes = { first: firstColumn, last: lastColumn };
// 1. Split cells vertically in two steps as first step might create cells that needs to split again.
doVerticalSplit( table, firstColumn, rowIndexes, writer );
doVerticalSplit( table, lastColumn + 1, rowIndexes, writer );
// 2. Split cells horizontally in two steps as first step might create cells that needs to split again.
doHorizontalSplit( table, firstRow, columnIndexes, writer );
doHorizontalSplit( table, lastRow + 1, columnIndexes, writer, firstRow );
}
function doHorizontalSplit( table, splitRow, limitColumns, writer, startRow = 0 ) {
// If selection starts at first row then no split is needed.
if ( splitRow < 1 ) {
return;
}
const overlappingCells = (0,_utils_structure__WEBPACK_IMPORTED_MODULE_4__.getVerticallyOverlappingCells)( table, splitRow, startRow );
// Filter out cells that are not touching insides of the rectangular selection.
const cellsToSplit = overlappingCells.filter( ( { column, cellWidth } ) => isAffectedBySelection( column, cellWidth, limitColumns ) );
return cellsToSplit.map( ( { cell } ) => (0,_utils_structure__WEBPACK_IMPORTED_MODULE_4__.splitHorizontally)( cell, splitRow, writer ) );
}
function doVerticalSplit( table, splitColumn, limitRows, writer ) {
// If selection starts at first column then no split is needed.
if ( splitColumn < 1 ) {
return;
}
const overlappingCells = (0,_utils_structure__WEBPACK_IMPORTED_MODULE_4__.getHorizontallyOverlappingCells)( table, splitColumn );
// Filter out cells that are not touching insides of the rectangular selection.
const cellsToSplit = overlappingCells.filter( ( { row, cellHeight } ) => isAffectedBySelection( row, cellHeight, limitRows ) );
return cellsToSplit.map( ( { cell, column } ) => (0,_utils_structure__WEBPACK_IMPORTED_MODULE_4__.splitVertically)( cell, column, splitColumn, writer ) );
}
// Checks if cell at given row (column) is affected by a rectangular selection defined by first/last column (row).
//
// The same check is used for row as for column.
function isAffectedBySelection( index, span, limit ) {
const endIndex = index + span - 1;
const { first, last } = limit;
const isInsideSelection = index >= first && index <= last;
const overlapsSelectionFromOutside = index < first && endIndex >= first;
return isInsideSelection || overlapsSelectionFromOutside;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/tableediting.js":
/*!********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/tableediting.js ***!
\********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ TableEditing)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var _converters_upcasttable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./converters/upcasttable */ "./node_modules/@ckeditor/ckeditor5-table/src/converters/upcasttable.js");
/* harmony import */ var _converters_downcast__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./converters/downcast */ "./node_modules/@ckeditor/ckeditor5-table/src/converters/downcast.js");
/* harmony import */ var _commands_inserttablecommand__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./commands/inserttablecommand */ "./node_modules/@ckeditor/ckeditor5-table/src/commands/inserttablecommand.js");
/* harmony import */ var _commands_insertrowcommand__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./commands/insertrowcommand */ "./node_modules/@ckeditor/ckeditor5-table/src/commands/insertrowcommand.js");
/* harmony import */ var _commands_insertcolumncommand__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./commands/insertcolumncommand */ "./node_modules/@ckeditor/ckeditor5-table/src/commands/insertcolumncommand.js");
/* harmony import */ var _commands_splitcellcommand__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./commands/splitcellcommand */ "./node_modules/@ckeditor/ckeditor5-table/src/commands/splitcellcommand.js");
/* harmony import */ var _commands_mergecellcommand__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./commands/mergecellcommand */ "./node_modules/@ckeditor/ckeditor5-table/src/commands/mergecellcommand.js");
/* harmony import */ var _commands_removerowcommand__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./commands/removerowcommand */ "./node_modules/@ckeditor/ckeditor5-table/src/commands/removerowcommand.js");
/* harmony import */ var _commands_removecolumncommand__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./commands/removecolumncommand */ "./node_modules/@ckeditor/ckeditor5-table/src/commands/removecolumncommand.js");
/* harmony import */ var _commands_setheaderrowcommand__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./commands/setheaderrowcommand */ "./node_modules/@ckeditor/ckeditor5-table/src/commands/setheaderrowcommand.js");
/* harmony import */ var _commands_setheadercolumncommand__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ./commands/setheadercolumncommand */ "./node_modules/@ckeditor/ckeditor5-table/src/commands/setheadercolumncommand.js");
/* harmony import */ var _commands_mergecellscommand__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! ./commands/mergecellscommand */ "./node_modules/@ckeditor/ckeditor5-table/src/commands/mergecellscommand.js");
/* harmony import */ var _commands_selectrowcommand__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(/*! ./commands/selectrowcommand */ "./node_modules/@ckeditor/ckeditor5-table/src/commands/selectrowcommand.js");
/* harmony import */ var _commands_selectcolumncommand__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(/*! ./commands/selectcolumncommand */ "./node_modules/@ckeditor/ckeditor5-table/src/commands/selectcolumncommand.js");
/* harmony import */ var _src_tableutils__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(/*! ../src/tableutils */ "./node_modules/@ckeditor/ckeditor5-table/src/tableutils.js");
/* harmony import */ var _converters_table_layout_post_fixer__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(/*! ./converters/table-layout-post-fixer */ "./node_modules/@ckeditor/ckeditor5-table/src/converters/table-layout-post-fixer.js");
/* harmony import */ var _converters_table_cell_paragraph_post_fixer__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(/*! ./converters/table-cell-paragraph-post-fixer */ "./node_modules/@ckeditor/ckeditor5-table/src/converters/table-cell-paragraph-post-fixer.js");
/* harmony import */ var _converters_table_headings_refresh_handler__WEBPACK_IMPORTED_MODULE_18__ = __webpack_require__(/*! ./converters/table-headings-refresh-handler */ "./node_modules/@ckeditor/ckeditor5-table/src/converters/table-headings-refresh-handler.js");
/* harmony import */ var _converters_table_cell_refresh_handler__WEBPACK_IMPORTED_MODULE_19__ = __webpack_require__(/*! ./converters/table-cell-refresh-handler */ "./node_modules/@ckeditor/ckeditor5-table/src/converters/table-cell-refresh-handler.js");
/* harmony import */ var _theme_tableediting_css__WEBPACK_IMPORTED_MODULE_20__ = __webpack_require__(/*! ../theme/tableediting.css */ "./node_modules/@ckeditor/ckeditor5-table/theme/tableediting.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/tableediting
*/
/**
* The table editing feature.
*
* @extends module:core/plugin~Plugin
*/
class TableEditing extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'TableEditing';
}
/**
* @inheritDoc
*/
static get requires() {
return [ _src_tableutils__WEBPACK_IMPORTED_MODULE_15__["default"] ];
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const model = editor.model;
const schema = model.schema;
const conversion = editor.conversion;
const tableUtils = editor.plugins.get( _src_tableutils__WEBPACK_IMPORTED_MODULE_15__["default"] );
schema.register( 'table', {
allowWhere: '$block',
allowAttributes: [ 'headingRows', 'headingColumns' ],
isObject: true,
isBlock: true
} );
schema.register( 'tableRow', {
allowIn: 'table',
isLimit: true
} );
schema.register( 'tableCell', {
allowIn: 'tableRow',
allowChildren: '$block',
allowAttributes: [ 'colspan', 'rowspan' ],
isLimit: true,
isSelectable: true
} );
// Figure conversion.
conversion.for( 'upcast' ).add( (0,_converters_upcasttable__WEBPACK_IMPORTED_MODULE_1__.upcastTableFigure)() );
// Table conversion.
conversion.for( 'upcast' ).add( (0,_converters_upcasttable__WEBPACK_IMPORTED_MODULE_1__["default"])() );
conversion.for( 'editingDowncast' ).elementToStructure( {
model: {
name: 'table',
attributes: [ 'headingRows' ]
},
view: (0,_converters_downcast__WEBPACK_IMPORTED_MODULE_2__.downcastTable)( tableUtils, { asWidget: true } )
} );
conversion.for( 'dataDowncast' ).elementToStructure( {
model: {
name: 'table',
attributes: [ 'headingRows' ]
},
view: (0,_converters_downcast__WEBPACK_IMPORTED_MODULE_2__.downcastTable)( tableUtils )
} );
// Table row conversion.
conversion.for( 'upcast' ).elementToElement( { model: 'tableRow', view: 'tr' } );
conversion.for( 'upcast' ).add( (0,_converters_upcasttable__WEBPACK_IMPORTED_MODULE_1__.skipEmptyTableRow)() );
conversion.for( 'downcast' ).elementToElement( {
model: 'tableRow',
view: (0,_converters_downcast__WEBPACK_IMPORTED_MODULE_2__.downcastRow)()
} );
// Table cell conversion.
conversion.for( 'upcast' ).elementToElement( { model: 'tableCell', view: 'td' } );
conversion.for( 'upcast' ).elementToElement( { model: 'tableCell', view: 'th' } );
conversion.for( 'upcast' ).add( (0,_converters_upcasttable__WEBPACK_IMPORTED_MODULE_1__.ensureParagraphInTableCell)( 'td' ) );
conversion.for( 'upcast' ).add( (0,_converters_upcasttable__WEBPACK_IMPORTED_MODULE_1__.ensureParagraphInTableCell)( 'th' ) );
conversion.for( 'editingDowncast' ).elementToElement( {
model: 'tableCell',
view: (0,_converters_downcast__WEBPACK_IMPORTED_MODULE_2__.downcastCell)( { asWidget: true } )
} );
conversion.for( 'dataDowncast' ).elementToElement( {
model: 'tableCell',
view: (0,_converters_downcast__WEBPACK_IMPORTED_MODULE_2__.downcastCell)()
} );
// Duplicates code - needed to properly refresh paragraph inside a table cell.
conversion.for( 'editingDowncast' ).elementToElement( {
model: 'paragraph',
view: (0,_converters_downcast__WEBPACK_IMPORTED_MODULE_2__.convertParagraphInTableCell)( { asWidget: true } ),
converterPriority: 'high'
} );
conversion.for( 'dataDowncast' ).elementToElement( {
model: 'paragraph',
view: (0,_converters_downcast__WEBPACK_IMPORTED_MODULE_2__.convertParagraphInTableCell)(),
converterPriority: 'high'
} );
// Table attributes conversion.
conversion.for( 'downcast' ).attributeToAttribute( { model: 'colspan', view: 'colspan' } );
conversion.for( 'upcast' ).attributeToAttribute( {
model: { key: 'colspan', value: upcastCellSpan( 'colspan' ) },
view: 'colspan'
} );
conversion.for( 'downcast' ).attributeToAttribute( { model: 'rowspan', view: 'rowspan' } );
conversion.for( 'upcast' ).attributeToAttribute( {
model: { key: 'rowspan', value: upcastCellSpan( 'rowspan' ) },
view: 'rowspan'
} );
// Manually adjust model position mappings in a special case, when a table cell contains a paragraph, which is bound
// to its parent (to the table cell). This custom model-to-view position mapping is necessary in data pipeline only,
// because only during this conversion a paragraph can be bound to its parent.
editor.data.mapper.on( 'modelToViewPosition', mapTableCellModelPositionToView() );
// Define the config.
editor.config.define( 'table.defaultHeadings.rows', 0 );
editor.config.define( 'table.defaultHeadings.columns', 0 );
// Define all the commands.
editor.commands.add( 'insertTable', new _commands_inserttablecommand__WEBPACK_IMPORTED_MODULE_3__["default"]( editor ) );
editor.commands.add( 'insertTableRowAbove', new _commands_insertrowcommand__WEBPACK_IMPORTED_MODULE_4__["default"]( editor, { order: 'above' } ) );
editor.commands.add( 'insertTableRowBelow', new _commands_insertrowcommand__WEBPACK_IMPORTED_MODULE_4__["default"]( editor, { order: 'below' } ) );
editor.commands.add( 'insertTableColumnLeft', new _commands_insertcolumncommand__WEBPACK_IMPORTED_MODULE_5__["default"]( editor, { order: 'left' } ) );
editor.commands.add( 'insertTableColumnRight', new _commands_insertcolumncommand__WEBPACK_IMPORTED_MODULE_5__["default"]( editor, { order: 'right' } ) );
editor.commands.add( 'removeTableRow', new _commands_removerowcommand__WEBPACK_IMPORTED_MODULE_8__["default"]( editor ) );
editor.commands.add( 'removeTableColumn', new _commands_removecolumncommand__WEBPACK_IMPORTED_MODULE_9__["default"]( editor ) );
editor.commands.add( 'splitTableCellVertically', new _commands_splitcellcommand__WEBPACK_IMPORTED_MODULE_6__["default"]( editor, { direction: 'vertically' } ) );
editor.commands.add( 'splitTableCellHorizontally', new _commands_splitcellcommand__WEBPACK_IMPORTED_MODULE_6__["default"]( editor, { direction: 'horizontally' } ) );
editor.commands.add( 'mergeTableCells', new _commands_mergecellscommand__WEBPACK_IMPORTED_MODULE_12__["default"]( editor ) );
editor.commands.add( 'mergeTableCellRight', new _commands_mergecellcommand__WEBPACK_IMPORTED_MODULE_7__["default"]( editor, { direction: 'right' } ) );
editor.commands.add( 'mergeTableCellLeft', new _commands_mergecellcommand__WEBPACK_IMPORTED_MODULE_7__["default"]( editor, { direction: 'left' } ) );
editor.commands.add( 'mergeTableCellDown', new _commands_mergecellcommand__WEBPACK_IMPORTED_MODULE_7__["default"]( editor, { direction: 'down' } ) );
editor.commands.add( 'mergeTableCellUp', new _commands_mergecellcommand__WEBPACK_IMPORTED_MODULE_7__["default"]( editor, { direction: 'up' } ) );
editor.commands.add( 'setTableColumnHeader', new _commands_setheadercolumncommand__WEBPACK_IMPORTED_MODULE_11__["default"]( editor ) );
editor.commands.add( 'setTableRowHeader', new _commands_setheaderrowcommand__WEBPACK_IMPORTED_MODULE_10__["default"]( editor ) );
editor.commands.add( 'selectTableRow', new _commands_selectrowcommand__WEBPACK_IMPORTED_MODULE_13__["default"]( editor ) );
editor.commands.add( 'selectTableColumn', new _commands_selectcolumncommand__WEBPACK_IMPORTED_MODULE_14__["default"]( editor ) );
(0,_converters_table_layout_post_fixer__WEBPACK_IMPORTED_MODULE_16__["default"])( model );
(0,_converters_table_cell_paragraph_post_fixer__WEBPACK_IMPORTED_MODULE_17__["default"])( model );
this.listenTo( model.document, 'change:data', () => {
(0,_converters_table_headings_refresh_handler__WEBPACK_IMPORTED_MODULE_18__["default"])( model, editor.editing );
(0,_converters_table_cell_refresh_handler__WEBPACK_IMPORTED_MODULE_19__["default"])( model, editor.editing );
} );
}
}
// Creates a mapper callback to adjust model position mappings in a table cell containing a paragraph, which is bound to its parent
// (to the table cell). Only positions after this paragraph have to be adjusted, because after binding this paragraph to the table cell,
// elements located after this paragraph would point either to a non-existent offset inside `tableCell` (if paragraph is empty), or after
// the first character of the paragraph's text. See https://github.com/ckeditor/ckeditor5/issues/10116.
//
// <tableCell><paragraph></paragraph>^</tableCell> -> <td>^ </td>
//
// <tableCell><paragraph>foobar</paragraph>^</tableCell> -> <td>foobar^</td>
//
// @returns {Function}
function mapTableCellModelPositionToView() {
return ( evt, data ) => {
const modelParent = data.modelPosition.parent;
const modelNodeBefore = data.modelPosition.nodeBefore;
if ( !modelParent.is( 'element', 'tableCell' ) ) {
return;
}
if ( !modelNodeBefore || !modelNodeBefore.is( 'element', 'paragraph' ) ) {
return;
}
const viewNodeBefore = data.mapper.toViewElement( modelNodeBefore );
const viewParent = data.mapper.toViewElement( modelParent );
if ( viewNodeBefore === viewParent ) {
// Since the paragraph has already been bound to its parent, update the current position in the model with paragraph's
// max offset, so it points to the place which should normally (in all other cases) be the end position of this paragraph.
data.viewPosition = data.mapper.findPositionIn( viewParent, modelNodeBefore.maxOffset );
}
};
}
// Returns fixed colspan and rowspan attrbutes values.
//
// @private
// @param {String} type colspan or rowspan.
// @returns {Function} conversion value function.
function upcastCellSpan( type ) {
return cell => {
const span = parseInt( cell.getAttribute( type ) );
if ( Number.isNaN( span ) || span <= 0 ) {
return null;
}
return span;
};
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/tablekeyboard.js":
/*!*********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/tablekeyboard.js ***!
\*********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ TableKeyboard)
/* harmony export */ });
/* harmony import */ var _tableselection__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./tableselection */ "./node_modules/@ckeditor/ckeditor5-table/src/tableselection.js");
/* harmony import */ var _tablewalker__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./tablewalker */ "./node_modules/@ckeditor/ckeditor5-table/src/tablewalker.js");
/* harmony import */ var _tableutils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./tableutils */ "./node_modules/@ckeditor/ckeditor5-table/src/tableutils.js");
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/tablekeyboard
*/
/**
* This plugin enables keyboard navigation for tables.
* It is loaded automatically by the {@link module:table/table~Table} plugin.
*
* @extends module:core/plugin~Plugin
*/
class TableKeyboard extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_3__.Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'TableKeyboard';
}
/**
* @inheritDoc
*/
static get requires() {
return [ _tableselection__WEBPACK_IMPORTED_MODULE_0__["default"], _tableutils__WEBPACK_IMPORTED_MODULE_2__["default"] ];
}
/**
* @inheritDoc
*/
init() {
const view = this.editor.editing.view;
const viewDocument = view.document;
// Handle Tab key navigation.
this.editor.keystrokes.set( 'Tab', ( ...args ) => this._handleTabOnSelectedTable( ...args ), { priority: 'low' } );
this.editor.keystrokes.set( 'Tab', this._getTabHandler( true ), { priority: 'low' } );
this.editor.keystrokes.set( 'Shift+Tab', this._getTabHandler( false ), { priority: 'low' } );
this.listenTo( viewDocument, 'arrowKey', ( ...args ) => this._onArrowKey( ...args ), { context: 'table' } );
}
/**
* Handles {@link module:engine/view/document~Document#event:keydown keydown} events for the <kbd>Tab</kbd> key executed
* when the table widget is selected.
*
* @private
* @param {module:engine/view/observer/keyobserver~KeyEventData} data Key event data.
* @param {Function} cancel The stop/stopPropagation/preventDefault function.
*/
_handleTabOnSelectedTable( data, cancel ) {
const editor = this.editor;
const selection = editor.model.document.selection;
const selectedElement = selection.getSelectedElement();
if ( !selectedElement || !selectedElement.is( 'element', 'table' ) ) {
return;
}
cancel();
editor.model.change( writer => {
writer.setSelection( writer.createRangeIn( selectedElement.getChild( 0 ).getChild( 0 ) ) );
} );
}
/**
* Returns a handler for {@link module:engine/view/document~Document#event:keydown keydown} events for the <kbd>Tab</kbd> key executed
* inside table cells.
*
* @private
* @param {Boolean} isForward Whether this handler will move the selection to the next or the previous cell.
*/
_getTabHandler( isForward ) {
const editor = this.editor;
const tableUtils = this.editor.plugins.get( _tableutils__WEBPACK_IMPORTED_MODULE_2__["default"] );
return ( domEventData, cancel ) => {
const selection = editor.model.document.selection;
let tableCell = tableUtils.getTableCellsContainingSelection( selection )[ 0 ];
if ( !tableCell ) {
tableCell = this.editor.plugins.get( 'TableSelection' ).getFocusCell();
}
if ( !tableCell ) {
return;
}
cancel();
const tableRow = tableCell.parent;
const table = tableRow.parent;
const currentRowIndex = table.getChildIndex( tableRow );
const currentCellIndex = tableRow.getChildIndex( tableCell );
const isFirstCellInRow = currentCellIndex === 0;
if ( !isForward && isFirstCellInRow && currentRowIndex === 0 ) {
// Set the selection over the whole table if the selection was in the first table cell.
editor.model.change( writer => {
writer.setSelection( writer.createRangeOn( table ) );
} );
return;
}
const isLastCellInRow = currentCellIndex === tableRow.childCount - 1;
const isLastRow = currentRowIndex === tableUtils.getRows( table ) - 1;
if ( isForward && isLastRow && isLastCellInRow ) {
editor.execute( 'insertTableRowBelow' );
// Check if the command actually added a row. If `insertTableRowBelow` execution didn't add a row (because it was disabled
// or it got overwritten) set the selection over the whole table to mirror the first cell case.
if ( currentRowIndex === tableUtils.getRows( table ) - 1 ) {
editor.model.change( writer => {
writer.setSelection( writer.createRangeOn( table ) );
} );
return;
}
}
let cellToFocus;
// Move to the first cell in the next row.
if ( isForward && isLastCellInRow ) {
const nextRow = table.getChild( currentRowIndex + 1 );
cellToFocus = nextRow.getChild( 0 );
}
// Move to the last cell in the previous row.
else if ( !isForward && isFirstCellInRow ) {
const previousRow = table.getChild( currentRowIndex - 1 );
cellToFocus = previousRow.getChild( previousRow.childCount - 1 );
}
// Move to the next/previous cell.
else {
cellToFocus = tableRow.getChild( currentCellIndex + ( isForward ? 1 : -1 ) );
}
editor.model.change( writer => {
writer.setSelection( writer.createRangeIn( cellToFocus ) );
} );
};
}
/**
* Handles {@link module:engine/view/document~Document#event:keydown keydown} events.
*
* @private
* @param {module:utils/eventinfo~EventInfo} eventInfo
* @param {module:engine/view/observer/domeventdata~DomEventData} domEventData
*/
_onArrowKey( eventInfo, domEventData ) {
const editor = this.editor;
const keyCode = domEventData.keyCode;
const direction = (0,ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_4__.getLocalizedArrowKeyCodeDirection)( keyCode, editor.locale.contentLanguageDirection );
const wasHandled = this._handleArrowKeys( direction, domEventData.shiftKey );
if ( wasHandled ) {
domEventData.preventDefault();
domEventData.stopPropagation();
eventInfo.stop();
}
}
/**
* Handles arrow keys to move the selection around the table.
*
* @private
* @param {'left'|'up'|'right'|'down'} direction The direction of the arrow key.
* @param {Boolean} expandSelection If the current selection should be expanded.
* @returns {Boolean} Returns `true` if key was handled.
*/
_handleArrowKeys( direction, expandSelection ) {
const tableUtils = this.editor.plugins.get( _tableutils__WEBPACK_IMPORTED_MODULE_2__["default"] );
const model = this.editor.model;
const selection = model.document.selection;
const isForward = [ 'right', 'down' ].includes( direction );
// In case one or more table cells are selected (from outside),
// move the selection to a cell adjacent to the selected table fragment.
const selectedCells = tableUtils.getSelectedTableCells( selection );
if ( selectedCells.length ) {
let focusCell;
if ( expandSelection ) {
focusCell = this.editor.plugins.get( 'TableSelection' ).getFocusCell();
} else {
focusCell = isForward ? selectedCells[ selectedCells.length - 1 ] : selectedCells[ 0 ];
}
this._navigateFromCellInDirection( focusCell, direction, expandSelection );
return true;
}
// Abort if we're not in a table cell.
const tableCell = selection.focus.findAncestor( 'tableCell' );
/* istanbul ignore if: paranoid check */
if ( !tableCell ) {
return false;
}
// When the selection is not collapsed.
if ( !selection.isCollapsed ) {
if ( expandSelection ) {
// Navigation is in the opposite direction than the selection direction so this is shrinking of the selection.
// Selection for sure will not approach cell edge.
//
// With a special case when all cell content is selected - then selection should expand to the other cell.
// Note: When the entire cell gets selected using CTRL+A, the selection is always forward.
if ( selection.isBackward == isForward && !selection.containsEntireContent( tableCell ) ) {
return false;
}
} else {
const selectedElement = selection.getSelectedElement();
// It will collapse for non-object selected so it's not going to move to other cell.
if ( !selectedElement || !model.schema.isObject( selectedElement ) ) {
return false;
}
}
}
// Let's check if the selection is at the beginning/end of the cell.
if ( this._isSelectionAtCellEdge( selection, tableCell, isForward ) ) {
this._navigateFromCellInDirection( tableCell, direction, expandSelection );
return true;
}
return false;
}
/**
* Returns `true` if the selection is at the boundary of a table cell according to the navigation direction.
*
* @private
* @param {module:engine/model/selection~Selection} selection The current selection.
* @param {module:engine/model/element~Element} tableCell The current table cell element.
* @param {Boolean} isForward The expected navigation direction.
* @returns {Boolean}
*/
_isSelectionAtCellEdge( selection, tableCell, isForward ) {
const model = this.editor.model;
const schema = this.editor.model.schema;
const focus = isForward ? selection.getLastPosition() : selection.getFirstPosition();
// If the current limit element is not table cell we are for sure not at the cell edge.
// Also `modifySelection` will not let us out of it.
if ( !schema.getLimitElement( focus ).is( 'element', 'tableCell' ) ) {
const boundaryPosition = model.createPositionAt( tableCell, isForward ? 'end' : 0 );
return boundaryPosition.isTouching( focus );
}
const probe = model.createSelection( focus );
model.modifySelection( probe, { direction: isForward ? 'forward' : 'backward' } );
// If there was no change in the focus position, then it's not possible to move the selection there.
return focus.isEqual( probe.focus );
}
/**
* Moves the selection from the given table cell in the specified direction.
*
* @protected
* @param {module:engine/model/element~Element} focusCell The table cell that is current multi-cell selection focus.
* @param {'left'|'up'|'right'|'down'} direction Direction in which selection should move.
* @param {Boolean} [expandSelection=false] If the current selection should be expanded.
*/
_navigateFromCellInDirection( focusCell, direction, expandSelection = false ) {
const model = this.editor.model;
const table = focusCell.findAncestor( 'table' );
const tableMap = [ ...new _tablewalker__WEBPACK_IMPORTED_MODULE_1__["default"]( table, { includeAllSlots: true } ) ];
const { row: lastRow, column: lastColumn } = tableMap[ tableMap.length - 1 ];
const currentCellInfo = tableMap.find( ( { cell } ) => cell == focusCell );
let { row, column } = currentCellInfo;
switch ( direction ) {
case 'left':
column--;
break;
case 'up':
row--;
break;
case 'right':
column += currentCellInfo.cellWidth;
break;
case 'down':
row += currentCellInfo.cellHeight;
break;
}
const isOutsideVertically = row < 0 || row > lastRow;
const isBeforeFirstCell = column < 0 && row <= 0;
const isAfterLastCell = column > lastColumn && row >= lastRow;
// Note that if the table cell at the end of a row is row-spanned then isAfterLastCell will never be true.
// However, we don't know if user was navigating on the last row or not, so let's stay in the table.
if ( isOutsideVertically || isBeforeFirstCell || isAfterLastCell ) {
model.change( writer => {
writer.setSelection( writer.createRangeOn( table ) );
} );
return;
}
if ( column < 0 ) {
column = expandSelection ? 0 : lastColumn;
row--;
} else if ( column > lastColumn ) {
column = expandSelection ? lastColumn : 0;
row++;
}
const cellToSelect = tableMap.find( cellInfo => cellInfo.row == row && cellInfo.column == column ).cell;
const isForward = [ 'right', 'down' ].includes( direction );
const tableSelection = this.editor.plugins.get( 'TableSelection' );
if ( expandSelection && tableSelection.isEnabled ) {
const anchorCell = tableSelection.getAnchorCell() || focusCell;
tableSelection.setCellSelection( anchorCell, cellToSelect );
} else {
const positionToSelect = model.createPositionAt( cellToSelect, isForward ? 0 : 'end' );
model.change( writer => {
writer.setSelection( positionToSelect );
} );
}
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/tablemouse.js":
/*!******************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/tablemouse.js ***!
\******************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ TableMouse)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var _tableselection__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./tableselection */ "./node_modules/@ckeditor/ckeditor5-table/src/tableselection.js");
/* harmony import */ var _tablemouse_mouseeventsobserver__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./tablemouse/mouseeventsobserver */ "./node_modules/@ckeditor/ckeditor5-table/src/tablemouse/mouseeventsobserver.js");
/* harmony import */ var _tableutils__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./tableutils */ "./node_modules/@ckeditor/ckeditor5-table/src/tableutils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/tablemouse
*/
/**
* This plugin enables a table cells' selection with the mouse.
* It is loaded automatically by the {@link module:table/table~Table} plugin.
*
* @extends module:core/plugin~Plugin
*/
class TableMouse extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'TableMouse';
}
/**
* @inheritDoc
*/
static get requires() {
return [ _tableselection__WEBPACK_IMPORTED_MODULE_1__["default"], _tableutils__WEBPACK_IMPORTED_MODULE_3__["default"] ];
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
// Currently the MouseObserver only handles `mousedown` and `mouseup` events.
// TODO move to the engine?
editor.editing.view.addObserver( _tablemouse_mouseeventsobserver__WEBPACK_IMPORTED_MODULE_2__["default"] );
this._enableShiftClickSelection();
this._enableMouseDragSelection();
}
/**
* Enables making cells selection by <kbd>Shift</kbd>+click. Creates a selection from the cell which previously held
* the selection to the cell which was clicked. It can be the same cell, in which case it selects a single cell.
*
* @private
*/
_enableShiftClickSelection() {
const editor = this.editor;
const tableUtils = editor.plugins.get( _tableutils__WEBPACK_IMPORTED_MODULE_3__["default"] );
let blockSelectionChange = false;
const tableSelection = editor.plugins.get( _tableselection__WEBPACK_IMPORTED_MODULE_1__["default"] );
this.listenTo( editor.editing.view.document, 'mousedown', ( evt, domEventData ) => {
const selection = editor.model.document.selection;
if ( !this.isEnabled || !tableSelection.isEnabled ) {
return;
}
if ( !domEventData.domEvent.shiftKey ) {
return;
}
const anchorCell = tableSelection.getAnchorCell() || tableUtils.getTableCellsContainingSelection( selection )[ 0 ];
if ( !anchorCell ) {
return;
}
const targetCell = this._getModelTableCellFromDomEvent( domEventData );
if ( targetCell && haveSameTableParent( anchorCell, targetCell ) ) {
blockSelectionChange = true;
tableSelection.setCellSelection( anchorCell, targetCell );
domEventData.preventDefault();
}
} );
this.listenTo( editor.editing.view.document, 'mouseup', () => {
blockSelectionChange = false;
} );
// We need to ignore a `selectionChange` event that is fired after we render our new table cells selection.
// When downcasting table cells selection to the view, we put the view selection in the last selected cell
// in a place that may not be natively a "correct" location. This is – we put it directly in the `<td>` element.
// All browsers fire the native `selectionchange` event.
// However, all browsers except Safari return the selection in the exact place where we put it
// (even though it's visually normalized). Safari returns `<td><p>^foo` that makes our selection observer
// fire our `selectionChange` event (because the view selection that we set in the first step differs from the DOM selection).
// Since `selectionChange` is fired, we automatically update the model selection that moves it that paragraph.
// This breaks our dear cells selection.
//
// Theoretically this issue concerns only Safari that is the only browser that do normalize the selection.
// However, to avoid code branching and to have a good coverage for this event blocker, I enabled it for all browsers.
//
// Note: I'm keeping the `blockSelectionChange` state separately for shift+click and mouse drag (exact same logic)
// so I don't have to try to analyze whether they don't overlap in some weird cases. Probably they don't.
// But I have other things to do, like writing this comment.
this.listenTo( editor.editing.view.document, 'selectionChange', evt => {
if ( blockSelectionChange ) {
// @if CK_DEBUG // console.log( 'Blocked selectionChange to avoid breaking table cells selection.' );
evt.stop();
}
}, { priority: 'highest' } );
}
/**
* Enables making cells selection by dragging.
*
* The selection is made only on mousemove. Mouse tracking is started on mousedown.
* However, the cells selection is enabled only after the mouse cursor left the anchor cell.
* Thanks to that normal text selection within one cell works just fine. However, you can still select
* just one cell by leaving the anchor cell and moving back to it.
*
* @private
*/
_enableMouseDragSelection() {
const editor = this.editor;
let anchorCell, targetCell;
let beganCellSelection = false;
let blockSelectionChange = false;
const tableSelection = editor.plugins.get( _tableselection__WEBPACK_IMPORTED_MODULE_1__["default"] );
this.listenTo( editor.editing.view.document, 'mousedown', ( evt, domEventData ) => {
if ( !this.isEnabled || !tableSelection.isEnabled ) {
return;
}
// Make sure to not conflict with the shift+click listener and any other possible handler.
if ( domEventData.domEvent.shiftKey || domEventData.domEvent.ctrlKey || domEventData.domEvent.altKey ) {
return;
}
anchorCell = this._getModelTableCellFromDomEvent( domEventData );
} );
this.listenTo( editor.editing.view.document, 'mousemove', ( evt, domEventData ) => {
if ( !domEventData.domEvent.buttons ) {
return;
}
if ( !anchorCell ) {
return;
}
const newTargetCell = this._getModelTableCellFromDomEvent( domEventData );
if ( newTargetCell && haveSameTableParent( anchorCell, newTargetCell ) ) {
targetCell = newTargetCell;
// Switch to the cell selection mode after the mouse cursor left the anchor cell.
// Switch off only on mouseup (makes selecting a single cell possible).
if ( !beganCellSelection && targetCell != anchorCell ) {
beganCellSelection = true;
}
}
// Yep, not making a cell selection yet. See method docs.
if ( !beganCellSelection ) {
return;
}
blockSelectionChange = true;
tableSelection.setCellSelection( anchorCell, targetCell );
domEventData.preventDefault();
} );
this.listenTo( editor.editing.view.document, 'mouseup', () => {
beganCellSelection = false;
blockSelectionChange = false;
anchorCell = null;
targetCell = null;
} );
// See the explanation in `_enableShiftClickSelection()`.
this.listenTo( editor.editing.view.document, 'selectionChange', evt => {
if ( blockSelectionChange ) {
// @if CK_DEBUG // console.log( 'Blocked selectionChange to avoid breaking table cells selection.' );
evt.stop();
}
}, { priority: 'highest' } );
}
/**
* Returns the model table cell element based on the target element of the passed DOM event.
*
* @private
* @param {module:engine/view/observer/domeventdata~DomEventData} domEventData
* @returns {module:engine/model/element~Element|undefined} Returns the table cell or `undefined`.
*/
_getModelTableCellFromDomEvent( domEventData ) {
// Note: Work with positions (not element mapping) because the target element can be an attribute or other non-mapped element.
const viewTargetElement = domEventData.target;
const viewPosition = this.editor.editing.view.createPositionAt( viewTargetElement, 0 );
const modelPosition = this.editor.editing.mapper.toModelPosition( viewPosition );
const modelElement = modelPosition.parent;
return modelElement.findAncestor( 'tableCell', { includeSelf: true } );
}
}
function haveSameTableParent( cellA, cellB ) {
return cellA.parent.parent == cellB.parent.parent;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/tablemouse/mouseeventsobserver.js":
/*!**************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/tablemouse/mouseeventsobserver.js ***!
\**************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ MouseEventsObserver)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/engine */ "./node_modules/ckeditor5/src/engine.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/tableselection/mouseeventsobserver
*/
/**
* The mouse selection event observer.
*
* It registers listeners for the following DOM events:
*
* - `'mousemove'`
* - `'mouseup'`
* - `'mouseleave'`
*
* Note that this observer is disabled by default. To enable this observer, it needs to be added to
* {@link module:engine/view/view~View} using the {@link module:engine/view/view~View#addObserver} method.
*
* The observer is registered by the {@link module:table/tableselection~TableSelection} plugin.
*
* @extends module:engine/view/observer/domeventobserver~DomEventObserver
*/
class MouseEventsObserver extends ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_0__.DomEventObserver {
/**
* @inheritDoc
*/
constructor( view ) {
super( view );
this.domEventType = [ 'mousemove', 'mouseleave' ];
}
/**
* @inheritDoc
*/
onDomEvent( domEvent ) {
this.fire( domEvent.type, domEvent );
}
}
/**
* Fired when the mouse is moved over one of the editables.
*
* Introduced by {@link module:table/tableselection/mouseeventsobserver~MouseEventsObserver}.
*
* Note that this event is not available by default. To make it available,
* {@link module:table/tableselection/mouseeventsobserver~MouseEventsObserver} needs to be added
* to {@link module:engine/view/view~View} using the {@link module:engine/view/view~View#addObserver} method.
*
* @see module:table/tableselection/mouseeventsobserver~MouseEventsObserver
* @event module:engine/view/document~Document#event:mousemove
* @param {module:engine/view/observer/domeventdata~DomEventData} data Event data.
*/
/**
* Fired when the mouse is moved out of one of the editables.
*
* Introduced by {@link module:table/tableselection/mouseeventsobserver~MouseEventsObserver}.
*
* Note that this event is not available by default. To make it available,
* {@link module:table/tableselection/mouseeventsobserver~MouseEventsObserver} needs to be added
* to {@link module:engine/view/view~View} using the {@link module:engine/view/view~View#addObserver} method.
*
* @see module:table/tableselection/mouseeventsobserver~MouseEventsObserver
* @event module:engine/view/document~Document#event:mouseleave
* @param {module:engine/view/observer/domeventdata~DomEventData} data Event data.
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/tableproperties.js":
/*!***********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/tableproperties.js ***!
\***********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ TableProperties)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var _tableproperties_tablepropertiesediting__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./tableproperties/tablepropertiesediting */ "./node_modules/@ckeditor/ckeditor5-table/src/tableproperties/tablepropertiesediting.js");
/* harmony import */ var _tableproperties_tablepropertiesui__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./tableproperties/tablepropertiesui */ "./node_modules/@ckeditor/ckeditor5-table/src/tableproperties/tablepropertiesui.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/tableproperties
*/
/**
* The table properties feature. Enables support for setting properties of tables (size, border, background, etc.).
*
* Read more in the {@glink features/table#table-and-cell-styling-tools Table and cell styling tools} section.
* See also the {@link module:table/tablecellproperties~TableCellProperties} plugin.
*
* This is a "glue" plugin that loads the
* {@link module:table/tableproperties/tablepropertiesediting~TablePropertiesEditing table properties editing feature} and
* the {@link module:table/tableproperties/tablepropertiesui~TablePropertiesUI table properties UI feature}.
*
* @extends module:core/plugin~Plugin
*/
class TableProperties extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'TableProperties';
}
/**
* @inheritDoc
*/
static get requires() {
return [ _tableproperties_tablepropertiesediting__WEBPACK_IMPORTED_MODULE_1__["default"], _tableproperties_tablepropertiesui__WEBPACK_IMPORTED_MODULE_2__["default"] ];
}
}
/**
* The configuration of the table properties user interface (balloon). It allows to define:
*
* * The color palette for the table border color style field (`tableProperties.borderColors`),
* * The color palette for the table background style field (`tableProperties.backgroundColors`).
*
* const tableConfig = {
* tableProperties: {
* borderColors: [
* {
* color: 'hsl(0, 0%, 90%)',
* label: 'Light grey'
* },
* // ...
* ],
* backgroundColors: [
* {
* color: 'hsl(120, 75%, 60%)',
* label: 'Green'
* },
* // ...
* ]
* }
* };
*
* * The default styles for tables (`tableProperties.defaultProperties`):
*
* const tableConfig = {
* tableProperties: {
* defaultProperties: {
* borderStyle: 'dashed',
* borderColor: 'hsl(0, 0%, 90%)',
* borderWidth: '3px',
* alignment: 'left'
* }
* }
* }
*
* {@link module:table/tableproperties~TablePropertiesOptions Read more about the supported properties.}
*
* **Note**: The `borderColors` and `backgroundColors` options do not impact the data loaded into the editor,
* i.e. they do not limit or filter the colors in the data. They are used only in the user interface
* allowing users to pick colors in a more convenient way. The `defaultProperties` option does impact the data.
* Default values will not be kept in the editor model.
*
* The default color palettes for the table background and the table border are the same
* ({@link module:table/utils/ui/table-properties~defaultColors check out their content}).
*
* Both color palette configurations must follow the
* {@link module:table/table~TableColorConfig table color configuration format}.
*
* Read more about configuring the table feature in {@link module:table/table~TableConfig}.
*
* @member {Object} module:table/table~TableConfig#tableProperties
*/
/**
* The configuration of the table default properties feature.
*
* @typedef {Object} module:table/tableproperties~TablePropertiesOptions
*
* @property {String} width The default `width` of the table.
*
* @property {String} height The default `height` of the table.
*
* @property {String} backgroundColor The default `background-color` of the table.
*
* @property {String} borderColor The default `border-color` of the table.
*
* @property {String} borderWidth The default `border-width` of the table.
*
* @property {String} [borderStyle='none'] The default `border-style` of the table.
*
* @property {String} [alignment='center'] The default `alignment` of the table.
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/tableproperties/commands/tablealignmentcommand.js":
/*!******************************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/tableproperties/commands/tablealignmentcommand.js ***!
\******************************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ TableAlignmentCommand)
/* harmony export */ });
/* harmony import */ var _tablepropertycommand__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./tablepropertycommand */ "./node_modules/@ckeditor/ckeditor5-table/src/tableproperties/commands/tablepropertycommand.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/tableproperties/commands/tablealignmentcommand
*/
/**
* The table alignment command.
*
* The command is registered by the {@link module:table/tableproperties/tablepropertiesediting~TablePropertiesEditing} as
* the `'tableAlignment'` editor command.
*
* To change the alignment of the selected table, execute the command:
*
* editor.execute( 'tableAlignment', {
* value: 'right'
* } );
*
* @extends module:table/tableproperties/commands/tablepropertycommand~TablePropertyCommand
*/
class TableAlignmentCommand extends _tablepropertycommand__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* Creates a new `TableAlignmentCommand` instance.
*
* @param {module:core/editor/editor~Editor} editor An editor in which this command will be used.
* @param {String} defaultValue The default value for the "alignment" attribute.
*/
constructor( editor, defaultValue ) {
super( editor, 'tableAlignment', defaultValue );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/tableproperties/commands/tablebackgroundcolorcommand.js":
/*!************************************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/tableproperties/commands/tablebackgroundcolorcommand.js ***!
\************************************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ TableBackgroundColorCommand)
/* harmony export */ });
/* harmony import */ var _tablepropertycommand__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./tablepropertycommand */ "./node_modules/@ckeditor/ckeditor5-table/src/tableproperties/commands/tablepropertycommand.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/tableproperties/commands/tablebackgroundcolorcommand
*/
/**
* The table background color command.
*
* The command is registered by the {@link module:table/tableproperties/tablepropertiesediting~TablePropertiesEditing} as
* the `'tableBackgroundColor'` editor command.
*
* To change the background color of the selected table, execute the command:
*
* editor.execute( 'tableBackgroundColor', {
* value: '#f00'
* } );
*
* @extends module:table/tableproperties/commands/tablepropertycommand~TablePropertyCommand
*/
class TableBackgroundColorCommand extends _tablepropertycommand__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* Creates a new `TableBackgroundColorCommand` instance.
*
* @param {module:core/editor/editor~Editor} editor An editor in which this command will be used.
* @param {String} defaultValue The default value of the attribute.
*/
constructor( editor, defaultValue ) {
super( editor, 'tableBackgroundColor', defaultValue );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/tableproperties/commands/tablebordercolorcommand.js":
/*!********************************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/tableproperties/commands/tablebordercolorcommand.js ***!
\********************************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ TableBorderColorCommand)
/* harmony export */ });
/* harmony import */ var _tablepropertycommand__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./tablepropertycommand */ "./node_modules/@ckeditor/ckeditor5-table/src/tableproperties/commands/tablepropertycommand.js");
/* harmony import */ var _utils_table_properties__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../../utils/table-properties */ "./node_modules/@ckeditor/ckeditor5-table/src/utils/table-properties.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/tableproperties/commands/tablebordercolorcommand
*/
/**
* The table border color command.
*
* The command is registered by the {@link module:table/tableproperties/tablepropertiesediting~TablePropertiesEditing} as
* the `'tableBorderColor'` editor command.
*
* To change the border color of the selected table, execute the command:
*
* editor.execute( 'tableBorderColor', {
* value: '#f00'
* } );
*
* @extends module:table/tableproperties/commands/tablepropertycommand~TablePropertyCommand
*/
class TableBorderColorCommand extends _tablepropertycommand__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* Creates a new `TableBorderColorCommand` instance.
*
* @param {module:core/editor/editor~Editor} editor An editor in which this command will be used.
* @param {String} defaultValue The default value of the attribute.
*/
constructor( editor, defaultValue ) {
super( editor, 'tableBorderColor', defaultValue );
}
/**
* @inheritDoc
*/
_getValue( table ) {
if ( !table ) {
return;
}
const value = (0,_utils_table_properties__WEBPACK_IMPORTED_MODULE_1__.getSingleValue)( table.getAttribute( this.attributeName ) );
if ( value === this._defaultValue ) {
return;
}
return value;
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/tableproperties/commands/tableborderstylecommand.js":
/*!********************************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/tableproperties/commands/tableborderstylecommand.js ***!
\********************************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ TableBorderStyleCommand)
/* harmony export */ });
/* harmony import */ var _tablepropertycommand__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./tablepropertycommand */ "./node_modules/@ckeditor/ckeditor5-table/src/tableproperties/commands/tablepropertycommand.js");
/* harmony import */ var _utils_table_properties__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../../utils/table-properties */ "./node_modules/@ckeditor/ckeditor5-table/src/utils/table-properties.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/tableproperties/commands/tableborderstylecommand
*/
/**
* The table style border command.
*
* The command is registered by the {@link module:table/tableproperties/tablepropertiesediting~TablePropertiesEditing} as
* the `'tableBorderStyle'` editor command.
*
* To change the border style of the selected table, execute the command:
*
* editor.execute( 'tableBorderStyle', {
* value: 'dashed'
* } );
*
* @extends module:table/tableproperties/commands/tablepropertycommand~TablePropertyCommand
*/
class TableBorderStyleCommand extends _tablepropertycommand__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* Creates a new `TableBorderStyleCommand` instance.
*
* @param {module:core/editor/editor~Editor} editor An editor in which this command will be used.
* @param {String} defaultValue The default value of the attribute.
*/
constructor( editor, defaultValue ) {
super( editor, 'tableBorderStyle', defaultValue );
}
/**
* @inheritDoc
*/
_getValue( table ) {
if ( !table ) {
return;
}
const value = (0,_utils_table_properties__WEBPACK_IMPORTED_MODULE_1__.getSingleValue)( table.getAttribute( this.attributeName ) );
if ( value === this._defaultValue ) {
return;
}
return value;
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/tableproperties/commands/tableborderwidthcommand.js":
/*!********************************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/tableproperties/commands/tableborderwidthcommand.js ***!
\********************************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ TableBorderWidthCommand)
/* harmony export */ });
/* harmony import */ var _tablepropertycommand__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./tablepropertycommand */ "./node_modules/@ckeditor/ckeditor5-table/src/tableproperties/commands/tablepropertycommand.js");
/* harmony import */ var _utils_table_properties__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../../utils/table-properties */ "./node_modules/@ckeditor/ckeditor5-table/src/utils/table-properties.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/tableproperties/commands/tableborderwidthcommand
*/
/**
* The table width border command.
*
* The command is registered by the {@link module:table/tableproperties/tablepropertiesediting~TablePropertiesEditing} as
* the `'tableBorderWidth'` editor command.
*
* To change the border width of the selected table, execute the command:
*
* editor.execute( 'tableBorderWidth', {
* value: '5px'
* } );
*
* **Note**: This command adds the default `'px'` unit to numeric values. Executing:
*
* editor.execute( 'tableBorderWidth', {
* value: '5'
* } );
*
* will set the `borderWidth` attribute to `'5px'` in the model.
*
* @extends module:table/tableproperties/commands/tablepropertycommand~TablePropertyCommand
*/
class TableBorderWidthCommand extends _tablepropertycommand__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* Creates a new `TableBorderWidthCommand` instance.
*
* @param {module:core/editor/editor~Editor} editor An editor in which this command will be used.
* @param {String} defaultValue The default value of the attribute.
*/
constructor( editor, defaultValue ) {
super( editor, 'tableBorderWidth', defaultValue );
}
/**
* @inheritDoc
*/
_getValue( table ) {
if ( !table ) {
return;
}
const value = (0,_utils_table_properties__WEBPACK_IMPORTED_MODULE_1__.getSingleValue)( table.getAttribute( this.attributeName ) );
if ( value === this._defaultValue ) {
return;
}
return value;
}
/**
* @inheritDoc
*/
_getValueToSet( value ) {
value = (0,_utils_table_properties__WEBPACK_IMPORTED_MODULE_1__.addDefaultUnitToNumericValue)( value, 'px' );
if ( value === this._defaultValue ) {
return;
}
return value;
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/tableproperties/commands/tableheightcommand.js":
/*!***************************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/tableproperties/commands/tableheightcommand.js ***!
\***************************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ TableHeightCommand)
/* harmony export */ });
/* harmony import */ var _tablepropertycommand__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./tablepropertycommand */ "./node_modules/@ckeditor/ckeditor5-table/src/tableproperties/commands/tablepropertycommand.js");
/* harmony import */ var _utils_table_properties__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../../utils/table-properties */ "./node_modules/@ckeditor/ckeditor5-table/src/utils/table-properties.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/tableproperties/commands/tableheightcommand
*/
/**
* The table height command.
*
* The command is registered by the {@link module:table/tableproperties/tablepropertiesediting~TablePropertiesEditing} as
* the `'tableHeight'` editor command.
*
* To change the height of the selected table, execute the command:
*
* editor.execute( 'tableHeight', {
* value: '500px'
* } );
*
* **Note**: This command adds the default `'px'` unit to numeric values. Executing:
*
* editor.execute( 'tableHeight', {
* value: '50'
* } );
*
* will set the `height` attribute to `'50px'` in the model.
*
* @extends module:table/tableproperties/commands/tablepropertycommand~TablePropertyCommand
*/
class TableHeightCommand extends _tablepropertycommand__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* Creates a new `TableHeightCommand` instance.
*
* @param {module:core/editor/editor~Editor} editor An editor in which this command will be used.
* @param {String} defaultValue The default value of the attribute.
*/
constructor( editor, defaultValue ) {
super( editor, 'tableHeight', defaultValue );
}
/**
* @inheritDoc
*/
_getValueToSet( value ) {
value = (0,_utils_table_properties__WEBPACK_IMPORTED_MODULE_1__.addDefaultUnitToNumericValue)( value, 'px' );
if ( value === this._defaultValue ) {
return null;
}
return value;
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/tableproperties/commands/tablepropertycommand.js":
/*!*****************************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/tableproperties/commands/tablepropertycommand.js ***!
\*****************************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ TablePropertyCommand)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/tableproperties/commands/tablepropertycommand
*/
/**
* The table cell attribute command.
*
* This command is a base command for other table property commands.
*
* @extends module:core/command~Command
*/
class TablePropertyCommand extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Command {
/**
* Creates a new `TablePropertyCommand` instance.
*
* @param {module:core/editor/editor~Editor} editor An editor in which this command will be used.
* @param {String} attributeName Table cell attribute name.
* @param {String} defaultValue The default value of the attribute.
*/
constructor( editor, attributeName, defaultValue ) {
super( editor );
/**
* The attribute that will be set by the command.
*
* @readonly
* @member {String}
*/
this.attributeName = attributeName;
/**
* The default value for the attribute.
*
* @readonly
* @protected
* @member {String}
*/
this._defaultValue = defaultValue;
}
/**
* @inheritDoc
*/
refresh() {
const editor = this.editor;
const selection = editor.model.document.selection;
const table = selection.getFirstPosition().findAncestor( 'table' );
this.isEnabled = !!table;
this.value = this._getValue( table );
}
/**
* Executes the command.
*
* @fires execute
* @param {Object} [options]
* @param {*} [options.value] If set, the command will set the attribute on the selected table.
* If not set, the command will remove the attribute from the selected table.
* @param {module:engine/model/batch~Batch} [options.batch] Pass the model batch instance to the command to aggregate changes,
* for example, to allow a single undo step for multiple executions.
*/
execute( options = {} ) {
const model = this.editor.model;
const selection = model.document.selection;
const { value, batch } = options;
const table = selection.getFirstPosition().findAncestor( 'table' );
const valueToSet = this._getValueToSet( value );
model.enqueueChange( batch, writer => {
if ( valueToSet ) {
writer.setAttribute( this.attributeName, valueToSet, table );
} else {
writer.removeAttribute( this.attributeName, table );
}
} );
}
/**
* Returns the attribute value for a table.
*
* @param {module:engine/model/element~Element} table
* @returns {String|undefined}
* @private
*/
_getValue( table ) {
if ( !table ) {
return;
}
const value = table.getAttribute( this.attributeName );
if ( value === this._defaultValue ) {
return;
}
return value;
}
/**
* Returns the proper model value. It can be used to add a default unit to numeric values.
*
* @private
* @param {*} value
* @returns {*}
*/
_getValueToSet( value ) {
if ( value === this._defaultValue ) {
return;
}
return value;
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/tableproperties/commands/tablewidthcommand.js":
/*!**************************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/tableproperties/commands/tablewidthcommand.js ***!
\**************************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ TableWidthCommand)
/* harmony export */ });
/* harmony import */ var _tablepropertycommand__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./tablepropertycommand */ "./node_modules/@ckeditor/ckeditor5-table/src/tableproperties/commands/tablepropertycommand.js");
/* harmony import */ var _utils_table_properties__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../../utils/table-properties */ "./node_modules/@ckeditor/ckeditor5-table/src/utils/table-properties.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/tableproperties/commands/tablewidthcommand
*/
/**
* The table width command.
*
* The command is registered by the {@link module:table/tableproperties/tablepropertiesediting~TablePropertiesEditing} as
* the `'tableWidth'` editor command.
*
* To change the width of the selected table, execute the command:
*
* editor.execute( 'tableWidth', {
* value: '400px'
* } );
*
* **Note**: This command adds the default `'px'` unit to numeric values. Executing:
*
* editor.execute( 'tableWidth', {
* value: '50'
* } );
*
* will set the `width` attribute to `'50px'` in the model.
*
* @extends module:table/tableproperties/commands/tablepropertycommand~TablePropertyCommand
*/
class TableWidthCommand extends _tablepropertycommand__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* Creates a new `TableWidthCommand` instance.
*
* @param {module:core/editor/editor~Editor} editor An editor in which this command will be used.
* @param {String} defaultValue The default value of the attribute.
*/
constructor( editor, defaultValue ) {
super( editor, 'tableWidth', defaultValue );
}
/**
* @inheritDoc
*/
_getValueToSet( value ) {
value = (0,_utils_table_properties__WEBPACK_IMPORTED_MODULE_1__.addDefaultUnitToNumericValue)( value, 'px' );
if ( value === this._defaultValue ) {
return;
}
return value;
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/tableproperties/tablepropertiesediting.js":
/*!**********************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/tableproperties/tablepropertiesediting.js ***!
\**********************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ TablePropertiesEditing)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/engine */ "./node_modules/ckeditor5/src/engine.js");
/* harmony import */ var _tableediting__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../tableediting */ "./node_modules/@ckeditor/ckeditor5-table/src/tableediting.js");
/* harmony import */ var _converters_tableproperties__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../converters/tableproperties */ "./node_modules/@ckeditor/ckeditor5-table/src/converters/tableproperties.js");
/* harmony import */ var _commands_tablebackgroundcolorcommand__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./commands/tablebackgroundcolorcommand */ "./node_modules/@ckeditor/ckeditor5-table/src/tableproperties/commands/tablebackgroundcolorcommand.js");
/* harmony import */ var _commands_tablebordercolorcommand__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./commands/tablebordercolorcommand */ "./node_modules/@ckeditor/ckeditor5-table/src/tableproperties/commands/tablebordercolorcommand.js");
/* harmony import */ var _commands_tableborderstylecommand__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./commands/tableborderstylecommand */ "./node_modules/@ckeditor/ckeditor5-table/src/tableproperties/commands/tableborderstylecommand.js");
/* harmony import */ var _commands_tableborderwidthcommand__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./commands/tableborderwidthcommand */ "./node_modules/@ckeditor/ckeditor5-table/src/tableproperties/commands/tableborderwidthcommand.js");
/* harmony import */ var _commands_tablewidthcommand__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./commands/tablewidthcommand */ "./node_modules/@ckeditor/ckeditor5-table/src/tableproperties/commands/tablewidthcommand.js");
/* harmony import */ var _commands_tableheightcommand__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./commands/tableheightcommand */ "./node_modules/@ckeditor/ckeditor5-table/src/tableproperties/commands/tableheightcommand.js");
/* harmony import */ var _commands_tablealignmentcommand__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./commands/tablealignmentcommand */ "./node_modules/@ckeditor/ckeditor5-table/src/tableproperties/commands/tablealignmentcommand.js");
/* harmony import */ var _utils_table_properties__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ../utils/table-properties */ "./node_modules/@ckeditor/ckeditor5-table/src/utils/table-properties.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/tableproperties/tablepropertiesediting
*/
const ALIGN_VALUES_REG_EXP = /^(left|center|right)$/;
const FLOAT_VALUES_REG_EXP = /^(left|none|right)$/;
/**
* The table properties editing feature.
*
* Introduces table's model attributes and their conversion:
*
* - border: `tableBorderStyle`, `tableBorderColor` and `tableBorderWidth`
* - background color: `tableBackgroundColor`
* - horizontal alignment: `tableAlignment`
* - width & height: `tableWidth` & `tableHeight`
*
* It also registers commands used to manipulate the above attributes:
*
* - border: `'tableBorderStyle'`, `'tableBorderColor'` and `'tableBorderWidth'` commands
* - background color: `'tableBackgroundColor'`
* - horizontal alignment: `'tableAlignment'`
* - width & height: `'tableWidth'` & `'tableHeight'`
*
* @extends module:core/plugin~Plugin
*/
class TablePropertiesEditing extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'TablePropertiesEditing';
}
/**
* @inheritDoc
*/
static get requires() {
return [ _tableediting__WEBPACK_IMPORTED_MODULE_2__["default"] ];
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const schema = editor.model.schema;
const conversion = editor.conversion;
editor.config.define( 'table.tableProperties.defaultProperties', {} );
const defaultTableProperties = (0,_utils_table_properties__WEBPACK_IMPORTED_MODULE_11__.getNormalizedDefaultProperties)( editor.config.get( 'table.tableProperties.defaultProperties' ), {
includeAlignmentProperty: true
} );
editor.data.addStyleProcessorRules( ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_1__.addBorderRules );
enableBorderProperties( schema, conversion, {
color: defaultTableProperties.borderColor,
style: defaultTableProperties.borderStyle,
width: defaultTableProperties.borderWidth
} );
editor.commands.add( 'tableBorderColor', new _commands_tablebordercolorcommand__WEBPACK_IMPORTED_MODULE_5__["default"]( editor, defaultTableProperties.borderColor ) );
editor.commands.add( 'tableBorderStyle', new _commands_tableborderstylecommand__WEBPACK_IMPORTED_MODULE_6__["default"]( editor, defaultTableProperties.borderStyle ) );
editor.commands.add( 'tableBorderWidth', new _commands_tableborderwidthcommand__WEBPACK_IMPORTED_MODULE_7__["default"]( editor, defaultTableProperties.borderWidth ) );
enableAlignmentProperty( schema, conversion, defaultTableProperties.alignment );
editor.commands.add( 'tableAlignment', new _commands_tablealignmentcommand__WEBPACK_IMPORTED_MODULE_10__["default"]( editor, defaultTableProperties.alignment ) );
enableTableToFigureProperty( schema, conversion, {
modelAttribute: 'tableWidth',
styleName: 'width',
defaultValue: defaultTableProperties.width
} );
editor.commands.add( 'tableWidth', new _commands_tablewidthcommand__WEBPACK_IMPORTED_MODULE_8__["default"]( editor, defaultTableProperties.width ) );
enableTableToFigureProperty( schema, conversion, {
modelAttribute: 'tableHeight',
styleName: 'height',
defaultValue: defaultTableProperties.height
} );
editor.commands.add( 'tableHeight', new _commands_tableheightcommand__WEBPACK_IMPORTED_MODULE_9__["default"]( editor, defaultTableProperties.height ) );
editor.data.addStyleProcessorRules( ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_1__.addBackgroundRules );
enableProperty( schema, conversion, {
modelAttribute: 'tableBackgroundColor',
styleName: 'background-color',
defaultValue: defaultTableProperties.backgroundColor
} );
editor.commands.add(
'tableBackgroundColor',
new _commands_tablebackgroundcolorcommand__WEBPACK_IMPORTED_MODULE_4__["default"]( editor, defaultTableProperties.backgroundColor )
);
}
}
// Enables `tableBorderStyle'`, `tableBorderColor'` and `tableBorderWidth'` attributes for table.
//
// @param {module:engine/model/schema~Schema} schema
// @param {module:engine/conversion/conversion~Conversion} conversion
// @param {Object} defaultBorder The default border values.
// @param {String} defaultBorder.color The default `tableBorderColor` value.
// @param {String} defaultBorder.style The default `tableBorderStyle` value.
// @param {String} defaultBorder.width The default `tableBorderWidth` value.
function enableBorderProperties( schema, conversion, defaultBorder ) {
const modelAttributes = {
width: 'tableBorderWidth',
color: 'tableBorderColor',
style: 'tableBorderStyle'
};
schema.extend( 'table', {
allowAttributes: Object.values( modelAttributes )
} );
(0,_converters_tableproperties__WEBPACK_IMPORTED_MODULE_3__.upcastBorderStyles)( conversion, 'table', modelAttributes, defaultBorder );
(0,_converters_tableproperties__WEBPACK_IMPORTED_MODULE_3__.downcastTableAttribute)( conversion, { modelAttribute: modelAttributes.color, styleName: 'border-color' } );
(0,_converters_tableproperties__WEBPACK_IMPORTED_MODULE_3__.downcastTableAttribute)( conversion, { modelAttribute: modelAttributes.style, styleName: 'border-style' } );
(0,_converters_tableproperties__WEBPACK_IMPORTED_MODULE_3__.downcastTableAttribute)( conversion, { modelAttribute: modelAttributes.width, styleName: 'border-width' } );
}
// Enables the `'alignment'` attribute for table.
//
// @param {module:engine/model/schema~Schema} schema
// @param {module:engine/conversion/conversion~Conversion} conversion
// @param {String} defaultValue The default alignment value.
function enableAlignmentProperty( schema, conversion, defaultValue ) {
schema.extend( 'table', {
allowAttributes: [ 'tableAlignment' ]
} );
conversion.for( 'downcast' )
.attributeToAttribute( {
model: {
name: 'table',
key: 'tableAlignment'
},
view: alignment => ( {
key: 'style',
value: {
// Model: `alignment:center` => CSS: `float:none`.
float: alignment === 'center' ? 'none' : alignment
}
} ),
converterPriority: 'high'
} );
conversion.for( 'upcast' )
// Support for the `float:*;` CSS definition for the table alignment.
.attributeToAttribute( {
view: {
name: /^(table|figure)$/,
styles: {
float: FLOAT_VALUES_REG_EXP
}
},
model: {
key: 'tableAlignment',
value: viewElement => {
let align = viewElement.getStyle( 'float' );
// CSS: `float:none` => Model: `alignment:center`.
if ( align === 'none' ) {
align = 'center';
}
return align === defaultValue ? null : align;
}
}
} )
// Support for the `align` attribute as the backward compatibility while pasting from other sources.
.attributeToAttribute( {
view: {
attributes: {
align: ALIGN_VALUES_REG_EXP
}
},
model: {
name: 'table',
key: 'tableAlignment',
value: viewElement => {
const align = viewElement.getAttribute( 'align' );
return align === defaultValue ? null : align;
}
}
} );
}
// Enables conversion for an attribute for simple view-model mappings.
//
// @param {module:engine/model/schema~Schema} schema
// @param {module:engine/conversion/conversion~Conversion} conversion
// @param {Object} options
// @param {String} options.modelAttribute
// @param {String} options.styleName
// @param {String} options.defaultValue The default value for the specified `modelAttribute`.
function enableProperty( schema, conversion, options ) {
const { modelAttribute } = options;
schema.extend( 'table', {
allowAttributes: [ modelAttribute ]
} );
(0,_converters_tableproperties__WEBPACK_IMPORTED_MODULE_3__.upcastStyleToAttribute)( conversion, { viewElement: 'table', ...options } );
(0,_converters_tableproperties__WEBPACK_IMPORTED_MODULE_3__.downcastTableAttribute)( conversion, options );
}
// Enables conversion for an attribute for simple view (figure) to model (table) mappings.
//
// @param {module:engine/model/schema~Schema} schema
// @param {module:engine/conversion/conversion~Conversion} conversion
// @param {Object} options
// @param {String} options.modelAttribute
// @param {String} options.styleName
function enableTableToFigureProperty( schema, conversion, options ) {
const { modelAttribute } = options;
schema.extend( 'table', {
allowAttributes: [ modelAttribute ]
} );
(0,_converters_tableproperties__WEBPACK_IMPORTED_MODULE_3__.upcastStyleToAttribute)( conversion, { viewElement: /^(table|figure)$/, ...options } );
(0,_converters_tableproperties__WEBPACK_IMPORTED_MODULE_3__.downcastAttributeToStyle)( conversion, { modelElement: 'table', ...options } );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/tableproperties/tablepropertiesui.js":
/*!*****************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/tableproperties/tablepropertiesui.js ***!
\*****************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ TablePropertiesUI)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/ui */ "./node_modules/ckeditor5/src/ui.js");
/* harmony import */ var lodash_es__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! lodash-es */ "./node_modules/lodash-es/debounce.js");
/* harmony import */ var _ui_tablepropertiesview__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./ui/tablepropertiesview */ "./node_modules/@ckeditor/ckeditor5-table/src/tableproperties/ui/tablepropertiesview.js");
/* harmony import */ var _theme_icons_table_properties_svg__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./../../theme/icons/table-properties.svg */ "./node_modules/@ckeditor/ckeditor5-table/theme/icons/table-properties.svg");
/* harmony import */ var _utils_ui_table_properties__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../utils/ui/table-properties */ "./node_modules/@ckeditor/ckeditor5-table/src/utils/ui/table-properties.js");
/* harmony import */ var _utils_ui_widget__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../utils/ui/widget */ "./node_modules/@ckeditor/ckeditor5-table/src/utils/ui/widget.js");
/* harmony import */ var _utils_ui_contextualballoon__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ../utils/ui/contextualballoon */ "./node_modules/@ckeditor/ckeditor5-table/src/utils/ui/contextualballoon.js");
/* harmony import */ var _utils_table_properties__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ../utils/table-properties */ "./node_modules/@ckeditor/ckeditor5-table/src/utils/table-properties.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/tableproperties/tablepropertiesui
*/
const ERROR_TEXT_TIMEOUT = 500;
// Map of view properties and related commands.
const propertyToCommandMap = {
borderStyle: 'tableBorderStyle',
borderColor: 'tableBorderColor',
borderWidth: 'tableBorderWidth',
backgroundColor: 'tableBackgroundColor',
width: 'tableWidth',
height: 'tableHeight',
alignment: 'tableAlignment'
};
/**
* The table properties UI plugin. It introduces the `'tableProperties'` button
* that opens a form allowing to specify visual styling of an entire table.
*
* It uses the
* {@link module:ui/panel/balloon/contextualballoon~ContextualBalloon contextual balloon plugin}.
*
* @extends module:core/plugin~Plugin
*/
class TablePropertiesUI extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [ ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.ContextualBalloon ];
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'TablePropertiesUI';
}
/**
* @inheritDoc
*/
constructor( editor ) {
super( editor );
editor.config.define( 'table.tableProperties', {
borderColors: _utils_ui_table_properties__WEBPACK_IMPORTED_MODULE_4__.defaultColors,
backgroundColors: _utils_ui_table_properties__WEBPACK_IMPORTED_MODULE_4__.defaultColors
} );
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const t = editor.t;
/**
* The default table properties.
*
* @protected
* @member {module:table/tableproperties~TablePropertiesOptions}
*/
this._defaultTableProperties = (0,_utils_table_properties__WEBPACK_IMPORTED_MODULE_7__.getNormalizedDefaultProperties)( editor.config.get( 'table.tableProperties.defaultProperties' ), {
includeAlignmentProperty: true
} );
/**
* The contextual balloon plugin instance.
*
* @private
* @member {module:ui/panel/balloon/contextualballoon~ContextualBalloon}
*/
this._balloon = editor.plugins.get( ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.ContextualBalloon );
/**
* The properties form view displayed inside the balloon.
*
* @member {module:table/tableproperties/ui/tablepropertiesview~TablePropertiesView}
*/
this.view = this._createPropertiesView();
/**
* The batch used to undo all changes made by the form (which are live, as the user types)
* when "Cancel" was pressed. Each time the view is shown, a new batch is created.
*
* @protected
* @member {module:engine/model/batch~Batch}
*/
this._undoStepBatch = null;
editor.ui.componentFactory.add( 'tableProperties', locale => {
const view = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.ButtonView( locale );
view.set( {
label: t( 'Table properties' ),
icon: _theme_icons_table_properties_svg__WEBPACK_IMPORTED_MODULE_3__["default"],
tooltip: true
} );
this.listenTo( view, 'execute', () => this._showView() );
const commands = Object.values( propertyToCommandMap )
.map( commandName => editor.commands.get( commandName ) );
view.bind( 'isEnabled' ).toMany( commands, 'isEnabled', ( ...areEnabled ) => (
areEnabled.some( isCommandEnabled => isCommandEnabled )
) );
return view;
} );
}
/**
* @inheritDoc
*/
destroy() {
super.destroy();
// Destroy created UI components as they are not automatically destroyed.
// See https://github.com/ckeditor/ckeditor5/issues/1341.
this.view.destroy();
}
/**
* Creates the {@link module:table/tableproperties/ui/tablepropertiesview~TablePropertiesView} instance.
*
* @private
* @returns {module:table/tableproperties/ui/tablepropertiesview~TablePropertiesView} The table
* properties form view instance.
*/
_createPropertiesView() {
const editor = this.editor;
const config = editor.config.get( 'table.tableProperties' );
const borderColorsConfig = (0,ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.normalizeColorOptions)( config.borderColors );
const localizedBorderColors = (0,ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.getLocalizedColorOptions)( editor.locale, borderColorsConfig );
const backgroundColorsConfig = (0,ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.normalizeColorOptions)( config.backgroundColors );
const localizedBackgroundColors = (0,ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.getLocalizedColorOptions)( editor.locale, backgroundColorsConfig );
const view = new _ui_tablepropertiesview__WEBPACK_IMPORTED_MODULE_2__["default"]( editor.locale, {
borderColors: localizedBorderColors,
backgroundColors: localizedBackgroundColors,
defaultTableProperties: this._defaultTableProperties
} );
const t = editor.t;
// Render the view so its #element is available for the clickOutsideHandler.
view.render();
this.listenTo( view, 'submit', () => {
this._hideView();
} );
this.listenTo( view, 'cancel', () => {
// https://github.com/ckeditor/ckeditor5/issues/6180
if ( this._undoStepBatch.operations.length ) {
editor.execute( 'undo', this._undoStepBatch );
}
this._hideView();
} );
// Close the balloon on Esc key press.
view.keystrokes.set( 'Esc', ( data, cancel ) => {
this._hideView();
cancel();
} );
// Close on click outside of balloon panel element.
(0,ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.clickOutsideHandler)( {
emitter: view,
activator: () => this._isViewInBalloon,
contextElements: [ this._balloon.view.element ],
callback: () => this._hideView()
} );
const colorErrorText = (0,_utils_ui_table_properties__WEBPACK_IMPORTED_MODULE_4__.getLocalizedColorErrorText)( t );
const lengthErrorText = (0,_utils_ui_table_properties__WEBPACK_IMPORTED_MODULE_4__.getLocalizedLengthErrorText)( t );
// Create the "UI -> editor data" binding.
// These listeners update the editor data (via table commands) when any observable
// property of the view has changed. They also validate the value and display errors in the UI
// when necessary. This makes the view live, which means the changes are
// visible in the editing as soon as the user types or changes fields' values.
view.on(
'change:borderStyle',
this._getPropertyChangeCallback( 'tableBorderStyle', this._defaultTableProperties.borderStyle )
);
view.on( 'change:borderColor', this._getValidatedPropertyChangeCallback( {
viewField: view.borderColorInput,
commandName: 'tableBorderColor',
errorText: colorErrorText,
validator: _utils_ui_table_properties__WEBPACK_IMPORTED_MODULE_4__.colorFieldValidator,
defaultValue: this._defaultTableProperties.borderColor
} ) );
view.on( 'change:borderWidth', this._getValidatedPropertyChangeCallback( {
viewField: view.borderWidthInput,
commandName: 'tableBorderWidth',
errorText: lengthErrorText,
validator: _utils_ui_table_properties__WEBPACK_IMPORTED_MODULE_4__.lineWidthFieldValidator,
defaultValue: this._defaultTableProperties.borderWidth
} ) );
view.on( 'change:backgroundColor', this._getValidatedPropertyChangeCallback( {
viewField: view.backgroundInput,
commandName: 'tableBackgroundColor',
errorText: colorErrorText,
validator: _utils_ui_table_properties__WEBPACK_IMPORTED_MODULE_4__.colorFieldValidator,
defaultValue: this._defaultTableProperties.backgroundColor
} ) );
view.on( 'change:width', this._getValidatedPropertyChangeCallback( {
viewField: view.widthInput,
commandName: 'tableWidth',
errorText: lengthErrorText,
validator: _utils_ui_table_properties__WEBPACK_IMPORTED_MODULE_4__.lengthFieldValidator,
defaultValue: this._defaultTableProperties.width
} ) );
view.on( 'change:height', this._getValidatedPropertyChangeCallback( {
viewField: view.heightInput,
commandName: 'tableHeight',
errorText: lengthErrorText,
validator: _utils_ui_table_properties__WEBPACK_IMPORTED_MODULE_4__.lengthFieldValidator,
defaultValue: this._defaultTableProperties.height
} ) );
view.on(
'change:alignment',
this._getPropertyChangeCallback( 'tableAlignment', this._defaultTableProperties.alignment )
);
return view;
}
/**
* In this method the "editor data -> UI" binding is happening.
*
* When executed, this method obtains selected table property values from various table commands
* and passes them to the {@link #view}.
*
* This way, the UI stays up–to–date with the editor data.
*
* @private
*/
_fillViewFormFromCommandValues() {
const commands = this.editor.commands;
const borderStyleCommand = commands.get( 'tableBorderStyle' );
Object.entries( propertyToCommandMap )
.map( ( [ property, commandName ] ) => {
const defaultValue = this._defaultTableProperties[ property ] || '';
return [ property, commands.get( commandName ).value || defaultValue ];
} )
.forEach( ( [ property, value ] ) => {
// Do not set the `border-color` and `border-width` fields if `border-style:none`.
if ( ( property === 'borderColor' || property === 'borderWidth' ) && borderStyleCommand.value === 'none' ) {
return;
}
this.view.set( property, value );
} );
}
/**
* Shows the {@link #view} in the {@link #_balloon}.
*
* **Note**: Each time a view is shown, the new {@link #_undoStepBatch} is created that contains
* all changes made to the document when the view is visible, allowing a single undo step
* for all of them.
*
* @protected
*/
_showView() {
const editor = this.editor;
this.listenTo( editor.ui, 'update', () => {
this._updateView();
} );
// Update the view with the model values.
this._fillViewFormFromCommandValues();
this._balloon.add( {
view: this.view,
position: (0,_utils_ui_contextualballoon__WEBPACK_IMPORTED_MODULE_6__.getBalloonTablePositionData)( editor )
} );
// Create a new batch. Clicking "Cancel" will undo this batch.
this._undoStepBatch = editor.model.createBatch();
// Basic a11y.
this.view.focus();
}
/**
* Removes the {@link #view} from the {@link #_balloon}.
*
* @protected
*/
_hideView() {
const editor = this.editor;
this.stopListening( editor.ui, 'update' );
// Blur any input element before removing it from DOM to prevent issues in some browsers.
// See https://github.com/ckeditor/ckeditor5/issues/1501.
this.view.saveButtonView.focus();
this._balloon.remove( this.view );
// Make sure the focus is not lost in the process by putting it directly
// into the editing view.
this.editor.editing.view.focus();
}
/**
* Repositions the {@link #_balloon} or hides the {@link #view} if a table is no longer selected.
*
* @protected
*/
_updateView() {
const editor = this.editor;
const viewDocument = editor.editing.view.document;
if ( !(0,_utils_ui_widget__WEBPACK_IMPORTED_MODULE_5__.getTableWidgetAncestor)( viewDocument.selection ) ) {
this._hideView();
} else if ( this._isViewVisible ) {
(0,_utils_ui_contextualballoon__WEBPACK_IMPORTED_MODULE_6__.repositionContextualBalloon)( editor, 'table' );
}
}
/**
* Returns `true` when the {@link #view} is the visible in the {@link #_balloon}.
*
* @private
* @type {Boolean}
*/
get _isViewVisible() {
return this._balloon.visibleView === this.view;
}
/**
* Returns `true` when the {@link #view} is in the {@link #_balloon}.
*
* @private
* @type {Boolean}
*/
get _isViewInBalloon() {
return this._balloon.hasView( this.view );
}
/**
* Creates a callback that when executed upon {@link #view view's} property change
* executes a related editor command with the new property value.
*
* If new value will be set to the default value, the command will not be executed.
*
* @private
* @param {String} commandName The command that will be executed.
* @param {String} defaultValue The default value of the command.
* @returns {Function}
*/
_getPropertyChangeCallback( commandName, defaultValue ) {
return ( evt, propertyName, newValue, oldValue ) => {
// If the "oldValue" is missing and "newValue" is set to the default value, do not execute the command.
// It is an initial call (when opening the table properties view).
if ( !oldValue && defaultValue === newValue ) {
return;
}
this.editor.execute( commandName, {
value: newValue,
batch: this._undoStepBatch
} );
};
}
/**
* Creates a callback that when executed upon {@link #view view's} property change:
* * executes a related editor command with the new property value if the value is valid,
* * or sets the error text next to the invalid field, if the value did not pass the validation.
*
* @private
* @param {Object} options
* @param {String} options.commandName
* @param {module:ui/view~View} options.viewField
* @param {Function} options.validator
* @param {String} options.errorText
* @param {String} options.defaultValue
* @returns {Function}
*/
_getValidatedPropertyChangeCallback( options ) {
const { commandName, viewField, validator, errorText, defaultValue } = options;
const setErrorTextDebounced = (0,lodash_es__WEBPACK_IMPORTED_MODULE_8__["default"])( () => {
viewField.errorText = errorText;
}, ERROR_TEXT_TIMEOUT );
return ( evt, propertyName, newValue, oldValue ) => {
setErrorTextDebounced.cancel();
// If the "oldValue" is missing and "newValue" is set to the default value, do not execute the command.
// It is an initial call (when opening the table properties view).
if ( !oldValue && defaultValue === newValue ) {
return;
}
if ( validator( newValue ) ) {
this.editor.execute( commandName, {
value: newValue,
batch: this._undoStepBatch
} );
viewField.errorText = null;
} else {
setErrorTextDebounced();
}
};
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/tableproperties/ui/tablepropertiesview.js":
/*!**********************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/tableproperties/ui/tablepropertiesview.js ***!
\**********************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ TablePropertiesView)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/ui */ "./node_modules/ckeditor5/src/ui.js");
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var _utils_ui_table_properties__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../../utils/ui/table-properties */ "./node_modules/@ckeditor/ckeditor5-table/src/utils/ui/table-properties.js");
/* harmony import */ var _ui_formrowview__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../../ui/formrowview */ "./node_modules/@ckeditor/ckeditor5-table/src/ui/formrowview.js");
/* harmony import */ var _theme_form_css__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../../../theme/form.css */ "./node_modules/@ckeditor/ckeditor5-table/theme/form.css");
/* harmony import */ var _theme_tableform_css__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ../../../theme/tableform.css */ "./node_modules/@ckeditor/ckeditor5-table/theme/tableform.css");
/* harmony import */ var _theme_tableproperties_css__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ../../../theme/tableproperties.css */ "./node_modules/@ckeditor/ckeditor5-table/theme/tableproperties.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/tableproperties/ui/tablepropertiesview
*/
const ALIGNMENT_ICONS = {
left: ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_2__.icons.objectLeft,
center: ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_2__.icons.objectCenter,
right: ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_2__.icons.objectRight
};
/**
* The class representing a table properties form, allowing users to customize
* certain style aspects of a table, for instance, border, background color, alignment, etc..
*
* @extends module:ui/view~View
*/
class TablePropertiesView extends ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.View {
/**
* @param {module:utils/locale~Locale} locale The {@link module:core/editor/editor~Editor#locale} instance.
* @param {Object} options Additional configuration of the view.
* @param {module:table/table~TableColorConfig} options.borderColors A configuration of the border
* color palette used by the
* {@link module:table/tableproperties/ui/tablepropertiesview~TablePropertiesView#borderColorInput}.
* @param {module:table/table~TableColorConfig} options.backgroundColors A configuration of the background
* color palette used by the
* {@link module:table/tableproperties/ui/tablepropertiesview~TablePropertiesView#backgroundInput}.
* @param {module:table/tableproperties~TablePropertiesOptions} options.defaultTableProperties The default table properties.
*/
constructor( locale, options ) {
super( locale );
this.set( {
/**
* The value of the border style.
*
* @observable
* @default ''
* @member #borderStyle
*/
borderStyle: '',
/**
* The value of the border width style.
*
* @observable
* @default ''
* @member #borderWidth
*/
borderWidth: '',
/**
* The value of the border color style.
*
* @observable
* @default ''
* @member #borderColor
*/
borderColor: '',
/**
* The value of the background color style.
*
* @observable
* @default ''
* @member #backgroundColor
*/
backgroundColor: '',
/**
* The value of the table width style.
*
* @observable
* @default ''
* @member #width
*/
width: '',
/**
* The value of the table height style.
*
* @observable
* @default ''
* @member #height
*/
height: '',
/**
* The value of the table alignment style.
*
* @observable
* @default ''
* @member #alignment
*/
alignment: ''
} );
/**
* Options passed to the view. See {@link #constructor} to learn more.
*
* @protected
* @member {Object}
*/
this.options = options;
const { borderStyleDropdown, borderWidthInput, borderColorInput, borderRowLabel } = this._createBorderFields();
const { backgroundRowLabel, backgroundInput } = this._createBackgroundFields();
const { widthInput, operatorLabel, heightInput, dimensionsLabel } = this._createDimensionFields();
const { alignmentToolbar, alignmentLabel } = this._createAlignmentFields();
/**
* Tracks information about the DOM focus in the form.
*
* @readonly
* @member {module:utils/focustracker~FocusTracker}
*/
this.focusTracker = new ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__.FocusTracker();
/**
* An instance of the {@link module:utils/keystrokehandler~KeystrokeHandler}.
*
* @readonly
* @member {module:utils/keystrokehandler~KeystrokeHandler}
*/
this.keystrokes = new ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__.KeystrokeHandler();
/**
* A collection of child views in the form.
*
* @readonly
* @type {module:ui/viewcollection~ViewCollection}
*/
this.children = this.createCollection();
/**
* A dropdown that allows selecting the style of the table border.
*
* @readonly
* @member {module:ui/dropdown/dropdownview~DropdownView}
*/
this.borderStyleDropdown = borderStyleDropdown;
/**
* An input that allows specifying the width of the table border.
*
* @readonly
* @member {module:ui/inputtext/inputtextview~InputTextView}
*/
this.borderWidthInput = borderWidthInput;
/**
* An input that allows specifying the color of the table border.
*
* @readonly
* @member {module:table/ui/colorinputview~ColorInputView}
*/
this.borderColorInput = borderColorInput;
/**
* An input that allows specifying the table background color.
*
* @readonly
* @member {module:table/ui/colorinputview~ColorInputView}
*/
this.backgroundInput = backgroundInput;
/**
* An input that allows specifying the table width.
*
* @readonly
* @member {module:ui/inputtext/inputtextview~InputTextView}
*/
this.widthInput = widthInput;
/**
* An input that allows specifying the table height.
*
* @readonly
* @member {module:ui/inputtext/inputtextview~InputTextView}
*/
this.heightInput = heightInput;
/**
* A toolbar with buttons that allow changing the alignment of an entire table.
* @readonly
* @member {module:ui/toolbar/toolbar~ToolbarView}
*/
this.alignmentToolbar = alignmentToolbar;
// Defer creating to make sure other fields are present and the Save button can
// bind its #isEnabled to their error messages so there's no way to save unless all
// fields are valid.
const { saveButtonView, cancelButtonView } = this._createActionButtons();
/**
* The "Save" button view.
*
* @member {module:ui/button/buttonview~ButtonView}
*/
this.saveButtonView = saveButtonView;
/**
* The "Cancel" button view.
*
* @member {module:ui/button/buttonview~ButtonView}
*/
this.cancelButtonView = cancelButtonView;
/**
* A collection of views that can be focused in the form.
*
* @readonly
* @protected
* @member {module:ui/viewcollection~ViewCollection}
*/
this._focusables = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.ViewCollection();
/**
* Helps cycling over {@link #_focusables} in the form.
*
* @readonly
* @protected
* @member {module:ui/focuscycler~FocusCycler}
*/
this._focusCycler = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.FocusCycler( {
focusables: this._focusables,
focusTracker: this.focusTracker,
keystrokeHandler: this.keystrokes,
actions: {
// Navigate form fields backwards using the Shift + Tab keystroke.
focusPrevious: 'shift + tab',
// Navigate form fields forwards using the Tab key.
focusNext: 'tab'
}
} );
// Form header.
this.children.add( new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.FormHeaderView( locale, {
label: this.t( 'Table properties' )
} ) );
// Border row.
this.children.add( new _ui_formrowview__WEBPACK_IMPORTED_MODULE_4__["default"]( locale, {
labelView: borderRowLabel,
children: [
borderRowLabel,
borderStyleDropdown,
borderColorInput,
borderWidthInput
],
class: 'ck-table-form__border-row'
} ) );
// Background row.
this.children.add( new _ui_formrowview__WEBPACK_IMPORTED_MODULE_4__["default"]( locale, {
labelView: backgroundRowLabel,
children: [
backgroundRowLabel,
backgroundInput
],
class: 'ck-table-form__background-row'
} ) );
this.children.add( new _ui_formrowview__WEBPACK_IMPORTED_MODULE_4__["default"]( locale, {
children: [
// Dimensions row.
new _ui_formrowview__WEBPACK_IMPORTED_MODULE_4__["default"]( locale, {
labelView: dimensionsLabel,
children: [
dimensionsLabel,
widthInput,
operatorLabel,
heightInput
],
class: 'ck-table-form__dimensions-row'
} ),
// Alignment row.
new _ui_formrowview__WEBPACK_IMPORTED_MODULE_4__["default"]( locale, {
labelView: alignmentLabel,
children: [
alignmentLabel,
alignmentToolbar
],
class: 'ck-table-properties-form__alignment-row'
} )
]
} ) );
// Action row.
this.children.add( new _ui_formrowview__WEBPACK_IMPORTED_MODULE_4__["default"]( locale, {
children: [
this.saveButtonView,
this.cancelButtonView
],
class: 'ck-table-form__action-row'
} ) );
this.setTemplate( {
tag: 'form',
attributes: {
class: [
'ck',
'ck-form',
'ck-table-form',
'ck-table-properties-form'
],
// https://github.com/ckeditor/ckeditor5-link/issues/90
tabindex: '-1'
},
children: this.children
} );
}
/**
* @inheritDoc
*/
render() {
super.render();
// Enable the "submit" event for this view. It can be triggered by the #saveButtonView
// which is of the "submit" DOM "type".
(0,ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.submitHandler)( {
view: this
} );
[
this.borderStyleDropdown,
this.borderColorInput,
this.borderWidthInput,
this.backgroundInput,
this.widthInput,
this.heightInput,
this.alignmentToolbar,
this.saveButtonView,
this.cancelButtonView
].forEach( view => {
// Register the view as focusable.
this._focusables.add( view );
// Register the view in the focus tracker.
this.focusTracker.add( view.element );
} );
// Mainly for closing using "Esc" and navigation using "Tab".
this.keystrokes.listenTo( this.element );
}
/**
* @inheritDoc
*/
destroy() {
super.destroy();
this.focusTracker.destroy();
this.keystrokes.destroy();
}
/**
* Focuses the fist focusable field in the form.
*/
focus() {
this._focusCycler.focusFirst();
}
/**
* Creates the following form fields:
*
* * {@link #borderStyleDropdown},
* * {@link #borderWidthInput},
* * {@link #borderColorInput}.
*
* @private
* @returns {Object.<String,module:ui/view~View>}
*/
_createBorderFields() {
const defaultTableProperties = this.options.defaultTableProperties;
const defaultBorder = {
style: defaultTableProperties.borderStyle,
width: defaultTableProperties.borderWidth,
color: defaultTableProperties.borderColor
};
const colorInputCreator = (0,_utils_ui_table_properties__WEBPACK_IMPORTED_MODULE_3__.getLabeledColorInputCreator)( {
colorConfig: this.options.borderColors,
columns: 5,
defaultColorValue: defaultBorder.color
} );
const locale = this.locale;
const t = this.t;
// -- Group label ---------------------------------------------
const borderRowLabel = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.LabelView( locale );
borderRowLabel.text = t( 'Border' );
// -- Style ---------------------------------------------------
const styleLabels = (0,_utils_ui_table_properties__WEBPACK_IMPORTED_MODULE_3__.getBorderStyleLabels)( this.t );
const borderStyleDropdown = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.LabeledFieldView( locale, ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.createLabeledDropdown );
borderStyleDropdown.set( {
label: t( 'Style' ),
class: 'ck-table-form__border-style'
} );
borderStyleDropdown.fieldView.buttonView.set( {
isOn: false,
withText: true,
tooltip: t( 'Style' )
} );
borderStyleDropdown.fieldView.buttonView.bind( 'label' ).to( this, 'borderStyle', value => {
return styleLabels[ value ? value : 'none' ];
} );
borderStyleDropdown.fieldView.on( 'execute', evt => {
this.borderStyle = evt.source._borderStyleValue;
} );
borderStyleDropdown.bind( 'isEmpty' ).to( this, 'borderStyle', value => !value );
(0,ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.addListToDropdown)( borderStyleDropdown.fieldView, (0,_utils_ui_table_properties__WEBPACK_IMPORTED_MODULE_3__.getBorderStyleDefinitions)( this, defaultBorder.style ) );
// -- Width ---------------------------------------------------
const borderWidthInput = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.LabeledFieldView( locale, ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.createLabeledInputText );
borderWidthInput.set( {
label: t( 'Width' ),
class: 'ck-table-form__border-width'
} );
borderWidthInput.fieldView.bind( 'value' ).to( this, 'borderWidth' );
borderWidthInput.bind( 'isEnabled' ).to( this, 'borderStyle', isBorderStyleSet );
borderWidthInput.fieldView.on( 'input', () => {
this.borderWidth = borderWidthInput.fieldView.element.value;
} );
// -- Color ---------------------------------------------------
const borderColorInput = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.LabeledFieldView( locale, colorInputCreator );
borderColorInput.set( {
label: t( 'Color' ),
class: 'ck-table-form__border-color'
} );
borderColorInput.fieldView.bind( 'value' ).to( this, 'borderColor' );
borderColorInput.bind( 'isEnabled' ).to( this, 'borderStyle', isBorderStyleSet );
borderColorInput.fieldView.on( 'input', () => {
this.borderColor = borderColorInput.fieldView.value;
} );
// Reset the border color and width fields depending on the `border-style` value.
this.on( 'change:borderStyle', ( evt, name, newValue, oldValue ) => {
// When removing the border (`border-style:none`), clear the remaining `border-*` properties.
// See: https://github.com/ckeditor/ckeditor5/issues/6227.
if ( !isBorderStyleSet( newValue ) ) {
this.borderColor = '';
this.borderWidth = '';
}
// When setting the `border-style` from `none`, set the default `border-color` and `border-width` properties.
if ( !isBorderStyleSet( oldValue ) ) {
this.borderColor = defaultBorder.color;
this.borderWidth = defaultBorder.width;
}
} );
return {
borderRowLabel,
borderStyleDropdown,
borderColorInput,
borderWidthInput
};
}
/**
* Creates the following form fields:
*
* * {@link #backgroundInput}.
*
* @private
* @returns {Object.<String,module:ui/view~View>}
*/
_createBackgroundFields() {
const locale = this.locale;
const t = this.t;
// -- Group label ---------------------------------------------
const backgroundRowLabel = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.LabelView( locale );
backgroundRowLabel.text = t( 'Background' );
// -- Background color input -----------------------------------
const backgroundInputCreator = (0,_utils_ui_table_properties__WEBPACK_IMPORTED_MODULE_3__.getLabeledColorInputCreator)( {
colorConfig: this.options.backgroundColors,
columns: 5,
defaultColorValue: this.options.defaultTableProperties.backgroundColor
} );
const backgroundInput = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.LabeledFieldView( locale, backgroundInputCreator );
backgroundInput.set( {
label: t( 'Color' ),
class: 'ck-table-properties-form__background'
} );
backgroundInput.fieldView.bind( 'value' ).to( this, 'backgroundColor' );
backgroundInput.fieldView.on( 'input', () => {
this.backgroundColor = backgroundInput.fieldView.value;
} );
return {
backgroundRowLabel,
backgroundInput
};
}
/**
* Creates the following form fields:
*
* * {@link #widthInput}.
* * {@link #heightInput}.
*
* @private
* @returns {module:ui/labeledfield/labeledfieldview~LabeledFieldView}
*/
_createDimensionFields() {
const locale = this.locale;
const t = this.t;
// -- Label ---------------------------------------------------
const dimensionsLabel = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.LabelView( locale );
dimensionsLabel.text = t( 'Dimensions' );
// -- Width ---------------------------------------------------
const widthInput = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.LabeledFieldView( locale, ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.createLabeledInputText );
widthInput.set( {
label: t( 'Width' ),
class: 'ck-table-form__dimensions-row__width'
} );
widthInput.fieldView.bind( 'value' ).to( this, 'width' );
widthInput.fieldView.on( 'input', () => {
this.width = widthInput.fieldView.element.value;
} );
// -- Operator ---------------------------------------------------
const operatorLabel = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.View( locale );
operatorLabel.setTemplate( {
tag: 'span',
attributes: {
class: [
'ck-table-form__dimension-operator'
]
},
children: [
{ text: '×' }
]
} );
// -- Height ---------------------------------------------------
const heightInput = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.LabeledFieldView( locale, ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.createLabeledInputText );
heightInput.set( {
label: t( 'Height' ),
class: 'ck-table-form__dimensions-row__height'
} );
heightInput.fieldView.bind( 'value' ).to( this, 'height' );
heightInput.fieldView.on( 'input', () => {
this.height = heightInput.fieldView.element.value;
} );
return {
dimensionsLabel,
widthInput,
operatorLabel,
heightInput
};
}
/**
* Creates the following form fields:
*
* * {@link #alignmentToolbar},
*
* @private
* @returns {Object.<String,module:ui/view~View>}
*/
_createAlignmentFields() {
const locale = this.locale;
const t = this.t;
// -- Label ---------------------------------------------------
const alignmentLabel = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.LabelView( locale );
alignmentLabel.text = t( 'Alignment' );
// -- Toolbar ---------------------------------------------------
const alignmentToolbar = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.ToolbarView( locale );
alignmentToolbar.set( {
isCompact: true,
ariaLabel: t( 'Table alignment toolbar' )
} );
(0,_utils_ui_table_properties__WEBPACK_IMPORTED_MODULE_3__.fillToolbar)( {
view: this,
icons: ALIGNMENT_ICONS,
toolbar: alignmentToolbar,
labels: this._alignmentLabels,
propertyName: 'alignment',
defaultValue: this.options.defaultTableProperties.alignment
} );
return {
alignmentLabel,
alignmentToolbar
};
}
/**
* Creates the following form controls:
*
* * {@link #saveButtonView},
* * {@link #cancelButtonView}.
*
* @private
* @returns {Object.<String,module:ui/view~View>}
*/
_createActionButtons() {
const locale = this.locale;
const t = this.t;
const saveButtonView = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.ButtonView( locale );
const cancelButtonView = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.ButtonView( locale );
const fieldsThatShouldValidateToSave = [
this.borderWidthInput,
this.borderColorInput,
this.backgroundInput,
this.widthInput,
this.heightInput
];
saveButtonView.set( {
label: t( 'Save' ),
icon: ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_2__.icons.check,
class: 'ck-button-save',
type: 'submit',
withText: true
} );
saveButtonView.bind( 'isEnabled' ).toMany( fieldsThatShouldValidateToSave, 'errorText', ( ...errorTexts ) => {
return errorTexts.every( errorText => !errorText );
} );
cancelButtonView.set( {
label: t( 'Cancel' ),
icon: ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_2__.icons.cancel,
class: 'ck-button-cancel',
withText: true
} );
cancelButtonView.delegate( 'execute' ).to( this, 'cancel' );
return {
saveButtonView, cancelButtonView
};
}
/**
* Provides localized labels for {@link #alignmentToolbar} buttons.
*
* @private
* @type {Object.<String,String>}
*/
get _alignmentLabels() {
const locale = this.locale;
const t = this.t;
const left = t( 'Align table to the left' );
const center = t( 'Center table' );
const right = t( 'Align table to the right' );
// Returns object with a proper order of labels.
if ( locale.uiLanguageDirection === 'rtl' ) {
return { right, center, left };
} else {
return { left, center, right };
}
}
}
function isBorderStyleSet( value ) {
return value !== 'none';
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/tableselection.js":
/*!**********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/tableselection.js ***!
\**********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ TableSelection)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/* harmony import */ var _tablewalker__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./tablewalker */ "./node_modules/@ckeditor/ckeditor5-table/src/tablewalker.js");
/* harmony import */ var _tableutils__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./tableutils */ "./node_modules/@ckeditor/ckeditor5-table/src/tableutils.js");
/* harmony import */ var _utils_structure__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./utils/structure */ "./node_modules/@ckeditor/ckeditor5-table/src/utils/structure.js");
/* harmony import */ var _theme_tableselection_css__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../theme/tableselection.css */ "./node_modules/@ckeditor/ckeditor5-table/theme/tableselection.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/tableselection
*/
/**
* This plugin enables the advanced table cells, rows and columns selection.
* It is loaded automatically by the {@link module:table/table~Table} plugin.
*
* @extends module:core/plugin~Plugin
*/
class TableSelection extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'TableSelection';
}
/**
* @inheritDoc
*/
static get requires() {
return [ _tableutils__WEBPACK_IMPORTED_MODULE_3__["default"], _tableutils__WEBPACK_IMPORTED_MODULE_3__["default"] ];
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const model = editor.model;
this.listenTo( model, 'deleteContent', ( evt, args ) => this._handleDeleteContent( evt, args ), { priority: 'high' } );
this._defineSelectionConverter();
this._enablePluginDisabling(); // sic!
}
/**
* Returns the currently selected table cells or `null` if it is not a table cells selection.
*
* @returns {Array.<module:engine/model/element~Element>|null}
*/
getSelectedTableCells() {
const tableUtils = this.editor.plugins.get( _tableutils__WEBPACK_IMPORTED_MODULE_3__["default"] );
const selection = this.editor.model.document.selection;
const selectedCells = tableUtils.getSelectedTableCells( selection );
if ( selectedCells.length == 0 ) {
return null;
}
// This should never happen, but let's know if it ever happens.
// @if CK_DEBUG // /* istanbul ignore next */
// @if CK_DEBUG // if ( selectedCells.length != selection.rangeCount ) {
// @if CK_DEBUG // console.warn( 'Mixed selection warning. The selection contains table cells and some other ranges.' );
// @if CK_DEBUG // }
return selectedCells;
}
/**
* Returns the selected table fragment as a document fragment.
*
* @returns {module:engine/model/documentfragment~DocumentFragment|null}
*/
getSelectionAsFragment() {
const tableUtils = this.editor.plugins.get( _tableutils__WEBPACK_IMPORTED_MODULE_3__["default"] );
const selectedCells = this.getSelectedTableCells();
if ( !selectedCells ) {
return null;
}
return this.editor.model.change( writer => {
const documentFragment = writer.createDocumentFragment();
const { first: firstColumn, last: lastColumn } = tableUtils.getColumnIndexes( selectedCells );
const { first: firstRow, last: lastRow } = tableUtils.getRowIndexes( selectedCells );
const sourceTable = selectedCells[ 0 ].findAncestor( 'table' );
let adjustedLastRow = lastRow;
let adjustedLastColumn = lastColumn;
// If the selection is rectangular there could be a case of all cells in the last row/column spanned over
// next row/column so the real lastRow/lastColumn should be updated.
if ( tableUtils.isSelectionRectangular( selectedCells ) ) {
const dimensions = {
firstColumn,
lastColumn,
firstRow,
lastRow
};
adjustedLastRow = (0,_utils_structure__WEBPACK_IMPORTED_MODULE_4__.adjustLastRowIndex)( sourceTable, dimensions );
adjustedLastColumn = (0,_utils_structure__WEBPACK_IMPORTED_MODULE_4__.adjustLastColumnIndex)( sourceTable, dimensions );
}
const cropDimensions = {
startRow: firstRow,
startColumn: firstColumn,
endRow: adjustedLastRow,
endColumn: adjustedLastColumn
};
const table = (0,_utils_structure__WEBPACK_IMPORTED_MODULE_4__.cropTableToDimensions)( sourceTable, cropDimensions, writer );
writer.insert( table, documentFragment, 0 );
return documentFragment;
} );
}
/**
* Sets the model selection based on given anchor and target cells (can be the same cell).
* Takes care of setting the backward flag.
*
* const modelRoot = editor.model.document.getRoot();
* const firstCell = modelRoot.getNodeByPath( [ 0, 0, 0 ] );
* const lastCell = modelRoot.getNodeByPath( [ 0, 0, 1 ] );
*
* const tableSelection = editor.plugins.get( 'TableSelection' );
* tableSelection.setCellSelection( firstCell, lastCell );
*
* @param {module:engine/model/element~Element} anchorCell
* @param {module:engine/model/element~Element} targetCell
*/
setCellSelection( anchorCell, targetCell ) {
const cellsToSelect = this._getCellsToSelect( anchorCell, targetCell );
this.editor.model.change( writer => {
writer.setSelection(
cellsToSelect.cells.map( cell => writer.createRangeOn( cell ) ),
{ backward: cellsToSelect.backward }
);
} );
}
/**
* Returns the focus cell from the current selection.
*
* @returns {module:engine/model/element~Element}
*/
getFocusCell() {
const selection = this.editor.model.document.selection;
const focusCellRange = [ ...selection.getRanges() ].pop();
const element = focusCellRange.getContainedElement();
if ( element && element.is( 'element', 'tableCell' ) ) {
return element;
}
return null;
}
/**
* Returns the anchor cell from the current selection.
*
* @returns {module:engine/model/element~Element} anchorCell
*/
getAnchorCell() {
const selection = this.editor.model.document.selection;
const anchorCellRange = (0,ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__.first)( selection.getRanges() );
const element = anchorCellRange.getContainedElement();
if ( element && element.is( 'element', 'tableCell' ) ) {
return element;
}
return null;
}
/**
* Defines a selection converter which marks the selected cells with a specific class.
*
* The real DOM selection is put in the last cell. Since the order of ranges is dependent on whether the
* selection is backward or not, the last cell will usually be close to the "focus" end of the selection
* (a selection has anchor and focus).
*
* The real DOM selection is then hidden with CSS.
*
* @private
*/
_defineSelectionConverter() {
const editor = this.editor;
const highlighted = new Set();
editor.conversion.for( 'editingDowncast' ).add( dispatcher => dispatcher.on( 'selection', ( evt, data, conversionApi ) => {
const viewWriter = conversionApi.writer;
clearHighlightedTableCells( viewWriter );
const selectedCells = this.getSelectedTableCells();
if ( !selectedCells ) {
return;
}
for ( const tableCell of selectedCells ) {
const viewElement = conversionApi.mapper.toViewElement( tableCell );
viewWriter.addClass( 'ck-editor__editable_selected', viewElement );
highlighted.add( viewElement );
}
const lastViewCell = conversionApi.mapper.toViewElement( selectedCells[ selectedCells.length - 1 ] );
viewWriter.setSelection( lastViewCell, 0 );
}, { priority: 'lowest' } ) );
function clearHighlightedTableCells( writer ) {
for ( const previouslyHighlighted of highlighted ) {
writer.removeClass( 'ck-editor__editable_selected', previouslyHighlighted );
}
highlighted.clear();
}
}
/**
* Creates a listener that reacts to changes in {@link #isEnabled} and, if the plugin was disabled,
* it collapses the multi-cell selection to a regular selection placed inside a table cell.
*
* This listener helps features that disable the table selection plugin bring the selection
* to a clear state they can work with (for instance, because they don't support multiple cell selection).
*/
_enablePluginDisabling() {
const editor = this.editor;
this.on( 'change:isEnabled', () => {
if ( !this.isEnabled ) {
const selectedCells = this.getSelectedTableCells();
if ( !selectedCells ) {
return;
}
editor.model.change( writer => {
const position = writer.createPositionAt( selectedCells[ 0 ], 0 );
const range = editor.model.schema.getNearestSelectionRange( position );
writer.setSelection( range );
} );
}
} );
}
/**
* Overrides the default `model.deleteContent()` behavior over a selected table fragment.
*
* @private
* @param {module:utils/eventinfo~EventInfo} event
* @param {Array.<*>} args Delete content method arguments.
*/
_handleDeleteContent( event, args ) {
const tableUtils = this.editor.plugins.get( _tableutils__WEBPACK_IMPORTED_MODULE_3__["default"] );
const [ selection, options ] = args;
const model = this.editor.model;
const isBackward = !options || options.direction == 'backward';
const selectedTableCells = tableUtils.getSelectedTableCells( selection );
if ( !selectedTableCells.length ) {
return;
}
event.stop();
model.change( writer => {
const tableCellToSelect = selectedTableCells[ isBackward ? selectedTableCells.length - 1 : 0 ];
model.change( writer => {
for ( const tableCell of selectedTableCells ) {
model.deleteContent( writer.createSelection( tableCell, 'in' ) );
}
} );
const rangeToSelect = model.schema.getNearestSelectionRange( writer.createPositionAt( tableCellToSelect, 0 ) );
// Note: we ignore the case where rangeToSelect may be null because deleteContent() will always (unless someone broke it)
// create an empty paragraph to accommodate the selection.
if ( selection.is( 'documentSelection' ) ) {
writer.setSelection( rangeToSelect );
} else {
selection.setTo( rangeToSelect );
}
} );
}
/**
* Returns an array of table cells that should be selected based on the
* given anchor cell and target (focus) cell.
*
* The cells are returned in a reverse direction if the selection is backward.
*
* @private
* @param {module:engine/model/element~Element} anchorCell
* @param {module:engine/model/element~Element} targetCell
* @returns {Array.<module:engine/model/element~Element>}
*/
_getCellsToSelect( anchorCell, targetCell ) {
const tableUtils = this.editor.plugins.get( 'TableUtils' );
const startLocation = tableUtils.getCellLocation( anchorCell );
const endLocation = tableUtils.getCellLocation( targetCell );
const startRow = Math.min( startLocation.row, endLocation.row );
const endRow = Math.max( startLocation.row, endLocation.row );
const startColumn = Math.min( startLocation.column, endLocation.column );
const endColumn = Math.max( startLocation.column, endLocation.column );
// 2-dimensional array of the selected cells to ease flipping the order of cells for backward selections.
const selectionMap = new Array( endRow - startRow + 1 ).fill( null ).map( () => [] );
const walkerOptions = {
startRow,
endRow,
startColumn,
endColumn
};
for ( const { row, cell } of new _tablewalker__WEBPACK_IMPORTED_MODULE_2__["default"]( anchorCell.findAncestor( 'table' ), walkerOptions ) ) {
selectionMap[ row - startRow ].push( cell );
}
const flipVertically = endLocation.row < startLocation.row;
const flipHorizontally = endLocation.column < startLocation.column;
if ( flipVertically ) {
selectionMap.reverse();
}
if ( flipHorizontally ) {
selectionMap.forEach( row => row.reverse() );
}
return {
cells: selectionMap.flat(),
backward: flipVertically || flipHorizontally
};
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/tabletoolbar.js":
/*!********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/tabletoolbar.js ***!
\********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ TableToolbar)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_widget__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/widget */ "./node_modules/ckeditor5/src/widget.js");
/* harmony import */ var _utils_ui_widget__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./utils/ui/widget */ "./node_modules/@ckeditor/ckeditor5-table/src/utils/ui/widget.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/tabletoolbar
*/
/**
* The table toolbar class. It creates toolbars for the table feature and its content (for now only for the table cell content).
*
* The table toolbar shows up when a table widget is selected. Its components (e.g. buttons) are created based on the
* {@link module:table/table~TableConfig#tableToolbar `table.tableToolbar` configuration option}.
*
* Table content toolbar shows up when the selection is inside the content of a table. It creates its component based on the
* {@link module:table/table~TableConfig#contentToolbar `table.contentToolbar` configuration option}.
*
* @extends module:core/plugin~Plugin
*/
class TableToolbar extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [ ckeditor5_src_widget__WEBPACK_IMPORTED_MODULE_1__.WidgetToolbarRepository ];
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'TableToolbar';
}
/**
* @inheritDoc
*/
afterInit() {
const editor = this.editor;
const t = editor.t;
const widgetToolbarRepository = editor.plugins.get( ckeditor5_src_widget__WEBPACK_IMPORTED_MODULE_1__.WidgetToolbarRepository );
const tableContentToolbarItems = editor.config.get( 'table.contentToolbar' );
const tableToolbarItems = editor.config.get( 'table.tableToolbar' );
if ( tableContentToolbarItems ) {
widgetToolbarRepository.register( 'tableContent', {
ariaLabel: t( 'Table toolbar' ),
items: tableContentToolbarItems,
getRelatedElement: _utils_ui_widget__WEBPACK_IMPORTED_MODULE_2__.getTableWidgetAncestor
} );
}
if ( tableToolbarItems ) {
widgetToolbarRepository.register( 'table', {
ariaLabel: t( 'Table toolbar' ),
items: tableToolbarItems,
getRelatedElement: _utils_ui_widget__WEBPACK_IMPORTED_MODULE_2__.getSelectedTableWidget
} );
}
}
}
/**
* Items to be placed in the table content toolbar.
* The {@link module:table/tabletoolbar~TableToolbar} plugin is required to make this toolbar work.
*
* Assuming that you use the {@link module:table/tableui~TableUI} feature, the following toolbar items will be available
* in {@link module:ui/componentfactory~ComponentFactory}:
*
* * `'tableRow'`,
* * `'tableColumn'`,
* * `'mergeTableCells'`.
*
* You can thus configure the toolbar like this:
*
* const tableConfig = {
* contentToolbar: [ 'tableRow', 'tableColumn', 'mergeTableCells' ]
* };
*
* Of course, the same buttons can also be used in the
* {@link module:core/editor/editorconfig~EditorConfig#toolbar main editor toolbar}.
*
* Read more about configuring the toolbar in {@link module:core/editor/editorconfig~EditorConfig#toolbar}.
*
* @member {Array.<String>} module:table/table~TableConfig#contentToolbar
*/
/**
* Items to be placed in the table toolbar.
* The {@link module:table/tabletoolbar~TableToolbar} plugin is required to make this toolbar work.
*
* You can thus configure the toolbar like this:
*
* const tableConfig = {
* tableToolbar: [ 'blockQuote' ]
* };
*
* Of course, the same buttons can also be used in the
* {@link module:core/editor/editorconfig~EditorConfig#toolbar main editor toolbar}.
*
* Read more about configuring the toolbar in {@link module:core/editor/editorconfig~EditorConfig#toolbar}.
*
* @member {Array.<String>} module:table/table~TableConfig#tableToolbar
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/tableui.js":
/*!***************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/tableui.js ***!
\***************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ TableUI)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/ui */ "./node_modules/ckeditor5/src/ui.js");
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/* harmony import */ var _ui_inserttableview__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./ui/inserttableview */ "./node_modules/@ckeditor/ckeditor5-table/src/ui/inserttableview.js");
/* harmony import */ var _theme_icons_table_svg__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./../theme/icons/table.svg */ "./node_modules/@ckeditor/ckeditor5-table/theme/icons/table.svg");
/* harmony import */ var _theme_icons_table_column_svg__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./../theme/icons/table-column.svg */ "./node_modules/@ckeditor/ckeditor5-table/theme/icons/table-column.svg");
/* harmony import */ var _theme_icons_table_row_svg__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./../theme/icons/table-row.svg */ "./node_modules/@ckeditor/ckeditor5-table/theme/icons/table-row.svg");
/* harmony import */ var _theme_icons_table_merge_cell_svg__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./../theme/icons/table-merge-cell.svg */ "./node_modules/@ckeditor/ckeditor5-table/theme/icons/table-merge-cell.svg");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/tableui
*/
/**
* The table UI plugin. It introduces:
*
* * The `'insertTable'` dropdown,
* * The `'tableColumn'` dropdown,
* * The `'tableRow'` dropdown,
* * The `'mergeTableCells'` split button.
*
* The `'tableColumn'`, `'tableRow'` and `'mergeTableCells'` dropdowns work best with {@link module:table/tabletoolbar~TableToolbar}.
*
* @extends module:core/plugin~Plugin
*/
class TableUI extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_0__.Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'TableUI';
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const t = this.editor.t;
const contentLanguageDirection = editor.locale.contentLanguageDirection;
const isContentLtr = contentLanguageDirection === 'ltr';
editor.ui.componentFactory.add( 'insertTable', locale => {
const command = editor.commands.get( 'insertTable' );
const dropdownView = (0,ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.createDropdown)( locale );
dropdownView.bind( 'isEnabled' ).to( command );
// Decorate dropdown's button.
dropdownView.buttonView.set( {
icon: _theme_icons_table_svg__WEBPACK_IMPORTED_MODULE_4__["default"],
label: t( 'Insert table' ),
tooltip: true
} );
let insertTableView;
dropdownView.on( 'change:isOpen', () => {
if ( insertTableView ) {
return;
}
// Prepare custom view for dropdown's panel.
insertTableView = new _ui_inserttableview__WEBPACK_IMPORTED_MODULE_3__["default"]( locale );
dropdownView.panelView.children.add( insertTableView );
insertTableView.delegate( 'execute' ).to( dropdownView );
dropdownView.buttonView.on( 'open', () => {
// Reset the chooser before showing it to the user.
insertTableView.rows = 0;
insertTableView.columns = 0;
} );
dropdownView.on( 'execute', () => {
editor.execute( 'insertTable', { rows: insertTableView.rows, columns: insertTableView.columns } );
editor.editing.view.focus();
} );
} );
return dropdownView;
} );
editor.ui.componentFactory.add( 'tableColumn', locale => {
const options = [
{
type: 'switchbutton',
model: {
commandName: 'setTableColumnHeader',
label: t( 'Header column' ),
bindIsOn: true
}
},
{ type: 'separator' },
{
type: 'button',
model: {
commandName: isContentLtr ? 'insertTableColumnLeft' : 'insertTableColumnRight',
label: t( 'Insert column left' )
}
},
{
type: 'button',
model: {
commandName: isContentLtr ? 'insertTableColumnRight' : 'insertTableColumnLeft',
label: t( 'Insert column right' )
}
},
{
type: 'button',
model: {
commandName: 'removeTableColumn',
label: t( 'Delete column' )
}
},
{
type: 'button',
model: {
commandName: 'selectTableColumn',
label: t( 'Select column' )
}
}
];
return this._prepareDropdown( t( 'Column' ), _theme_icons_table_column_svg__WEBPACK_IMPORTED_MODULE_5__["default"], options, locale );
} );
editor.ui.componentFactory.add( 'tableRow', locale => {
const options = [
{
type: 'switchbutton',
model: {
commandName: 'setTableRowHeader',
label: t( 'Header row' ),
bindIsOn: true
}
},
{ type: 'separator' },
{
type: 'button',
model: {
commandName: 'insertTableRowAbove',
label: t( 'Insert row above' )
}
},
{
type: 'button',
model: {
commandName: 'insertTableRowBelow',
label: t( 'Insert row below' )
}
},
{
type: 'button',
model: {
commandName: 'removeTableRow',
label: t( 'Delete row' )
}
},
{
type: 'button',
model: {
commandName: 'selectTableRow',
label: t( 'Select row' )
}
}
];
return this._prepareDropdown( t( 'Row' ), _theme_icons_table_row_svg__WEBPACK_IMPORTED_MODULE_6__["default"], options, locale );
} );
editor.ui.componentFactory.add( 'mergeTableCells', locale => {
const options = [
{
type: 'button',
model: {
commandName: 'mergeTableCellUp',
label: t( 'Merge cell up' )
}
},
{
type: 'button',
model: {
commandName: isContentLtr ? 'mergeTableCellRight' : 'mergeTableCellLeft',
label: t( 'Merge cell right' )
}
},
{
type: 'button',
model: {
commandName: 'mergeTableCellDown',
label: t( 'Merge cell down' )
}
},
{
type: 'button',
model: {
commandName: isContentLtr ? 'mergeTableCellLeft' : 'mergeTableCellRight',
label: t( 'Merge cell left' )
}
},
{ type: 'separator' },
{
type: 'button',
model: {
commandName: 'splitTableCellVertically',
label: t( 'Split cell vertically' )
}
},
{
type: 'button',
model: {
commandName: 'splitTableCellHorizontally',
label: t( 'Split cell horizontally' )
}
}
];
return this._prepareMergeSplitButtonDropdown( t( 'Merge cells' ), _theme_icons_table_merge_cell_svg__WEBPACK_IMPORTED_MODULE_7__["default"], options, locale );
} );
}
/**
* Creates a dropdown view from a set of options.
*
* @private
* @param {String} label The dropdown button label.
* @param {String} icon An icon for the dropdown button.
* @param {Array.<module:ui/dropdown/utils~ListDropdownItemDefinition>} options The list of options for the dropdown.
* @param {module:utils/locale~Locale} locale
* @returns {module:ui/dropdown/dropdownview~DropdownView}
*/
_prepareDropdown( label, icon, options, locale ) {
const editor = this.editor;
const dropdownView = (0,ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.createDropdown)( locale );
const commands = this._fillDropdownWithListOptions( dropdownView, options );
// Decorate dropdown's button.
dropdownView.buttonView.set( {
label,
icon,
tooltip: true
} );
// Make dropdown button disabled when all options are disabled.
dropdownView.bind( 'isEnabled' ).toMany( commands, 'isEnabled', ( ...areEnabled ) => {
return areEnabled.some( isEnabled => isEnabled );
} );
this.listenTo( dropdownView, 'execute', evt => {
editor.execute( evt.source.commandName );
editor.editing.view.focus();
} );
return dropdownView;
}
/**
* Creates a dropdown view with a {@link module:ui/dropdown/button/splitbuttonview~SplitButtonView} for
* merge (and split)–related commands.
*
* @private
* @param {String} label The dropdown button label.
* @param {String} icon An icon for the dropdown button.
* @param {Array.<module:ui/dropdown/utils~ListDropdownItemDefinition>} options The list of options for the dropdown.
* @param {module:utils/locale~Locale} locale
* @returns {module:ui/dropdown/dropdownview~DropdownView}
*/
_prepareMergeSplitButtonDropdown( label, icon, options, locale ) {
const editor = this.editor;
const dropdownView = (0,ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.createDropdown)( locale, ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.SplitButtonView );
const mergeCommandName = 'mergeTableCells';
// Main command.
const mergeCommand = editor.commands.get( mergeCommandName );
// Subcommands in the dropdown.
const commands = this._fillDropdownWithListOptions( dropdownView, options );
dropdownView.buttonView.set( {
label,
icon,
tooltip: true,
isEnabled: true
} );
// Make dropdown button disabled when all options are disabled together with the main command.
dropdownView.bind( 'isEnabled' ).toMany( [ mergeCommand, ...commands ], 'isEnabled', ( ...areEnabled ) => {
return areEnabled.some( isEnabled => isEnabled );
} );
// Merge selected table cells when the main part of the split button is clicked.
this.listenTo( dropdownView.buttonView, 'execute', () => {
editor.execute( mergeCommandName );
editor.editing.view.focus();
} );
// Execute commands for events coming from the list in the dropdown panel.
this.listenTo( dropdownView, 'execute', evt => {
editor.execute( evt.source.commandName );
editor.editing.view.focus();
} );
return dropdownView;
}
/**
* Injects a {@link module:ui/list/listview~ListView} into the passed dropdown with buttons
* which execute editor commands as configured in passed options.
*
* @private
* @param {module:ui/dropdown/dropdownview~DropdownView} dropdownView
* @param {Array.<module:ui/dropdown/utils~ListDropdownItemDefinition>} options The list of options for the dropdown.
* @returns {Array.<module:core/command~Command>} Commands the list options are interacting with.
*/
_fillDropdownWithListOptions( dropdownView, options ) {
const editor = this.editor;
const commands = [];
const itemDefinitions = new ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_2__.Collection();
for ( const option of options ) {
addListOption( option, editor, commands, itemDefinitions );
}
(0,ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.addListToDropdown)( dropdownView, itemDefinitions, editor.ui.componentFactory );
return commands;
}
}
// Adds an option to a list view.
//
// @param {module:table/tableui~DropdownOption} option A configuration option.
// @param {module:core/editor/editor~Editor} editor
// @param {Array.<module:core/command~Command>} commands The list of commands to update.
// @param {Iterable.<module:ui/dropdown/utils~ListDropdownItemDefinition>} itemDefinitions
// A collection of dropdown items to update with the given option.
function addListOption( option, editor, commands, itemDefinitions ) {
const model = option.model = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.Model( option.model );
const { commandName, bindIsOn } = option.model;
if ( option.type === 'button' || option.type === 'switchbutton' ) {
const command = editor.commands.get( commandName );
commands.push( command );
model.set( { commandName } );
model.bind( 'isEnabled' ).to( command );
if ( bindIsOn ) {
model.bind( 'isOn' ).to( command, 'value' );
}
}
model.set( {
withText: true
} );
itemDefinitions.add( option );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/tableutils.js":
/*!******************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/tableutils.js ***!
\******************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ TableUtils)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var _tablewalker__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./tablewalker */ "./node_modules/@ckeditor/ckeditor5-table/src/tablewalker.js");
/* harmony import */ var _utils_common__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./utils/common */ "./node_modules/@ckeditor/ckeditor5-table/src/utils/common.js");
/* harmony import */ var _utils_structure__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./utils/structure */ "./node_modules/@ckeditor/ckeditor5-table/src/utils/structure.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/tableutils
*/
/**
* The table utilities plugin.
*
* @extends module:core/plugin~Plugin
*/
class TableUtils extends ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_1__.Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'TableUtils';
}
/**
* @inheritDoc
*/
init() {
this.decorate( 'insertColumns' );
this.decorate( 'insertRows' );
}
/**
* Returns the table cell location as an object with table row and table column indexes.
*
* For instance, in the table below:
*
* 0 1 2 3
* +---+---+---+---+
* 0 | a | b | c |
* + + +---+
* 1 | | | d |
* +---+---+ +---+
* 2 | e | | f |
* +---+---+---+---+
*
* the method will return:
*
* const cellA = table.getNodeByPath( [ 0, 0 ] );
* editor.plugins.get( 'TableUtils' ).getCellLocation( cellA );
* // will return { row: 0, column: 0 }
*
* const cellD = table.getNodeByPath( [ 1, 0 ] );
* editor.plugins.get( 'TableUtils' ).getCellLocation( cellD );
* // will return { row: 1, column: 3 }
*
* @param {module:engine/model/element~Element} tableCell
* @returns {Object} Returns a `{row, column}` object.
*/
getCellLocation( tableCell ) {
const tableRow = tableCell.parent;
const table = tableRow.parent;
const rowIndex = table.getChildIndex( tableRow );
const tableWalker = new _tablewalker__WEBPACK_IMPORTED_MODULE_2__["default"]( table, { row: rowIndex } );
for ( const { cell, row, column } of tableWalker ) {
if ( cell === tableCell ) {
return { row, column };
}
}
}
/**
* Creates an empty table with a proper structure. The table needs to be inserted into the model,
* for example, by using the {@link module:engine/model/model~Model#insertContent} function.
*
* model.change( ( writer ) => {
* // Create a table of 2 rows and 7 columns:
* const table = tableUtils.createTable( writer, { rows: 2, columns: 7 } );
*
* // Insert a table to the model at the best position taking the current selection:
* model.insertContent( table );
* }
*
* @param {module:engine/model/writer~Writer} writer The model writer.
* @param {Object} options
* @param {Number} [options.rows=2] The number of rows to create.
* @param {Number} [options.columns=2] The number of columns to create.
* @param {Number} [options.headingRows=0] The number of heading rows.
* @param {Number} [options.headingColumns=0] The number of heading columns.
* @returns {module:engine/model/element~Element} The created table element.
*/
createTable( writer, options ) {
const table = writer.createElement( 'table' );
const rows = parseInt( options.rows ) || 2;
const columns = parseInt( options.columns ) || 2;
createEmptyRows( writer, table, 0, rows, columns );
if ( options.headingRows ) {
(0,_utils_common__WEBPACK_IMPORTED_MODULE_3__.updateNumericAttribute)( 'headingRows', Math.min( options.headingRows, rows ), table, writer, 0 );
}
if ( options.headingColumns ) {
(0,_utils_common__WEBPACK_IMPORTED_MODULE_3__.updateNumericAttribute)( 'headingColumns', Math.min( options.headingColumns, columns ), table, writer, 0 );
}
return table;
}
/**
* Inserts rows into a table.
*
* editor.plugins.get( 'TableUtils' ).insertRows( table, { at: 1, rows: 2 } );
*
* Assuming the table on the left, the above code will transform it to the table on the right:
*
* row index
* 0 +---+---+---+ `at` = 1, +---+---+---+ 0
* | a | b | c | `rows` = 2, | a | b | c |
* 1 + +---+---+ <-- insert here + +---+---+ 1
* | | d | e | | | | |
* 2 + +---+---+ will give: + +---+---+ 2
* | | f | g | | | | |
* 3 +---+---+---+ + +---+---+ 3
* | | d | e |
* + +---+---+ 4
* + + f | g |
* +---+---+---+ 5
*
* @param {module:engine/model/element~Element} table The table model element where the rows will be inserted.
* @param {Object} options
* @param {Number} [options.at=0] The row index at which the rows will be inserted.
* @param {Number} [options.rows=1] The number of rows to insert.
* @param {Boolean|undefined} [options.copyStructureFromAbove] The flag for copying row structure. Note that
* the row structure will not be copied if this option is not provided.
*/
insertRows( table, options = {} ) {
const model = this.editor.model;
const insertAt = options.at || 0;
const rowsToInsert = options.rows || 1;
const isCopyStructure = options.copyStructureFromAbove !== undefined;
const copyStructureFrom = options.copyStructureFromAbove ? insertAt - 1 : insertAt;
const rows = this.getRows( table );
const columns = this.getColumns( table );
if ( insertAt > rows ) {
/**
* The `options.at` points at a row position that does not exist.
*
* @error tableutils-insertrows-insert-out-of-range
*/
throw new ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_0__.CKEditorError(
'tableutils-insertrows-insert-out-of-range',
this,
{ options }
);
}
model.change( writer => {
const headingRows = table.getAttribute( 'headingRows' ) || 0;
// Inserting rows inside heading section requires to update `headingRows` attribute as the heading section will grow.
if ( headingRows > insertAt ) {
(0,_utils_common__WEBPACK_IMPORTED_MODULE_3__.updateNumericAttribute)( 'headingRows', headingRows + rowsToInsert, table, writer, 0 );
}
// Inserting at the end or at the beginning of a table doesn't require to calculate anything special.
if ( !isCopyStructure && ( insertAt === 0 || insertAt === rows ) ) {
createEmptyRows( writer, table, insertAt, rowsToInsert, columns );
return;
}
// Iterate over all the rows above the inserted rows in order to check for the row-spanned cells.
const walkerEndRow = isCopyStructure ? Math.max( insertAt, copyStructureFrom ) : insertAt;
const tableIterator = new _tablewalker__WEBPACK_IMPORTED_MODULE_2__["default"]( table, { endRow: walkerEndRow } );
// Store spans of the reference row to reproduce it's structure. This array is column number indexed.
const rowColSpansMap = new Array( columns ).fill( 1 );
for ( const { row, column, cellHeight, cellWidth, cell } of tableIterator ) {
const lastCellRow = row + cellHeight - 1;
const isOverlappingInsertedRow = row < insertAt && insertAt <= lastCellRow;
const isReferenceRow = row <= copyStructureFrom && copyStructureFrom <= lastCellRow;
// If the cell is row-spanned and overlaps the inserted row, then reserve space for it in the row map.
if ( isOverlappingInsertedRow ) {
// This cell overlaps the inserted rows so we need to expand it further.
writer.setAttribute( 'rowspan', cellHeight + rowsToInsert, cell );
// Mark this cell with negative number to indicate how many cells should be skipped when adding the new cells.
rowColSpansMap[ column ] = -cellWidth;
}
// Store the colspan from reference row.
else if ( isCopyStructure && isReferenceRow ) {
rowColSpansMap[ column ] = cellWidth;
}
}
for ( let rowIndex = 0; rowIndex < rowsToInsert; rowIndex++ ) {
const tableRow = writer.createElement( 'tableRow' );
writer.insert( tableRow, table, insertAt );
for ( let cellIndex = 0; cellIndex < rowColSpansMap.length; cellIndex++ ) {
const colspan = rowColSpansMap[ cellIndex ];
const insertPosition = writer.createPositionAt( tableRow, 'end' );
// Insert the empty cell only if this slot is not row-spanned from any other cell.
if ( colspan > 0 ) {
(0,_utils_common__WEBPACK_IMPORTED_MODULE_3__.createEmptyTableCell)( writer, insertPosition, colspan > 1 ? { colspan } : null );
}
// Skip the col-spanned slots, there won't be any cells.
cellIndex += Math.abs( colspan ) - 1;
}
}
} );
}
/**
* Inserts columns into a table.
*
* editor.plugins.get( 'TableUtils' ).insertColumns( table, { at: 1, columns: 2 } );
*
* Assuming the table on the left, the above code will transform it to the table on the right:
*
* 0 1 2 3 0 1 2 3 4 5
* +---+---+---+ +---+---+---+---+---+
* | a | b | | a | b |
* + +---+ + +---+
* | | c | | | c |
* +---+---+---+ will give: +---+---+---+---+---+
* | d | e | f | | d | | | e | f |
* +---+ +---+ +---+---+---+ +---+
* | g | | h | | g | | | | h |
* +---+---+---+ +---+---+---+---+---+
* | i | | i |
* +---+---+---+ +---+---+---+---+---+
* ^---- insert here, `at` = 1, `columns` = 2
*
* @param {module:engine/model/element~Element} table The table model element where the columns will be inserted.
* @param {Object} options
* @param {Number} [options.at=0] The column index at which the columns will be inserted.
* @param {Number} [options.columns=1] The number of columns to insert.
*/
insertColumns( table, options = {} ) {
const model = this.editor.model;
const insertAt = options.at || 0;
const columnsToInsert = options.columns || 1;
model.change( writer => {
const headingColumns = table.getAttribute( 'headingColumns' );
// Inserting columns inside heading section requires to update `headingColumns` attribute as the heading section will grow.
if ( insertAt < headingColumns ) {
writer.setAttribute( 'headingColumns', headingColumns + columnsToInsert, table );
}
const tableColumns = this.getColumns( table );
// Inserting at the end and at the beginning of a table doesn't require to calculate anything special.
if ( insertAt === 0 || tableColumns === insertAt ) {
for ( const tableRow of table.getChildren() ) {
// Ignore non-row elements inside the table (e.g. caption).
if ( !tableRow.is( 'element', 'tableRow' ) ) {
continue;
}
createCells( columnsToInsert, writer, writer.createPositionAt( tableRow, insertAt ? 'end' : 0 ) );
}
return;
}
const tableWalker = new _tablewalker__WEBPACK_IMPORTED_MODULE_2__["default"]( table, { column: insertAt, includeAllSlots: true } );
for ( const tableSlot of tableWalker ) {
const { row, cell, cellAnchorColumn, cellAnchorRow, cellWidth, cellHeight } = tableSlot;
// When iterating over column the table walker outputs either:
// - cells at given column index (cell "e" from method docs),
// - spanned columns (spanned cell from row between cells "g" and "h" - spanned by "e", only if `includeAllSlots: true`),
// - or a cell from the same row which spans over this column (cell "a").
if ( cellAnchorColumn < insertAt ) {
// If cell is anchored in previous column, it is a cell that spans over an inserted column (cell "a" & "i").
// For such cells expand them by a number of columns inserted.
writer.setAttribute( 'colspan', cellWidth + columnsToInsert, cell );
// This cell will overlap cells in rows below so skip them (because of `includeAllSlots` option) - (cell "a")
const lastCellRow = cellAnchorRow + cellHeight - 1;
for ( let i = row; i <= lastCellRow; i++ ) {
tableWalker.skipRow( i );
}
} else {
// It's either cell at this column index or spanned cell by a row-spanned cell from row above.
// In table above it's cell "e" and a spanned position from row below (empty cell between cells "g" and "h")
createCells( columnsToInsert, writer, tableSlot.getPositionBefore() );
}
}
} );
}
/**
* Removes rows from the given `table`.
*
* This method re-calculates the table geometry including `rowspan` attribute of table cells overlapping removed rows
* and table headings values.
*
* editor.plugins.get( 'TableUtils' ).removeRows( table, { at: 1, rows: 2 } );
*
* Executing the above code in the context of the table on the left will transform its structure as presented on the right:
*
* row index
* ┌───┬───┬───┐ `at` = 1 ┌───┬───┬───┐
* 0 │ a │ b │ c │ `rows` = 2 │ a │ b │ c │ 0
* │ ├───┼───┤ │ ├───┼───┤
* 1 │ │ d │ e │ <-- remove from here │ │ d │ g │ 1
* │ │ ├───┤ will give: ├───┼───┼───┤
* 2 │ │ │ f │ │ h │ i │ j │ 2
* │ │ ├───┤ └───┴───┴───┘
* 3 │ │ │ g │
* ├───┼───┼───┤
* 4 │ h │ i │ j │
* └───┴───┴───┘
*
* @param {module:engine/model/element~Element} table
* @param {Object} options
* @param {Number} options.at The row index at which the removing rows will start.
* @param {Number} [options.rows=1] The number of rows to remove.
*/
removeRows( table, options ) {
const model = this.editor.model;
const rowsToRemove = options.rows || 1;
const rowCount = this.getRows( table );
const first = options.at;
const last = first + rowsToRemove - 1;
if ( last > rowCount - 1 ) {
/**
* The `options.at` param must point at existing row and `options.rows` must not exceed the rows in the table.
*
* @error tableutils-removerows-row-index-out-of-range
*/
throw new ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_0__.CKEditorError(
'tableutils-removerows-row-index-out-of-range',
this,
{ table, options }
);
}
model.change( writer => {
// Removing rows from the table require that most calculations to be done prior to changing table structure.
// Preparations must be done in the same enqueueChange callback to use the current table structure.
// 1. Preparation - get row-spanned cells that have to be modified after removing rows.
const { cellsToMove, cellsToTrim } = getCellsToMoveAndTrimOnRemoveRow( table, first, last );
// 2. Execution
// 2a. Move cells from removed rows that extends over a removed section - must be done before removing rows.
// This will fill any gaps in a rows below that previously were empty because of row-spanned cells.
if ( cellsToMove.size ) {
const rowAfterRemovedSection = last + 1;
moveCellsToRow( table, rowAfterRemovedSection, cellsToMove, writer );
}
// 2b. Remove all required rows.
for ( let i = last; i >= first; i-- ) {
writer.remove( table.getChild( i ) );
}
// 2c. Update cells from rows above that overlap removed section. Similar to step 2 but does not involve moving cells.
for ( const { rowspan, cell } of cellsToTrim ) {
(0,_utils_common__WEBPACK_IMPORTED_MODULE_3__.updateNumericAttribute)( 'rowspan', rowspan, cell, writer );
}
// 2d. Adjust heading rows if removed rows were in a heading section.
updateHeadingRows( table, first, last, writer );
// 2e. Remove empty columns (without anchored cells) if there are any.
if ( !(0,_utils_structure__WEBPACK_IMPORTED_MODULE_4__.removeEmptyColumns)( table, this ) ) {
// If there wasn't any empty columns then we still need to check if this wasn't called
// because of cleaning empty rows and we only removed one of them.
(0,_utils_structure__WEBPACK_IMPORTED_MODULE_4__.removeEmptyRows)( table, this );
}
} );
}
/**
* Removes columns from the given `table`.
*
* This method re-calculates the table geometry including the `colspan` attribute of table cells overlapping removed columns
* and table headings values.
*
* editor.plugins.get( 'TableUtils' ).removeColumns( table, { at: 1, columns: 2 } );
*
* Executing the above code in the context of the table on the left will transform its structure as presented on the right:
*
* 0 1 2 3 4 0 1 2
* ┌───────────────┬───┐ ┌───────┬───┐
* │ a │ b │ │ a │ b │
* │ ├───┤ │ ├───┤
* │ │ c │ │ │ c │
* ├───┬───┬───┬───┼───┤ will give: ├───┬───┼───┤
* │ d │ e │ f │ g │ h │ │ d │ g │ h │
* ├───┼───┼───┤ ├───┤ ├───┤ ├───┤
* │ i │ j │ k │ │ l │ │ i │ │ l │
* ├───┴───┴───┴───┴───┤ ├───┴───┴───┤
* │ m │ │ m │
* └───────────────────┘ └───────────┘
* ^---- remove from here, `at` = 1, `columns` = 2
*
* @param {module:engine/model/element~Element} table
* @param {Object} options
* @param {Number} options.at The row index at which the removing columns will start.
* @param {Number} [options.columns=1] The number of columns to remove.
*/
removeColumns( table, options ) {
const model = this.editor.model;
const first = options.at;
const columnsToRemove = options.columns || 1;
const last = options.at + columnsToRemove - 1;
model.change( writer => {
adjustHeadingColumns( table, { first, last }, writer );
for ( let removedColumnIndex = last; removedColumnIndex >= first; removedColumnIndex-- ) {
for ( const { cell, column, cellWidth } of [ ...new _tablewalker__WEBPACK_IMPORTED_MODULE_2__["default"]( table ) ] ) {
// If colspaned cell overlaps removed column decrease its span.
if ( column <= removedColumnIndex && cellWidth > 1 && column + cellWidth > removedColumnIndex ) {
(0,_utils_common__WEBPACK_IMPORTED_MODULE_3__.updateNumericAttribute)( 'colspan', cellWidth - 1, cell, writer );
} else if ( column === removedColumnIndex ) {
// The cell in removed column has colspan of 1.
writer.remove( cell );
}
}
}
// Remove empty rows that could appear after removing columns.
if ( !(0,_utils_structure__WEBPACK_IMPORTED_MODULE_4__.removeEmptyRows)( table, this ) ) {
// If there wasn't any empty rows then we still need to check if this wasn't called
// because of cleaning empty columns and we only removed one of them.
(0,_utils_structure__WEBPACK_IMPORTED_MODULE_4__.removeEmptyColumns)( table, this );
}
} );
}
/**
* Divides a table cell vertically into several ones.
*
* The cell will be visually split into more cells by updating colspans of other cells in a column
* and inserting cells (columns) after that cell.
*
* In the table below, if cell "a" is split into 3 cells:
*
* +---+---+---+
* | a | b | c |
* +---+---+---+
* | d | e | f |
* +---+---+---+
*
* it will result in the table below:
*
* +---+---+---+---+---+
* | a | | | b | c |
* +---+---+---+---+---+
* | d | e | f |
* +---+---+---+---+---+
*
* So cell "d" will get its `colspan` updated to `3` and 2 cells will be added (2 columns will be created).
*
* Splitting a cell that already has a `colspan` attribute set will distribute the cell `colspan` evenly and the remainder
* will be left to the original cell:
*
* +---+---+---+
* | a |
* +---+---+---+
* | b | c | d |
* +---+---+---+
*
* Splitting cell "a" with `colspan=3` into 2 cells will create 1 cell with a `colspan=a` and cell "a" that will have `colspan=2`:
*
* +---+---+---+
* | a | |
* +---+---+---+
* | b | c | d |
* +---+---+---+
*
* @param {module:engine/model/element~Element} tableCell
* @param {Number} numberOfCells
*/
splitCellVertically( tableCell, numberOfCells = 2 ) {
const model = this.editor.model;
const tableRow = tableCell.parent;
const table = tableRow.parent;
const rowspan = parseInt( tableCell.getAttribute( 'rowspan' ) || 1 );
const colspan = parseInt( tableCell.getAttribute( 'colspan' ) || 1 );
model.change( writer => {
// First check - the cell spans over multiple rows so before doing anything else just split this cell.
if ( colspan > 1 ) {
// Get spans of new (inserted) cells and span to update of split cell.
const { newCellsSpan, updatedSpan } = breakSpanEvenly( colspan, numberOfCells );
(0,_utils_common__WEBPACK_IMPORTED_MODULE_3__.updateNumericAttribute)( 'colspan', updatedSpan, tableCell, writer );
// Each inserted cell will have the same attributes:
const newCellsAttributes = {};
// Do not store default value in the model.
if ( newCellsSpan > 1 ) {
newCellsAttributes.colspan = newCellsSpan;
}
// Copy rowspan of split cell.
if ( rowspan > 1 ) {
newCellsAttributes.rowspan = rowspan;
}
const cellsToInsert = colspan > numberOfCells ? numberOfCells - 1 : colspan - 1;
createCells( cellsToInsert, writer, writer.createPositionAfter( tableCell ), newCellsAttributes );
}
// Second check - the cell has colspan of 1 or we need to create more cells then the currently one spans over.
if ( colspan < numberOfCells ) {
const cellsToInsert = numberOfCells - colspan;
// First step: expand cells on the same column as split cell.
const tableMap = [ ...new _tablewalker__WEBPACK_IMPORTED_MODULE_2__["default"]( table ) ];
// Get the column index of split cell.
const { column: splitCellColumn } = tableMap.find( ( { cell } ) => cell === tableCell );
// Find cells which needs to be expanded vertically - those on the same column or those that spans over split cell's column.
const cellsToUpdate = tableMap.filter( ( { cell, cellWidth, column } ) => {
const isOnSameColumn = cell !== tableCell && column === splitCellColumn;
const spansOverColumn = ( column < splitCellColumn && column + cellWidth > splitCellColumn );
return isOnSameColumn || spansOverColumn;
} );
// Expand cells vertically.
for ( const { cell, cellWidth } of cellsToUpdate ) {
writer.setAttribute( 'colspan', cellWidth + cellsToInsert, cell );
}
// Second step: create columns after split cell.
// Each inserted cell will have the same attributes:
const newCellsAttributes = {};
// Do not store default value in the model.
// Copy rowspan of split cell.
if ( rowspan > 1 ) {
newCellsAttributes.rowspan = rowspan;
}
createCells( cellsToInsert, writer, writer.createPositionAfter( tableCell ), newCellsAttributes );
const headingColumns = table.getAttribute( 'headingColumns' ) || 0;
// Update heading section if split cell is in heading section.
if ( headingColumns > splitCellColumn ) {
(0,_utils_common__WEBPACK_IMPORTED_MODULE_3__.updateNumericAttribute)( 'headingColumns', headingColumns + cellsToInsert, table, writer );
}
}
} );
}
/**
* Divides a table cell horizontally into several ones.
*
* The cell will be visually split into more cells by updating rowspans of other cells in the row and inserting rows with a single cell
* below.
*
* If in the table below cell "b" is split into 3 cells:
*
* +---+---+---+
* | a | b | c |
* +---+---+---+
* | d | e | f |
* +---+---+---+
*
* It will result in the table below:
*
* +---+---+---+
* | a | b | c |
* + +---+ +
* | | | |
* + +---+ +
* | | | |
* +---+---+---+
* | d | e | f |
* +---+---+---+
*
* So cells "a" and "b" will get their `rowspan` updated to `3` and 2 rows with a single cell will be added.
*
* Splitting a cell that already has a `rowspan` attribute set will distribute the cell `rowspan` evenly and the remainder
* will be left to the original cell:
*
* +---+---+---+
* | a | b | c |
* + +---+---+
* | | d | e |
* + +---+---+
* | | f | g |
* + +---+---+
* | | h | i |
* +---+---+---+
*
* Splitting cell "a" with `rowspan=4` into 3 cells will create 2 cells with a `rowspan=1` and cell "a" will have `rowspan=2`:
*
* +---+---+---+
* | a | b | c |
* + +---+---+
* | | d | e |
* +---+---+---+
* | | f | g |
* +---+---+---+
* | | h | i |
* +---+---+---+
*
* @param {module:engine/model/element~Element} tableCell
* @param {Number} numberOfCells
*/
splitCellHorizontally( tableCell, numberOfCells = 2 ) {
const model = this.editor.model;
const tableRow = tableCell.parent;
const table = tableRow.parent;
const splitCellRow = table.getChildIndex( tableRow );
const rowspan = parseInt( tableCell.getAttribute( 'rowspan' ) || 1 );
const colspan = parseInt( tableCell.getAttribute( 'colspan' ) || 1 );
model.change( writer => {
// First check - the cell spans over multiple rows so before doing anything else just split this cell.
if ( rowspan > 1 ) {
// Cache table map before updating table.
const tableMap = [ ...new _tablewalker__WEBPACK_IMPORTED_MODULE_2__["default"]( table, {
startRow: splitCellRow,
endRow: splitCellRow + rowspan - 1,
includeAllSlots: true
} ) ];
// Get spans of new (inserted) cells and span to update of split cell.
const { newCellsSpan, updatedSpan } = breakSpanEvenly( rowspan, numberOfCells );
(0,_utils_common__WEBPACK_IMPORTED_MODULE_3__.updateNumericAttribute)( 'rowspan', updatedSpan, tableCell, writer );
const { column: cellColumn } = tableMap.find( ( { cell } ) => cell === tableCell );
// Each inserted cell will have the same attributes:
const newCellsAttributes = {};
// Do not store default value in the model.
if ( newCellsSpan > 1 ) {
newCellsAttributes.rowspan = newCellsSpan;
}
// Copy colspan of split cell.
if ( colspan > 1 ) {
newCellsAttributes.colspan = colspan;
}
for ( const tableSlot of tableMap ) {
const { column, row } = tableSlot;
// As both newly created cells and the split cell might have rowspan,
// the insertion of new cells must go to appropriate rows:
//
// 1. It's a row after split cell + it's height.
const isAfterSplitCell = row >= splitCellRow + updatedSpan;
// 2. Is on the same column.
const isOnSameColumn = column === cellColumn;
// 3. And it's row index is after previous cell height.
const isInEvenlySplitRow = ( row + splitCellRow + updatedSpan ) % newCellsSpan === 0;
if ( isAfterSplitCell && isOnSameColumn && isInEvenlySplitRow ) {
createCells( 1, writer, tableSlot.getPositionBefore(), newCellsAttributes );
}
}
}
// Second check - the cell has rowspan of 1 or we need to create more cells than the current cell spans over.
if ( rowspan < numberOfCells ) {
// We already split the cell in check one so here we split to the remaining number of cells only.
const cellsToInsert = numberOfCells - rowspan;
// This check is needed since we need to check if there are any cells from previous rows than spans over this cell's row.
const tableMap = [ ...new _tablewalker__WEBPACK_IMPORTED_MODULE_2__["default"]( table, { startRow: 0, endRow: splitCellRow } ) ];
// First step: expand cells.
for ( const { cell, cellHeight, row } of tableMap ) {
// Expand rowspan of cells that are either:
// - on the same row as current cell,
// - or are below split cell row and overlaps that row.
if ( cell !== tableCell && row + cellHeight > splitCellRow ) {
const rowspanToSet = cellHeight + cellsToInsert;
writer.setAttribute( 'rowspan', rowspanToSet, cell );
}
}
// Second step: create rows with single cell below split cell.
const newCellsAttributes = {};
// Copy colspan of split cell.
if ( colspan > 1 ) {
newCellsAttributes.colspan = colspan;
}
createEmptyRows( writer, table, splitCellRow + 1, cellsToInsert, 1, newCellsAttributes );
// Update heading section if split cell is in heading section.
const headingRows = table.getAttribute( 'headingRows' ) || 0;
if ( headingRows > splitCellRow ) {
(0,_utils_common__WEBPACK_IMPORTED_MODULE_3__.updateNumericAttribute)( 'headingRows', headingRows + cellsToInsert, table, writer );
}
}
} );
}
/**
* Returns the number of columns for a given table.
*
* editor.plugins.get( 'TableUtils' ).getColumns( table );
*
* @param {module:engine/model/element~Element} table The table to analyze.
* @returns {Number}
*/
getColumns( table ) {
// Analyze first row only as all the rows should have the same width.
// Using the first row without checking if it's a tableRow because we expect
// that table will have only tableRow model elements at the beginning.
const row = table.getChild( 0 );
return [ ...row.getChildren() ].reduce( ( columns, row ) => {
const columnWidth = parseInt( row.getAttribute( 'colspan' ) || 1 );
return columns + columnWidth;
}, 0 );
}
/**
* Returns the number of rows for a given table. Any other element present in the table model is omitted.
*
* editor.plugins.get( 'TableUtils' ).getRows( table );
*
* @param {module:engine/model/element~Element} table The table to analyze.
* @returns {Number}
*/
getRows( table ) {
// Rowspan not included due to #6427.
return Array.from( table.getChildren() )
.reduce( ( rowCount, child ) => child.is( 'element', 'tableRow' ) ? rowCount + 1 : rowCount, 0 );
}
/**
* Creates an instance of the table walker.
*
* The table walker iterates internally by traversing the table from row index = 0 and column index = 0.
* It walks row by row and column by column in order to output values defined in the options.
* By default it will output only the locations that are occupied by a cell. To include also spanned rows and columns,
* pass the `includeAllSlots` option.
*
* @protected
* @param {module:engine/model/element~Element} table A table over which the walker iterates.
* @param {Object} [options={}] An object with configuration.
* @param {Number} [options.row] A row index for which this iterator will output cells.
* Can't be used together with `startRow` and `endRow`.
* @param {Number} [options.startRow=0] A row index from which this iterator should start. Can't be used together with `row`.
* @param {Number} [options.endRow] A row index at which this iterator should end. Can't be used together with `row`.
* @param {Number} [options.column] A column index for which this iterator will output cells.
* Can't be used together with `startColumn` and `endColumn`.
* @param {Number} [options.startColumn=0] A column index from which this iterator should start. Can't be used together with `column`.
* @param {Number} [options.endColumn] A column index at which this iterator should end. Can't be used together with `column`.
* @param {Boolean} [options.includeAllSlots=false] Also return values for spanned cells.
*/
createTableWalker( table, options = {} ) {
return new _tablewalker__WEBPACK_IMPORTED_MODULE_2__["default"]( table, options );
}
/**
* Returns all model table cells that are fully selected (from the outside)
* within the provided model selection's ranges.
*
* To obtain the cells selected from the inside, use
* {@link #getTableCellsContainingSelection}.
*
* @param {module:engine/model/selection~Selection} selection
* @returns {Array.<module:engine/model/element~Element>}
*/
getSelectedTableCells( selection ) {
const cells = [];
for ( const range of this.sortRanges( selection.getRanges() ) ) {
const element = range.getContainedElement();
if ( element && element.is( 'element', 'tableCell' ) ) {
cells.push( element );
}
}
return cells;
}
/**
* Returns all model table cells that the provided model selection's ranges
* {@link module:engine/model/range~Range#start} inside.
*
* To obtain the cells selected from the outside, use
* {@link #getSelectedTableCells}.
*
* @param {module:engine/model/selection~Selection} selection
* @returns {Array.<module:engine/model/element~Element>}
*/
getTableCellsContainingSelection( selection ) {
const cells = [];
for ( const range of selection.getRanges() ) {
const cellWithSelection = range.start.findAncestor( 'tableCell' );
if ( cellWithSelection ) {
cells.push( cellWithSelection );
}
}
return cells;
}
/**
* Returns all model table cells that are either completely selected
* by selection ranges or host selection range
* {@link module:engine/model/range~Range#start start positions} inside them.
*
* Combines {@link #getTableCellsContainingSelection} and
* {@link #getSelectedTableCells}.
*
* @param {module:engine/model/selection~Selection} selection
* @returns {Array.<module:engine/model/element~Element>}
*/
getSelectionAffectedTableCells( selection ) {
const selectedCells = this.getSelectedTableCells( selection );
if ( selectedCells.length ) {
return selectedCells;
}
return this.getTableCellsContainingSelection( selection );
}
/**
* Returns an object with the `first` and `last` row index contained in the given `tableCells`.
*
* const selectedTableCells = getSelectedTableCells( editor.model.document.selection );
*
* const { first, last } = getRowIndexes( selectedTableCells );
*
* console.log( `Selected rows: ${ first } to ${ last }` );
*
* @param {Array.<module:engine/model/element~Element>} tableCells
* @returns {Object} Returns an object with the `first` and `last` table row indexes.
*/
getRowIndexes( tableCells ) {
const indexes = tableCells.map( cell => cell.parent.index );
return this._getFirstLastIndexesObject( indexes );
}
/**
* Returns an object with the `first` and `last` column index contained in the given `tableCells`.
*
* const selectedTableCells = getSelectedTableCells( editor.model.document.selection );
*
* const { first, last } = getColumnIndexes( selectedTableCells );
*
* console.log( `Selected columns: ${ first } to ${ last }` );
*
* @param {Array.<module:engine/model/element~Element>} tableCells
* @returns {Object} Returns an object with the `first` and `last` table column indexes.
*/
getColumnIndexes( tableCells ) {
const table = tableCells[ 0 ].findAncestor( 'table' );
const tableMap = [ ...new _tablewalker__WEBPACK_IMPORTED_MODULE_2__["default"]( table ) ];
const indexes = tableMap
.filter( entry => tableCells.includes( entry.cell ) )
.map( entry => entry.column );
return this._getFirstLastIndexesObject( indexes );
}
/**
* Checks if the selection contains cells that do not exceed rectangular selection.
*
* In a table below:
*
* ┌───┬───┬───┬───┐
* │ a │ b │ c │ d │
* ├───┴───┼───┤ │
* │ e │ f │ │
* │ ├───┼───┤
* │ │ g │ h │
* └───────┴───┴───┘
*
* Valid selections are these which create a solid rectangle (without gaps), such as:
* - a, b (two horizontal cells)
* - c, f (two vertical cells)
* - a, b, e (cell "e" spans over four cells)
* - c, d, f (cell d spans over a cell in the row below)
*
* While an invalid selection would be:
* - a, c (the unselected cell "b" creates a gap)
* - f, g, h (cell "d" spans over a cell from the row of "f" cell - thus creates a gap)
*
* @param {Array.<module:engine/model/element~Element>} selectedTableCells
* @returns {Boolean}
*/
isSelectionRectangular( selectedTableCells ) {
if ( selectedTableCells.length < 2 || !this._areCellInTheSameTableSection( selectedTableCells ) ) {
return false;
}
// A valid selection is a fully occupied rectangle composed of table cells.
// Below we will calculate the area of a selected table cells and the area of valid selection.
// The area of a valid selection is defined by top-left and bottom-right cells.
const rows = new Set();
const columns = new Set();
let areaOfSelectedCells = 0;
for ( const tableCell of selectedTableCells ) {
const { row, column } = this.getCellLocation( tableCell );
const rowspan = parseInt( tableCell.getAttribute( 'rowspan' ) || 1 );
const colspan = parseInt( tableCell.getAttribute( 'colspan' ) || 1 );
// Record row & column indexes of current cell.
rows.add( row );
columns.add( column );
// For cells that spans over multiple rows add also the last row that this cell spans over.
if ( rowspan > 1 ) {
rows.add( row + rowspan - 1 );
}
// For cells that spans over multiple columns add also the last column that this cell spans over.
if ( colspan > 1 ) {
columns.add( column + colspan - 1 );
}
areaOfSelectedCells += ( rowspan * colspan );
}
// We can only merge table cells that are in adjacent rows...
const areaOfValidSelection = getBiggestRectangleArea( rows, columns );
return areaOfValidSelection == areaOfSelectedCells;
}
/**
* Returns array of sorted ranges.
*
* @param {Iterable.<module:engine/model/range~Range>} ranges
* @return {Array.<module:engine/model/range~Range>}
*/
sortRanges( ranges ) {
return Array.from( ranges ).sort( compareRangeOrder );
}
/**
* Helper method to get an object with `first` and `last` indexes from an unsorted array of indexes.
*
* @private
* @param {Number[]} indexes
* @returns {Object}
*/
_getFirstLastIndexesObject( indexes ) {
const allIndexesSorted = indexes.sort( ( indexA, indexB ) => indexA - indexB );
const first = allIndexesSorted[ 0 ];
const last = allIndexesSorted[ allIndexesSorted.length - 1 ];
return { first, last };
}
/**
* Checks if the selection does not mix a header (column or row) with other cells.
*
* For instance, in the table below valid selections consist of cells with the same letter only.
* So, a-a (same heading row and column) or d-d (body cells) are valid while c-d or a-b are not.
*
* header columns
* ↓ ↓
* ┌───┬───┬───┬───┐
* │ a │ a │ b │ b │ ← header row
* ├───┼───┼───┼───┤
* │ c │ c │ d │ d │
* ├───┼───┼───┼───┤
* │ c │ c │ d │ d │
* └───┴───┴───┴───┘
*
* @private
* @param {Array.<module:engine/model/element~Element>} tableCells
* @returns {Boolean}
*/
_areCellInTheSameTableSection( tableCells ) {
const table = tableCells[ 0 ].findAncestor( 'table' );
const rowIndexes = this.getRowIndexes( tableCells );
const headingRows = parseInt( table.getAttribute( 'headingRows' ) || 0 );
// Calculating row indexes is a bit cheaper so if this check fails we can't merge.
if ( !this._areIndexesInSameSection( rowIndexes, headingRows ) ) {
return false;
}
const headingColumns = parseInt( table.getAttribute( 'headingColumns' ) || 0 );
const columnIndexes = this.getColumnIndexes( tableCells );
// Similarly cells must be in same column section.
return this._areIndexesInSameSection( columnIndexes, headingColumns );
}
/**
* Unified check if table rows/columns indexes are in the same heading/body section.
*
* @private
* @param {Object} params
* @param {Number} params.first
* @param {Number} params.last
* @param {Number} headingSectionSize
*/
_areIndexesInSameSection( { first, last }, headingSectionSize ) {
const firstCellIsInHeading = first < headingSectionSize;
const lastCellIsInHeading = last < headingSectionSize;
return firstCellIsInHeading === lastCellIsInHeading;
}
}
// Creates empty rows at the given index in an existing table.
//
// @param {module:engine/model/writer~Writer} writer
// @param {module:engine/model/element~Element} table
// @param {Number} insertAt The row index of row insertion.
// @param {Number} rows The number of rows to create.
// @param {Number} tableCellToInsert The number of cells to insert in each row.
function createEmptyRows( writer, table, insertAt, rows, tableCellToInsert, attributes = {} ) {
for ( let i = 0; i < rows; i++ ) {
const tableRow = writer.createElement( 'tableRow' );
writer.insert( tableRow, table, insertAt );
createCells( tableCellToInsert, writer, writer.createPositionAt( tableRow, 'end' ), attributes );
}
}
// Creates cells at a given position.
//
// @param {Number} columns The number of columns to create
// @param {module:engine/model/writer~Writer} writer
// @param {module:engine/model/position~Position} insertPosition
function createCells( cells, writer, insertPosition, attributes = {} ) {
for ( let i = 0; i < cells; i++ ) {
(0,_utils_common__WEBPACK_IMPORTED_MODULE_3__.createEmptyTableCell)( writer, insertPosition, attributes );
}
}
// Evenly distributes the span of a cell to a number of provided cells.
// The resulting spans will always be integer values.
//
// For instance breaking a span of 7 into 3 cells will return:
//
// { newCellsSpan: 2, updatedSpan: 3 }
//
// as two cells will have a span of 2 and the remainder will go the first cell so its span will change to 3.
//
// @param {Number} span The span value do break.
// @param {Number} numberOfCells The number of resulting spans.
// @returns {{newCellsSpan: Number, updatedSpan: Number}}
function breakSpanEvenly( span, numberOfCells ) {
if ( span < numberOfCells ) {
return { newCellsSpan: 1, updatedSpan: 1 };
}
const newCellsSpan = Math.floor( span / numberOfCells );
const updatedSpan = ( span - newCellsSpan * numberOfCells ) + newCellsSpan;
return { newCellsSpan, updatedSpan };
}
// Updates heading columns attribute if removing a row from head section.
function adjustHeadingColumns( table, removedColumnIndexes, writer ) {
const headingColumns = table.getAttribute( 'headingColumns' ) || 0;
if ( headingColumns && removedColumnIndexes.first < headingColumns ) {
const headingsRemoved = Math.min( headingColumns - 1 /* Other numbers are 0-based */, removedColumnIndexes.last ) -
removedColumnIndexes.first + 1;
writer.setAttribute( 'headingColumns', headingColumns - headingsRemoved, table );
}
}
// Calculates a new heading rows value for removing rows from heading section.
function updateHeadingRows( table, first, last, writer ) {
const headingRows = table.getAttribute( 'headingRows' ) || 0;
if ( first < headingRows ) {
const newRows = last < headingRows ? headingRows - ( last - first + 1 ) : first;
(0,_utils_common__WEBPACK_IMPORTED_MODULE_3__.updateNumericAttribute)( 'headingRows', newRows, table, writer, 0 );
}
}
// Finds cells that will be:
// - trimmed - Cells that are "above" removed rows sections and overlap the removed section - their rowspan must be trimmed.
// - moved - Cells from removed rows section might stick out of. These cells are moved to the next row after a removed section.
//
// Sample table with overlapping & sticking out cells:
//
// +----+----+----+----+----+
// | 00 | 01 | 02 | 03 | 04 |
// +----+ + + + +
// | 10 | | | | |
// +----+----+ + + +
// | 20 | 21 | | | | <-- removed row
// + + +----+ + +
// | | | 32 | | | <-- removed row
// +----+ + +----+ +
// | 40 | | | 43 | |
// +----+----+----+----+----+
//
// In a table above:
// - cells to trim: '02', '03' & '04'.
// - cells to move: '21' & '32'.
function getCellsToMoveAndTrimOnRemoveRow( table, first, last ) {
const cellsToMove = new Map();
const cellsToTrim = [];
for ( const { row, column, cellHeight, cell } of new _tablewalker__WEBPACK_IMPORTED_MODULE_2__["default"]( table, { endRow: last } ) ) {
const lastRowOfCell = row + cellHeight - 1;
const isCellStickingOutFromRemovedRows = row >= first && row <= last && lastRowOfCell > last;
if ( isCellStickingOutFromRemovedRows ) {
const rowspanInRemovedSection = last - row + 1;
const rowSpanToSet = cellHeight - rowspanInRemovedSection;
cellsToMove.set( column, {
cell,
rowspan: rowSpanToSet
} );
}
const isCellOverlappingRemovedRows = row < first && lastRowOfCell >= first;
if ( isCellOverlappingRemovedRows ) {
let rowspanAdjustment;
// Cell fully covers removed section - trim it by removed rows count.
if ( lastRowOfCell >= last ) {
rowspanAdjustment = last - first + 1;
}
// Cell partially overlaps removed section - calculate cell's span that is in removed section.
else {
rowspanAdjustment = lastRowOfCell - first + 1;
}
cellsToTrim.push( {
cell,
rowspan: cellHeight - rowspanAdjustment
} );
}
}
return { cellsToMove, cellsToTrim };
}
function moveCellsToRow( table, targetRowIndex, cellsToMove, writer ) {
const tableWalker = new _tablewalker__WEBPACK_IMPORTED_MODULE_2__["default"]( table, {
includeAllSlots: true,
row: targetRowIndex
} );
const tableRowMap = [ ...tableWalker ];
const row = table.getChild( targetRowIndex );
let previousCell;
for ( const { column, cell, isAnchor } of tableRowMap ) {
if ( cellsToMove.has( column ) ) {
const { cell: cellToMove, rowspan } = cellsToMove.get( column );
const targetPosition = previousCell ?
writer.createPositionAfter( previousCell ) :
writer.createPositionAt( row, 0 );
writer.move( writer.createRangeOn( cellToMove ), targetPosition );
(0,_utils_common__WEBPACK_IMPORTED_MODULE_3__.updateNumericAttribute)( 'rowspan', rowspan, cellToMove, writer );
previousCell = cellToMove;
} else if ( isAnchor ) {
// If cell is spanned then `cell` holds reference to overlapping cell. See ckeditor/ckeditor5#6502.
previousCell = cell;
}
}
}
function compareRangeOrder( rangeA, rangeB ) {
// Since table cell ranges are disjoint, it's enough to check their start positions.
const posA = rangeA.start;
const posB = rangeB.start;
// Checking for equal position (returning 0) is not needed because this would be either:
// a. Intersecting range (not allowed by model)
// b. Collapsed range on the same position (allowed by model but should not happen).
return posA.isBefore( posB ) ? -1 : 1;
}
// Calculates the area of a maximum rectangle that can span over the provided row & column indexes.
//
// @param {Array.<Number>} rows
// @param {Array.<Number>} columns
// @returns {Number}
function getBiggestRectangleArea( rows, columns ) {
const rowsIndexes = Array.from( rows.values() );
const columnIndexes = Array.from( columns.values() );
const lastRow = Math.max( ...rowsIndexes );
const firstRow = Math.min( ...rowsIndexes );
const lastColumn = Math.max( ...columnIndexes );
const firstColumn = Math.min( ...columnIndexes );
return ( lastRow - firstRow + 1 ) * ( lastColumn - firstColumn + 1 );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/tablewalker.js":
/*!*******************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/tablewalker.js ***!
\*******************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ TableWalker)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/tablewalker
*/
// @if CK_DEBUG // import { CKEditorError } from 'ckeditor5/src/utils';
/**
* The table iterator class. It allows to iterate over table cells. For each cell the iterator yields
* {@link module:table/tablewalker~TableSlot} with proper table cell attributes.
*/
class TableWalker {
/**
* Creates an instance of the table walker.
*
* The table walker iterates internally by traversing the table from row index = 0 and column index = 0.
* It walks row by row and column by column in order to output values defined in the constructor.
* By default it will output only the locations that are occupied by a cell. To include also spanned rows and columns,
* pass the `includeAllSlots` option to the constructor.
*
* The most important values of the iterator are column and row indexes of a cell.
*
* See {@link module:table/tablewalker~TableSlot} what values are returned by the table walker.
*
* To iterate over a given row:
*
* const tableWalker = new TableWalker( table, { startRow: 1, endRow: 2 } );
*
* for ( const tableSlot of tableWalker ) {
* console.log( 'A cell at row', tableSlot.row, 'and column', tableSlot.column );
* }
*
* For instance the code above for the following table:
*
* +----+----+----+----+----+----+
* | 00 | 02 | 03 | 04 | 05 |
* | +----+----+----+----+
* | | 12 | 14 | 15 |
* | +----+----+----+ +
* | | 22 | |
* |----+----+----+----+----+ +
* | 30 | 31 | 32 | 33 | 34 | |
* +----+----+----+----+----+----+
*
* will log in the console:
*
* 'A cell at row 1 and column 2'
* 'A cell at row 1 and column 4'
* 'A cell at row 1 and column 5'
* 'A cell at row 2 and column 2'
*
* To also iterate over spanned cells:
*
* const tableWalker = new TableWalker( table, { row: 1, includeAllSlots: true } );
*
* for ( const tableSlot of tableWalker ) {
* console.log( 'Slot at', tableSlot.row, 'x', tableSlot.column, ':', tableSlot.isAnchor ? 'is anchored' : 'is spanned' );
* }
*
* will log in the console for the table from the previous example:
*
* 'Cell at 1 x 0 : is spanned'
* 'Cell at 1 x 1 : is spanned'
* 'Cell at 1 x 2 : is anchored'
* 'Cell at 1 x 3 : is spanned'
* 'Cell at 1 x 4 : is anchored'
* 'Cell at 1 x 5 : is anchored'
*
* **Note**: Option `row` is a shortcut that sets both `startRow` and `endRow` to the same row.
* (Use either `row` or `startRow` and `endRow` but never together). Similarly the `column` option sets both `startColumn`
* and `endColumn` to the same column (Use either `column` or `startColumn` and `endColumn` but never together).
*
* @constructor
* @param {module:engine/model/element~Element} table A table over which the walker iterates.
* @param {Object} [options={}] An object with configuration.
* @param {Number} [options.row] A row index for which this iterator will output cells.
* Can't be used together with `startRow` and `endRow`.
* @param {Number} [options.startRow=0] A row index from which this iterator should start. Can't be used together with `row`.
* @param {Number} [options.endRow] A row index at which this iterator should end. Can't be used together with `row`.
* @param {Number} [options.column] A column index for which this iterator will output cells.
* Can't be used together with `startColumn` and `endColumn`.
* @param {Number} [options.startColumn=0] A column index from which this iterator should start. Can't be used together with `column`.
* @param {Number} [options.endColumn] A column index at which this iterator should end. Can't be used together with `column`.
* @param {Boolean} [options.includeAllSlots=false] Also return values for spanned cells.
*/
constructor( table, options = {} ) {
/**
* The walker's table element.
*
* @readonly
* @member {module:engine/model/element~Element}
* @protected
*/
this._table = table;
/**
* A row index from which this iterator will start.
*
* @readonly
* @member {Number}
* @private
*/
this._startRow = options.row !== undefined ? options.row : options.startRow || 0;
/**
* A row index at which this iterator will end.
*
* @readonly
* @member {Number}
* @private
*/
this._endRow = options.row !== undefined ? options.row : options.endRow;
/**
* If set, the table walker will only output cells from a given column and following ones or cells that overlap them.
*
* @readonly
* @member {Number}
* @private
*/
this._startColumn = options.column !== undefined ? options.column : options.startColumn || 0;
/**
* If set, the table walker will only output cells up to a given column.
*
* @readonly
* @member {Number}
* @private
*/
this._endColumn = options.column !== undefined ? options.column : options.endColumn;
/**
* Enables output of spanned cells that are normally not yielded.
*
* @readonly
* @member {Boolean}
* @private
*/
this._includeAllSlots = !!options.includeAllSlots;
/**
* Row indexes to skip from the iteration.
*
* @readonly
* @member {Set<Number>}
* @private
*/
this._skipRows = new Set();
/**
* The current row index.
*
* @member {Number}
* @protected
*/
this._row = 0;
/**
* The index of the current row element in the table.
*
* @type {Number}
* @protected
*/
this._rowIndex = 0;
/**
* The current column index.
*
* @member {Number}
* @protected
*/
this._column = 0;
/**
* The cell index in a parent row. For spanned cells when {@link #_includeAllSlots} is set to `true`,
* this represents the index of the next table cell.
*
* @member {Number}
* @protected
*/
this._cellIndex = 0;
/**
* Holds a map of spanned cells in a table.
*
* @readonly
* @member {Map.<Number, Map.<Number, Object>>}
* @private
*/
this._spannedCells = new Map();
/**
* Index of the next column where a cell is anchored.
*
* @member {Number}
* @private
*/
this._nextCellAtColumn = -1;
}
/**
* Iterable interface.
*
* @returns {Iterable.<module:table/tablewalker~TableSlot>}
*/
[ Symbol.iterator ]() {
return this;
}
/**
* Gets the next table walker's value.
*
* @returns {module:table/tablewalker~TableSlot} The next table walker's value.
*/
next() {
const row = this._table.getChild( this._rowIndex );
// Iterator is done when there's no row (table ended) or the row is after `endRow` limit.
if ( !row || this._isOverEndRow() ) {
return { done: true };
}
// We step over current element when it is not a tableRow instance.
if ( !row.is( 'element', 'tableRow' ) ) {
this._rowIndex++;
return this.next();
}
if ( this._isOverEndColumn() ) {
return this._advanceToNextRow();
}
let outValue = null;
const spanData = this._getSpanned();
if ( spanData ) {
if ( this._includeAllSlots && !this._shouldSkipSlot() ) {
outValue = this._formatOutValue( spanData.cell, spanData.row, spanData.column );
}
} else {
const cell = row.getChild( this._cellIndex );
if ( !cell ) {
// If there are no more cells left in row advance to the next row.
return this._advanceToNextRow();
}
const colspan = parseInt( cell.getAttribute( 'colspan' ) || 1 );
const rowspan = parseInt( cell.getAttribute( 'rowspan' ) || 1 );
// Record this cell spans if it's not 1x1 cell.
if ( colspan > 1 || rowspan > 1 ) {
this._recordSpans( cell, rowspan, colspan );
}
if ( !this._shouldSkipSlot() ) {
outValue = this._formatOutValue( cell );
}
this._nextCellAtColumn = this._column + colspan;
}
// Advance to the next column before returning value.
this._column++;
if ( this._column == this._nextCellAtColumn ) {
this._cellIndex++;
}
// The current value will be returned only if current row and column are not skipped.
return outValue || this.next();
}
/**
* Marks a row to skip in the next iteration. It will also skip cells from the current row if there are any cells from the current row
* to output.
*
* @param {Number} row The row index to skip.
*/
skipRow( row ) {
this._skipRows.add( row );
}
/**
* Advances internal cursor to the next row.
*
* @private
* @returns {module:table/tablewalker~TableSlot}
*/
_advanceToNextRow() {
this._row++;
this._rowIndex++;
this._column = 0;
this._cellIndex = 0;
this._nextCellAtColumn = -1;
return this.next();
}
/**
* Checks if the current row is over {@link #_endRow}.
*
* @private
* @returns {Boolean}
*/
_isOverEndRow() {
// If #_endRow is defined skip all rows after it.
return this._endRow !== undefined && this._row > this._endRow;
}
/**
* Checks if the current cell is over {@link #_endColumn}
*
* @private
* @returns {Boolean}
*/
_isOverEndColumn() {
// If #_endColumn is defined skip all cells after it.
return this._endColumn !== undefined && this._column > this._endColumn;
}
/**
* A common method for formatting the iterator's output value.
*
* @private
* @param {module:engine/model/element~Element} cell The table cell to output.
* @param {Number} [anchorRow] The row index of a cell anchor slot.
* @param {Number} [anchorColumn] The column index of a cell anchor slot.
* @returns {{done: Boolean, value: {cell: *, row: Number, column: *, rowspan: *, colspan: *, cellIndex: Number}}}
*/
_formatOutValue( cell, anchorRow = this._row, anchorColumn = this._column ) {
return {
done: false,
value: new TableSlot( this, cell, anchorRow, anchorColumn )
};
}
/**
* Checks if the current slot should be skipped.
*
* @private
* @returns {Boolean}
*/
_shouldSkipSlot() {
const rowIsMarkedAsSkipped = this._skipRows.has( this._row );
const rowIsBeforeStartRow = this._row < this._startRow;
const columnIsBeforeStartColumn = this._column < this._startColumn;
const columnIsAfterEndColumn = this._endColumn !== undefined && this._column > this._endColumn;
return rowIsMarkedAsSkipped || rowIsBeforeStartRow || columnIsBeforeStartColumn || columnIsAfterEndColumn;
}
/**
* Returns the cell element that is spanned over the current cell location.
*
* @private
* @returns {module:engine/model/element~Element}
*/
_getSpanned() {
const rowMap = this._spannedCells.get( this._row );
// No spans for given row.
if ( !rowMap ) {
return null;
}
// If spans for given rows has entry for column it means that this location if spanned by other cell.
return rowMap.get( this._column ) || null;
}
/**
* Updates spanned cells map relative to the current cell location and its span dimensions.
*
* @private
* @param {module:engine/model/element~Element} cell A cell that is spanned.
* @param {Number} rowspan Cell height.
* @param {Number} colspan Cell width.
*/
_recordSpans( cell, rowspan, colspan ) {
const data = {
cell,
row: this._row,
column: this._column
};
for ( let rowToUpdate = this._row; rowToUpdate < this._row + rowspan; rowToUpdate++ ) {
for ( let columnToUpdate = this._column; columnToUpdate < this._column + colspan; columnToUpdate++ ) {
if ( rowToUpdate != this._row || columnToUpdate != this._column ) {
this._markSpannedCell( rowToUpdate, columnToUpdate, data );
}
}
}
}
/**
* Marks the cell location as spanned by another cell.
*
* @private
* @param {Number} row The row index of the cell location.
* @param {Number} column The column index of the cell location.
* @param {Object} data A spanned cell details (cell element, anchor row and column).
*/
_markSpannedCell( row, column, data ) {
if ( !this._spannedCells.has( row ) ) {
this._spannedCells.set( row, new Map() );
}
const rowSpans = this._spannedCells.get( row );
rowSpans.set( column, data );
}
}
/**
* An object returned by {@link module:table/tablewalker~TableWalker} when traversing table cells.
*/
class TableSlot {
/**
* Creates an instance of the table walker value.
*
* @protected
* @param {module:table/tablewalker~TableWalker} tableWalker The table walker instance.
* @param {module:engine/model/element~Element} cell The current table cell.
* @param {Number} anchorRow The row index of a cell anchor slot.
* @param {Number} anchorColumn The column index of a cell anchor slot.
*/
constructor( tableWalker, cell, anchorRow, anchorColumn ) {
/**
* The current table cell.
*
* @readonly
* @member {module:engine/model/element~Element}
*/
this.cell = cell;
/**
* The row index of a table slot.
*
* @readonly
* @member {Number}
*/
this.row = tableWalker._row;
/**
* The column index of a table slot.
*
* @readonly
* @member {Number}
*/
this.column = tableWalker._column;
/**
* The row index of a cell anchor slot.
*
* @readonly
* @member {Number}
*/
this.cellAnchorRow = anchorRow;
/**
* The column index of a cell anchor slot.
*
* @readonly
* @member {Number}
*/
this.cellAnchorColumn = anchorColumn;
/**
* The index of the current cell in the parent row.
*
* @readonly
* @member {Number}
* @private
*/
this._cellIndex = tableWalker._cellIndex;
/**
* The index of the current row element in the table.
*
* @readonly
* @member {Number}
* @private
*/
this._rowIndex = tableWalker._rowIndex;
/**
* The table element.
*
* @readonly
* @member {module:engine/model/element~Element}
* @private
*/
this._table = tableWalker._table;
}
/**
* Whether the cell is anchored in the current slot.
*
* @readonly
* @returns {Boolean}
*/
get isAnchor() {
return this.row === this.cellAnchorRow && this.column === this.cellAnchorColumn;
}
/**
* The width of a cell defined by a `colspan` attribute. If the model attribute is not present, it is set to `1`.
*
* @readonly
* @returns {Number}
*/
get cellWidth() {
return parseInt( this.cell.getAttribute( 'colspan' ) || 1 );
}
/**
* The height of a cell defined by a `rowspan` attribute. If the model attribute is not present, it is set to `1`.
*
* @readonly
* @returns {Number}
*/
get cellHeight() {
return parseInt( this.cell.getAttribute( 'rowspan' ) || 1 );
}
/**
* The index of the current row element in the table.
*
* @readonly
* @returns {Number}
*/
get rowIndex() {
return this._rowIndex;
}
/**
* Returns the {@link module:engine/model/position~Position} before the table slot.
*
* @returns {module:engine/model/position~Position}
*/
getPositionBefore() {
const model = this._table.root.document.model;
return model.createPositionAt( this._table.getChild( this.row ), this._cellIndex );
}
// @if CK_DEBUG // get isSpanned() { throwMissingGetterError( 'isSpanned' ); }
// @if CK_DEBUG // get colspan() { throwMissingGetterError( 'colspan' ); }
// @if CK_DEBUG // get rowspan() { throwMissingGetterError( 'rowspan' ); }
// @if CK_DEBUG // get cellIndex() { throwMissingGetterError( 'cellIndex' ); }
}
/**
* This `TableSlot`'s getter (property) was removed in CKEditor 5 v20.0.0.
*
* Check out the new `TableWalker`'s API in the documentation.
*
* @error tableslot-getter-removed
* @param {String} getterName
*/
// @if CK_DEBUG // function throwMissingGetterError( getterName ) {
// @if CK_DEBUG // throw new CKEditorError( 'tableslot-getter-removed', this, {
// @if CK_DEBUG // getterName
// @if CK_DEBUG // } );
// @if CK_DEBUG // }
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/ui/colorinputview.js":
/*!*************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/ui/colorinputview.js ***!
\*************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ColorInputView)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/ui */ "./node_modules/ckeditor5/src/ui.js");
/* harmony import */ var ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/core */ "./node_modules/ckeditor5/src/core.js");
/* harmony import */ var _theme_colorinput_css__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../theme/colorinput.css */ "./node_modules/@ckeditor/ckeditor5-table/theme/colorinput.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/ui/colorinputview
*/
/**
* The color input view class. It allows the user to type in a color (hex, rgb, etc.)
* or choose it from the configurable color palette with a preview.
*
* @private
* @extends module:ui/view~View
*/
class ColorInputView extends ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.View {
/**
* Creates an instance of the color input view.
*
* @param {module:utils/locale~Locale} locale The locale instance.
* @param {Object} options The input options.
* @param {Array.<module:ui/colorgrid/colorgrid~ColorDefinition>} options.colorDefinitions The colors to be displayed
* in the palette inside the input's dropdown.
* @param {Number} options.columns The number of columns in which the colors will be displayed.
* @param {String} [options.defaultColorValue] If specified, the color input view will replace the "Remove color" button with
* the "Restore default" button. Instead of clearing the input field, the default color value will be set.
*/
constructor( locale, options ) {
super( locale );
const bind = this.bindTemplate;
/**
* The value of the input.
*
* @observable
* @member {String} #value
* @default ''
*/
this.set( 'value', '' );
/**
* The `id` attribute of the input (i.e. to pair with the `<label>` element).
*
* @observable
* @member {String} #id
*/
this.set( 'id' );
/**
* Controls whether the input view is in read-only mode.
*
* @observable
* @member {Boolean} #isReadOnly
* @default false
*/
this.set( 'isReadOnly', false );
/**
* Set to `true` when the field has some error. Usually controlled via
* {@link module:ui/labeledinput/labeledinputview~LabeledInputView#errorText}.
*
* @observable
* @member {Boolean} #hasError
* @default false
*/
this.set( 'hasError', false );
/**
* An observable flag set to `true` when the input is focused by the user.
* `false` otherwise.
*
* @readonly
* @observable
* @member {Boolean} #isFocused
* @default false
*/
this.set( 'isFocused', false );
/**
* An observable flag set to `true` when the input contains no text.
*
* @readonly
* @observable
* @member {Boolean} #isEmpty
* @default true
*/
this.set( 'isEmpty', true );
/**
* The `id` of the element describing this field. When the field has
* some error, it helps screen readers read the error text.
*
* @observable
* @member {String} #ariaDescribedById
*/
this.set( 'ariaDescribedById' );
/**
* A cached reference to the options passed to the constructor.
*
* @member {Object}
*/
this.options = options;
/**
* An instance of the dropdown allowing to select a color from a grid.
*
* @protected
* @member {module:ui/dropdown/dropdown~DropdownView}
*/
this._dropdownView = this._createDropdownView();
/**
* An instance of the input allowing the user to type a color value.
*
* @protected
* @member {module:ui/inputtext/inputtextview~InputTextView}
*/
this._inputView = this._createInputTextView();
/**
* The flag that indicates whether the user is still typing.
* If set to true, it means that the text input field ({@link #_inputView}) still has the focus.
* So, we should interrupt the user by replacing the input's value.
*
* @protected
* @member {Boolean}
*/
this._stillTyping = false;
this.setTemplate( {
tag: 'div',
attributes: {
class: [
'ck',
'ck-input-color',
bind.if( 'hasError', 'ck-error' )
],
id: bind.to( 'id' ),
'aria-invalid': bind.if( 'hasError', true ),
'aria-describedby': bind.to( 'ariaDescribedById' )
},
children: [
this._dropdownView,
this._inputView
]
} );
this.on( 'change:value', ( evt, name, inputValue ) => this._setInputValue( inputValue ) );
}
/**
* Focuses the input.
*/
focus() {
this._inputView.focus();
}
/**
* Creates and configures the {@link #_dropdownView}.
*
* @private
*/
_createDropdownView() {
const locale = this.locale;
const t = locale.t;
const bind = this.bindTemplate;
const colorGrid = this._createColorGrid( locale );
const dropdown = (0,ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.createDropdown)( locale );
const colorPreview = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.View();
const removeColorButton = this._createRemoveColorButton();
colorPreview.setTemplate( {
tag: 'span',
attributes: {
class: [
'ck',
'ck-input-color__button__preview'
],
style: {
backgroundColor: bind.to( 'value' )
}
},
children: [ {
tag: 'span',
attributes: {
class: [
'ck',
'ck-input-color__button__preview__no-color-indicator',
bind.if( 'value', 'ck-hidden', value => value != '' )
]
}
} ]
} );
dropdown.buttonView.extendTemplate( {
attributes: {
class: 'ck-input-color__button'
}
} );
dropdown.buttonView.children.add( colorPreview );
dropdown.buttonView.tooltip = t( 'Color picker' );
dropdown.panelPosition = locale.uiLanguageDirection === 'rtl' ? 'se' : 'sw';
dropdown.panelView.children.add( removeColorButton );
dropdown.panelView.children.add( colorGrid );
dropdown.bind( 'isEnabled' ).to( this, 'isReadOnly', value => !value );
return dropdown;
}
/**
* Creates and configures an instance of {@link module:ui/inputtext/inputtextview~InputTextView}.
*
* @private
* @returns {module:ui/inputtext/inputtextview~InputTextView} A configured instance to be set as {@link #_inputView}.
*/
_createInputTextView() {
const locale = this.locale;
const inputView = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.InputTextView( locale );
inputView.extendTemplate( {
on: {
blur: inputView.bindTemplate.to( 'blur' )
}
} );
inputView.value = this.value;
inputView.bind( 'isReadOnly', 'hasError' ).to( this );
this.bind( 'isFocused', 'isEmpty' ).to( inputView );
inputView.on( 'input', () => {
const inputValue = inputView.element.value;
// Check if the value matches one of our defined colors' label.
const mappedColor = this.options.colorDefinitions.find( def => inputValue === def.label );
this._stillTyping = true;
this.value = mappedColor && mappedColor.color || inputValue;
} );
inputView.on( 'blur', () => {
this._stillTyping = false;
this._setInputValue( inputView.element.value );
} );
inputView.delegate( 'input' ).to( this );
return inputView;
}
/**
* Creates and configures the button that clears the color.
*
* @private
*/
_createRemoveColorButton() {
const locale = this.locale;
const t = locale.t;
const removeColorButton = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.ButtonView( locale );
const defaultColor = this.options.defaultColorValue || '';
const removeColorButtonLabel = defaultColor ? t( 'Restore default' ) : t( 'Remove color' );
removeColorButton.class = 'ck-input-color__remove-color';
removeColorButton.withText = true;
removeColorButton.icon = ckeditor5_src_core__WEBPACK_IMPORTED_MODULE_1__.icons.eraser;
removeColorButton.label = removeColorButtonLabel;
removeColorButton.on( 'execute', () => {
this.value = defaultColor;
this._dropdownView.isOpen = false;
this.fire( 'input' );
} );
return removeColorButton;
}
/**
* Creates and configures the color grid inside the {@link #_dropdownView}.
*
* @private
*/
_createColorGrid( locale ) {
const colorGrid = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.ColorGridView( locale, {
colorDefinitions: this.options.colorDefinitions,
columns: this.options.columns
} );
colorGrid.on( 'execute', ( evtData, data ) => {
this.value = data.value;
this._dropdownView.isOpen = false;
this.fire( 'input' );
} );
colorGrid.bind( 'selectedColor' ).to( this, 'value' );
return colorGrid;
}
/**
* Sets {@link #_inputView}'s value property to the color value or color label,
* if there is one and the user is not typing.
*
* Handles cases like:
*
* * Someone picks the color in the grid.
* * The color is set from the plugin level.
*
* @private
* @param {String} inputValue Color value to be set.
*/
_setInputValue( inputValue ) {
if ( !this._stillTyping ) {
const normalizedInputValue = normalizeColor( inputValue );
// Check if the value matches one of our defined colors.
const mappedColor = this.options.colorDefinitions.find( def => normalizedInputValue === normalizeColor( def.color ) );
if ( mappedColor ) {
this._inputView.value = mappedColor.label;
} else {
this._inputView.value = inputValue || '';
}
}
}
}
// Normalizes color value, by stripping extensive whitespace.
// For example., transforms:
// * ` rgb( 25 50 0 )` to `rgb(25 50 0)`,
// * "\t rgb( 25 , 50,0 ) " to `rgb(25 50 0)`.
//
// @param {String} colorString The value to be normalized.
// @returns {String}
function normalizeColor( colorString ) {
return colorString
// Remove any whitespace right after `(` or `,`.
.replace( /([(,])\s+/g, '$1' )
// Remove any whitespace at the beginning or right before the end, `)`, `,`, or another whitespace.
.replace( /^\s+|\s+(?=[),\s]|$)/g, '' )
// Then, replace `,` or whitespace with a single space.
.replace( /,|\s/g, ' ' );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/ui/formrowview.js":
/*!**********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/ui/formrowview.js ***!
\**********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ FormRowView)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/ui */ "./node_modules/ckeditor5/src/ui.js");
/* harmony import */ var _theme_formrow_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../../theme/formrow.css */ "./node_modules/@ckeditor/ckeditor5-table/theme/formrow.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/ui/formrowview
*/
/**
* The class representing a single row in a complex form,
* used by {@link module:table/tablecellproperties/ui/tablecellpropertiesview~TableCellPropertiesView}.
*
* **Note**: For now this class is private. When more use cases arrive (beyond ckeditor5-table),
* it will become a component in ckeditor5-ui.
*
* @private
* @extends module:ui/view~View
*/
class FormRowView extends ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.View {
/**
* Creates an instance of the form row class.
*
* @param {module:utils/locale~Locale} locale The locale instance.
* @param {Object} options
* @param {Array.<module:ui/view~View>} options.children
* @param {String} [options.class]
* @param {module:ui/view~View} [options.labelView] When passed, the row gets the `group` and `aria-labelledby`
* DOM attributes and gets described by the label.
*/
constructor( locale, options = {} ) {
super( locale );
const bind = this.bindTemplate;
/**
* An additional CSS class added to the {@link #element}.
*
* @observable
* @member {String} #class
*/
this.set( 'class', options.class || null );
/**
* A collection of row items (buttons, dropdowns, etc.).
*
* @readonly
* @member {module:ui/viewcollection~ViewCollection}
*/
this.children = this.createCollection();
if ( options.children ) {
options.children.forEach( child => this.children.add( child ) );
}
/**
* The role property reflected by the `role` DOM attribute of the {@link #element}.
*
* **Note**: Used only when a `labelView` is passed to constructor `options`.
*
* @private
* @observable
* @member {String} #role
*/
this.set( '_role', null );
/**
* The ARIA property reflected by the `aria-labelledby` DOM attribute of the {@link #element}.
*
* **Note**: Used only when a `labelView` is passed to constructor `options`.
*
* @private
* @observable
* @member {String} #ariaLabelledBy
*/
this.set( '_ariaLabelledBy', null );
if ( options.labelView ) {
this.set( {
_role: 'group',
_ariaLabelledBy: options.labelView.id
} );
}
this.setTemplate( {
tag: 'div',
attributes: {
class: [
'ck',
'ck-form__row',
bind.to( 'class' )
],
role: bind.to( '_role' ),
'aria-labelledby': bind.to( '_ariaLabelledBy' )
},
children: this.children
} );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/ui/inserttableview.js":
/*!**************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/ui/inserttableview.js ***!
\**************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ InsertTableView)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/ui */ "./node_modules/ckeditor5/src/ui.js");
/* harmony import */ var _theme_inserttable_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./../../theme/inserttable.css */ "./node_modules/@ckeditor/ckeditor5-table/theme/inserttable.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/ui/inserttableview
*/
/**
* The table size view.
*
* It renders a 10x10 grid to choose the inserted table size.
*
* @extends module:ui/view~View
* @implements module:ui/dropdown/dropdownpanelfocusable~DropdownPanelFocusable
*/
class InsertTableView extends ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.View {
/**
* @inheritDoc
*/
constructor( locale ) {
super( locale );
const bind = this.bindTemplate;
/**
* A collection of table size box items.
*
* @readonly
* @member {module:ui/viewcollection~ViewCollection}
*/
this.items = this._createGridCollection();
/**
* The currently selected number of rows of the new table.
*
* @observable
* @member {Number} #rows
*/
this.set( 'rows', 0 );
/**
* The currently selected number of columns of the new table.
*
* @observable
* @member {Number} #columns
*/
this.set( 'columns', 0 );
/**
* The label text displayed under the boxes.
*
* @observable
* @member {String} #label
*/
this.bind( 'label' )
.to( this, 'columns', this, 'rows', ( columns, rows ) => `${ rows } × ${ columns }` );
this.setTemplate( {
tag: 'div',
attributes: {
class: [ 'ck' ]
},
children: [
{
tag: 'div',
attributes: {
class: [ 'ck-insert-table-dropdown__grid' ]
},
on: {
'mouseover@.ck-insert-table-dropdown-grid-box': bind.to( 'boxover' )
},
children: this.items
},
{
tag: 'div',
attributes: {
class: [ 'ck-insert-table-dropdown__label' ]
},
children: [
{
text: bind.to( 'label' )
}
]
}
],
on: {
mousedown: bind.to( evt => {
evt.preventDefault();
} ),
click: bind.to( () => {
this.fire( 'execute' );
} )
}
} );
this.on( 'boxover', ( evt, domEvt ) => {
const { row, column } = domEvt.target.dataset;
// As row & column indexes are zero-based transform it to number of selected rows & columns.
this.set( {
rows: parseInt( row ),
columns: parseInt( column )
} );
} );
this.on( 'change:columns', () => {
this._highlightGridBoxes();
} );
this.on( 'change:rows', () => {
this._highlightGridBoxes();
} );
}
/**
* @inheritDoc
*/
focus() {
// The dropdown panel expects DropdownPanelFocusable interface on views passed to dropdown panel. See #30.
// The method should be implemented while working on keyboard support for this view. See #22.
}
/**
* @inheritDoc
*/
focusLast() {
// The dropdown panel expects DropdownPanelFocusable interface on views passed to dropdown panel. See #30.
// The method should be implemented while working on keyboard support for this view. See #22.
}
/**
* Highlights grid boxes depending on rows and columns selected.
*
* @private
*/
_highlightGridBoxes() {
const rows = this.rows;
const columns = this.columns;
this.items.map( ( boxView, index ) => {
// Translate box index to the row & column index.
const itemRow = Math.floor( index / 10 );
const itemColumn = index % 10;
// Grid box is highlighted when its row & column index belongs to selected number of rows & columns.
const isOn = itemRow < rows && itemColumn < columns;
boxView.set( 'isOn', isOn );
} );
}
/**
* @private
* @returns {module:ui/viewcollection~ViewCollection} A view collection containing boxes to be placed in a table grid.
*/
_createGridCollection() {
const boxes = [];
// Add grid boxes to table selection view.
for ( let index = 0; index < 100; index++ ) {
const row = Math.floor( index / 10 );
const column = index % 10;
boxes.push( new TableSizeGridBoxView( this.locale, row + 1, column + 1 ) );
}
return this.createCollection( boxes );
}
/**
* Fired when the mouse hover over one of the {@link #items child grid boxes}.
*
* @event boxover
*/
}
/**
* A single grid box view element.
*
* This class is used to render the table size selection grid in {@link module:table/ui/inserttableview~InsertTableView}.
*
* @private
*/
class TableSizeGridBoxView extends ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.View {
/**
* @inheritDoc
*/
constructor( locale, row, column ) {
super( locale );
const bind = this.bindTemplate;
/**
* Controls whether the grid box view is "on".
*
* @observable
* @member {Boolean} #isOn
*/
this.set( 'isOn', false );
this.setTemplate( {
tag: 'div',
attributes: {
class: [
'ck-insert-table-dropdown-grid-box',
bind.if( 'isOn', 'ck-on' )
],
'data-row': row,
'data-column': column
}
} );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/utils/common.js":
/*!********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/utils/common.js ***!
\********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "createEmptyTableCell": () => (/* binding */ createEmptyTableCell),
/* harmony export */ "isHeadingColumnCell": () => (/* binding */ isHeadingColumnCell),
/* harmony export */ "updateNumericAttribute": () => (/* binding */ updateNumericAttribute)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/utils/common
*/
/**
* A common method to update the numeric value. If a value is the default one, it will be unset.
*
* @param {String} key An attribute key.
* @param {*} value The new attribute value.
* @param {module:engine/model/item~Item} item A model item on which the attribute will be set.
* @param {module:engine/model/writer~Writer} writer
* @param {*} defaultValue The default attribute value. If a value is lower or equal, it will be unset.
*/
function updateNumericAttribute( key, value, item, writer, defaultValue = 1 ) {
if ( value > defaultValue ) {
writer.setAttribute( key, value, item );
} else {
writer.removeAttribute( key, item );
}
}
/**
* A common method to create an empty table cell. It creates a proper model structure as a table cell must have at least one block inside.
*
* @param {module:engine/model/writer~Writer} writer The model writer.
* @param {module:engine/model/position~Position} insertPosition The position at which the table cell should be inserted.
* @param {Object} attributes The element attributes.
* @returns {module:engine/model/element~Element} Created table cell.
*/
function createEmptyTableCell( writer, insertPosition, attributes = {} ) {
const tableCell = writer.createElement( 'tableCell', attributes );
writer.insertElement( 'paragraph', tableCell );
writer.insert( tableCell, insertPosition );
return tableCell;
}
/**
* Checks if a table cell belongs to the heading column section.
*
* @param {module:table/tableutils~TableUtils} tableUtils
* @param {module:engine/model/element~Element} tableCell
* @returns {Boolean}
*/
function isHeadingColumnCell( tableUtils, tableCell ) {
const table = tableCell.parent.parent;
const headingColumns = parseInt( table.getAttribute( 'headingColumns' ) || 0 );
const { column } = tableUtils.getCellLocation( tableCell );
return !!headingColumns && column < headingColumns;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/utils/structure.js":
/*!***********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/utils/structure.js ***!
\***********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "adjustLastColumnIndex": () => (/* binding */ adjustLastColumnIndex),
/* harmony export */ "adjustLastRowIndex": () => (/* binding */ adjustLastRowIndex),
/* harmony export */ "cropTableToDimensions": () => (/* binding */ cropTableToDimensions),
/* harmony export */ "getHorizontallyOverlappingCells": () => (/* binding */ getHorizontallyOverlappingCells),
/* harmony export */ "getVerticallyOverlappingCells": () => (/* binding */ getVerticallyOverlappingCells),
/* harmony export */ "removeEmptyColumns": () => (/* binding */ removeEmptyColumns),
/* harmony export */ "removeEmptyRows": () => (/* binding */ removeEmptyRows),
/* harmony export */ "removeEmptyRowsColumns": () => (/* binding */ removeEmptyRowsColumns),
/* harmony export */ "splitHorizontally": () => (/* binding */ splitHorizontally),
/* harmony export */ "splitVertically": () => (/* binding */ splitVertically),
/* harmony export */ "trimTableCellIfNeeded": () => (/* binding */ trimTableCellIfNeeded)
/* harmony export */ });
/* harmony import */ var _tablewalker__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../tablewalker */ "./node_modules/@ckeditor/ckeditor5-table/src/tablewalker.js");
/* harmony import */ var _common__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./common */ "./node_modules/@ckeditor/ckeditor5-table/src/utils/common.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/utils/structure
*/
/**
* Returns a cropped table according to given dimensions.
* To return a cropped table that starts at first row and first column and end in third row and column:
*
* const croppedTable = cropTableToDimensions( table, {
* startRow: 1,
* endRow: 3,
* startColumn: 1,
* endColumn: 3
* }, writer );
*
* Calling the code above for the table below:
*
* 0 1 2 3 4 0 1 2
* ┌───┬───┬───┬───┬───┐
* 0 │ a │ b │ c │ d │ e │
* ├───┴───┤ ├───┴───┤ ┌───┬───┬───┐
* 1 │ f │ │ g │ │ │ │ g │ 0
* ├───┬───┴───┼───┬───┤ will return: ├───┴───┼───┤
* 2 │ h │ i │ j │ k │ │ i │ j │ 1
* ├───┤ ├───┤ │ │ ├───┤
* 3 │ l │ │ m │ │ │ │ m │ 2
* ├───┼───┬───┤ ├───┤ └───────┴───┘
* 4 │ n │ o │ p │ │ q │
* └───┴───┴───┴───┴───┘
*
* @param {module:engine/model/element~Element} sourceTable
* @param {Object} cropDimensions
* @param {Number} cropDimensions.startRow
* @param {Number} cropDimensions.startColumn
* @param {Number} cropDimensions.endRow
* @param {Number} cropDimensions.endColumn
* @param {module:engine/model/writer~Writer} writer
* @returns {module:engine/model/element~Element}
*/
function cropTableToDimensions( sourceTable, cropDimensions, writer ) {
const { startRow, startColumn, endRow, endColumn } = cropDimensions;
// Create empty table with empty rows equal to crop height.
const croppedTable = writer.createElement( 'table' );
const cropHeight = endRow - startRow + 1;
for ( let i = 0; i < cropHeight; i++ ) {
writer.insertElement( 'tableRow', croppedTable, 'end' );
}
const tableMap = [ ...new _tablewalker__WEBPACK_IMPORTED_MODULE_0__["default"]( sourceTable, { startRow, endRow, startColumn, endColumn, includeAllSlots: true } ) ];
// Iterate over source table slots (including empty - spanned - ones).
for ( const { row: sourceRow, column: sourceColumn, cell: tableCell, isAnchor, cellAnchorRow, cellAnchorColumn } of tableMap ) {
// Row index in cropped table.
const rowInCroppedTable = sourceRow - startRow;
const row = croppedTable.getChild( rowInCroppedTable );
// For empty slots: fill the gap with empty table cell.
if ( !isAnchor ) {
// But fill the gap only if the spanning cell is anchored outside cropped area.
// In the table from method jsdoc those cells are: "c" & "f".
if ( cellAnchorRow < startRow || cellAnchorColumn < startColumn ) {
(0,_common__WEBPACK_IMPORTED_MODULE_1__.createEmptyTableCell)( writer, writer.createPositionAt( row, 'end' ) );
}
}
// Otherwise clone the cell with all children and trim if it exceeds cropped area.
else {
const tableCellCopy = writer.cloneElement( tableCell );
writer.append( tableCellCopy, row );
// Trim table if it exceeds cropped area.
// In the table from method jsdoc those cells are: "g" & "m".
trimTableCellIfNeeded( tableCellCopy, sourceRow, sourceColumn, endRow, endColumn, writer );
}
}
// Adjust heading rows & columns in cropped table if crop selection includes headings parts.
addHeadingsToCroppedTable( croppedTable, sourceTable, startRow, startColumn, writer );
return croppedTable;
}
/**
* Returns slot info of cells that starts above and overlaps a given row.
*
* In a table below, passing `overlapRow = 3`
*
* ┌───┬───┬───┬───┬───┐
* 0 │ a │ b │ c │ d │ e │
* │ ├───┼───┼───┼───┤
* 1 │ │ f │ g │ h │ i │
* ├───┤ ├───┼───┤ │
* 2 │ j │ │ k │ l │ │
* │ │ │ ├───┼───┤
* 3 │ │ │ │ m │ n │ <- overlap row to check
* ├───┼───┤ │ ├───│
* 4 │ o │ p │ │ │ q │
* └───┴───┴───┴───┴───┘
*
* will return slot info for cells: "j", "f", "k".
*
* @param {module:engine/model/element~Element} table The table to check.
* @param {Number} overlapRow The index of the row to check.
* @param {Number} [startRow=0] A row to start analysis. Use it when it is known that the cells above that row will not overlap.
* @returns {Array.<module:table/tablewalker~TableSlot>}
*/
function getVerticallyOverlappingCells( table, overlapRow, startRow = 0 ) {
const cells = [];
const tableWalker = new _tablewalker__WEBPACK_IMPORTED_MODULE_0__["default"]( table, { startRow, endRow: overlapRow - 1 } );
for ( const slotInfo of tableWalker ) {
const { row, cellHeight } = slotInfo;
const cellEndRow = row + cellHeight - 1;
if ( row < overlapRow && overlapRow <= cellEndRow ) {
cells.push( slotInfo );
}
}
return cells;
}
/**
* Splits the table cell horizontally.
*
* @param {module:engine/model/element~Element} tableCell
* @param {Number} splitRow
* @param {module:engine/model/writer~Writer} writer
* @returns {module:engine/model/element~Element} Created table cell.
*/
function splitHorizontally( tableCell, splitRow, writer ) {
const tableRow = tableCell.parent;
const table = tableRow.parent;
const rowIndex = tableRow.index;
const rowspan = parseInt( tableCell.getAttribute( 'rowspan' ) );
const newRowspan = splitRow - rowIndex;
const newCellAttributes = {};
const newCellRowSpan = rowspan - newRowspan;
if ( newCellRowSpan > 1 ) {
newCellAttributes.rowspan = newCellRowSpan;
}
const colspan = parseInt( tableCell.getAttribute( 'colspan' ) || 1 );
if ( colspan > 1 ) {
newCellAttributes.colspan = colspan;
}
const startRow = rowIndex;
const endRow = startRow + newRowspan;
const tableMap = [ ...new _tablewalker__WEBPACK_IMPORTED_MODULE_0__["default"]( table, { startRow, endRow, includeAllSlots: true } ) ];
let newCell = null;
let columnIndex;
for ( const tableSlot of tableMap ) {
const { row, column, cell } = tableSlot;
if ( cell === tableCell && columnIndex === undefined ) {
columnIndex = column;
}
if ( columnIndex !== undefined && columnIndex === column && row === endRow ) {
newCell = (0,_common__WEBPACK_IMPORTED_MODULE_1__.createEmptyTableCell)( writer, tableSlot.getPositionBefore(), newCellAttributes );
}
}
// Update the rowspan attribute after updating table.
(0,_common__WEBPACK_IMPORTED_MODULE_1__.updateNumericAttribute)( 'rowspan', newRowspan, tableCell, writer );
return newCell;
}
/**
* Returns slot info of cells that starts before and overlaps a given column.
*
* In a table below, passing `overlapColumn = 3`
*
* 0 1 2 3 4
* ┌───────┬───────┬───┐
* │ a │ b │ c │
* │───┬───┴───────┼───┤
* │ d │ e │ f │
* ├───┼───┬───────┴───┤
* │ g │ h │ i │
* ├───┼───┼───┬───────┤
* │ j │ k │ l │ m │
* ├───┼───┴───┼───┬───┤
* │ n │ o │ p │ q │
* └───┴───────┴───┴───┘
* ^
* Overlap column to check
*
* will return slot info for cells: "b", "e", "i".
*
* @param {module:engine/model/element~Element} table The table to check.
* @param {Number} overlapColumn The index of the column to check.
* @returns {Array.<module:table/tablewalker~TableSlot>}
*/
function getHorizontallyOverlappingCells( table, overlapColumn ) {
const cellsToSplit = [];
const tableWalker = new _tablewalker__WEBPACK_IMPORTED_MODULE_0__["default"]( table );
for ( const slotInfo of tableWalker ) {
const { column, cellWidth } = slotInfo;
const cellEndColumn = column + cellWidth - 1;
if ( column < overlapColumn && overlapColumn <= cellEndColumn ) {
cellsToSplit.push( slotInfo );
}
}
return cellsToSplit;
}
/**
* Splits the table cell vertically.
*
* @param {module:engine/model/element~Element} tableCell
* @param {Number} columnIndex The table cell column index.
* @param {Number} splitColumn The index of column to split cell on.
* @param {module:engine/model/writer~Writer} writer
* @returns {module:engine/model/element~Element} Created table cell.
*/
function splitVertically( tableCell, columnIndex, splitColumn, writer ) {
const colspan = parseInt( tableCell.getAttribute( 'colspan' ) );
const newColspan = splitColumn - columnIndex;
const newCellAttributes = {};
const newCellColSpan = colspan - newColspan;
if ( newCellColSpan > 1 ) {
newCellAttributes.colspan = newCellColSpan;
}
const rowspan = parseInt( tableCell.getAttribute( 'rowspan' ) || 1 );
if ( rowspan > 1 ) {
newCellAttributes.rowspan = rowspan;
}
const newCell = (0,_common__WEBPACK_IMPORTED_MODULE_1__.createEmptyTableCell)( writer, writer.createPositionAfter( tableCell ), newCellAttributes );
// Update the colspan attribute after updating table.
(0,_common__WEBPACK_IMPORTED_MODULE_1__.updateNumericAttribute)( 'colspan', newColspan, tableCell, writer );
return newCell;
}
/**
* Adjusts table cell dimensions to not exceed limit row and column.
*
* If table cell width (or height) covers a column (or row) that is after a limit column (or row)
* this method will trim "colspan" (or "rowspan") attribute so the table cell will fit in a defined limits.
*
* @param {module:engine/model/element~Element} tableCell
* @param {Number} cellRow
* @param {Number} cellColumn
* @param {Number} limitRow
* @param {Number} limitColumn
* @param {module:engine/model/writer~Writer} writer
*/
function trimTableCellIfNeeded( tableCell, cellRow, cellColumn, limitRow, limitColumn, writer ) {
const colspan = parseInt( tableCell.getAttribute( 'colspan' ) || 1 );
const rowspan = parseInt( tableCell.getAttribute( 'rowspan' ) || 1 );
const endColumn = cellColumn + colspan - 1;
if ( endColumn > limitColumn ) {
const trimmedSpan = limitColumn - cellColumn + 1;
(0,_common__WEBPACK_IMPORTED_MODULE_1__.updateNumericAttribute)( 'colspan', trimmedSpan, tableCell, writer, 1 );
}
const endRow = cellRow + rowspan - 1;
if ( endRow > limitRow ) {
const trimmedSpan = limitRow - cellRow + 1;
(0,_common__WEBPACK_IMPORTED_MODULE_1__.updateNumericAttribute)( 'rowspan', trimmedSpan, tableCell, writer, 1 );
}
}
// Sets proper heading attributes to a cropped table.
function addHeadingsToCroppedTable( croppedTable, sourceTable, startRow, startColumn, writer ) {
const headingRows = parseInt( sourceTable.getAttribute( 'headingRows' ) || 0 );
if ( headingRows > 0 ) {
const headingRowsInCrop = headingRows - startRow;
(0,_common__WEBPACK_IMPORTED_MODULE_1__.updateNumericAttribute)( 'headingRows', headingRowsInCrop, croppedTable, writer, 0 );
}
const headingColumns = parseInt( sourceTable.getAttribute( 'headingColumns' ) || 0 );
if ( headingColumns > 0 ) {
const headingColumnsInCrop = headingColumns - startColumn;
(0,_common__WEBPACK_IMPORTED_MODULE_1__.updateNumericAttribute)( 'headingColumns', headingColumnsInCrop, croppedTable, writer, 0 );
}
}
/**
* Removes columns that have no cells anchored.
*
* In table below:
*
* +----+----+----+----+----+----+----+
* | 00 | 01 | 03 | 04 | 06 |
* +----+----+----+----+ +----+
* | 10 | 11 | 13 | | 16 |
* +----+----+----+----+----+----+----+
* | 20 | 21 | 23 | 24 | 26 |
* +----+----+----+----+----+----+----+
* ^--- empty ---^
*
* Will remove columns 2 and 5.
*
* **Note:** This is a low-level helper method for clearing invalid model state when doing table modifications.
* To remove a column from a table use {@link module:table/tableutils~TableUtils#removeColumns `TableUtils.removeColumns()`}.
*
* @protected
* @param {module:engine/model/element~Element} table
* @param {module:table/tableutils~TableUtils} tableUtils
* @returns {Boolean} True if removed some columns.
*/
function removeEmptyColumns( table, tableUtils ) {
const width = tableUtils.getColumns( table );
const columnsMap = new Array( width ).fill( 0 );
for ( const { column } of new _tablewalker__WEBPACK_IMPORTED_MODULE_0__["default"]( table ) ) {
columnsMap[ column ]++;
}
const emptyColumns = columnsMap.reduce( ( result, cellsCount, column ) => {
return cellsCount ? result : [ ...result, column ];
}, [] );
if ( emptyColumns.length > 0 ) {
// Remove only last empty column because it will recurrently trigger removing empty rows.
const emptyColumn = emptyColumns[ emptyColumns.length - 1 ];
// @if CK_DEBUG_TABLE // console.log( `Removing empty column: ${ emptyColumn }.` );
tableUtils.removeColumns( table, { at: emptyColumn } );
return true;
}
return false;
}
/**
* Removes rows that have no cells anchored.
*
* In table below:
*
* +----+----+----+
* | 00 | 01 | 02 |
* +----+----+----+
* | 10 | 11 | 12 |
* + + + +
* | | | | <-- empty
* +----+----+----+
* | 30 | 31 | 32 |
* +----+----+----+
* | 40 | 42 |
* + + +
* | | | <-- empty
* +----+----+----+
* | 60 | 61 | 62 |
* +----+----+----+
*
* Will remove rows 2 and 5.
*
* **Note:** This is a low-level helper method for clearing invalid model state when doing table modifications.
* To remove a row from a table use {@link module:table/tableutils~TableUtils#removeRows `TableUtils.removeRows()`}.
*
* @protected
* @param {module:engine/model/element~Element} table
* @param {module:table/tableutils~TableUtils} tableUtils
* @returns {Boolean} True if removed some rows.
*/
function removeEmptyRows( table, tableUtils ) {
const emptyRows = [];
const tableRowCount = tableUtils.getRows( table );
for ( let rowIndex = 0; rowIndex < tableRowCount; rowIndex++ ) {
const tableRow = table.getChild( rowIndex );
if ( tableRow.isEmpty ) {
emptyRows.push( rowIndex );
}
}
if ( emptyRows.length > 0 ) {
// Remove only last empty row because it will recurrently trigger removing empty columns.
const emptyRow = emptyRows[ emptyRows.length - 1 ];
// @if CK_DEBUG_TABLE // console.log( `Removing empty row: ${ emptyRow }.` );
tableUtils.removeRows( table, { at: emptyRow } );
return true;
}
return false;
}
/**
* Removes rows and columns that have no cells anchored.
*
* In table below:
*
* +----+----+----+----+
* | 00 | 02 |
* +----+----+ +
* | 10 | |
* +----+----+----+----+
* | 20 | 22 | 23 |
* + + + +
* | | | | <-- empty row
* +----+----+----+----+
* ^--- empty column
*
* Will remove row 3 and column 1.
*
* **Note:** This is a low-level helper method for clearing invalid model state when doing table modifications.
* To remove a rows from a table use {@link module:table/tableutils~TableUtils#removeRows `TableUtils.removeRows()`} and
* {@link module:table/tableutils~TableUtils#removeColumns `TableUtils.removeColumns()`} to remove a column.
*
* @protected
* @param {module:engine/model/element~Element} table
* @param {module:table/tableutils~TableUtils} tableUtils
*/
function removeEmptyRowsColumns( table, tableUtils ) {
const removedColumns = removeEmptyColumns( table, tableUtils );
// If there was some columns removed then cleaning empty rows was already triggered.
if ( !removedColumns ) {
removeEmptyRows( table, tableUtils );
}
}
/**
* Returns adjusted last row index if selection covers part of a row with empty slots (spanned by other cells).
* The `dimensions.lastRow` is equal to last row index but selection might be bigger.
*
* This happens *only* on rectangular selection so we analyze a case like this:
*
* +---+---+---+---+
* 0 | a | b | c | d |
* + + +---+---+
* 1 | | e | f | g |
* + +---+ +---+
* 2 | | h | | i | <- last row, each cell has rowspan = 2,
* + + + + + so we need to return 3, not 2
* 3 | | | | |
* +---+---+---+---+
*
* @param {module:engine/model/element~Element} table
* @param {Object} dimensions
* @param {Number} dimensions.firstRow
* @param {Number} dimensions.firstColumn
* @param {Number} dimensions.lastRow
* @param {Number} dimensions.lastColumn
* @returns {Number} Adjusted last row index.
*/
function adjustLastRowIndex( table, dimensions ) {
const lastRowMap = Array.from( new _tablewalker__WEBPACK_IMPORTED_MODULE_0__["default"]( table, {
startColumn: dimensions.firstColumn,
endColumn: dimensions.lastColumn,
row: dimensions.lastRow
} ) );
const everyCellHasSingleRowspan = lastRowMap.every( ( { cellHeight } ) => cellHeight === 1 );
// It is a "flat" row, so the last row index is OK.
if ( everyCellHasSingleRowspan ) {
return dimensions.lastRow;
}
// Otherwise get any cell's rowspan and adjust the last row index.
const rowspanAdjustment = lastRowMap[ 0 ].cellHeight - 1;
return dimensions.lastRow + rowspanAdjustment;
}
/**
* Returns adjusted last column index if selection covers part of a column with empty slots (spanned by other cells).
* The `dimensions.lastColumn` is equal to last column index but selection might be bigger.
*
* This happens *only* on rectangular selection so we analyze a case like this:
*
* 0 1 2 3
* +---+---+---+---+
* | a |
* +---+---+---+---+
* | b | c | d |
* +---+---+---+---+
* | e | f |
* +---+---+---+---+
* | g | h |
* +---+---+---+---+
* ^
* last column, each cell has colspan = 2, so we need to return 3, not 2
*
* @param {module:engine/model/element~Element} table
* @param {Object} dimensions
* @param {Number} dimensions.firstRow
* @param {Number} dimensions.firstColumn
* @param {Number} dimensions.lastRow
* @param {Number} dimensions.lastColumn
* @returns {Number} Adjusted last column index.
*/
function adjustLastColumnIndex( table, dimensions ) {
const lastColumnMap = Array.from( new _tablewalker__WEBPACK_IMPORTED_MODULE_0__["default"]( table, {
startRow: dimensions.firstRow,
endRow: dimensions.lastRow,
column: dimensions.lastColumn
} ) );
const everyCellHasSingleColspan = lastColumnMap.every( ( { cellWidth } ) => cellWidth === 1 );
// It is a "flat" column, so the last column index is OK.
if ( everyCellHasSingleColspan ) {
return dimensions.lastColumn;
}
// Otherwise get any cell's colspan and adjust the last column index.
const colspanAdjustment = lastColumnMap[ 0 ].cellWidth - 1;
return dimensions.lastColumn + colspanAdjustment;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/utils/table-properties.js":
/*!******************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/utils/table-properties.js ***!
\******************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "addDefaultUnitToNumericValue": () => (/* binding */ addDefaultUnitToNumericValue),
/* harmony export */ "getNormalizedDefaultProperties": () => (/* binding */ getNormalizedDefaultProperties),
/* harmony export */ "getSingleValue": () => (/* binding */ getSingleValue)
/* harmony export */ });
/* harmony import */ var lodash_es__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! lodash-es */ "./node_modules/lodash-es/isObject.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/utils/table-properties
*/
/**
* Returns a string if all four values of box sides are equal.
*
* If a string is passed, it is treated as a single value (pass-through).
*
* // Returns 'foo':
* getSingleValue( { top: 'foo', right: 'foo', bottom: 'foo', left: 'foo' } );
* getSingleValue( 'foo' );
*
* // Returns undefined:
* getSingleValue( { top: 'foo', right: 'foo', bottom: 'bar', left: 'foo' } );
* getSingleValue( { top: 'foo', right: 'foo' } );
*
* @param objectOrString
* @returns {module:engine/view/stylesmap~BoxSides|String}
*/
function getSingleValue( objectOrString ) {
if ( !objectOrString || !(0,lodash_es__WEBPACK_IMPORTED_MODULE_0__["default"])( objectOrString ) ) {
return objectOrString;
}
const { top, right, bottom, left } = objectOrString;
if ( top == right && right == bottom && bottom == left ) {
return top;
}
}
/**
* Adds a unit to a value if the value is a number or a string representing a number.
*
* **Note**: It does nothing to non-numeric values.
*
* getSingleValue( 25, 'px' ); // '25px'
* getSingleValue( 25, 'em' ); // '25em'
* getSingleValue( '25em', 'px' ); // '25em'
* getSingleValue( 'foo', 'px' ); // 'foo'
*
* @param {*} value
* @param {String} defaultUnit A default unit added to a numeric value.
* @returns {String|*}
*/
function addDefaultUnitToNumericValue( value, defaultUnit ) {
const numericValue = parseFloat( value );
if ( Number.isNaN( numericValue ) ) {
return value;
}
if ( String( numericValue ) !== String( value ) ) {
return value;
}
return `${ numericValue }${ defaultUnit }`;
}
/**
* Returns the normalized configuration.
*
* @param {Object} config
* @param {Object} [options={}]
* @param {Boolean} [options.includeAlignmentProperty=false] Whether the "alignment" property should be added.
* @param {Boolean} [options.includePaddingProperty=false] Whether the "padding" property should be added.
* @param {Boolean} [options.includeVerticalAlignmentProperty=false] Whether the "verticalAlignment" property should be added.
* @param {Boolean} [options.includeHorizontalAlignmentProperty=false] Whether the "horizontalAlignment" property should be added.
* @param {Boolean} [options.isRightToLeftContent=false] Whether the content is right-to-left.
* @returns {Object}
*/
function getNormalizedDefaultProperties( config, options = {} ) {
const normalizedConfig = Object.assign( {
borderStyle: 'none',
borderWidth: '',
borderColor: '',
backgroundColor: '',
width: '',
height: ''
}, config );
if ( options.includeAlignmentProperty && !normalizedConfig.alignment ) {
normalizedConfig.alignment = 'center';
}
if ( options.includePaddingProperty && !normalizedConfig.padding ) {
normalizedConfig.padding = '';
}
if ( options.includeVerticalAlignmentProperty && !normalizedConfig.verticalAlignment ) {
normalizedConfig.verticalAlignment = 'middle';
}
if ( options.includeHorizontalAlignmentProperty && !normalizedConfig.horizontalAlignment ) {
normalizedConfig.horizontalAlignment = options.isRightToLeftContent ? 'right' : 'left';
}
return normalizedConfig;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/utils/ui/contextualballoon.js":
/*!**********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/utils/ui/contextualballoon.js ***!
\**********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "getBalloonCellPositionData": () => (/* binding */ getBalloonCellPositionData),
/* harmony export */ "getBalloonTablePositionData": () => (/* binding */ getBalloonTablePositionData),
/* harmony export */ "repositionContextualBalloon": () => (/* binding */ repositionContextualBalloon)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/* harmony import */ var ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/ui */ "./node_modules/ckeditor5/src/ui.js");
/* harmony import */ var _widget__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./widget */ "./node_modules/@ckeditor/ckeditor5-table/src/utils/ui/widget.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/utils/ui/contextualballoon
*/
const DEFAULT_BALLOON_POSITIONS = ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_1__.BalloonPanelView.defaultPositions;
const BALLOON_POSITIONS = [
DEFAULT_BALLOON_POSITIONS.northArrowSouth,
DEFAULT_BALLOON_POSITIONS.northArrowSouthWest,
DEFAULT_BALLOON_POSITIONS.northArrowSouthEast,
DEFAULT_BALLOON_POSITIONS.southArrowNorth,
DEFAULT_BALLOON_POSITIONS.southArrowNorthWest,
DEFAULT_BALLOON_POSITIONS.southArrowNorthEast,
DEFAULT_BALLOON_POSITIONS.viewportStickyNorth
];
/**
* A helper utility that positions the
* {@link module:ui/panel/balloon/contextualballoon~ContextualBalloon contextual balloon} instance
* with respect to the table in the editor content, if one is selected.
*
* @param {module:core/editor/editor~Editor} editor The editor instance.
* @param {String} target Either "cell" or "table". Determines the target the balloon will
* be attached to.
*/
function repositionContextualBalloon( editor, target ) {
const balloon = editor.plugins.get( 'ContextualBalloon' );
if ( (0,_widget__WEBPACK_IMPORTED_MODULE_2__.getTableWidgetAncestor)( editor.editing.view.document.selection ) ) {
let position;
if ( target === 'cell' ) {
position = getBalloonCellPositionData( editor );
} else {
position = getBalloonTablePositionData( editor );
}
balloon.updatePosition( position );
}
}
/**
* Returns the positioning options that control the geometry of the
* {@link module:ui/panel/balloon/contextualballoon~ContextualBalloon contextual balloon} with respect
* to the selected table in the editor content.
*
* @param {module:core/editor/editor~Editor} editor The editor instance.
* @returns {module:utils/dom/position~Options}
*/
function getBalloonTablePositionData( editor ) {
const firstPosition = editor.model.document.selection.getFirstPosition();
const modelTable = firstPosition.findAncestor( 'table' );
const viewTable = editor.editing.mapper.toViewElement( modelTable );
return {
target: editor.editing.view.domConverter.viewToDom( viewTable ),
positions: BALLOON_POSITIONS
};
}
/**
* Returns the positioning options that control the geometry of the
* {@link module:ui/panel/balloon/contextualballoon~ContextualBalloon contextual balloon} with respect
* to the selected table cell in the editor content.
*
* @param {module:core/editor/editor~Editor} editor The editor instance.
* @returns {module:utils/dom/position~Options}
*/
function getBalloonCellPositionData( editor ) {
const mapper = editor.editing.mapper;
const domConverter = editor.editing.view.domConverter;
const selection = editor.model.document.selection;
if ( selection.rangeCount > 1 ) {
return {
target: () => createBoundingRect( selection.getRanges(), editor ),
positions: BALLOON_POSITIONS
};
}
const modelTableCell = getTableCellAtPosition( selection.getFirstPosition() );
const viewTableCell = mapper.toViewElement( modelTableCell );
return {
target: domConverter.viewToDom( viewTableCell ),
positions: BALLOON_POSITIONS
};
}
// Returns the first selected table cell from a multi-cell or in-cell selection.
//
// @param {module:engine/model/position~Position} position Document position.
// @returns {module:engine/model/element~Element}
function getTableCellAtPosition( position ) {
const isTableCellSelected = position.nodeAfter && position.nodeAfter.is( 'element', 'tableCell' );
return isTableCellSelected ? position.nodeAfter : position.findAncestor( 'tableCell' );
}
// Returns bounding rectangle for given model ranges.
//
// @param {Iterable.<module:engine/model/range~Range>} ranges Model ranges that the bounding rect should be returned for.
// @param {module:core/editor/editor~Editor} editor The editor instance.
// @returns {module:utils/dom/rect~Rect}
function createBoundingRect( ranges, editor ) {
const mapper = editor.editing.mapper;
const domConverter = editor.editing.view.domConverter;
const rects = Array.from( ranges ).map( range => {
const modelTableCell = getTableCellAtPosition( range.start );
const viewTableCell = mapper.toViewElement( modelTableCell );
return new ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_0__.Rect( domConverter.viewToDom( viewTableCell ) );
} );
return ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_0__.Rect.getBoundingRect( rects );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/utils/ui/table-properties.js":
/*!*********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/utils/ui/table-properties.js ***!
\*********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "colorFieldValidator": () => (/* binding */ colorFieldValidator),
/* harmony export */ "defaultColors": () => (/* binding */ defaultColors),
/* harmony export */ "fillToolbar": () => (/* binding */ fillToolbar),
/* harmony export */ "getBorderStyleDefinitions": () => (/* binding */ getBorderStyleDefinitions),
/* harmony export */ "getBorderStyleLabels": () => (/* binding */ getBorderStyleLabels),
/* harmony export */ "getLabeledColorInputCreator": () => (/* binding */ getLabeledColorInputCreator),
/* harmony export */ "getLocalizedColorErrorText": () => (/* binding */ getLocalizedColorErrorText),
/* harmony export */ "getLocalizedLengthErrorText": () => (/* binding */ getLocalizedLengthErrorText),
/* harmony export */ "lengthFieldValidator": () => (/* binding */ lengthFieldValidator),
/* harmony export */ "lineWidthFieldValidator": () => (/* binding */ lineWidthFieldValidator)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/ui */ "./node_modules/ckeditor5/src/ui.js");
/* harmony import */ var ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ckeditor5/src/utils */ "./node_modules/ckeditor5/src/utils.js");
/* harmony import */ var ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ckeditor5/src/engine */ "./node_modules/ckeditor5/src/engine.js");
/* harmony import */ var _ui_colorinputview__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../../ui/colorinputview */ "./node_modules/@ckeditor/ckeditor5-table/src/ui/colorinputview.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/utils/ui/table-properties
*/
const isEmpty = val => val === '';
/**
* Returns an object containing pairs of CSS border style values and their localized UI
* labels. Used by {@link module:table/tablecellproperties/ui/tablecellpropertiesview~TableCellPropertiesView}
* and {@link module:table/tableproperties/ui/tablepropertiesview~TablePropertiesView}.
*
* @param {module:utils/locale~Locale#t} t The "t" function provided by the editor
* that is used to localize strings.
* @returns {Object.<String,String>}
*/
function getBorderStyleLabels( t ) {
return {
none: t( 'None' ),
solid: t( 'Solid' ),
dotted: t( 'Dotted' ),
dashed: t( 'Dashed' ),
double: t( 'Double' ),
groove: t( 'Groove' ),
ridge: t( 'Ridge' ),
inset: t( 'Inset' ),
outset: t( 'Outset' )
};
}
/**
* Returns a localized error string that can be displayed next to color (background, border)
* fields that have an invalid value.
*
* @param {module:utils/locale~Locale#t} t The "t" function provided by the editor
* that is used to localize strings.
* @returns {String}
*/
function getLocalizedColorErrorText( t ) {
return t( 'The color is invalid. Try "#FF0000" or "rgb(255,0,0)" or "red".' );
}
/**
* Returns a localized error string that can be displayed next to length (padding, border width)
* fields that have an invalid value.
*
* @param {module:utils/locale~Locale#t} t The "t" function provided by the editor
* that is used to localize strings.
* @returns {String}
*/
function getLocalizedLengthErrorText( t ) {
return t( 'The value is invalid. Try "10px" or "2em" or simply "2".' );
}
/**
* Returns `true` when the passed value is an empty string or a valid CSS color expression.
* Otherwise, `false` is returned.
*
* See {@link module:engine/view/styles/utils~isColor}.
*
* @param {String} value
* @returns {Boolean}
*/
function colorFieldValidator( value ) {
value = value.trim();
return isEmpty( value ) || (0,ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_2__.isColor)( value );
}
/**
* Returns `true` when the passed value is an empty string, a number without a unit or a valid CSS length expression.
* Otherwise, `false` is returned.
*
* See {@link module:engine/view/styles/utils~isLength}.
* See {@link module:engine/view/styles/utils~isPercentage}.
*
* @param {String} value
* @returns {Boolean}
*/
function lengthFieldValidator( value ) {
value = value.trim();
return isEmpty( value ) || isNumberString( value ) || (0,ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_2__.isLength)( value ) || (0,ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_2__.isPercentage)( value );
}
/**
* Returns `true` when the passed value is an empty string, a number without a unit or a valid CSS length expression.
* Otherwise, `false` is returned.
*
* See {@link module:engine/view/styles/utils~isLength}.
*
* @param {String} value
* @returns {Boolean}
*/
function lineWidthFieldValidator( value ) {
value = value.trim();
return isEmpty( value ) || isNumberString( value ) || (0,ckeditor5_src_engine__WEBPACK_IMPORTED_MODULE_2__.isLength)( value );
}
/**
* Generates item definitions for a UI dropdown that allows changing the border style of a table or a table cell.
*
* @param {module:table/tablecellproperties/ui/tablecellpropertiesview~TableCellPropertiesView|
* module:table/tableproperties/ui/tablepropertiesview~TablePropertiesView} view
* @param {String} defaultStyle The default border.
* @returns {Iterable.<module:ui/dropdown/utils~ListDropdownItemDefinition>}
*/
function getBorderStyleDefinitions( view, defaultStyle ) {
const itemDefinitions = new ckeditor5_src_utils__WEBPACK_IMPORTED_MODULE_1__.Collection();
const styleLabels = getBorderStyleLabels( view.t );
for ( const style in styleLabels ) {
const definition = {
type: 'button',
model: new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.Model( {
_borderStyleValue: style,
label: styleLabels[ style ],
withText: true
} )
};
if ( style === 'none' ) {
definition.model.bind( 'isOn' ).to( view, 'borderStyle', value => {
if ( defaultStyle === 'none' ) {
return !value;
}
return value === style;
} );
} else {
definition.model.bind( 'isOn' ).to( view, 'borderStyle', value => {
return value === style;
} );
}
itemDefinitions.add( definition );
}
return itemDefinitions;
}
/**
* A helper that fills a toolbar with buttons that:
*
* * have some labels,
* * have some icons,
* * set a certain UI view property value upon execution.
*
* @param {Object} options
* @param {module:table/tablecellproperties/ui/tablecellpropertiesview~TableCellPropertiesView|
* module:table/tableproperties/ui/tablepropertiesview~TablePropertiesView} options.view
* @param {Array.<String>} options.icons
* @param {module:ui/toolbar/toolbarview~ToolbarView} options.toolbar
* @param {Object.<String,String>} labels
* @param {String} propertyName
* @param {Function} nameToValue A function that maps a button name to a value. By default names are the same as values.
*/
function fillToolbar( options ) {
const { view, icons, toolbar, labels, propertyName, nameToValue, defaultValue } = options;
for ( const name in labels ) {
const button = new ckeditor5_src_ui__WEBPACK_IMPORTED_MODULE_0__.ButtonView( view.locale );
button.set( {
label: labels[ name ],
icon: icons[ name ],
tooltip: labels[ name ]
} );
// If specified the `nameToValue()` callback, map the value based on the option's name.
const buttonValue = nameToValue ? nameToValue( name ) : name;
button.bind( 'isOn' ).to( view, propertyName, value => {
// `value` comes from `view[ propertyName ]`.
let valueToCompare = value;
// If it's empty, and the `defaultValue` is specified, use it instead.
if ( value === '' && defaultValue ) {
valueToCompare = defaultValue;
}
return buttonValue === valueToCompare;
} );
button.on( 'execute', () => {
view[ propertyName ] = buttonValue;
} );
toolbar.items.add( button );
}
}
/**
* A default color palette used by various user interfaces related to tables, for instance,
* by {@link module:table/tablecellproperties/tablecellpropertiesui~TableCellPropertiesUI} or
* {@link module:table/tableproperties/tablepropertiesui~TablePropertiesUI}.
*
* The color palette follows the {@link module:table/table~TableColorConfig table color configuration format}
* and contains the following color definitions:
*
* const defaultColors = [
* {
* color: 'hsl(0, 0%, 0%)',
* label: 'Black'
* },
* {
* color: 'hsl(0, 0%, 30%)',
* label: 'Dim grey'
* },
* {
* color: 'hsl(0, 0%, 60%)',
* label: 'Grey'
* },
* {
* color: 'hsl(0, 0%, 90%)',
* label: 'Light grey'
* },
* {
* color: 'hsl(0, 0%, 100%)',
* label: 'White',
* hasBorder: true
* },
* {
* color: 'hsl(0, 75%, 60%)',
* label: 'Red'
* },
* {
* color: 'hsl(30, 75%, 60%)',
* label: 'Orange'
* },
* {
* color: 'hsl(60, 75%, 60%)',
* label: 'Yellow'
* },
* {
* color: 'hsl(90, 75%, 60%)',
* label: 'Light green'
* },
* {
* color: 'hsl(120, 75%, 60%)',
* label: 'Green'
* },
* {
* color: 'hsl(150, 75%, 60%)',
* label: 'Aquamarine'
* },
* {
* color: 'hsl(180, 75%, 60%)',
* label: 'Turquoise'
* },
* {
* color: 'hsl(210, 75%, 60%)',
* label: 'Light blue'
* },
* {
* color: 'hsl(240, 75%, 60%)',
* label: 'Blue'
* },
* {
* color: 'hsl(270, 75%, 60%)',
* label: 'Purple'
* }
* ];
*/
const defaultColors = [
{
color: 'hsl(0, 0%, 0%)',
label: 'Black'
},
{
color: 'hsl(0, 0%, 30%)',
label: 'Dim grey'
},
{
color: 'hsl(0, 0%, 60%)',
label: 'Grey'
},
{
color: 'hsl(0, 0%, 90%)',
label: 'Light grey'
},
{
color: 'hsl(0, 0%, 100%)',
label: 'White',
hasBorder: true
},
{
color: 'hsl(0, 75%, 60%)',
label: 'Red'
},
{
color: 'hsl(30, 75%, 60%)',
label: 'Orange'
},
{
color: 'hsl(60, 75%, 60%)',
label: 'Yellow'
},
{
color: 'hsl(90, 75%, 60%)',
label: 'Light green'
},
{
color: 'hsl(120, 75%, 60%)',
label: 'Green'
},
{
color: 'hsl(150, 75%, 60%)',
label: 'Aquamarine'
},
{
color: 'hsl(180, 75%, 60%)',
label: 'Turquoise'
},
{
color: 'hsl(210, 75%, 60%)',
label: 'Light blue'
},
{
color: 'hsl(240, 75%, 60%)',
label: 'Blue'
},
{
color: 'hsl(270, 75%, 60%)',
label: 'Purple'
}
];
/**
* Returns a creator for a color input with a label.
*
* For given options, it returns a function that creates an instance of a
* {@link module:table/ui/colorinputview~ColorInputView color input} logically related to
* a {@link module:ui/labeledfield/labeledfieldview~LabeledFieldView labeled view} in the DOM.
*
* The helper does the following:
*
* * It sets the color input `id` and `ariaDescribedById` attributes.
* * It binds the color input `isReadOnly` to the labeled view.
* * It binds the color input `hasError` to the labeled view.
* * It enables a logic that cleans up the error when the user starts typing in the color input.
*
* Usage:
*
* const colorInputCreator = getLabeledColorInputCreator( {
* colorConfig: [ ... ],
* columns: 3,
* } );
*
* const labeledInputView = new LabeledFieldView( locale, colorInputCreator );
* console.log( labeledInputView.view ); // A color input instance.
*
* @private
* @param options Color input options.
* @param {module:table/table~TableColorConfig} options.colorConfig The configuration of the color palette
* displayed in the input's dropdown.
* @param {Number} options.columns The configuration of the number of columns the color palette consists of
* in the input's dropdown.
* @param {String} [options.defaultColorValue] If specified, the color input view will replace the "Remove color" button with
* the "Restore default" button. Instead of clearing the input field, the default color value will be set.
* @returns {Function}
*/
function getLabeledColorInputCreator( options ) {
return ( labeledFieldView, viewUid, statusUid ) => {
const inputView = new _ui_colorinputview__WEBPACK_IMPORTED_MODULE_3__["default"]( labeledFieldView.locale, {
colorDefinitions: colorConfigToColorGridDefinitions( options.colorConfig ),
columns: options.columns,
defaultColorValue: options.defaultColorValue
} );
inputView.set( {
id: viewUid,
ariaDescribedById: statusUid
} );
inputView.bind( 'isReadOnly' ).to( labeledFieldView, 'isEnabled', value => !value );
inputView.bind( 'hasError' ).to( labeledFieldView, 'errorText', value => !!value );
inputView.on( 'input', () => {
// UX: Make the error text disappear and disable the error indicator as the user
// starts fixing the errors.
labeledFieldView.errorText = null;
} );
labeledFieldView.bind( 'isEmpty', 'isFocused' ).to( inputView );
return inputView;
};
}
// A simple helper method to detect number strings.
// I allows full number notation, so omitting 0 is not allowed:
function isNumberString( value ) {
const parsedValue = parseFloat( value );
return !Number.isNaN( parsedValue ) && value === String( parsedValue );
}
// @param {Array.<Object>} colorConfig
// @returns {Array.<module:ui/colorgrid/colorgrid~ColorDefinition>}
function colorConfigToColorGridDefinitions( colorConfig ) {
return colorConfig.map( item => ( {
color: item.model,
label: item.label,
options: {
hasBorder: item.hasBorder
}
} ) );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/src/utils/ui/widget.js":
/*!***********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/src/utils/ui/widget.js ***!
\***********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "getSelectedTableWidget": () => (/* binding */ getSelectedTableWidget),
/* harmony export */ "getTableWidgetAncestor": () => (/* binding */ getTableWidgetAncestor)
/* harmony export */ });
/* harmony import */ var ckeditor5_src_widget__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ckeditor5/src/widget */ "./node_modules/ckeditor5/src/widget.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module table/utils/ui/widget
*/
/**
* Returns a table widget editing view element if one is selected.
*
* @param {module:engine/view/selection~Selection|module:engine/view/documentselection~DocumentSelection} selection
* @returns {module:engine/view/element~Element|null}
*/
function getSelectedTableWidget( selection ) {
const viewElement = selection.getSelectedElement();
if ( viewElement && isTableWidget( viewElement ) ) {
return viewElement;
}
return null;
}
/**
* Returns a table widget editing view element if one is among the selection's ancestors.
*
* @param {module:engine/view/selection~Selection|module:engine/view/documentselection~DocumentSelection} selection
* @returns {module:engine/view/element~Element|null}
*/
function getTableWidgetAncestor( selection ) {
let parent = selection.getFirstPosition().parent;
while ( parent ) {
if ( parent.is( 'element' ) && isTableWidget( parent ) ) {
return parent;
}
parent = parent.parent;
}
return null;
}
// Checks if a given view element is a table widget.
//
// @param {module:engine/view/element~Element} viewElement
// @returns {Boolean}
function isTableWidget( viewElement ) {
return !!viewElement.getCustomProperty( 'table' ) && (0,ckeditor5_src_widget__WEBPACK_IMPORTED_MODULE_0__.isWidget)( viewElement );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-typing/src/delete.js":
/*!***************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-typing/src/delete.js ***!
\***************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ Delete)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_core_src_plugin__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-core/src/plugin */ "./node_modules/@ckeditor/ckeditor5-core/src/plugin.js");
/* harmony import */ var _deletecommand__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./deletecommand */ "./node_modules/@ckeditor/ckeditor5-typing/src/deletecommand.js");
/* harmony import */ var _deleteobserver__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./deleteobserver */ "./node_modules/@ckeditor/ckeditor5-typing/src/deleteobserver.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_env__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/env */ "./node_modules/@ckeditor/ckeditor5-utils/src/env.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module typing/delete
*/
/**
* The delete and backspace feature. Handles the <kbd>Delete</kbd> and <kbd>Backspace</kbd> keys in the editor.
*
* @extends module:core/plugin~Plugin
*/
class Delete extends _ckeditor_ckeditor5_core_src_plugin__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* Whether pressing backspace should trigger undo action
*
* @private
* @member {Boolean} #_undoOnBackspace
*/
/**
* @inheritDoc
*/
static get pluginName() {
return 'Delete';
}
init() {
const editor = this.editor;
const view = editor.editing.view;
const viewDocument = view.document;
const modelDocument = editor.model.document;
view.addObserver( _deleteobserver__WEBPACK_IMPORTED_MODULE_2__["default"] );
this._undoOnBackspace = false;
const deleteForwardCommand = new _deletecommand__WEBPACK_IMPORTED_MODULE_1__["default"]( editor, 'forward' );
// Register `deleteForward` command and add `forwardDelete` command as an alias for backward compatibility.
editor.commands.add( 'deleteForward', deleteForwardCommand );
editor.commands.add( 'forwardDelete', deleteForwardCommand );
editor.commands.add( 'delete', new _deletecommand__WEBPACK_IMPORTED_MODULE_1__["default"]( editor, 'backward' ) );
this.listenTo( viewDocument, 'delete', ( evt, data ) => {
const deleteCommandParams = { unit: data.unit, sequence: data.sequence };
// If a specific (view) selection to remove was set, convert it to a model selection and set as a parameter for `DeleteCommand`.
if ( data.selectionToRemove ) {
const modelSelection = editor.model.createSelection();
const ranges = [];
for ( const viewRange of data.selectionToRemove.getRanges() ) {
ranges.push( editor.editing.mapper.toModelRange( viewRange ) );
}
modelSelection.setTo( ranges );
deleteCommandParams.selection = modelSelection;
}
editor.execute( data.direction == 'forward' ? 'deleteForward' : 'delete', deleteCommandParams );
data.preventDefault();
view.scrollToTheSelection();
}, { priority: 'low' } );
// Android IMEs have a quirk - they change DOM selection after the input changes were performed by the browser.
// This happens on `keyup` event. Android doesn't know anything about our deletion and selection handling. Even if the selection
// was changed during input events, IME remembers the position where the selection "should" be placed and moves it there.
//
// To prevent incorrect selection, we save the selection after deleting here and then re-set it on `keyup`. This has to be done
// on DOM selection level, because on `keyup` the model selection is still the same as it was just after deletion, so it
// wouldn't be changed and the fix would do nothing.
//
if ( _ckeditor_ckeditor5_utils_src_env__WEBPACK_IMPORTED_MODULE_3__["default"].isAndroid ) {
let domSelectionAfterDeletion = null;
this.listenTo( viewDocument, 'delete', ( evt, data ) => {
const domSelection = data.domTarget.ownerDocument.defaultView.getSelection();
domSelectionAfterDeletion = {
anchorNode: domSelection.anchorNode,
anchorOffset: domSelection.anchorOffset,
focusNode: domSelection.focusNode,
focusOffset: domSelection.focusOffset
};
}, { priority: 'lowest' } );
this.listenTo( viewDocument, 'keyup', ( evt, data ) => {
if ( domSelectionAfterDeletion ) {
const domSelection = data.domTarget.ownerDocument.defaultView.getSelection();
domSelection.collapse( domSelectionAfterDeletion.anchorNode, domSelectionAfterDeletion.anchorOffset );
domSelection.extend( domSelectionAfterDeletion.focusNode, domSelectionAfterDeletion.focusOffset );
domSelectionAfterDeletion = null;
}
} );
}
if ( this.editor.plugins.has( 'UndoEditing' ) ) {
this.listenTo( viewDocument, 'delete', ( evt, data ) => {
if ( this._undoOnBackspace && data.direction == 'backward' && data.sequence == 1 && data.unit == 'codePoint' ) {
this._undoOnBackspace = false;
editor.execute( 'undo' );
data.preventDefault();
evt.stop();
}
}, { context: '$capture' } );
this.listenTo( modelDocument, 'change', () => {
this._undoOnBackspace = false;
} );
}
}
/**
* If the next user action after calling this method is pressing backspace, it would undo the last change.
*
* Requires {@link module:undo/undoediting~UndoEditing} plugin. If not loaded, does nothing.
*/
requestUndoOnBackspace() {
if ( this.editor.plugins.has( 'UndoEditing' ) ) {
this._undoOnBackspace = true;
}
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-typing/src/deletecommand.js":
/*!**********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-typing/src/deletecommand.js ***!
\**********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ DeleteCommand)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_core_src_command__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-core/src/command */ "./node_modules/@ckeditor/ckeditor5-core/src/command.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_count__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/count */ "./node_modules/@ckeditor/ckeditor5-utils/src/count.js");
/* harmony import */ var _utils_changebuffer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./utils/changebuffer */ "./node_modules/@ckeditor/ckeditor5-typing/src/utils/changebuffer.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module typing/deletecommand
*/
/**
* The delete command. Used by the {@link module:typing/delete~Delete delete feature} to handle the <kbd>Delete</kbd> and
* <kbd>Backspace</kbd> keys.
*
* @extends module:core/command~Command
*/
class DeleteCommand extends _ckeditor_ckeditor5_core_src_command__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* Creates an instance of the command.
*
* @param {module:core/editor/editor~Editor} editor
* @param {'forward'|'backward'} direction The directionality of the delete describing in what direction it
* should consume the content when the selection is collapsed.
*/
constructor( editor, direction ) {
super( editor );
/**
* The directionality of the delete describing in what direction it should
* consume the content when the selection is collapsed.
*
* @readonly
* @member {'forward'|'backward'} #direction
*/
this.direction = direction;
/**
* Delete's change buffer used to group subsequent changes into batches.
*
* @readonly
* @private
* @type {module:typing/utils/changebuffer~ChangeBuffer}
*/
this._buffer = new _utils_changebuffer__WEBPACK_IMPORTED_MODULE_2__["default"]( editor.model, editor.config.get( 'typing.undoStep' ) );
}
/**
* The current change buffer.
*
* @type {module:typing/utils/changebuffer~ChangeBuffer}
*/
get buffer() {
return this._buffer;
}
/**
* Executes the delete command. Depending on whether the selection is collapsed or not, deletes its content
* or a piece of content in the {@link #direction defined direction}.
*
* @fires execute
* @param {Object} [options] The command options.
* @param {'character'|'codePoint'|'word'} [options.unit='character']
* See {@link module:engine/model/utils/modifyselection~modifySelection}'s options.
* @param {Number} [options.sequence=1] A number describing which subsequent delete event it is without the key being released.
* See the {@link module:engine/view/document~Document#event:delete} event data.
* @param {module:engine/model/selection~Selection} [options.selection] Selection to remove. If not set, current model selection
* will be used.
*/
execute( options = {} ) {
const model = this.editor.model;
const doc = model.document;
model.enqueueChange( this._buffer.batch, writer => {
this._buffer.lock();
const selection = writer.createSelection( options.selection || doc.selection );
const sequence = options.sequence || 1;
// Do not replace the whole selected content if selection was collapsed.
// This prevents such situation:
//
// <h1></h1><p>[]</p> --> <h1>[</h1><p>]</p> --> <p></p>
// starting content --> after `modifySelection` --> after `deleteContent`.
const doNotResetEntireContent = selection.isCollapsed;
// Try to extend the selection in the specified direction.
if ( selection.isCollapsed ) {
model.modifySelection( selection, {
direction: this.direction,
unit: options.unit,
treatEmojiAsSingleUnit: true
} );
}
// Check if deleting in an empty editor. See #61.
if ( this._shouldEntireContentBeReplacedWithParagraph( sequence ) ) {
this._replaceEntireContentWithParagraph( writer );
return;
}
// Check if deleting in the first empty block.
// See https://github.com/ckeditor/ckeditor5/issues/8137.
if ( this._shouldReplaceFirstBlockWithParagraph( selection, sequence ) ) {
this.editor.execute( 'paragraph', { selection } );
return;
}
// If selection is still collapsed, then there's nothing to delete.
if ( selection.isCollapsed ) {
return;
}
let changeCount = 0;
selection.getFirstRange().getMinimalFlatRanges().forEach( range => {
changeCount += (0,_ckeditor_ckeditor5_utils_src_count__WEBPACK_IMPORTED_MODULE_1__["default"])(
range.getWalker( { singleCharacters: true, ignoreElementEnd: true, shallow: true } )
);
} );
model.deleteContent( selection, {
doNotResetEntireContent,
direction: this.direction
} );
this._buffer.input( changeCount );
writer.setSelection( selection );
this._buffer.unlock();
} );
}
/**
* If the user keeps <kbd>Backspace</kbd> or <kbd>Delete</kbd> key pressed, the content of the current
* editable will be cleared. However, this will not yet lead to resetting the remaining block to a paragraph
* (which happens e.g. when the user does <kbd>Ctrl</kbd> + <kbd>A</kbd>, <kbd>Backspace</kbd>).
*
* But, if the user pressed the key in an empty editable for the first time,
* we want to replace the entire content with a paragraph if:
*
* * the current limit element is empty,
* * the paragraph is allowed in the limit element,
* * the limit doesn't already have a paragraph inside.
*
* See https://github.com/ckeditor/ckeditor5-typing/issues/61.
*
* @private
* @param {Number} sequence A number describing which subsequent delete event it is without the key being released.
* @returns {Boolean}
*/
_shouldEntireContentBeReplacedWithParagraph( sequence ) {
// Does nothing if user pressed and held the "Backspace" or "Delete" key.
if ( sequence > 1 ) {
return false;
}
const model = this.editor.model;
const doc = model.document;
const selection = doc.selection;
const limitElement = model.schema.getLimitElement( selection );
// If a collapsed selection contains the whole content it means that the content is empty
// (from the user perspective).
const limitElementIsEmpty = selection.isCollapsed && selection.containsEntireContent( limitElement );
if ( !limitElementIsEmpty ) {
return false;
}
if ( !model.schema.checkChild( limitElement, 'paragraph' ) ) {
return false;
}
const limitElementFirstChild = limitElement.getChild( 0 );
// Does nothing if the limit element already contains only a paragraph.
// We ignore the case when paragraph might have some inline elements (<p><inlineWidget>[]</inlineWidget></p>)
// because we don't support such cases yet and it's unclear whether inlineWidget shouldn't be a limit itself.
if ( limitElementFirstChild && limitElementFirstChild.name === 'paragraph' ) {
return false;
}
return true;
}
/**
* The entire content is replaced with the paragraph. Selection is moved inside the paragraph.
*
* @private
* @param {module:engine/model/writer~Writer} writer The model writer.
*/
_replaceEntireContentWithParagraph( writer ) {
const model = this.editor.model;
const doc = model.document;
const selection = doc.selection;
const limitElement = model.schema.getLimitElement( selection );
const paragraph = writer.createElement( 'paragraph' );
writer.remove( writer.createRangeIn( limitElement ) );
writer.insert( paragraph, limitElement );
writer.setSelection( paragraph, 0 );
}
/**
* Checks if the selection is inside an empty element that is the first child of the limit element
* and should be replaced with a paragraph.
*
* @private
* @param {module:engine/model/selection~Selection} selection The selection.
* @param {Number} sequence A number describing which subsequent delete event it is without the key being released.
* @returns {Boolean}
*/
_shouldReplaceFirstBlockWithParagraph( selection, sequence ) {
const model = this.editor.model;
// Does nothing if user pressed and held the "Backspace" key or it was a "Delete" button.
if ( sequence > 1 || this.direction != 'backward' ) {
return false;
}
if ( !selection.isCollapsed ) {
return false;
}
const position = selection.getFirstPosition();
const limitElement = model.schema.getLimitElement( position );
const limitElementFirstChild = limitElement.getChild( 0 );
// Only elements that are direct children of the limit element can be replaced.
// Unwrapping from a block quote should be handled in a dedicated feature.
if ( position.parent != limitElementFirstChild ) {
return false;
}
// A block should be replaced only if it was empty.
if ( !selection.containsEntireContent( limitElementFirstChild ) ) {
return false;
}
// Replace with a paragraph only if it's allowed there.
if ( !model.schema.checkChild( limitElement, 'paragraph' ) ) {
return false;
}
// Does nothing if the limit element already contains only a paragraph.
if ( limitElementFirstChild.name == 'paragraph' ) {
return false;
}
return true;
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-typing/src/deleteobserver.js":
/*!***********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-typing/src/deleteobserver.js ***!
\***********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ DeleteObserver)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_engine_src_view_observer_observer__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-engine/src/view/observer/observer */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/observer.js");
/* harmony import */ var _ckeditor_ckeditor5_engine_src_view_observer_domeventdata__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-engine/src/view/observer/domeventdata */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/domeventdata.js");
/* harmony import */ var _ckeditor_ckeditor5_engine_src_view_observer_bubblingeventinfo__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @ckeditor/ckeditor5-engine/src/view/observer/bubblingeventinfo */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/bubblingeventinfo.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_keyboard__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/keyboard */ "./node_modules/@ckeditor/ckeditor5-utils/src/keyboard.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_env__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/env */ "./node_modules/@ckeditor/ckeditor5-utils/src/env.js");
/* harmony import */ var _utils_utils__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./utils/utils */ "./node_modules/@ckeditor/ckeditor5-typing/src/utils/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module typing/deleteobserver
*/
/**
* Delete observer introduces the {@link module:engine/view/document~Document#event:delete} event.
*
* @extends module:engine/view/observer/observer~Observer
*/
class DeleteObserver extends _ckeditor_ckeditor5_engine_src_view_observer_observer__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* @inheritDoc
*/
constructor( view ) {
super( view );
const document = view.document;
let sequence = 0;
document.on( 'keyup', ( evt, data ) => {
if ( data.keyCode == _ckeditor_ckeditor5_utils_src_keyboard__WEBPACK_IMPORTED_MODULE_3__.keyCodes["delete"] || data.keyCode == _ckeditor_ckeditor5_utils_src_keyboard__WEBPACK_IMPORTED_MODULE_3__.keyCodes.backspace ) {
sequence = 0;
}
} );
document.on( 'keydown', ( evt, data ) => {
// Do not fire the `delete` event, if Shift + Delete key combination was pressed on a non-collapsed selection on Windows.
//
// The Shift + Delete key combination should work in the same way as the `cut` event on a non-collapsed selection on Windows.
// In fact, the native `cut` event is actually emitted in this case, but with lower priority. Therefore, in order to handle the
// Shift + Delete key combination correctly, it is enough not to emit the `delete` event.
if ( _ckeditor_ckeditor5_utils_src_env__WEBPACK_IMPORTED_MODULE_4__["default"].isWindows && (0,_utils_utils__WEBPACK_IMPORTED_MODULE_5__.isShiftDeleteOnNonCollapsedSelection)( data, document ) ) {
return;
}
const deleteData = {};
if ( data.keyCode == _ckeditor_ckeditor5_utils_src_keyboard__WEBPACK_IMPORTED_MODULE_3__.keyCodes["delete"] ) {
deleteData.direction = 'forward';
deleteData.unit = 'character';
} else if ( data.keyCode == _ckeditor_ckeditor5_utils_src_keyboard__WEBPACK_IMPORTED_MODULE_3__.keyCodes.backspace ) {
deleteData.direction = 'backward';
deleteData.unit = 'codePoint';
} else {
return;
}
const hasWordModifier = _ckeditor_ckeditor5_utils_src_env__WEBPACK_IMPORTED_MODULE_4__["default"].isMac ? data.altKey : data.ctrlKey;
deleteData.unit = hasWordModifier ? 'word' : deleteData.unit;
deleteData.sequence = ++sequence;
fireViewDeleteEvent( evt, data.domEvent, deleteData );
} );
// `beforeinput` is handled only for Android devices. Desktop Chrome and iOS are skipped because they are working fine now.
if ( _ckeditor_ckeditor5_utils_src_env__WEBPACK_IMPORTED_MODULE_4__["default"].isAndroid ) {
document.on( 'beforeinput', ( evt, data ) => {
// If event type is other than `deleteContentBackward` then this is not deleting.
if ( data.domEvent.inputType != 'deleteContentBackward' ) {
return;
}
const deleteData = {
unit: 'codepoint',
direction: 'backward',
sequence: 1
};
// Android IMEs may change the DOM selection on `beforeinput` event so that the selection contains all the text
// that the IME wants to remove. We will pass this information to `delete` event so proper part of the content is removed.
//
// Sometimes it is only expanding by a one character (in case of collapsed selection). In this case we don't need to
// set a different selection to remove, it will work just fine.
const domSelection = data.domTarget.ownerDocument.defaultView.getSelection();
if ( domSelection.anchorNode == domSelection.focusNode && domSelection.anchorOffset + 1 != domSelection.focusOffset ) {
deleteData.selectionToRemove = view.domConverter.domSelectionToView( domSelection );
}
fireViewDeleteEvent( evt, data.domEvent, deleteData );
} );
}
function fireViewDeleteEvent( originalEvent, domEvent, deleteData ) {
const event = new _ckeditor_ckeditor5_engine_src_view_observer_bubblingeventinfo__WEBPACK_IMPORTED_MODULE_2__["default"]( document, 'delete', document.selection.getFirstRange() );
document.fire( event, new _ckeditor_ckeditor5_engine_src_view_observer_domeventdata__WEBPACK_IMPORTED_MODULE_1__["default"]( document, domEvent, deleteData ) );
// Stop the original event if `delete` event was stopped.
// https://github.com/ckeditor/ckeditor5/issues/753
if ( event.stop.called ) {
originalEvent.stop();
}
}
}
/**
* @inheritDoc
*/
observe() {}
}
/**
* Event fired when the user tries to delete content (e.g. presses <kbd>Delete</kbd> or <kbd>Backspace</kbd>).
*
* Note: This event is fired by the {@link module:typing/deleteobserver~DeleteObserver observer}
* (usually registered by the {@link module:typing/delete~Delete delete feature}).
*
* @event module:engine/view/document~Document#event:delete
* @param {module:engine/view/observer/domeventdata~DomEventData} data
* @param {'forward'|'delete'} data.direction The direction in which the deletion should happen.
* @param {'character'|'codePoint'|'word'} data.unit The "amount" of content that should be deleted.
* @param {Number} data.sequence A number describing which subsequent delete event it is without the key being released.
* If it's 2 or more it means that the key was pressed and hold.
* @param {module:engine/view/selection~Selection} [data.selectionToRemove] View selection which content should be removed. If not set,
* current selection should be used.
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-typing/src/index.js":
/*!**************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-typing/src/index.js ***!
\**************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "Delete": () => (/* reexport safe */ _delete__WEBPACK_IMPORTED_MODULE_2__["default"]),
/* harmony export */ "Input": () => (/* reexport safe */ _input__WEBPACK_IMPORTED_MODULE_1__["default"]),
/* harmony export */ "TextTransformation": () => (/* reexport safe */ _texttransformation__WEBPACK_IMPORTED_MODULE_5__["default"]),
/* harmony export */ "TextWatcher": () => (/* reexport safe */ _textwatcher__WEBPACK_IMPORTED_MODULE_3__["default"]),
/* harmony export */ "TwoStepCaretMovement": () => (/* reexport safe */ _twostepcaretmovement__WEBPACK_IMPORTED_MODULE_4__["default"]),
/* harmony export */ "Typing": () => (/* reexport safe */ _typing__WEBPACK_IMPORTED_MODULE_0__["default"]),
/* harmony export */ "findAttributeRange": () => (/* reexport safe */ _utils_findattributerange__WEBPACK_IMPORTED_MODULE_7__["default"]),
/* harmony export */ "getLastTextLine": () => (/* reexport safe */ _utils_getlasttextline__WEBPACK_IMPORTED_MODULE_8__["default"]),
/* harmony export */ "inlineHighlight": () => (/* reexport safe */ _utils_inlinehighlight__WEBPACK_IMPORTED_MODULE_6__["default"]),
/* harmony export */ "isNonTypingKeystroke": () => (/* reexport safe */ _utils_injectunsafekeystrokeshandling__WEBPACK_IMPORTED_MODULE_9__.isNonTypingKeystroke)
/* harmony export */ });
/* harmony import */ var _typing__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./typing */ "./node_modules/@ckeditor/ckeditor5-typing/src/typing.js");
/* harmony import */ var _input__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./input */ "./node_modules/@ckeditor/ckeditor5-typing/src/input.js");
/* harmony import */ var _delete__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./delete */ "./node_modules/@ckeditor/ckeditor5-typing/src/delete.js");
/* harmony import */ var _textwatcher__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./textwatcher */ "./node_modules/@ckeditor/ckeditor5-typing/src/textwatcher.js");
/* harmony import */ var _twostepcaretmovement__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./twostepcaretmovement */ "./node_modules/@ckeditor/ckeditor5-typing/src/twostepcaretmovement.js");
/* harmony import */ var _texttransformation__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./texttransformation */ "./node_modules/@ckeditor/ckeditor5-typing/src/texttransformation.js");
/* harmony import */ var _utils_inlinehighlight__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./utils/inlinehighlight */ "./node_modules/@ckeditor/ckeditor5-typing/src/utils/inlinehighlight.js");
/* harmony import */ var _utils_findattributerange__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./utils/findattributerange */ "./node_modules/@ckeditor/ckeditor5-typing/src/utils/findattributerange.js");
/* harmony import */ var _utils_getlasttextline__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./utils/getlasttextline */ "./node_modules/@ckeditor/ckeditor5-typing/src/utils/getlasttextline.js");
/* harmony import */ var _utils_injectunsafekeystrokeshandling__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./utils/injectunsafekeystrokeshandling */ "./node_modules/@ckeditor/ckeditor5-typing/src/utils/injectunsafekeystrokeshandling.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module typing
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-typing/src/input.js":
/*!**************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-typing/src/input.js ***!
\**************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ Input)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_core_src_plugin__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-core/src/plugin */ "./node_modules/@ckeditor/ckeditor5-core/src/plugin.js");
/* harmony import */ var _inputcommand__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./inputcommand */ "./node_modules/@ckeditor/ckeditor5-typing/src/inputcommand.js");
/* harmony import */ var _utils_injectunsafekeystrokeshandling__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./utils/injectunsafekeystrokeshandling */ "./node_modules/@ckeditor/ckeditor5-typing/src/utils/injectunsafekeystrokeshandling.js");
/* harmony import */ var _utils_injecttypingmutationshandling__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./utils/injecttypingmutationshandling */ "./node_modules/@ckeditor/ckeditor5-typing/src/utils/injecttypingmutationshandling.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module typing/input
*/
/**
* Handles text input coming from the keyboard or other input methods.
*
* @extends module:core/plugin~Plugin
*/
class Input extends _ckeditor_ckeditor5_core_src_plugin__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* @inheritDoc
*/
static get pluginName() {
return 'Input';
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
// TODO The above default configuration value should be defined using editor.config.define() once it's fixed.
const inputCommand = new _inputcommand__WEBPACK_IMPORTED_MODULE_1__["default"]( editor, editor.config.get( 'typing.undoStep' ) || 20 );
editor.commands.add( 'input', inputCommand );
(0,_utils_injectunsafekeystrokeshandling__WEBPACK_IMPORTED_MODULE_2__["default"])( editor );
(0,_utils_injecttypingmutationshandling__WEBPACK_IMPORTED_MODULE_3__["default"])( editor );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-typing/src/inputcommand.js":
/*!*********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-typing/src/inputcommand.js ***!
\*********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ InputCommand)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_core_src_command__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-core/src/command */ "./node_modules/@ckeditor/ckeditor5-core/src/command.js");
/* harmony import */ var _utils_changebuffer__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./utils/changebuffer */ "./node_modules/@ckeditor/ckeditor5-typing/src/utils/changebuffer.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module typing/inputcommand
*/
/**
* The input command. Used by the {@link module:typing/input~Input input feature} to handle typing.
*
* @extends module:core/command~Command
*/
class InputCommand extends _ckeditor_ckeditor5_core_src_command__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* Creates an instance of the command.
*
* @param {module:core/editor/editor~Editor} editor
* @param {Number} undoStepSize The maximum number of atomic changes
* which can be contained in one batch in the command buffer.
*/
constructor( editor, undoStepSize ) {
super( editor );
/**
* Typing's change buffer used to group subsequent changes into batches.
*
* @readonly
* @private
* @member {module:typing/utils/changebuffer~ChangeBuffer} #_buffer
*/
this._buffer = new _utils_changebuffer__WEBPACK_IMPORTED_MODULE_1__["default"]( editor.model, undoStepSize );
}
/**
* The current change buffer.
*
* @type {module:typing/utils/changebuffer~ChangeBuffer}
*/
get buffer() {
return this._buffer;
}
/**
* @inheritDoc
*/
destroy() {
super.destroy();
this._buffer.destroy();
}
/**
* Executes the input command. It replaces the content within the given range with the given text.
* Replacing is a two step process, first the content within the range is removed and then the new text is inserted
* at the beginning of the range (which after the removal is a collapsed range).
*
* @fires execute
* @param {Object} [options] The command options.
* @param {String} [options.text=''] The text to be inserted.
* @param {module:engine/model/range~Range} [options.range] The range in which the text is inserted. Defaults
* to the first range in the current selection.
* @param {module:engine/model/range~Range} [options.resultRange] The range where the selection
* should be placed after the insertion. If not specified, the selection will be placed right after
* the inserted text.
*/
execute( options = {} ) {
const model = this.editor.model;
const doc = model.document;
const text = options.text || '';
const textInsertions = text.length;
const selection = options.range ? model.createSelection( options.range ) : doc.selection;
const resultRange = options.resultRange;
model.enqueueChange( this._buffer.batch, writer => {
this._buffer.lock();
model.deleteContent( selection );
if ( text ) {
model.insertContent( writer.createText( text, doc.selection.getAttributes() ), selection );
}
if ( resultRange ) {
writer.setSelection( resultRange );
} else if ( !selection.is( 'documentSelection' ) ) {
writer.setSelection( selection );
}
this._buffer.unlock();
this._buffer.input( textInsertions );
} );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-typing/src/texttransformation.js":
/*!***************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-typing/src/texttransformation.js ***!
\***************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ TextTransformation)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_core_src_plugin__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-core/src/plugin */ "./node_modules/@ckeditor/ckeditor5-core/src/plugin.js");
/* harmony import */ var _textwatcher__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./textwatcher */ "./node_modules/@ckeditor/ckeditor5-typing/src/textwatcher.js");
/* harmony import */ var lodash_es__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! lodash-es */ "./node_modules/lodash-es/escapeRegExp.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module typing/texttransformation
*/
// All named transformations.
const TRANSFORMATIONS = {
// Common symbols:
copyright: { from: '(c)', to: '©' },
registeredTrademark: { from: '(r)', to: '®' },
trademark: { from: '(tm)', to: '™' },
// Mathematical:
oneHalf: { from: /(^|[^/a-z0-9])(1\/2)([^/a-z0-9])$/i, to: [ null, '½', null ] },
oneThird: { from: /(^|[^/a-z0-9])(1\/3)([^/a-z0-9])$/i, to: [ null, '⅓', null ] },
twoThirds: { from: /(^|[^/a-z0-9])(2\/3)([^/a-z0-9])$/i, to: [ null, '⅔', null ] },
oneForth: { from: /(^|[^/a-z0-9])(1\/4)([^/a-z0-9])$/i, to: [ null, '¼', null ] },
threeQuarters: { from: /(^|[^/a-z0-9])(3\/4)([^/a-z0-9])$/i, to: [ null, '¾', null ] },
lessThanOrEqual: { from: '<=', to: '≤' },
greaterThanOrEqual: { from: '>=', to: '≥' },
notEqual: { from: '!=', to: '≠' },
arrowLeft: { from: '<-', to: '←' },
arrowRight: { from: '->', to: '→' },
// Typography:
horizontalEllipsis: { from: '...', to: '…' },
enDash: { from: /(^| )(--)( )$/, to: [ null, '–', null ] },
emDash: { from: /(^| )(---)( )$/, to: [ null, '—', null ] },
// Quotations:
// English, US
quotesPrimary: { from: buildQuotesRegExp( '"' ), to: [ null, '“', null, '”' ] },
quotesSecondary: { from: buildQuotesRegExp( '\'' ), to: [ null, '‘', null, '’' ] },
// English, UK
quotesPrimaryEnGb: { from: buildQuotesRegExp( '\'' ), to: [ null, '‘', null, '’' ] },
quotesSecondaryEnGb: { from: buildQuotesRegExp( '"' ), to: [ null, '“', null, '”' ] },
// Polish
quotesPrimaryPl: { from: buildQuotesRegExp( '"' ), to: [ null, '„', null, '”' ] },
quotesSecondaryPl: { from: buildQuotesRegExp( '\'' ), to: [ null, '‚', null, '’' ] }
};
// Transformation groups.
const TRANSFORMATION_GROUPS = {
symbols: [ 'copyright', 'registeredTrademark', 'trademark' ],
mathematical: [
'oneHalf', 'oneThird', 'twoThirds', 'oneForth', 'threeQuarters',
'lessThanOrEqual', 'greaterThanOrEqual', 'notEqual',
'arrowLeft', 'arrowRight'
],
typography: [ 'horizontalEllipsis', 'enDash', 'emDash' ],
quotes: [ 'quotesPrimary', 'quotesSecondary' ]
};
// A set of default transformations provided by the feature.
const DEFAULT_TRANSFORMATIONS = [
'symbols',
'mathematical',
'typography',
'quotes'
];
/**
* The text transformation plugin.
*
* @extends module:core/plugin~Plugin
*/
class TextTransformation extends _ckeditor_ckeditor5_core_src_plugin__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* @inheritDoc
*/
static get requires() {
return [ 'Delete', 'Input' ];
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'TextTransformation';
}
/**
* @inheritDoc
*/
constructor( editor ) {
super( editor );
editor.config.define( 'typing', {
transformations: {
include: DEFAULT_TRANSFORMATIONS
}
} );
}
/**
* @inheritDoc
*/
init() {
const model = this.editor.model;
const modelSelection = model.document.selection;
modelSelection.on( 'change:range', () => {
// Disable plugin when selection is inside a code block.
this.isEnabled = !modelSelection.anchor.parent.is( 'element', 'codeBlock' );
} );
this._enableTransformationWatchers();
}
/**
* Create new TextWatcher listening to the editor for typing and selection events.
*
* @private
*/
_enableTransformationWatchers() {
const editor = this.editor;
const model = editor.model;
const deletePlugin = editor.plugins.get( 'Delete' );
const normalizedTransformations = normalizeTransformations( editor.config.get( 'typing.transformations' ) );
const testCallback = text => {
for ( const normalizedTransformation of normalizedTransformations ) {
const from = normalizedTransformation.from;
const match = from.test( text );
if ( match ) {
return { normalizedTransformation };
}
}
};
const watcherCallback = ( evt, data ) => {
if ( !data.batch.isTyping ) {
return;
}
const { from, to } = data.normalizedTransformation;
const matches = from.exec( data.text );
const replaces = to( matches.slice( 1 ) );
const matchedRange = data.range;
let changeIndex = matches.index;
model.enqueueChange( writer => {
for ( let i = 1; i < matches.length; i++ ) {
const match = matches[ i ];
const replaceWith = replaces[ i - 1 ];
if ( replaceWith == null ) {
changeIndex += match.length;
continue;
}
const replacePosition = matchedRange.start.getShiftedBy( changeIndex );
const replaceRange = model.createRange( replacePosition, replacePosition.getShiftedBy( match.length ) );
const attributes = getTextAttributesAfterPosition( replacePosition );
model.insertContent( writer.createText( replaceWith, attributes ), replaceRange );
changeIndex += replaceWith.length;
}
model.enqueueChange( () => {
deletePlugin.requestUndoOnBackspace();
} );
} );
};
const watcher = new _textwatcher__WEBPACK_IMPORTED_MODULE_1__["default"]( editor.model, testCallback );
watcher.on( 'matched:data', watcherCallback );
watcher.bind( 'isEnabled' ).to( this );
}
}
// Normalizes the configuration `from` parameter value.
// The normalized value for the `from` parameter is a RegExp instance. If the passed `from` is already a RegExp instance,
// it is returned unchanged.
//
// @param {String|RegExp} from
// @returns {RegExp}
function normalizeFrom( from ) {
if ( typeof from == 'string' ) {
return new RegExp( `(${ (0,lodash_es__WEBPACK_IMPORTED_MODULE_2__["default"])( from ) })$` );
}
// `from` is already a regular expression.
return from;
}
// Normalizes the configuration `to` parameter value.
// The normalized value for the `to` parameter is a function that takes an array and returns an array. See more in the
// configuration description. If the passed `to` is already a function, it is returned unchanged.
//
// @param {String|Array.<null|String>|Function} to
// @returns {Function}
function normalizeTo( to ) {
if ( typeof to == 'string' ) {
return () => [ to ];
} else if ( to instanceof Array ) {
return () => to;
}
// `to` is already a function.
return to;
}
// For given `position` returns attributes for the text that is after that position.
// The text can be in the same text node as the position (`foo[]bar`) or in the next text node (`foo[]<$text bold="true">bar</$text>`).
//
// @param {module:engine/model/position~Position} position
// @returns {Iterable.<*>}
function getTextAttributesAfterPosition( position ) {
const textNode = position.textNode ? position.textNode : position.nodeAfter;
return textNode.getAttributes();
}
// Returns a RegExp pattern string that detects a sentence inside a quote.
//
// @param {String} quoteCharacter The character to create a pattern for.
// @returns {String}
function buildQuotesRegExp( quoteCharacter ) {
return new RegExp( `(^|\\s)(${ quoteCharacter })([^${ quoteCharacter }]*)(${ quoteCharacter })$` );
}
// Reads text transformation config and returns normalized array of transformations objects.
//
// @param {module:typing/texttransformation~TextTransformationDescription} config
// @returns {Array.<{from:String,to:Function}>}
function normalizeTransformations( config ) {
const extra = config.extra || [];
const remove = config.remove || [];
const isNotRemoved = transformation => !remove.includes( transformation );
const configured = config.include.concat( extra ).filter( isNotRemoved );
return expandGroupsAndRemoveDuplicates( configured )
.filter( isNotRemoved ) // Filter out 'remove' transformations as they might be set in group.
.map( transformation => TRANSFORMATIONS[ transformation ] || transformation )
.filter( transformation => typeof transformation === 'object' ) // Filter out transformations set as string that has not been found.
.map( transformation => ( {
from: normalizeFrom( transformation.from ),
to: normalizeTo( transformation.to )
} ) );
}
// Reads definitions and expands named groups if needed to transformation names.
// This method also removes duplicated named transformations if any.
//
// @param {Array.<String|Object>} definitions
// @returns {Array.<String|Object>}
function expandGroupsAndRemoveDuplicates( definitions ) {
// Set is using to make sure that transformation names are not duplicated.
const definedTransformations = new Set();
for ( const transformationOrGroup of definitions ) {
if ( TRANSFORMATION_GROUPS[ transformationOrGroup ] ) {
for ( const transformation of TRANSFORMATION_GROUPS[ transformationOrGroup ] ) {
definedTransformations.add( transformation );
}
} else {
definedTransformations.add( transformationOrGroup );
}
}
return Array.from( definedTransformations );
}
/**
* The text transformation definition object. It describes what should be replaced with what.
*
* The input value (`from`) can be passed either as a string or as a regular expression.
*
* * If a string is passed, it will be simply checked if the end of the input matches it.
* * If a regular expression is passed, its entire length must be covered with capturing groups (e.g. `/(foo)(bar)$/`).
* Also, since it is compared against the end of the input, it has to end with `$` to be correctly matched.
* See examples below.
*
* The output value (`to`) can be passed as a string, as an array or as a function.
*
* * If a string is passed, it will be used as a replacement value as-is. Note that a string output value can be used only if
* the input value is a string, too.
* * If an array is passed, it has to have the same number of elements as there are capturing groups in the input value regular expression.
* Each capture group will be replaced with a corresponding string from the passed array. If a given capturing group should not be replaced,
* use `null` instead of passing a string.
* * If a function is used, it should return an array as described above. The function is passed one parameter — an array with matches
* by the regular expression. See the examples below.
*
* A simple string-to-string replacement:
*
* { from: '(c)', to: '©' }
*
* Change quote styles using a regular expression. Note how all the parts are in separate capturing groups and the space at the beginning
* and the text inside quotes are not replaced (`null` passed as the first and the third value in the `to` parameter):
*
* {
* from: /(^|\s)(")([^"]*)(")$/,
* to: [ null, '“', null, '”' ]
* }
*
* Automatic uppercase after a dot using a callback:
*
* {
* from: /(\. )([a-z])$/,
* to: matches => [ null, matches[ 1 ].toUpperCase() ]
* }
*
* @typedef {Object} module:typing/texttransformation~TextTransformationDescription
* @property {String|RegExp} from The string or regular expression to transform.
* @property {String} to The text to transform compatible with `String.replace()`.
*/
/**
* The configuration of the {@link module:typing/texttransformation~TextTransformation} feature.
*
* Read more in {@link module:typing/texttransformation~TextTransformationConfig}.
*
* @member {module:typing/texttransformation~TextTransformationConfig} module:typing/typing~TypingConfig#transformations
*/
/**
* The configuration of the text transformation feature.
*
* ClassicEditor
* .create( editorElement, {
* typing: {
* transformations: ... // Text transformation feature options.
* }
* } )
* .then( ... )
* .catch( ... );
*
* By default, the feature comes pre-configured
* (via {@link module:typing/texttransformation~TextTransformationConfig#include `config.typing.transformations.include`}) with the
* following groups of transformations:
*
* * Typography (group name: `typography`)
* - `ellipsis`: transforms `...` to `…`
* - `enDash`: transforms ` -- ` to ` – `
* - `emDash`: transforms ` --- ` to ` — `
* * Quotations (group name: `quotes`)
* - `quotesPrimary`: transforms `"Foo bar"` to `“Foo bar”`
* - `quotesSecondary`: transforms `'Foo bar'` to `‘Foo bar’`
* * Symbols (group name: `symbols`)
* - `trademark`: transforms `(tm)` to `™`
* - `registeredTrademark`: transforms `(r)` to `®`
* - `copyright`: transforms `(c)` to `©`
* * Mathematical (group name: `mathematical`)
* - `oneHalf`: transforms `1/2` to: `½`
* - `oneThird`: transforms `1/3` to: `⅓`
* - `twoThirds`: transforms `2/3` to: `⅔`
* - `oneForth`: transforms `1/4` to: `¼`
* - `threeQuarters`: transforms `3/4` to: `¾`
* - `lessThanOrEqual`: transforms `<=` to: `≤`
* - `greaterThanOrEqual`: transforms `>=` to: `≥`
* - `notEqual`: transforms `!=` to: `≠`
* - `arrowLeft`: transforms `<-` to: `←`
* - `arrowRight`: transforms `->` to: `→`
* * Misc:
* - `quotesPrimaryEnGb`: transforms `'Foo bar'` to `‘Foo bar’`
* - `quotesSecondaryEnGb`: transforms `"Foo bar"` to `“Foo bar”`
* - `quotesPrimaryPl`: transforms `"Foo bar"` to `„Foo bar”`
* - `quotesSecondaryPl`: transforms `'Foo bar'` to `‚Foo bar’`
*
* In order to load additional transformations, use the
* {@link module:typing/texttransformation~TextTransformationConfig#extra `transformations.extra` option}.
*
* In order to narrow down the list of transformations, use the
* {@link module:typing/texttransformation~TextTransformationConfig#remove `transformations.remove` option}.
*
* In order to completely override the supported transformations, use the
* {@link module:typing/texttransformation~TextTransformationConfig#include `transformations.include` option}.
*
* Examples:
*
* const transformationsConfig = {
* include: [
* // Use only the 'quotes' and 'typography' groups.
* 'quotes',
* 'typography',
*
* // Plus, some custom transformation.
* { from: 'CKE', to: 'CKEditor' }
* ]
* };
*
* const transformationsConfig = {
* // Remove the 'ellipsis' transformation loaded by the 'typography' group.
* remove: [ 'ellipsis' ]
* }
*
* @interface TextTransformationConfig
*/
/* eslint-disable max-len */
/**
* The standard list of text transformations supported by the editor. By default it comes pre-configured with a couple dozen of them
* (see {@link module:typing/texttransformation~TextTransformationConfig} for the full list). You can override this list completely
* by setting this option or use the other two options
* ({@link module:typing/texttransformation~TextTransformationConfig#extra `transformations.extra`},
* {@link module:typing/texttransformation~TextTransformationConfig#remove `transformations.remove`}) to fine-tune the default list.
*
* @member {Array.<module:typing/texttransformation~TextTransformationDescription>} module:typing/texttransformation~TextTransformationConfig#include
*/
/**
* Additional text transformations that are added to the transformations defined in
* {@link module:typing/texttransformation~TextTransformationConfig#include `transformations.include`}.
*
* const transformationsConfig = {
* extra: [
* { from: 'CKE', to: 'CKEditor' }
* ]
* };
*
* @member {Array.<module:typing/texttransformation~TextTransformationDescription>} module:typing/texttransformation~TextTransformationConfig#extra
*/
/**
* The text transformation names that are removed from transformations defined in
* {@link module:typing/texttransformation~TextTransformationConfig#include `transformations.include`} or
* {@link module:typing/texttransformation~TextTransformationConfig#extra `transformations.extra`}.
*
* const transformationsConfig = {
* remove: [
* 'ellipsis', // Remove only 'ellipsis' from the 'typography' group.
* 'mathematical' // Remove all transformations from the 'mathematical' group.
* ]
* }
*
* @member {Array.<module:typing/texttransformation~TextTransformationDescription>} module:typing/texttransformation~TextTransformationConfig#remove
*/
/* eslint-enable max-len */
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-typing/src/textwatcher.js":
/*!********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-typing/src/textwatcher.js ***!
\********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ TextWatcher)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/mix */ "./node_modules/@ckeditor/ckeditor5-utils/src/mix.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_observablemixin__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/observablemixin */ "./node_modules/@ckeditor/ckeditor5-utils/src/observablemixin.js");
/* harmony import */ var _utils_getlasttextline__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./utils/getlasttextline */ "./node_modules/@ckeditor/ckeditor5-typing/src/utils/getlasttextline.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module typing/textwatcher
*/
/**
* The text watcher feature.
*
* Fires the {@link module:typing/textwatcher~TextWatcher#event:matched:data `matched:data`},
* {@link module:typing/textwatcher~TextWatcher#event:matched:selection `matched:selection`} and
* {@link module:typing/textwatcher~TextWatcher#event:unmatched `unmatched`} events on typing or selection changes.
*
* @private
* @mixes module:utils/observablemixin~ObservableMixin
*/
class TextWatcher {
/**
* Creates a text watcher instance.
*
* @param {module:engine/model/model~Model} model
* @param {Function} testCallback See {@link module:typing/textwatcher~TextWatcher#testCallback}.
*/
constructor( model, testCallback ) {
/**
* The editor's model.
*
* @readonly
* @member {module:engine/model/model~Model}
*/
this.model = model;
/**
* The function used to match the text.
*
* The test callback can return 3 values:
*
* * `false` if there is no match,
* * `true` if there is a match,
* * an object if there is a match and we want to pass some additional information to the {@link #event:matched:data} event.
*
* @member {Function} #testCallback
* @returns {Object} testResult
*/
this.testCallback = testCallback;
/**
* Whether there is a match currently.
*
* @readonly
* @member {Boolean}
*/
this.hasMatch = false;
/**
* Flag indicating whether the `TextWatcher` instance is enabled or disabled.
* A disabled TextWatcher will not evaluate text.
*
* To disable TextWatcher:
*
* const watcher = new TextWatcher( editor.model, testCallback );
*
* // After this a testCallback will not be called.
* watcher.isEnabled = false;
*
* @observable
* @member {Boolean} #isEnabled
*/
this.set( 'isEnabled', true );
// Toggle text watching on isEnabled state change.
this.on( 'change:isEnabled', () => {
if ( this.isEnabled ) {
this._startListening();
} else {
this.stopListening( model.document.selection );
this.stopListening( model.document );
}
} );
this._startListening();
}
/**
* Starts listening to the editor for typing and selection events.
*
* @private
*/
_startListening() {
const model = this.model;
const document = model.document;
this.listenTo( document.selection, 'change:range', ( evt, { directChange } ) => {
// Indirect changes (i.e. when the user types or external changes are applied) are handled in the document's change event.
if ( !directChange ) {
return;
}
// Act only on collapsed selection.
if ( !document.selection.isCollapsed ) {
if ( this.hasMatch ) {
this.fire( 'unmatched' );
this.hasMatch = false;
}
return;
}
this._evaluateTextBeforeSelection( 'selection' );
} );
this.listenTo( document, 'change:data', ( evt, batch ) => {
if ( batch.isUndo || !batch.isLocal ) {
return;
}
this._evaluateTextBeforeSelection( 'data', { batch } );
} );
}
/**
* Checks the editor content for matched text.
*
* @fires matched:data
* @fires matched:selection
* @fires unmatched
*
* @private
* @param {'data'|'selection'} suffix A suffix used for generating the event name.
* @param {Object} data Data object for event.
*/
_evaluateTextBeforeSelection( suffix, data = {} ) {
const model = this.model;
const document = model.document;
const selection = document.selection;
const rangeBeforeSelection = model.createRange( model.createPositionAt( selection.focus.parent, 0 ), selection.focus );
const { text, range } = (0,_utils_getlasttextline__WEBPACK_IMPORTED_MODULE_2__["default"])( rangeBeforeSelection, model );
const testResult = this.testCallback( text );
if ( !testResult && this.hasMatch ) {
this.fire( 'unmatched' );
}
this.hasMatch = !!testResult;
if ( testResult ) {
const eventData = Object.assign( data, { text, range } );
// If the test callback returns an object with additional data, assign the data as well.
if ( typeof testResult == 'object' ) {
Object.assign( eventData, testResult );
}
this.fire( `matched:${ suffix }`, eventData );
}
}
}
(0,_ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_0__["default"])( TextWatcher, _ckeditor_ckeditor5_utils_src_observablemixin__WEBPACK_IMPORTED_MODULE_1__["default"] );
/**
* Fired whenever the text watcher found a match for data changes.
*
* @event matched:data
* @param {Object} data Event data.
* @param {String} data.text The full text before selection to which the regexp was applied.
* @param {module:engine/model/range~Range} data.range The range representing the position of the `data.text`.
* @param {Object} [data.testResult] The additional data returned from the {@link module:typing/textwatcher~TextWatcher#testCallback}.
*/
/**
* Fired whenever the text watcher found a match for selection changes.
*
* @event matched:selection
* @param {Object} data Event data.
* @param {String} data.text The full text before selection.
* @param {module:engine/model/range~Range} data.range The range representing the position of the `data.text`.
* @param {Object} [data.testResult] The additional data returned from the {@link module:typing/textwatcher~TextWatcher#testCallback}.
*/
/**
* Fired whenever the text does not match anymore. Fired only when the text watcher found a match.
*
* @event unmatched
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-typing/src/twostepcaretmovement.js":
/*!*****************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-typing/src/twostepcaretmovement.js ***!
\*****************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ TwoStepCaretMovement)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_core_src_plugin__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-core/src/plugin */ "./node_modules/@ckeditor/ckeditor5-core/src/plugin.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_keyboard__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/keyboard */ "./node_modules/@ckeditor/ckeditor5-utils/src/keyboard.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module typing/twostepcaretmovement
*/
/**
* This plugin enables the two-step caret (phantom) movement behavior for
* {@link module:typing/twostepcaretmovement~TwoStepCaretMovement#registerAttribute registered attributes}
* on arrow right (<kbd>→</kbd>) and left (<kbd>←</kbd>) key press.
*
* Thanks to this (phantom) caret movement the user is able to type before/after as well as at the
* beginning/end of an attribute.
*
* **Note:** This plugin support right–to–left (Arabic, Hebrew, etc.) content by mirroring its behavior
* but for the sake of simplicity examples showcase only left–to–right use–cases.
*
* # Forward movement
*
* ## "Entering" an attribute:
*
* When this plugin is enabled and registered for the `a` attribute and the selection is right before it
* (at the attribute boundary), pressing the right arrow key will not move the selection but update its
* attributes accordingly:
*
* * When enabled:
*
* foo{}<$text a="true">bar</$text>
*
* <kbd>→</kbd>
*
* foo<$text a="true">{}bar</$text>
*
* * When disabled:
*
* foo{}<$text a="true">bar</$text>
*
* <kbd>→</kbd>
*
* foo<$text a="true">b{}ar</$text>
*
*
* ## "Leaving" an attribute:
*
* * When enabled:
*
* <$text a="true">bar{}</$text>baz
*
* <kbd>→</kbd>
*
* <$text a="true">bar</$text>{}baz
*
* * When disabled:
*
* <$text a="true">bar{}</$text>baz
*
* <kbd>→</kbd>
*
* <$text a="true">bar</$text>b{}az
*
* # Backward movement
*
* * When enabled:
*
* <$text a="true">bar</$text>{}baz
*
* <kbd>←</kbd>
*
* <$text a="true">bar{}</$text>baz
*
* * When disabled:
*
* <$text a="true">bar</$text>{}baz
*
* <kbd>←</kbd>
*
* <$text a="true">ba{}r</$text>b{}az
*
* # Multiple attributes
*
* * When enabled and many attributes starts or ends at the same position:
*
* <$text a="true" b="true">bar</$text>{}baz
*
* <kbd>←</kbd>
*
* <$text a="true" b="true">bar{}</$text>baz
*
* * When enabled and one procedes another:
*
* <$text a="true">bar</$text><$text b="true">{}bar</$text>
*
* <kbd>←</kbd>
*
* <$text a="true">bar{}</$text><$text b="true">bar</$text>
*
*/
class TwoStepCaretMovement extends _ckeditor_ckeditor5_core_src_plugin__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* @inheritDoc
*/
static get pluginName() {
return 'TwoStepCaretMovement';
}
/**
* @inheritDoc
*/
constructor( editor ) {
super( editor );
/**
* A set of attributes to handle.
*
* @protected
* @property {module:typing/twostepcaretmovement~TwoStepCaretMovement}
*/
this.attributes = new Set();
/**
* The current UID of the overridden gravity, as returned by
* {@link module:engine/model/writer~Writer#overrideSelectionGravity}.
*
* @private
* @member {String}
*/
this._overrideUid = null;
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const model = editor.model;
const view = editor.editing.view;
const locale = editor.locale;
const modelSelection = model.document.selection;
// Listen to keyboard events and handle the caret movement according to the 2-step caret logic.
this.listenTo( view.document, 'arrowKey', ( evt, data ) => {
// This implementation works only for collapsed selection.
if ( !modelSelection.isCollapsed ) {
return;
}
// When user tries to expand the selection or jump over the whole word or to the beginning/end then
// two-steps movement is not necessary.
if ( data.shiftKey || data.altKey || data.ctrlKey ) {
return;
}
const arrowRightPressed = data.keyCode == _ckeditor_ckeditor5_utils_src_keyboard__WEBPACK_IMPORTED_MODULE_1__.keyCodes.arrowright;
const arrowLeftPressed = data.keyCode == _ckeditor_ckeditor5_utils_src_keyboard__WEBPACK_IMPORTED_MODULE_1__.keyCodes.arrowleft;
// When neither left or right arrow has been pressed then do noting.
if ( !arrowRightPressed && !arrowLeftPressed ) {
return;
}
const contentDirection = locale.contentLanguageDirection;
let isMovementHandled = false;
if ( ( contentDirection === 'ltr' && arrowRightPressed ) || ( contentDirection === 'rtl' && arrowLeftPressed ) ) {
isMovementHandled = this._handleForwardMovement( data );
} else {
isMovementHandled = this._handleBackwardMovement( data );
}
// Stop the keydown event if the two-step caret movement handled it. Avoid collisions
// with other features which may also take over the caret movement (e.g. Widget).
if ( isMovementHandled === true ) {
evt.stop();
}
}, { context: '$text', priority: 'highest' } );
/**
* A flag indicating that the automatic gravity restoration should not happen upon the next
* gravity restoration.
* {@link module:engine/model/selection~Selection#event:change:range} event.
*
* @private
* @member {String}
*/
this._isNextGravityRestorationSkipped = false;
// The automatic gravity restoration logic.
this.listenTo( modelSelection, 'change:range', ( evt, data ) => {
// Skipping the automatic restoration is needed if the selection should change
// but the gravity must remain overridden afterwards. See the #handleBackwardMovement
// to learn more.
if ( this._isNextGravityRestorationSkipped ) {
this._isNextGravityRestorationSkipped = false;
return;
}
// Skip automatic restore when the gravity is not overridden — simply, there's nothing to restore
// at this moment.
if ( !this._isGravityOverridden ) {
return;
}
// Skip automatic restore when the change is indirect AND the selection is at the attribute boundary.
// It means that e.g. if the change was external (collaboration) and the user had their
// selection around the link, its gravity should remain intact in this change:range event.
if ( !data.directChange && isBetweenDifferentAttributes( modelSelection.getFirstPosition(), this.attributes ) ) {
return;
}
this._restoreGravity();
} );
}
/**
* Registers a given attribute for the two-step caret movement.
*
* @param {String} attribute Name of the attribute to handle.
*/
registerAttribute( attribute ) {
this.attributes.add( attribute );
}
/**
* Updates the document selection and the view according to the two–step caret movement state
* when moving **forwards**. Executed upon `keypress` in the {@link module:engine/view/view~View}.
*
* @private
* @param {module:engine/view/observer/domeventdata~DomEventData} data Data of the key press.
* @returns {Boolean} `true` when the handler prevented caret movement
*/
_handleForwardMovement( data ) {
const attributes = this.attributes;
const model = this.editor.model;
const selection = model.document.selection;
const position = selection.getFirstPosition();
// DON'T ENGAGE 2-SCM if gravity is already overridden. It means that we just entered
//
// <paragraph>foo<$text attribute>{}bar</$text>baz</paragraph>
//
// or left the attribute
//
// <paragraph>foo<$text attribute>bar</$text>{}baz</paragraph>
//
// and the gravity will be restored automatically.
if ( this._isGravityOverridden ) {
return false;
}
// DON'T ENGAGE 2-SCM when the selection is at the beginning of the block AND already has the
// attribute:
// * when the selection was initially set there using the mouse,
// * when the editor has just started
//
// <paragraph><$text attribute>{}bar</$text>baz</paragraph>
//
if ( position.isAtStart && hasAnyAttribute( selection, attributes ) ) {
return false;
}
// ENGAGE 2-SCM When at least one of the observed attributes changes its value (incl. starts, ends).
//
// <paragraph>foo<$text attribute>bar{}</$text>baz</paragraph>
// <paragraph>foo<$text attribute>bar{}</$text><$text otherAttribute>baz</$text></paragraph>
// <paragraph>foo<$text attribute=1>bar{}</$text><$text attribute=2>baz</$text></paragraph>
// <paragraph>foo{}<$text attribute>bar</$text>baz</paragraph>
//
if ( isBetweenDifferentAttributes( position, attributes ) ) {
preventCaretMovement( data );
this._overrideGravity();
return true;
}
}
/**
* Updates the document selection and the view according to the two–step caret movement state
* when moving **backwards**. Executed upon `keypress` in the {@link module:engine/view/view~View}.
*
* @private
* @param {module:engine/view/observer/domeventdata~DomEventData} data Data of the key press.
* @returns {Boolean} `true` when the handler prevented caret movement
*/
_handleBackwardMovement( data ) {
const attributes = this.attributes;
const model = this.editor.model;
const selection = model.document.selection;
const position = selection.getFirstPosition();
// When the gravity is already overridden (by this plugin), it means we are on the two-step position.
// Prevent the movement, restore the gravity and update selection attributes.
//
// <paragraph>foo<$text attribute=1>bar</$text><$text attribute=2>{}baz</$text></paragraph>
// <paragraph>foo<$text attribute>bar</$text><$text otherAttribute>{}baz</$text></paragraph>
// <paragraph>foo<$text attribute>{}bar</$text>baz</paragraph>
// <paragraph>foo<$text attribute>bar</$text>{}baz</paragraph>
//
if ( this._isGravityOverridden ) {
preventCaretMovement( data );
this._restoreGravity();
setSelectionAttributesFromTheNodeBefore( model, attributes, position );
return true;
} else {
// REMOVE SELECTION ATTRIBUTE when restoring gravity towards a non-existent content at the
// beginning of the block.
//
// <paragraph>{}<$text attribute>bar</$text></paragraph>
//
if ( position.isAtStart ) {
if ( hasAnyAttribute( selection, attributes ) ) {
preventCaretMovement( data );
setSelectionAttributesFromTheNodeBefore( model, attributes, position );
return true;
}
return false;
}
// When we are moving from natural gravity, to the position of the 2SCM, we need to override the gravity,
// and make sure it won't be restored. Unless it's at the end of the block and an observed attribute.
// We need to check if the caret is a one position before the attribute boundary:
//
// <paragraph>foo<$text attribute=1>bar</$text><$text attribute=2>b{}az</$text></paragraph>
// <paragraph>foo<$text attribute>bar</$text><$text otherAttribute>b{}az</$text></paragraph>
// <paragraph>foo<$text attribute>b{}ar</$text>baz</paragraph>
// <paragraph>foo<$text attribute>bar</$text>b{}az</paragraph>
//
if ( isStepAfterAnyAttributeBoundary( position, attributes ) ) {
// ENGAGE 2-SCM if the selection has no attribute. This may happen when the user
// left the attribute using a FORWARD 2-SCM.
//
// <paragraph><$text attribute>bar</$text>{}</paragraph>
//
if (
position.isAtEnd &&
!hasAnyAttribute( selection, attributes ) &&
isBetweenDifferentAttributes( position, attributes )
) {
preventCaretMovement( data );
setSelectionAttributesFromTheNodeBefore( model, attributes, position );
return true;
}
// Skip the automatic gravity restore upon the next selection#change:range event.
// If not skipped, it would automatically restore the gravity, which should remain
// overridden.
this._isNextGravityRestorationSkipped = true;
this._overrideGravity();
// Don't return "true" here because we didn't call _preventCaretMovement.
// Returning here will destabilize the filler logic, which also listens to
// keydown (and the event would be stopped).
return false;
}
}
}
/**
* `true` when the gravity is overridden for the plugin.
*
* @readonly
* @private
* @type {Boolean}
*/
get _isGravityOverridden() {
return !!this._overrideUid;
}
/**
* Overrides the gravity using the {@link module:engine/model/writer~Writer model writer}
* and stores the information about this fact in the {@link #_overrideUid}.
*
* A shorthand for {@link module:engine/model/writer~Writer#overrideSelectionGravity}.
*
* @private
*/
_overrideGravity() {
this._overrideUid = this.editor.model.change( writer => {
return writer.overrideSelectionGravity();
} );
}
/**
* Restores the gravity using the {@link module:engine/model/writer~Writer model writer}.
*
* A shorthand for {@link module:engine/model/writer~Writer#restoreSelectionGravity}.
*
* @private
*/
_restoreGravity() {
this.editor.model.change( writer => {
writer.restoreSelectionGravity( this._overrideUid );
this._overrideUid = null;
} );
}
}
// Checks whether the selection has any of given attributes.
//
// @param {module:engine/model/documentselection~DocumentSelection} selection
// @param {Iterable.<String>} attributes
function hasAnyAttribute( selection, attributes ) {
for ( const observedAttribute of attributes ) {
if ( selection.hasAttribute( observedAttribute ) ) {
return true;
}
}
return false;
}
// Applies the given attributes to the current selection using using the
// values from the node before the current position. Uses
// the {@link module:engine/model/writer~Writer model writer}.
//
// @param {module:engine/model/model~Model}
// @param {Iterable.<String>} attributess
// @param {module:engine/model/position~Position} position
function setSelectionAttributesFromTheNodeBefore( model, attributes, position ) {
const nodeBefore = position.nodeBefore;
model.change( writer => {
if ( nodeBefore ) {
writer.setSelectionAttribute( nodeBefore.getAttributes() );
} else {
writer.removeSelectionAttribute( attributes );
}
} );
}
// Prevents the caret movement in the view by calling `preventDefault` on the event data.
//
// @alias data.preventDefault
function preventCaretMovement( data ) {
data.preventDefault();
}
// Checks whether the step before `isBetweenDifferentAttributes()`.
//
// @param {module:engine/model/position~Position} position
// @param {String} attribute
function isStepAfterAnyAttributeBoundary( position, attributes ) {
const positionBefore = position.getShiftedBy( -1 );
return isBetweenDifferentAttributes( positionBefore, attributes );
}
// Checks whether the given position is between different values of given attributes.
//
// @param {module:engine/model/position~Position} position
// @param {Iterable.<String>} attributes
function isBetweenDifferentAttributes( position, attributes ) {
const { nodeBefore, nodeAfter } = position;
for ( const observedAttribute of attributes ) {
const attrBefore = nodeBefore ? nodeBefore.getAttribute( observedAttribute ) : undefined;
const attrAfter = nodeAfter ? nodeAfter.getAttribute( observedAttribute ) : undefined;
if ( attrAfter !== attrBefore ) {
return true;
}
}
return false;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-typing/src/typing.js":
/*!***************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-typing/src/typing.js ***!
\***************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ Typing)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_core_src_plugin__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-core/src/plugin */ "./node_modules/@ckeditor/ckeditor5-core/src/plugin.js");
/* harmony import */ var _input__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./input */ "./node_modules/@ckeditor/ckeditor5-typing/src/input.js");
/* harmony import */ var _delete__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./delete */ "./node_modules/@ckeditor/ckeditor5-typing/src/delete.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module typing/typing
*/
/**
* The typing feature. It handles typing.
*
* This is a "glue" plugin which loads the {@link module:typing/input~Input} and {@link module:typing/delete~Delete}
* plugins.
*
* @extends module:core/plugin~Plugin
*/
class Typing extends _ckeditor_ckeditor5_core_src_plugin__WEBPACK_IMPORTED_MODULE_0__["default"] {
static get requires() {
return [ _input__WEBPACK_IMPORTED_MODULE_1__["default"], _delete__WEBPACK_IMPORTED_MODULE_2__["default"] ];
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'Typing';
}
}
/**
* The configuration of the typing features. Used by the features from the `@ckeditor/ckeditor5-typing` package.
*
* Read more in {@link module:typing/typing~TypingConfig}.
*
* @member {module:typing/typing~TypingConfig} module:core/editor/editorconfig~EditorConfig#typing
*/
/**
* The configuration of the typing features. Used by the typing features in `@ckeditor/ckeditor5-typing` package.
*
* ClassicEditor
* .create( editorElement, {
* typing: ... // Typing feature options.
* } )
* .then( ... )
* .catch( ... );
*
* See {@link module:core/editor/editorconfig~EditorConfig all editor options}.
*
* @interface TypingConfig
*/
/**
* The granularity of undo/redo for typing and deleting. The value `20` means (more or less) that a new undo step
* is created every 20 characters are inserted or deleted.
*
* @member {Number} [module:typing/typing~TypingConfig#undoStep=20]
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-typing/src/utils/changebuffer.js":
/*!***************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-typing/src/utils/changebuffer.js ***!
\***************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ChangeBuffer)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module typing/utils/changebuffer
*/
/**
* Change buffer allows to group atomic changes (like characters that have been typed) into
* {@link module:engine/model/batch~Batch batches}.
*
* Batches represent single undo steps, hence changes added to one single batch are undone together.
*
* The buffer has a configurable limit of atomic changes that it can accommodate. After the limit was
* exceeded (see {@link ~ChangeBuffer#input}), a new batch is created in {@link ~ChangeBuffer#batch}.
*
* To use the change buffer you need to let it know about the number of changes that were added to the batch:
*
* const buffer = new ChangeBuffer( model, LIMIT );
*
* // Later on in your feature:
* buffer.batch.insert( pos, insertedCharacters );
* buffer.input( insertedCharacters.length );
*
*/
class ChangeBuffer {
/**
* Creates a new instance of the change buffer.
*
* @param {module:engine/model/model~Model} model
* @param {Number} [limit=20] The maximum number of atomic changes which can be contained in one batch.
*/
constructor( model, limit = 20 ) {
/**
* The model instance.
*
* @readonly
* @member {module:engine/model/model~Model} #model
*/
this.model = model;
/**
* The number of atomic changes in the buffer. Once it exceeds the {@link #limit},
* the {@link #batch batch} is set to a new one.
*
* @readonly
* @member {Number} #size
*/
this.size = 0;
/**
* The maximum number of atomic changes which can be contained in one batch.
*
* @readonly
* @member {Number} #limit
*/
this.limit = limit;
/**
* Whether the buffer is locked. A locked buffer cannot be reset unless it gets unlocked.
*
* @readonly
* @member {Boolean} #isLocked
*/
this.isLocked = false;
// The function to be called in order to notify the buffer about batches which appeared in the document.
// The callback will check whether it is a new batch and in that case the buffer will be flushed.
//
// The reason why the buffer needs to be flushed whenever a new batch appears is that the changes added afterwards
// should be added to a new batch. For instance, when the user types, then inserts an image, and then types again,
// the characters typed after inserting the image should be added to a different batch than the characters typed before.
this._changeCallback = ( evt, batch ) => {
if ( batch.isLocal && batch.isUndoable && batch !== this._batch ) {
this._reset( true );
}
};
this._selectionChangeCallback = () => {
this._reset();
};
this.model.document.on( 'change', this._changeCallback );
this.model.document.selection.on( 'change:range', this._selectionChangeCallback );
this.model.document.selection.on( 'change:attribute', this._selectionChangeCallback );
/**
* The current batch instance.
*
* @private
* @member #_batch
*/
/**
* The callback to document the change event which later needs to be removed.
*
* @private
* @member #_changeCallback
*/
/**
* The callback to document selection `change:attribute` and `change:range` events which resets the buffer.
*
* @private
* @member #_selectionChangeCallback
*/
}
/**
* The current batch to which a feature should add its operations. Once the {@link #size}
* is reached or exceeds the {@link #limit}, the batch is set to a new instance and the size is reset.
*
* @type {module:engine/model/batch~Batch}
*/
get batch() {
if ( !this._batch ) {
this._batch = this.model.createBatch( { isTyping: true } );
}
return this._batch;
}
/**
* The input number of changes into the buffer. Once the {@link #size} is
* reached or exceeds the {@link #limit}, the batch is set to a new instance and the size is reset.
*
* @param {Number} changeCount The number of atomic changes to input.
*/
input( changeCount ) {
this.size += changeCount;
if ( this.size >= this.limit ) {
this._reset( true );
}
}
/**
* Locks the buffer.
*/
lock() {
this.isLocked = true;
}
/**
* Unlocks the buffer.
*/
unlock() {
this.isLocked = false;
}
/**
* Destroys the buffer.
*/
destroy() {
this.model.document.off( 'change', this._changeCallback );
this.model.document.selection.off( 'change:range', this._selectionChangeCallback );
this.model.document.selection.off( 'change:attribute', this._selectionChangeCallback );
}
/**
* Resets the change buffer.
*
* @private
* @param {Boolean} [ignoreLock] Whether internal lock {@link #isLocked} should be ignored.
*/
_reset( ignoreLock ) {
if ( !this.isLocked || ignoreLock ) {
this._batch = null;
this.size = 0;
}
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-typing/src/utils/findattributerange.js":
/*!*********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-typing/src/utils/findattributerange.js ***!
\*********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ findAttributeRange)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module typing/utils/findattributerange
*/
/**
* Returns a model range that covers all consecutive nodes with the same `attributeName` and its `value`
* that intersect the given `position`.
*
* It can be used e.g. to get the entire range on which the `linkHref` attribute needs to be changed when having a
* selection inside a link.
*
* @param {module:engine/model/position~Position} position The start position.
* @param {String} attributeName The attribute name.
* @param {String} value The attribute value.
* @param {module:engine/model/model~Model} model The model instance.
* @returns {module:engine/model/range~Range} The link range.
*/
function findAttributeRange( position, attributeName, value, model ) {
return model.createRange(
_findBound( position, attributeName, value, true, model ),
_findBound( position, attributeName, value, false, model )
);
}
// Walks forward or backward (depends on the `lookBack` flag), node by node, as long as they have the same attribute value
// and returns a position just before or after (depends on the `lookBack` flag) the last matched node.
//
// @param {module:engine/model/position~Position} position The start position.
// @param {String} attributeName The attribute name.
// @param {String} value The attribute value.
// @param {Boolean} lookBack Whether the walk direction is forward (`false`) or backward (`true`).
// @returns {module:engine/model/position~Position} The position just before the last matched node.
function _findBound( position, attributeName, value, lookBack, model ) {
// Get node before or after position (depends on `lookBack` flag).
// When position is inside text node then start searching from text node.
let node = position.textNode || ( lookBack ? position.nodeBefore : position.nodeAfter );
let lastNode = null;
while ( node && node.getAttribute( attributeName ) == value ) {
lastNode = node;
node = lookBack ? node.previousSibling : node.nextSibling;
}
return lastNode ? model.createPositionAt( lastNode, lookBack ? 'before' : 'after' ) : position;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-typing/src/utils/getlasttextline.js":
/*!******************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-typing/src/utils/getlasttextline.js ***!
\******************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ getLastTextLine)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module typing/utils/getlasttextline
*/
/**
* Returns the last text line from the given range.
*
* "The last text line" is understood as text (from one or more text nodes) which is limited either by a parent block
* or by inline elements (e.g. `<softBreak>`).
*
* const rangeToCheck = model.createRange(
* model.createPositionAt( paragraph, 0 ),
* model.createPositionAt( paragraph, 'end' )
* );
*
* const { text, range } = getLastTextLine( rangeToCheck, model );
*
* For model below, the returned `text` will be "Foo bar baz" and `range` will be set on whole `<paragraph>` content:
*
* <paragraph>Foo bar baz<paragraph>
*
* However, in below case, `text` will be set to "baz" and `range` will be set only on "baz".
*
* <paragraph>Foo<softBreak></softBreak>bar<softBreak></softBreak>baz<paragraph>
*
* @protected
* @param {module:engine/model/range~Range} range
* @param {module:engine/model/model~Model} model
* @returns {module:typing/utils/getlasttextline~LastTextLineData}
*/
function getLastTextLine( range, model ) {
let start = range.start;
const text = Array.from( range.getItems() ).reduce( ( rangeText, node ) => {
// Trim text to a last occurrence of an inline element and update range start.
if ( !( node.is( '$text' ) || node.is( '$textProxy' ) ) ) {
start = model.createPositionAfter( node );
return '';
}
return rangeText + node.data;
}, '' );
return { text, range: model.createRange( start, range.end ) };
}
/**
* The value returned by {@link module:typing/utils/getlasttextline~getLastTextLine}.
*
* @typedef {Object} module:typing/utils/getlasttextline~LastTextLineData
*
* @property {String} text The text from the text nodes in the last text line.
* @property {module:engine/model/range~Range} range The range set on the text nodes in the last text line.
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-typing/src/utils/injecttypingmutationshandling.js":
/*!********************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-typing/src/utils/injecttypingmutationshandling.js ***!
\********************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ injectTypingMutationsHandling)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_utils_src_diff__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/diff */ "./node_modules/@ckeditor/ckeditor5-utils/src/diff.js");
/* harmony import */ var _ckeditor_ckeditor5_engine_src_view_domconverter__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-engine/src/view/domconverter */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/domconverter.js");
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./utils */ "./node_modules/@ckeditor/ckeditor5-typing/src/utils/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module typing/utils/injecttypingmutationshandling
*/
/**
* Handles mutations caused by normal typing.
*
* @param {module:core/editor/editor~Editor} editor The editor instance.
*/
function injectTypingMutationsHandling( editor ) {
editor.editing.view.document.on( 'mutations', ( evt, mutations, viewSelection ) => {
new MutationHandler( editor ).handle( mutations, viewSelection );
} );
}
/**
* Helper class for translating DOM mutations into model changes.
*
* @private
*/
class MutationHandler {
/**
* Creates an instance of the mutation handler.
*
* @param {module:core/editor/editor~Editor} editor
*/
constructor( editor ) {
/**
* Editor instance for which mutations are handled.
*
* @readonly
* @member {module:core/editor/editor~Editor} #editor
*/
this.editor = editor;
/**
* The editing controller.
*
* @readonly
* @member {module:engine/controller/editingcontroller~EditingController} #editing
*/
this.editing = this.editor.editing;
}
/**
* Handles given mutations.
*
* @param {Array.<module:engine/view/observer/mutationobserver~MutatedText|
* module:engine/view/observer/mutationobserver~MutatedChildren>} mutations
* @param {module:engine/view/selection~Selection|null} viewSelection
*/
handle( mutations, viewSelection ) {
if ( (0,_utils__WEBPACK_IMPORTED_MODULE_2__.containerChildrenMutated)( mutations ) ) {
this._handleContainerChildrenMutations( mutations, viewSelection );
} else {
for ( const mutation of mutations ) {
// Fortunately it will never be both.
this._handleTextMutation( mutation, viewSelection );
this._handleTextNodeInsertion( mutation );
}
}
}
/**
* Handles situations when container's children mutated during input. This can happen when
* the browser is trying to "fix" DOM in certain situations. For example, when the user starts to type
* in `<p><a href=""><i>Link{}</i></a></p>`, the browser might change the order of elements
* to `<p><i><a href="">Link</a>x{}</i></p>`. A similar situation happens when the spell checker
* replaces a word wrapped with `<strong>` with a word wrapped with a `<b>` element.
*
* To handle such situations, the common DOM ancestor of all mutations is converted to the model representation
* and then compared with the current model to calculate the proper text change.
*
* Note: Single text node insertion is handled in {@link #_handleTextNodeInsertion} and text node mutation is handled
* in {@link #_handleTextMutation}).
*
* @private
* @param {Array.<module:engine/view/observer/mutationobserver~MutatedText|
* module:engine/view/observer/mutationobserver~MutatedChildren>} mutations
* @param {module:engine/view/selection~Selection|null} viewSelection
*/
_handleContainerChildrenMutations( mutations, viewSelection ) {
// Get common ancestor of all mutations.
const mutationsCommonAncestor = getMutationsContainer( mutations );
// Quit if there is no common ancestor.
if ( !mutationsCommonAncestor ) {
return;
}
const domConverter = this.editor.editing.view.domConverter;
// Get common ancestor in DOM.
const domMutationCommonAncestor = domConverter.mapViewToDom( mutationsCommonAncestor );
// Create fresh DomConverter so it will not use existing mapping and convert current DOM to model.
// This wouldn't be needed if DomConverter would allow to create fresh view without checking any mappings.
const freshDomConverter = new _ckeditor_ckeditor5_engine_src_view_domconverter__WEBPACK_IMPORTED_MODULE_1__["default"]( this.editor.editing.view.document );
const modelFromCurrentDom = this.editor.data.toModel(
freshDomConverter.domToView( domMutationCommonAncestor )
).getChild( 0 );
// Current model.
const currentModel = this.editor.editing.mapper.toModelElement( mutationsCommonAncestor );
// If common ancestor is not mapped, do not do anything. It probably is a parent of another view element.
// That means that we would need to diff model elements (see `if` below). Better return early instead of
// trying to get a reasonable model ancestor. It will fell into the `if` below anyway.
// This situation happens for example for lists. If `<ul>` is a common ancestor, `currentModel` is `undefined`
// because `<ul>` is not mapped (`<li>`s are).
// See https://github.com/ckeditor/ckeditor5/issues/718.
if ( !currentModel ) {
return;
}
// Get children from both ancestors.
const modelFromDomChildren = Array.from( modelFromCurrentDom.getChildren() );
const currentModelChildren = Array.from( currentModel.getChildren() );
// Remove the last `<softBreak>` from the end of `modelFromDomChildren` if there is no `<softBreak>` in current model.
// If the described scenario happened, it means that this is a bogus `<br />` added by a browser.
const lastDomChild = modelFromDomChildren[ modelFromDomChildren.length - 1 ];
const lastCurrentChild = currentModelChildren[ currentModelChildren.length - 1 ];
const isLastDomChildSoftBreak = lastDomChild && lastDomChild.is( 'element', 'softBreak' );
const isLastCurrentChildSoftBreak = lastCurrentChild && !lastCurrentChild.is( 'element', 'softBreak' );
if ( isLastDomChildSoftBreak && isLastCurrentChildSoftBreak ) {
modelFromDomChildren.pop();
}
const schema = this.editor.model.schema;
// Skip situations when common ancestor has any container elements.
if ( !isSafeForTextMutation( modelFromDomChildren, schema ) || !isSafeForTextMutation( currentModelChildren, schema ) ) {
return;
}
// Replace inserted by the browser with normal space. See comment in `_handleTextMutation`.
// Replace non-texts with any character. This is potentially dangerous but passes in manual tests. The thing is
// that we need to take care of proper indexes so we cannot simply remove non-text elements from the content.
// By inserting a character we keep all the real texts on their indexes.
const newText = modelFromDomChildren.map( item => item.is( '$text' ) ? item.data : '@' ).join( '' ).replace( /\u00A0/g, ' ' );
const oldText = currentModelChildren.map( item => item.is( '$text' ) ? item.data : '@' ).join( '' ).replace( /\u00A0/g, ' ' );
// Do nothing if mutations created same text.
if ( oldText === newText ) {
return;
}
const diffResult = (0,_ckeditor_ckeditor5_utils_src_diff__WEBPACK_IMPORTED_MODULE_0__["default"])( oldText, newText );
const { firstChangeAt, insertions, deletions } = calculateChanges( diffResult );
// Try setting new model selection according to passed view selection.
let modelSelectionRange = null;
if ( viewSelection ) {
modelSelectionRange = this.editing.mapper.toModelRange( viewSelection.getFirstRange() );
}
const insertText = newText.substr( firstChangeAt, insertions );
const removeRange = this.editor.model.createRange(
this.editor.model.createPositionAt( currentModel, firstChangeAt ),
this.editor.model.createPositionAt( currentModel, firstChangeAt + deletions )
);
this.editor.execute( 'input', {
text: insertText,
range: removeRange,
resultRange: modelSelectionRange
} );
}
/**
* @private
*/
_handleTextMutation( mutation, viewSelection ) {
if ( mutation.type != 'text' ) {
return;
}
// Replace inserted by the browser with normal space.
// We want only normal spaces in the model and in the view. Renderer and DOM Converter will be then responsible
// for rendering consecutive spaces using , but the model and the view has to be clear.
// Other feature may introduce inserting non-breakable space on specific key stroke (for example shift + space).
// However then it will be handled outside of mutations, like enter key is.
// The replacing is here because it has to be done before `diff` and `diffToChanges` functions, as they
// take `newText` and compare it to (cleaned up) view.
// It could also be done in mutation observer too, however if any outside plugin would like to
// introduce additional events for mutations, they would get already cleaned up version (this may be good or not).
const newText = mutation.newText.replace( /\u00A0/g, ' ' );
// To have correct `diffResult`, we also compare view node text data with replaced by space.
const oldText = mutation.oldText.replace( /\u00A0/g, ' ' );
// Do nothing if mutations created same text.
if ( oldText === newText ) {
return;
}
const diffResult = (0,_ckeditor_ckeditor5_utils_src_diff__WEBPACK_IMPORTED_MODULE_0__["default"])( oldText, newText );
const { firstChangeAt, insertions, deletions } = calculateChanges( diffResult );
// Try setting new model selection according to passed view selection.
let modelSelectionRange = null;
if ( viewSelection ) {
modelSelectionRange = this.editing.mapper.toModelRange( viewSelection.getFirstRange() );
}
// Get the position in view and model where the changes will happen.
const viewPos = this.editing.view.createPositionAt( mutation.node, firstChangeAt );
const modelPos = this.editing.mapper.toModelPosition( viewPos );
const removeRange = this.editor.model.createRange( modelPos, modelPos.getShiftedBy( deletions ) );
const insertText = newText.substr( firstChangeAt, insertions );
this.editor.execute( 'input', {
text: insertText,
range: removeRange,
resultRange: modelSelectionRange
} );
}
/**
* @private
*/
_handleTextNodeInsertion( mutation ) {
if ( mutation.type != 'children' ) {
return;
}
const change = (0,_utils__WEBPACK_IMPORTED_MODULE_2__.getSingleTextNodeChange)( mutation );
const viewPos = this.editing.view.createPositionAt( mutation.node, change.index );
const modelPos = this.editing.mapper.toModelPosition( viewPos );
const insertedText = change.values[ 0 ].data;
this.editor.execute( 'input', {
// Replace inserted by the browser with normal space.
// See comment in `_handleTextMutation`.
// In this case we don't need to do this before `diff` because we diff whole nodes.
// Just change in case there are some.
text: insertedText.replace( /\u00A0/g, ' ' ),
range: this.editor.model.createRange( modelPos )
} );
}
}
// Returns first common ancestor of all mutations that is either {@link module:engine/view/containerelement~ContainerElement}
// or {@link module:engine/view/rootelement~RootElement}.
//
// @private
// @param {Array.<module:engine/view/observer/mutationobserver~MutatedText|
// module:engine/view/observer/mutationobserver~MutatedChildren>} mutations
// @returns {module:engine/view/containerelement~ContainerElement|engine/view/rootelement~RootElement|undefined}
function getMutationsContainer( mutations ) {
const lca = mutations
.map( mutation => mutation.node )
.reduce( ( commonAncestor, node ) => {
return commonAncestor.getCommonAncestor( node, { includeSelf: true } );
} );
if ( !lca ) {
return;
}
// We need to look for container and root elements only, so check all LCA's
// ancestors (starting from itself).
return lca.getAncestors( { includeSelf: true, parentFirst: true } )
.find( element => element.is( 'containerElement' ) || element.is( 'rootElement' ) );
}
// Returns true if provided array contains content that won't be problematic during diffing and text mutation handling.
//
// @param {Array.<module:engine/model/node~Node>} children
// @param {module:engine/model/schema~Schema} schema
// @returns {Boolean}
function isSafeForTextMutation( children, schema ) {
return children.every( child => schema.isInline( child ) );
}
// Calculates first change index and number of characters that should be inserted and deleted starting from that index.
//
// @private
// @param diffResult
// @returns {{insertions: number, deletions: number, firstChangeAt: *}}
function calculateChanges( diffResult ) {
// Index where the first change happens. Used to set the position from which nodes will be removed and where will be inserted.
let firstChangeAt = null;
// Index where the last change happens. Used to properly count how many characters have to be removed and inserted.
let lastChangeAt = null;
// Get `firstChangeAt` and `lastChangeAt`.
for ( let i = 0; i < diffResult.length; i++ ) {
const change = diffResult[ i ];
if ( change != 'equal' ) {
firstChangeAt = firstChangeAt === null ? i : firstChangeAt;
lastChangeAt = i;
}
}
// How many characters, starting from `firstChangeAt`, should be removed.
let deletions = 0;
// How many characters, starting from `firstChangeAt`, should be inserted.
let insertions = 0;
for ( let i = firstChangeAt; i <= lastChangeAt; i++ ) {
// If there is no change (equal) or delete, the character is existing in `oldText`. We count it for removing.
if ( diffResult[ i ] != 'insert' ) {
deletions++;
}
// If there is no change (equal) or insert, the character is existing in `newText`. We count it for inserting.
if ( diffResult[ i ] != 'delete' ) {
insertions++;
}
}
return { insertions, deletions, firstChangeAt };
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-typing/src/utils/injectunsafekeystrokeshandling.js":
/*!*********************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-typing/src/utils/injectunsafekeystrokeshandling.js ***!
\*********************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ injectUnsafeKeystrokesHandling),
/* harmony export */ "isNonTypingKeystroke": () => (/* binding */ isNonTypingKeystroke)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_utils_src_keyboard__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/keyboard */ "./node_modules/@ckeditor/ckeditor5-utils/src/keyboard.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_env__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/env */ "./node_modules/@ckeditor/ckeditor5-utils/src/env.js");
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./utils */ "./node_modules/@ckeditor/ckeditor5-typing/src/utils/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module typing/utils/injectunsafekeystrokeshandling
*/
/**
* Handles keystrokes which are unsafe for typing. This handler's logic is explained
* in https://github.com/ckeditor/ckeditor5-typing/issues/83#issuecomment-398690251.
*
* @param {module:core/editor/editor~Editor} editor The editor instance.
*/
function injectUnsafeKeystrokesHandling( editor ) {
let latestCompositionSelection = null;
const model = editor.model;
const view = editor.editing.view;
const inputCommand = editor.commands.get( 'input' );
// For Android, we want to handle keystrokes on `beforeinput` to be sure that code in `DeleteObserver` already had a chance to be fired.
if ( _ckeditor_ckeditor5_utils_src_env__WEBPACK_IMPORTED_MODULE_1__["default"].isAndroid ) {
view.document.on( 'beforeinput', ( evt, evtData ) => handleUnsafeKeystroke( evtData ), { priority: 'lowest' } );
} else {
view.document.on( 'keydown', ( evt, evtData ) => handleUnsafeKeystroke( evtData ), { priority: 'lowest' } );
}
view.document.on( 'compositionstart', handleCompositionStart, { priority: 'lowest' } );
view.document.on( 'compositionend', () => {
latestCompositionSelection = model.createSelection( model.document.selection );
}, { priority: 'lowest' } );
// Handles the keydown event. We need to guess whether such keystroke is going to result
// in typing. If so, then before character insertion happens, any selected content needs
// to be deleted. Otherwise the default browser deletion mechanism would be
// triggered, resulting in:
//
// * Hundreds of mutations which could not be handled.
// * But most importantly, loss of control over how the content is being deleted.
//
// The method is used in a low-priority listener, hence allowing other listeners (e.g. delete or enter features)
// to handle the event.
//
// @param {module:engine/view/observer/keyobserver~KeyEventData} evtData
function handleUnsafeKeystroke( evtData ) {
// Do not delete the content, if Shift + Delete key combination was pressed on a non-collapsed selection on Windows.
//
// The Shift + Delete key combination should work in the same way as the `cut` event on a non-collapsed selection on Windows.
// In fact, the native `cut` event is actually emitted in this case, but with lower priority. Therefore, in order to handle the
// Shift + Delete key combination correctly, it is enough to prevent the content deletion here.
if ( _ckeditor_ckeditor5_utils_src_env__WEBPACK_IMPORTED_MODULE_1__["default"].isWindows && (0,_utils__WEBPACK_IMPORTED_MODULE_2__.isShiftDeleteOnNonCollapsedSelection)( evtData, view.document ) ) {
return;
}
const doc = model.document;
const isComposing = view.document.isComposing;
const isSelectionUnchanged = latestCompositionSelection && latestCompositionSelection.isEqual( doc.selection );
// Reset stored composition selection.
latestCompositionSelection = null;
// By relying on the state of the input command we allow disabling the entire input easily
// by just disabling the input command. We could’ve used here the delete command but that
// would mean requiring the delete feature which would block loading one without the other.
// We could also check the editor.isReadOnly property, but that wouldn't allow to block
// the input without blocking other features.
if ( !inputCommand.isEnabled ) {
return;
}
if ( isNonTypingKeystroke( evtData ) || doc.selection.isCollapsed ) {
return;
}
// If during composition, deletion should be prevented as it may remove composed sequence (#83).
if ( isComposing && evtData.keyCode === 229 ) {
return;
}
// If there is a `keydown` event fired with '229' keycode it might be related
// to recent composition. Check if selection is the same as upon ending recent composition,
// if so do not remove selected content as it will remove composed sequence (#83).
if ( !isComposing && evtData.keyCode === 229 && isSelectionUnchanged ) {
return;
}
deleteSelectionContent();
}
// Handles the `compositionstart` event. It is used only in special cases to remove the contents
// of a non-collapsed selection so composition itself does not result in complex mutations.
//
// The special case mentioned above is a situation in which the `keydown` event is fired after
// `compositionstart` event. In such cases {@link #handleKeydown} cannot clear current selection
// contents (because it is too late and will break the composition) so the composition handler takes care of it.
function handleCompositionStart() {
const doc = model.document;
const isFlatSelection = doc.selection.rangeCount === 1 ? doc.selection.getFirstRange().isFlat : true;
// If on `compositionstart` there is a non-collapsed selection which start and end have different parents
// it means the `handleKeydown()` method did not remove its contents. It happens usually because
// of different order of events (`compositionstart` before `keydown` - in Safari). In such cases
// we need to remove selection contents on composition start (#83).
if ( doc.selection.isCollapsed || isFlatSelection ) {
return;
}
deleteSelectionContent();
}
function deleteSelectionContent() {
const buffer = inputCommand.buffer;
buffer.lock();
const batch = buffer.batch;
model.enqueueChange( batch, () => {
model.deleteContent( model.document.selection );
} );
buffer.unlock();
}
}
const safeKeycodes = [
(0,_ckeditor_ckeditor5_utils_src_keyboard__WEBPACK_IMPORTED_MODULE_0__.getCode)( 'arrowUp' ),
(0,_ckeditor_ckeditor5_utils_src_keyboard__WEBPACK_IMPORTED_MODULE_0__.getCode)( 'arrowRight' ),
(0,_ckeditor_ckeditor5_utils_src_keyboard__WEBPACK_IMPORTED_MODULE_0__.getCode)( 'arrowDown' ),
(0,_ckeditor_ckeditor5_utils_src_keyboard__WEBPACK_IMPORTED_MODULE_0__.getCode)( 'arrowLeft' ),
9, // Tab
16, // Shift
17, // Ctrl
18, // Alt
19, // Pause
20, // CapsLock
27, // Escape
33, // PageUp
34, // PageDown
35, // Home
36, // End,
45, // Insert,
91, // Windows,
93, // Menu key,
144, // NumLock
145, // ScrollLock,
173, // Mute/Unmute
174, // Volume up
175, // Volume down,
176, // Next song,
177, // Previous song,
178, // Stop,
179, // Play/Pause,
255 // Display brightness (increase and decrease)
];
// Function keys.
for ( let code = 112; code <= 135; code++ ) {
safeKeycodes.push( code );
}
/**
* Returns `true` if a keystroke will **not** result in "typing".
*
* For instance, keystrokes that result in typing are letters "a-zA-Z", numbers "0-9", delete, backspace, etc.
*
* Keystrokes that do not cause typing are, for instance, Fn keys (F5, F8, etc.), arrow keys (←, →, ↑, ↓),
* Tab (↹), "Windows logo key" (⊞ Win), etc.
*
* Note: This implementation is very simple and will need to be refined with time.
*
* @param {module:engine/view/observer/keyobserver~KeyEventData} keyData
* @returns {Boolean}
*/
function isNonTypingKeystroke( keyData ) {
// Keystrokes which contain Ctrl or Cmd don't represent typing.
if ( keyData.ctrlKey || keyData.metaKey ) {
return true;
}
return safeKeycodes.includes( keyData.keyCode );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-typing/src/utils/inlinehighlight.js":
/*!******************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-typing/src/utils/inlinehighlight.js ***!
\******************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ inlineHighlight)
/* harmony export */ });
/* harmony import */ var _findattributerange__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./findattributerange */ "./node_modules/@ckeditor/ckeditor5-typing/src/utils/findattributerange.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module typing/utils/inlinehighlight
*/
/**
* Adds a visual highlight style to an attribute element in which the selection is anchored.
* Together with two-step caret movement, they indicate that the user is typing inside the element.
*
* Highlight is turned on by adding the given class to the attribute element in the view:
*
* * The class is removed before the conversion has started, as callbacks added with the `'highest'` priority
* to {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher} events.
* * The class is added in the view post fixer, after other changes in the model tree were converted to the view.
*
* This way, adding and removing the highlight does not interfere with conversion.
*
* Usage:
*
* import inlineHighlight from '@ckeditor/ckeditor5-typing/src/utils/inlinehighlight';
*
* // Make `ck-link_selected` class be applied on an `a` element
* // whenever the corresponding `linkHref` attribute element is selected.
* inlineHighlight( editor, 'linkHref', 'a', 'ck-link_selected' );
*
* @param {module:core/editor/editor~Editor} editor The editor instance.
* @param {String} attributeName The attribute name to check.
* @param {String} tagName The tagName of a view item.
* @param {String} className The class name to apply in the view.
*/
function inlineHighlight( editor, attributeName, tagName, className ) {
const view = editor.editing.view;
const highlightedElements = new Set();
// Adding the class.
view.document.registerPostFixer( writer => {
const selection = editor.model.document.selection;
let changed = false;
if ( selection.hasAttribute( attributeName ) ) {
const modelRange = (0,_findattributerange__WEBPACK_IMPORTED_MODULE_0__["default"])(
selection.getFirstPosition(),
attributeName,
selection.getAttribute( attributeName ),
editor.model
);
const viewRange = editor.editing.mapper.toViewRange( modelRange );
// There might be multiple view elements in the `viewRange`, for example, when the `a` element is
// broken by a UIElement.
for ( const item of viewRange.getItems() ) {
if ( item.is( 'element', tagName ) && !item.hasClass( className ) ) {
writer.addClass( className, item );
highlightedElements.add( item );
changed = true;
}
}
}
return changed;
} );
// Removing the class.
editor.conversion.for( 'editingDowncast' ).add( dispatcher => {
// Make sure the highlight is removed on every possible event, before conversion is started.
dispatcher.on( 'insert', removeHighlight, { priority: 'highest' } );
dispatcher.on( 'remove', removeHighlight, { priority: 'highest' } );
dispatcher.on( 'attribute', removeHighlight, { priority: 'highest' } );
dispatcher.on( 'selection', removeHighlight, { priority: 'highest' } );
function removeHighlight() {
view.change( writer => {
for ( const item of highlightedElements.values() ) {
writer.removeClass( className, item );
highlightedElements.delete( item );
}
} );
}
} );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-typing/src/utils/utils.js":
/*!********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-typing/src/utils/utils.js ***!
\********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "compareChildNodes": () => (/* binding */ compareChildNodes),
/* harmony export */ "containerChildrenMutated": () => (/* binding */ containerChildrenMutated),
/* harmony export */ "getSingleTextNodeChange": () => (/* binding */ getSingleTextNodeChange),
/* harmony export */ "isShiftDeleteOnNonCollapsedSelection": () => (/* binding */ isShiftDeleteOnNonCollapsedSelection)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_utils_src_diff__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/diff */ "./node_modules/@ckeditor/ckeditor5-utils/src/diff.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_difftochanges__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/difftochanges */ "./node_modules/@ckeditor/ckeditor5-utils/src/difftochanges.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_keyboard__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/keyboard */ "./node_modules/@ckeditor/ckeditor5-utils/src/keyboard.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module typing/utils/utils
*/
/**
* Returns true if container children have mutated or more than a single text node was changed.
*
* @private
* @param {Array.<module:engine/view/observer/mutationobserver~MutatedText|
* module:engine/view/observer/mutationobserver~MutatedChildren>} mutations
* @returns {Boolean}
*/
function containerChildrenMutated( mutations ) {
if ( mutations.length == 0 ) {
return false;
}
// Check if there is any mutation of `children` type or any mutation that changes more than one text node.
for ( const mutation of mutations ) {
if ( mutation.type === 'children' && !getSingleTextNodeChange( mutation ) ) {
return true;
}
}
return false;
}
/**
* Returns change made to a single text node.
*
* @private
* @param {module:engine/view/observer/mutationobserver~MutatedText|
* module:engine/view/observer/mutationobserver~MutatedChildren} mutation
* @returns {Object|undefined} Change object (see {@link module:utils/difftochanges~diffToChanges} output)
* or undefined if more than a single text node was changed.
*/
function getSingleTextNodeChange( mutation ) {
// One new node.
if ( mutation.newChildren.length - mutation.oldChildren.length != 1 ) {
return;
}
// Which is text.
const diffResult = (0,_ckeditor_ckeditor5_utils_src_diff__WEBPACK_IMPORTED_MODULE_0__["default"])( mutation.oldChildren, mutation.newChildren, compareChildNodes );
const changes = (0,_ckeditor_ckeditor5_utils_src_difftochanges__WEBPACK_IMPORTED_MODULE_1__["default"])( diffResult, mutation.newChildren );
// In case of [ delete, insert, insert ] the previous check will not exit.
if ( changes.length > 1 ) {
return;
}
const change = changes[ 0 ];
// Which is text.
if ( !( !!change.values[ 0 ] && change.values[ 0 ].is( '$text' ) ) ) {
return;
}
return change;
}
/**
* Checks whether two view nodes are identical, which means they are the same object
* or contain exactly same data (in case of text nodes).
*
* @private
* @param {module:engine/view/node~Node} oldChild
* @param {module:engine/view/node~Node} newChild
* @returns {Boolean}
*/
function compareChildNodes( oldChild, newChild ) {
if ( !!oldChild && oldChild.is( '$text' ) && !!newChild && newChild.is( '$text' ) ) {
return oldChild.data === newChild.data;
} else {
return oldChild === newChild;
}
}
/**
* Checks if <kbd>Shift</kbd> + <kbd>Delete</kbd> keystroke was pressed on a non-collapsed selection.
*
* This key combination has a special meaning on Windows machines and it should work in the same way as the `cut` event on a non-collapsed
* selection.
*
* @param {module:engine/view/observer/domeventdata~DomEventData} domEventData Event data.
* @param {module:engine/view/document~Document} document The document instance on which the event has been fired.
* @returns {Boolean}
*/
function isShiftDeleteOnNonCollapsedSelection( domEventData, document ) {
const selection = document.selection;
const isShiftDelete = domEventData.shiftKey && domEventData.keyCode === _ckeditor_ckeditor5_utils_src_keyboard__WEBPACK_IMPORTED_MODULE_2__.keyCodes["delete"];
const isNonCollapsedSelection = !selection.isCollapsed;
return isShiftDelete && isNonCollapsedSelection;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/src/bindings/clickoutsidehandler.js":
/*!*********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/src/bindings/clickoutsidehandler.js ***!
\*********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ clickOutsideHandler)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module ui/bindings/clickoutsidehandler
*/
/* global document */
/**
* Handles clicking **outside** of a specified set of elements, then fires an action.
*
* **Note**: Actually, the action is executed upon `mousedown`, not `click`. It prevents
* certain issues when the user keeps holding the mouse button and the UI cannot react
* properly.
*
* @param {Object} options Configuration options.
* @param {module:utils/dom/emittermixin~Emitter} options.emitter The emitter to which this behavior
* should be added.
* @param {Function} options.activator Function returning a `Boolean`, to determine whether the handler is active.
* @param {Array.<HTMLElement>} options.contextElements HTML elements that determine the scope of the
* handler. Clicking any of them or their descendants will **not** fire the callback.
* @param {Function} options.callback An action executed by the handler.
*/
function clickOutsideHandler( { emitter, activator, callback, contextElements } ) {
emitter.listenTo( document, 'mousedown', ( evt, domEvt ) => {
if ( !activator() ) {
return;
}
// Check if `composedPath` is `undefined` in case the browser does not support native shadow DOM.
// Can be removed when all supported browsers support native shadow DOM.
const path = typeof domEvt.composedPath == 'function' ? domEvt.composedPath() : [];
for ( const contextElement of contextElements ) {
if ( contextElement.contains( domEvt.target ) || path.includes( contextElement ) ) {
return;
}
}
callback();
} );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/src/bindings/injectcsstransitiondisabler.js":
/*!*****************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/src/bindings/injectcsstransitiondisabler.js ***!
\*****************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ injectCssTransitionDisabler)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module ui/bindings/injectcsstransitiondisabler
*/
/**
* A decorator that brings the possibility to temporarily disable CSS transitions using
* {@link module:ui/view~View} methods. It is helpful when, for instance, the transitions should not happen
* when the view is first displayed but they should work normal in other cases.
*
* The methods to control the CSS transitions are:
* * `disableCssTransitions()` – Adds the `.ck-transitions-disabled` class to the
* {@link module:ui/view~View#element view element}.
* * `enableCssTransitions()` – Removes the `.ck-transitions-disabled` class from the
* {@link module:ui/view~View#element view element}.
*
* **Note**: This helper extends the {@link module:ui/view~View#template template} and must be used **after**
* {@link module:ui/view~View#setTemplate} is called:
*
* import injectCssTransitionDisabler from '@ckeditor/ckeditor5-ui/src/bindings/injectcsstransitiondisabler';
*
* class MyView extends View {
* constructor() {
* super();
*
* // ...
*
* this.setTemplate( { ... } );
*
* // ...
*
* injectCssTransitionDisabler( this );
*
* // ...
* }
* }
*
* The usage comes down to:
*
* const view = new MyView();
*
* // ...
*
* view.disableCssTransitions();
* view.show();
* view.enableCssTransitions();
*
* @param {module:ui/view~View} view View instance that should get this functionality.
*/
function injectCssTransitionDisabler( view ) {
view.set( '_isCssTransitionsDisabled', false );
view.disableCssTransitions = () => {
view._isCssTransitionsDisabled = true;
};
view.enableCssTransitions = () => {
view._isCssTransitionsDisabled = false;
};
view.extendTemplate( {
attributes: {
class: [
view.bindTemplate.if( '_isCssTransitionsDisabled', 'ck-transitions-disabled' )
]
}
} );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/src/bindings/preventdefault.js":
/*!****************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/src/bindings/preventdefault.js ***!
\****************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ preventDefault)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module ui/bindings/preventdefault
*/
/**
* A helper which executes a native `Event.preventDefault()` if the target of an event equals the
* {@link module:ui/view~View#element element of the view}. It shortens the definition of a
* {@link module:ui/view~View#template template}.
*
* // In a class extending View.
* import preventDefault from '@ckeditor/ckeditor5-ui/src/bindings/preventdefault';
*
* // ...
*
* this.setTemplate( {
* tag: 'div',
*
* on: {
* // Prevent the default mousedown action on this view.
* mousedown: preventDefault( this )
* }
* } );
*
* @param {module:ui/view~View} view View instance that defines the template.
* @returns {module:ui/template~TemplateToBinding}
*/
function preventDefault( view ) {
return view.bindTemplate.to( evt => {
if ( evt.target === view.element ) {
evt.preventDefault();
}
} );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/src/bindings/submithandler.js":
/*!***************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/src/bindings/submithandler.js ***!
\***************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ submitHandler)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module ui/bindings/submithandler
*/
/**
* A handler useful for {@link module:ui/view~View views} working as HTML forms. It intercepts a native DOM
* `submit` event, prevents the default web browser behavior (navigation and page reload) and
* fires the `submit` event on a view instead. Such a custom event can be then used by any
* {@link module:utils/dom/emittermixin~Emitter emitter}, e.g. to serialize the form data.
*
* import submitHandler from '@ckeditor/ckeditor5-ui/src/bindings/submithandler';
*
* // ...
*
* class AnyFormView extends View {
* constructor() {
* super();
*
* // ...
*
* submitHandler( {
* view: this
* } );
* }
* }
*
* // ...
*
* const view = new AnyFormView();
*
* // A sample listener attached by an emitter working with the view.
* this.listenTo( view, 'submit', () => {
* saveTheFormData();
* hideTheForm();
* } );
*
* @param {Object} [options] Configuration options.
* @param {module:ui/view~View} options.view The view which DOM `submit` events should be handled.
*/
function submitHandler( { view } ) {
view.listenTo( view.element, 'submit', ( evt, domEvt ) => {
domEvt.preventDefault();
view.fire( 'submit' );
}, { useCapture: true } );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/src/button/buttonview.js":
/*!**********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/src/button/buttonview.js ***!
\**********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ButtonView)
/* harmony export */ });
/* harmony import */ var _view__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../view */ "./node_modules/@ckeditor/ckeditor5-ui/src/view.js");
/* harmony import */ var _icon_iconview__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../icon/iconview */ "./node_modules/@ckeditor/ckeditor5-ui/src/icon/iconview.js");
/* harmony import */ var _tooltip_tooltipview__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../tooltip/tooltipview */ "./node_modules/@ckeditor/ckeditor5-ui/src/tooltip/tooltipview.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_uid__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/uid */ "./node_modules/@ckeditor/ckeditor5-utils/src/uid.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_keyboard__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/keyboard */ "./node_modules/@ckeditor/ckeditor5-utils/src/keyboard.js");
/* harmony import */ var _theme_components_button_button_css__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../../theme/components/button/button.css */ "./node_modules/@ckeditor/ckeditor5-ui/theme/components/button/button.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module ui/button/buttonview
*/
/**
* The button view class.
*
* const view = new ButtonView();
*
* view.set( {
* label: 'A button',
* keystroke: 'Ctrl+B',
* tooltip: true,
* withText: true
* } );
*
* view.render();
*
* document.body.append( view.element );
*
* @extends module:ui/view~View
* @implements module:ui/button/button~Button
*/
class ButtonView extends _view__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* @inheritDoc
*/
constructor( locale ) {
super( locale );
const bind = this.bindTemplate;
const ariaLabelUid = (0,_ckeditor_ckeditor5_utils_src_uid__WEBPACK_IMPORTED_MODULE_3__["default"])();
// Implement the Button interface.
this.set( 'class' );
this.set( 'labelStyle' );
this.set( 'icon' );
this.set( 'isEnabled', true );
this.set( 'isOn', false );
this.set( 'isVisible', true );
this.set( 'isToggleable', false );
this.set( 'keystroke' );
this.set( 'label' );
this.set( 'tabindex', -1 );
this.set( 'tooltip' );
this.set( 'tooltipPosition', 's' );
this.set( 'type', 'button' );
this.set( 'withText', false );
this.set( 'withKeystroke', false );
/**
* Collection of the child views inside of the button {@link #element}.
*
* @readonly
* @member {module:ui/viewcollection~ViewCollection}
*/
this.children = this.createCollection();
/**
* Tooltip of the button view. It is configurable using the {@link #tooltip tooltip attribute}.
*
* @readonly
* @member {module:ui/tooltip/tooltipview~TooltipView} #tooltipView
*/
this.tooltipView = this._createTooltipView();
/**
* Label of the button view. It is configurable using the {@link #label label attribute}.
*
* @readonly
* @member {module:ui/view~View} #labelView
*/
this.labelView = this._createLabelView( ariaLabelUid );
/**
* The icon view of the button. Will be added to {@link #children} when the
* {@link #icon icon attribute} is defined.
*
* @readonly
* @member {module:ui/icon/iconview~IconView} #iconView
*/
this.iconView = new _icon_iconview__WEBPACK_IMPORTED_MODULE_1__["default"]();
this.iconView.extendTemplate( {
attributes: {
class: 'ck-button__icon'
}
} );
/**
* A view displaying the keystroke of the button next to the {@link #labelView label}.
* Added to {@link #children} when the {@link #withKeystroke `withKeystroke` attribute}
* is defined.
*
* @readonly
* @member {module:ui/view/view~View} #keystrokeView
*/
this.keystrokeView = this._createKeystrokeView();
/**
* Tooltip of the button bound to the template.
*
* @see #tooltip
* @see #_getTooltipString
* @private
* @observable
* @member {Boolean} #_tooltipString
*/
this.bind( '_tooltipString' ).to(
this, 'tooltip',
this, 'label',
this, 'keystroke',
this._getTooltipString.bind( this )
);
this.setTemplate( {
tag: 'button',
attributes: {
class: [
'ck',
'ck-button',
bind.to( 'class' ),
bind.if( 'isEnabled', 'ck-disabled', value => !value ),
bind.if( 'isVisible', 'ck-hidden', value => !value ),
bind.to( 'isOn', value => value ? 'ck-on' : 'ck-off' ),
bind.if( 'withText', 'ck-button_with-text' ),
bind.if( 'withKeystroke', 'ck-button_with-keystroke' )
],
type: bind.to( 'type', value => value ? value : 'button' ),
tabindex: bind.to( 'tabindex' ),
'aria-labelledby': `ck-editor__aria-label_${ ariaLabelUid }`,
'aria-disabled': bind.if( 'isEnabled', true, value => !value ),
'aria-pressed': bind.to( 'isOn', value => this.isToggleable ? String( value ) : false )
},
children: this.children,
on: {
mousedown: bind.to( evt => {
evt.preventDefault();
} ),
click: bind.to( evt => {
// We can't make the button disabled using the disabled attribute, because it won't be focusable.
// Though, shouldn't this condition be moved to the button controller?
if ( this.isEnabled ) {
this.fire( 'execute' );
} else {
// Prevent the default when button is disabled, to block e.g.
// automatic form submitting. See ckeditor/ckeditor5-link#74.
evt.preventDefault();
}
} )
}
} );
}
/**
* @inheritDoc
*/
render() {
super.render();
if ( this.icon ) {
this.iconView.bind( 'content' ).to( this, 'icon' );
this.children.add( this.iconView );
}
this.children.add( this.tooltipView );
this.children.add( this.labelView );
if ( this.withKeystroke && this.keystroke ) {
this.children.add( this.keystrokeView );
}
}
/**
* Focuses the {@link #element} of the button.
*/
focus() {
this.element.focus();
}
/**
* Creates a {@link module:ui/tooltip/tooltipview~TooltipView} instance and binds it with button
* attributes.
*
* @private
* @returns {module:ui/tooltip/tooltipview~TooltipView}
*/
_createTooltipView() {
const tooltipView = new _tooltip_tooltipview__WEBPACK_IMPORTED_MODULE_2__["default"]();
tooltipView.bind( 'text' ).to( this, '_tooltipString' );
tooltipView.bind( 'position' ).to( this, 'tooltipPosition' );
return tooltipView;
}
/**
* Creates a label view instance and binds it with button attributes.
*
* @private
* @param {String} ariaLabelUid The aria label UID.
* @returns {module:ui/view~View}
*/
_createLabelView( ariaLabelUid ) {
const labelView = new _view__WEBPACK_IMPORTED_MODULE_0__["default"]();
const bind = this.bindTemplate;
labelView.setTemplate( {
tag: 'span',
attributes: {
class: [
'ck',
'ck-button__label'
],
style: bind.to( 'labelStyle' ),
id: `ck-editor__aria-label_${ ariaLabelUid }`
},
children: [
{
text: this.bindTemplate.to( 'label' )
}
]
} );
return labelView;
}
/**
* Creates a view that displays a keystroke next to a {@link #labelView label }
* and binds it with button attributes.
*
* @private
* @returns {module:ui/view~View}
*/
_createKeystrokeView() {
const keystrokeView = new _view__WEBPACK_IMPORTED_MODULE_0__["default"]();
keystrokeView.setTemplate( {
tag: 'span',
attributes: {
class: [
'ck',
'ck-button__keystroke'
]
},
children: [
{
text: this.bindTemplate.to( 'keystroke', text => (0,_ckeditor_ckeditor5_utils_src_keyboard__WEBPACK_IMPORTED_MODULE_4__.getEnvKeystrokeText)( text ) )
}
]
} );
return keystrokeView;
}
/**
* Gets the text for the {@link #tooltipView} from the combination of
* {@link #tooltip}, {@link #label} and {@link #keystroke} attributes.
*
* @private
* @see #tooltip
* @see #_tooltipString
* @param {Boolean|String|Function} tooltip Button tooltip.
* @param {String} label Button label.
* @param {String} keystroke Button keystroke.
* @returns {String}
*/
_getTooltipString( tooltip, label, keystroke ) {
if ( tooltip ) {
if ( typeof tooltip == 'string' ) {
return tooltip;
} else {
if ( keystroke ) {
keystroke = (0,_ckeditor_ckeditor5_utils_src_keyboard__WEBPACK_IMPORTED_MODULE_4__.getEnvKeystrokeText)( keystroke );
}
if ( tooltip instanceof Function ) {
return tooltip( label, keystroke );
} else {
return `${ label }${ keystroke ? ` (${ keystroke })` : '' }`;
}
}
}
return '';
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/src/button/switchbuttonview.js":
/*!****************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/src/button/switchbuttonview.js ***!
\****************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ SwitchButtonView)
/* harmony export */ });
/* harmony import */ var _view__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../view */ "./node_modules/@ckeditor/ckeditor5-ui/src/view.js");
/* harmony import */ var _buttonview__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./buttonview */ "./node_modules/@ckeditor/ckeditor5-ui/src/button/buttonview.js");
/* harmony import */ var _theme_components_button_switchbutton_css__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../theme/components/button/switchbutton.css */ "./node_modules/@ckeditor/ckeditor5-ui/theme/components/button/switchbutton.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module ui/button/switchbuttonview
*/
/**
* The switch button view class.
*
* const view = new SwitchButtonView();
*
* view.set( {
* withText: true,
* label: 'Switch me!'
* } );
*
* view.render();
*
* document.body.append( view.element );
*
* @extends module:ui/button/buttonview~ButtonView
*/
class SwitchButtonView extends _buttonview__WEBPACK_IMPORTED_MODULE_1__["default"] {
/**
* @inheritDoc
*/
constructor( locale ) {
super( locale );
this.isToggleable = true;
/**
* The toggle switch of the button.
*
* @readonly
* @member {module:ui/view~View} #toggleSwitchView
*/
this.toggleSwitchView = this._createToggleView();
this.extendTemplate( {
attributes: {
class: 'ck-switchbutton'
}
} );
}
/**
* @inheritDoc
*/
render() {
super.render();
this.children.add( this.toggleSwitchView );
}
/**
* Creates a toggle child view.
*
* @private
* @returns {module:ui/view~View}
*/
_createToggleView() {
const toggleSwitchView = new _view__WEBPACK_IMPORTED_MODULE_0__["default"]();
toggleSwitchView.setTemplate( {
tag: 'span',
attributes: {
class: [
'ck',
'ck-button__toggle'
]
},
children: [
{
tag: 'span',
attributes: {
class: [
'ck',
'ck-button__toggle__inner'
]
}
}
]
} );
return toggleSwitchView;
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/src/colorgrid/colorgridview.js":
/*!****************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/src/colorgrid/colorgridview.js ***!
\****************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ColorGridView)
/* harmony export */ });
/* harmony import */ var _view__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../view */ "./node_modules/@ckeditor/ckeditor5-ui/src/view.js");
/* harmony import */ var _colortileview__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./colortileview */ "./node_modules/@ckeditor/ckeditor5-ui/src/colorgrid/colortileview.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_focustracker__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/focustracker */ "./node_modules/@ckeditor/ckeditor5-utils/src/focustracker.js");
/* harmony import */ var _focuscycler__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../focuscycler */ "./node_modules/@ckeditor/ckeditor5-ui/src/focuscycler.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_keystrokehandler__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/keystrokehandler */ "./node_modules/@ckeditor/ckeditor5-utils/src/keystrokehandler.js");
/* harmony import */ var _theme_components_colorgrid_colorgrid_css__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../../theme/components/colorgrid/colorgrid.css */ "./node_modules/@ckeditor/ckeditor5-ui/theme/components/colorgrid/colorgrid.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module ui/colorgrid/colorgrid
*/
/**
* A grid of {@link module:ui/colorgrid/colortile~ColorTileView color tiles}.
*
* @extends module:ui/view~View
*/
class ColorGridView extends _view__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* Creates an instance of a color grid containing {@link module:ui/colorgrid/colortile~ColorTileView tiles}.
*
* @param {module:utils/locale~Locale} [locale] The localization services instance.
* @param {Object} options Component configuration
* @param {Array.<module:ui/colorgrid/colorgrid~ColorDefinition>} [options.colorDefinitions] Array with definitions
* required to create the {@link module:ui/colorgrid/colortile~ColorTileView tiles}.
* @param {Number} options.columns A number of columns to display the tiles.
*/
constructor( locale, options ) {
super( locale );
const colorDefinitions = options && options.colorDefinitions || [];
const viewStyleAttribute = {};
if ( options && options.columns ) {
viewStyleAttribute.gridTemplateColumns = `repeat( ${ options.columns }, 1fr)`;
}
/**
* The color of the currently selected color tile in {@link #items}.
*
* @observable
* @type {String}
*/
this.set( 'selectedColor' );
/**
* Collection of the child tile views.
*
* @readonly
* @member {module:ui/viewcollection~ViewCollection}
*/
this.items = this.createCollection();
/**
* Tracks information about DOM focus in the grid.
*
* @readonly
* @member {module:utils/focustracker~FocusTracker}
*/
this.focusTracker = new _ckeditor_ckeditor5_utils_src_focustracker__WEBPACK_IMPORTED_MODULE_2__["default"]();
/**
* Instance of the {@link module:utils/keystrokehandler~KeystrokeHandler}.
*
* @readonly
* @member {module:utils/keystrokehandler~KeystrokeHandler}
*/
this.keystrokes = new _ckeditor_ckeditor5_utils_src_keystrokehandler__WEBPACK_IMPORTED_MODULE_4__["default"]();
/**
* Helps cycling over focusable {@link #items} in the grid.
*
* @readonly
* @protected
* @member {module:ui/focuscycler~FocusCycler}
*/
this._focusCycler = new _focuscycler__WEBPACK_IMPORTED_MODULE_3__["default"]( {
focusables: this.items,
focusTracker: this.focusTracker,
keystrokeHandler: this.keystrokes,
actions: {
// Navigate grid items backwards using the arrowup key.
focusPrevious: 'arrowleft',
// Navigate grid items forwards using the arrowdown key.
focusNext: 'arrowright'
}
} );
this.items.on( 'add', ( evt, colorTile ) => {
colorTile.isOn = colorTile.color === this.selectedColor;
} );
colorDefinitions.forEach( color => {
const colorTile = new _colortileview__WEBPACK_IMPORTED_MODULE_1__["default"]();
colorTile.set( {
color: color.color,
label: color.label,
tooltip: true,
hasBorder: color.options.hasBorder
} );
colorTile.on( 'execute', () => {
this.fire( 'execute', {
value: color.color,
hasBorder: color.options.hasBorder,
label: color.label
} );
} );
this.items.add( colorTile );
} );
this.setTemplate( {
tag: 'div',
children: this.items,
attributes: {
class: [
'ck',
'ck-color-grid'
],
style: viewStyleAttribute
}
} );
this.on( 'change:selectedColor', ( evt, name, selectedColor ) => {
for ( const item of this.items ) {
item.isOn = item.color === selectedColor;
}
} );
}
/**
* Focuses the first focusable in {@link #items}.
*/
focus() {
if ( this.items.length ) {
this.items.first.focus();
}
}
/**
* Focuses the last focusable in {@link #items}.
*/
focusLast() {
if ( this.items.length ) {
this.items.last.focus();
}
}
/**
* @inheritDoc
*/
render() {
super.render();
// Items added before rendering should be known to the #focusTracker.
for ( const item of this.items ) {
this.focusTracker.add( item.element );
}
this.items.on( 'add', ( evt, item ) => {
this.focusTracker.add( item.element );
} );
this.items.on( 'remove', ( evt, item ) => {
this.focusTracker.remove( item.element );
} );
// Start listening for the keystrokes coming from #element.
this.keystrokes.listenTo( this.element );
}
/**
* @inheritDoc
*/
destroy() {
super.destroy();
this.focusTracker.destroy();
this.keystrokes.destroy();
}
/**
* Fired when the `ColorTileView` for the picked item is executed.
*
* @event execute
* @param {Object} data Additional information about the event.
* @param {String} data.value The value of the selected color
* ({@link module:ui/colorgrid/colorgrid~ColorDefinition#color `color.color`}).
* @param {Boolean} data.hasBorder The `hasBorder` property of the selected color
* ({@link module:ui/colorgrid/colorgrid~ColorDefinition#options `color.options.hasBorder`}).
* @param {String} data.Label The label of the selected color
* ({@link module:ui/colorgrid/colorgrid~ColorDefinition#label `color.label`})
*/
}
/**
* A color definition used to create a {@link module:ui/colorgrid/colortile~ColorTileView}.
*
* {
* color: 'hsl(0, 0%, 75%)',
* label: 'Light Grey',
* options: {
* hasBorder: true
* }
* }
*
* @typedef {Object} module:ui/colorgrid/colorgrid~ColorDefinition
* @type Object
*
* @property {String} color String representing a color.
* It is used as value of background-color style in {@link module:ui/colorgrid/colortile~ColorTileView}.
* @property {String} label String used as label for {@link module:ui/colorgrid/colortile~ColorTileView}.
* @property {Object} options Additional options passed to create a {@link module:ui/colorgrid/colortile~ColorTileView}.
* @property {Boolean} options.hasBorder A flag that indicates if special a CSS class should be added
* to {@link module:ui/colorgrid/colortile~ColorTileView}, which renders a border around it.
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/src/colorgrid/colortileview.js":
/*!****************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/src/colorgrid/colortileview.js ***!
\****************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ColorTileView)
/* harmony export */ });
/* harmony import */ var _button_buttonview__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../button/buttonview */ "./node_modules/@ckeditor/ckeditor5-ui/src/button/buttonview.js");
/* harmony import */ var _theme_icons_color_tile_check_svg__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../../theme/icons/color-tile-check.svg */ "./node_modules/@ckeditor/ckeditor5-ui/theme/icons/color-tile-check.svg");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module ui/colorgrid/colortile
*/
/**
* This class represents a single color tile in the {@link module:ui/colorgrid/colorgrid~ColorGridView}.
*
* @extends module:ui/button/buttonview~ButtonView
*/
class ColorTileView extends _button_buttonview__WEBPACK_IMPORTED_MODULE_0__["default"] {
constructor( locale ) {
super( locale );
const bind = this.bindTemplate;
/**
* String representing a color shown as tile's background.
*
* @type {String}
*/
this.set( 'color' );
/**
* A flag that toggles a special CSS class responsible for displaying
* a border around the button.
*
* @type {Boolean}
*/
this.set( 'hasBorder' );
this.icon = _theme_icons_color_tile_check_svg__WEBPACK_IMPORTED_MODULE_1__["default"];
this.extendTemplate( {
attributes: {
style: {
backgroundColor: bind.to( 'color' )
},
class: [
'ck',
'ck-color-grid__tile',
bind.if( 'hasBorder', 'ck-color-table__color-tile_bordered' )
]
}
} );
}
/**
* @inheritDoc
*/
render() {
super.render();
this.iconView.fillColor = 'hsl(0, 0%, 100%)';
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/src/colorgrid/utils.js":
/*!********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/src/colorgrid/utils.js ***!
\********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "getLocalizedColorOptions": () => (/* binding */ getLocalizedColorOptions),
/* harmony export */ "normalizeColorOptions": () => (/* binding */ normalizeColorOptions),
/* harmony export */ "normalizeSingleColorDefinition": () => (/* binding */ normalizeSingleColorDefinition)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module ui/colorgrid/utils
*/
/**
* Returns color configuration options as defined in `editor.config.(fontColor|fontBackgroundColor).colors` or
* `editor.config.table.(tableProperties|tableCellProperties).(background|border).colors
* but processed to account for editor localization in the correct language.
*
* Note: The reason behind this method is that there is no way to use {@link module:utils/locale~Locale#t}
* when the user configuration is defined because the editor does not exist yet.
*
* @param {module:utils/locale~Locale} locale The {@link module:core/editor/editor~Editor#locale} instance.
* @param {Array.<module:ui/colorgrid/colorgrid~ColorDefinition>} options
* @returns {Array.<module:ui/colorgrid/colorgrid~ColorDefinition>}.
*/
function getLocalizedColorOptions( locale, options ) {
const t = locale.t;
const localizedColorNames = {
Black: t( 'Black' ),
'Dim grey': t( 'Dim grey' ),
Grey: t( 'Grey' ),
'Light grey': t( 'Light grey' ),
White: t( 'White' ),
Red: t( 'Red' ),
Orange: t( 'Orange' ),
Yellow: t( 'Yellow' ),
'Light green': t( 'Light green' ),
Green: t( 'Green' ),
Aquamarine: t( 'Aquamarine' ),
Turquoise: t( 'Turquoise' ),
'Light blue': t( 'Light blue' ),
Blue: t( 'Blue' ),
Purple: t( 'Purple' )
};
return options.map( colorOption => {
const label = localizedColorNames[ colorOption.label ];
if ( label && label != colorOption.label ) {
colorOption.label = label;
}
return colorOption;
} );
}
/**
* Creates a unified color definition object from color configuration options.
* The object contains the information necessary to both render the UI and initialize the conversion.
*
* @param {module:ui/colorgrid/colorgrid~ColorDefinition} options
* @returns {Array.<module:ui/colorgrid/colorgrid~ColorDefinition>}
*/
function normalizeColorOptions( options ) {
return options
.map( normalizeSingleColorDefinition )
.filter( option => !!option );
}
// Creates a normalized color definition from the user-defined configuration.
// The "normalization" means it will create full
// {@link module:ui/colorgrid/colorgrid~ColorDefinition `ColorDefinition-like`}
// object for string values, and add a `view` property, for each definition.
//
// @param {String|module:ui/colorgrid/colorgrid~ColorDefinition}
// @returns {module:ui/colorgrid/colorgrid~ColorDefinition}
function normalizeSingleColorDefinition( color ) {
if ( typeof color === 'string' ) {
return {
model: color,
label: color,
hasBorder: false,
view: {
name: 'span',
styles: {
color
}
}
};
} else {
return {
model: color.color,
label: color.label || color.color,
hasBorder: color.hasBorder === undefined ? false : color.hasBorder,
view: {
name: 'span',
styles: {
color: `${ color.color }`
}
}
};
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/src/componentfactory.js":
/*!*********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/src/componentfactory.js ***!
\*********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ComponentFactory)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module ui/componentfactory
*/
/**
* A helper class implementing the UI component ({@link module:ui/view~View view}) factory.
*
* It allows functions producing specific UI components to be registered under their unique names
* in the factory. A registered component can be then instantiated by providing its name.
* Note that names are case insensitive.
*
* // The editor provides localization tools for the factory.
* const factory = new ComponentFactory( editor );
*
* factory.add( 'foo', locale => new FooView( locale ) );
* factory.add( 'bar', locale => new BarView( locale ) );
*
* // An instance of FooView.
* const fooInstance = factory.create( 'foo' );
*
* // Names are case insensitive so this is also allowed:
* const barInstance = factory.create( 'Bar' );
*
* The {@link module:core/editor/editor~Editor#locale editor locale} is passed to the factory
* function when {@link module:ui/componentfactory~ComponentFactory#create} is called.
*/
class ComponentFactory {
/**
* Creates an instance of the factory.
*
* @constructor
* @param {module:core/editor/editor~Editor} editor The editor instance.
*/
constructor( editor ) {
/**
* The editor instance that the factory belongs to.
*
* @readonly
* @member {module:core/editor/editor~Editor}
*/
this.editor = editor;
/**
* Registered component factories.
*
* @private
* @member {Map}
*/
this._components = new Map();
}
/**
* Returns an iterator of registered component names. Names are returned in lower case.
*
* @returns {Iterable.<String>}
*/
* names() {
for ( const value of this._components.values() ) {
yield value.originalName;
}
}
/**
* Registers a component factory function that will be used by the
* {@link #create create} method and called with the
* {@link module:core/editor/editor~Editor#locale editor locale} as an argument,
* allowing localization of the {@link module:ui/view~View view}.
*
* @param {String} name The name of the component.
* @param {Function} callback The callback that returns the component.
*/
add( name, callback ) {
this._components.set( getNormalized( name ), { callback, originalName: name } );
}
/**
* Creates an instance of a component registered in the factory under a specific name.
*
* When called, the {@link module:core/editor/editor~Editor#locale editor locale} is passed to
* the previously {@link #add added} factory function, allowing localization of the
* {@link module:ui/view~View view}.
*
* @param {String} name The name of the component.
* @returns {module:ui/view~View} The instantiated component view.
*/
create( name ) {
if ( !this.has( name ) ) {
/**
* The required component is not registered in the component factory. Please make sure
* the provided name is correct and the component has been correctly
* {@link #add added} to the factory.
*
* @error componentfactory-item-missing
* @param {String} name The name of the missing component.
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_0__["default"](
'componentfactory-item-missing',
this,
{ name }
);
}
return this._components.get( getNormalized( name ) ).callback( this.editor.locale );
}
/**
* Checks if a component of a given name is registered in the factory.
*
* @param {String} name The name of the component.
* @returns {Boolean}
*/
has( name ) {
return this._components.has( getNormalized( name ) );
}
}
//
// Ensures that the component name used as the key in the internal map is in lower case.
//
// @private
// @param {String} name
// @returns {String}
function getNormalized( name ) {
return String( name ).toLowerCase();
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/src/dropdown/button/dropdownbuttonview.js":
/*!***************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/src/dropdown/button/dropdownbuttonview.js ***!
\***************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ DropdownButtonView)
/* harmony export */ });
/* harmony import */ var _button_buttonview__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../../button/buttonview */ "./node_modules/@ckeditor/ckeditor5-ui/src/button/buttonview.js");
/* harmony import */ var _theme_icons_dropdown_arrow_svg__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../../../theme/icons/dropdown-arrow.svg */ "./node_modules/@ckeditor/ckeditor5-ui/theme/icons/dropdown-arrow.svg");
/* harmony import */ var _icon_iconview__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../icon/iconview */ "./node_modules/@ckeditor/ckeditor5-ui/src/icon/iconview.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module ui/dropdown/button/dropdownbuttonview
*/
/**
* The default dropdown button view class.
*
* const view = new DropdownButtonView();
*
* view.set( {
* label: 'A button',
* keystroke: 'Ctrl+B',
* tooltip: true
* } );
*
* view.render();
*
* document.body.append( view.element );
*
* Also see the {@link module:ui/dropdown/utils~createDropdown `createDropdown()` util}.
*
* @implements module:ui/dropdown/button/dropdownbutton~DropdownButton
* @extends module:ui/button/buttonview~ButtonView
*/
class DropdownButtonView extends _button_buttonview__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* @inheritDoc
*/
constructor( locale ) {
super( locale );
/**
* An icon that displays arrow to indicate a dropdown button.
*
* @readonly
* @member {module:ui/icon/iconview~IconView}
*/
this.arrowView = this._createArrowView();
this.extendTemplate( {
attributes: {
'aria-haspopup': true
}
} );
// The DropdownButton interface expects the open event upon which will open the dropdown.
this.delegate( 'execute' ).to( this, 'open' );
}
/**
* @inheritDoc
*/
render() {
super.render();
this.children.add( this.arrowView );
}
/**
* Creates a {@link module:ui/icon/iconview~IconView} instance as {@link #arrowView}.
*
* @private
* @returns {module:ui/icon/iconview~IconView}
*/
_createArrowView() {
const arrowView = new _icon_iconview__WEBPACK_IMPORTED_MODULE_2__["default"]();
arrowView.content = _theme_icons_dropdown_arrow_svg__WEBPACK_IMPORTED_MODULE_1__["default"];
arrowView.extendTemplate( {
attributes: {
class: 'ck-dropdown__arrow'
}
} );
return arrowView;
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/src/dropdown/button/splitbuttonview.js":
/*!************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/src/dropdown/button/splitbuttonview.js ***!
\************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ SplitButtonView)
/* harmony export */ });
/* harmony import */ var _view__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../../view */ "./node_modules/@ckeditor/ckeditor5-ui/src/view.js");
/* harmony import */ var _button_buttonview__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../../button/buttonview */ "./node_modules/@ckeditor/ckeditor5-ui/src/button/buttonview.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_keystrokehandler__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/keystrokehandler */ "./node_modules/@ckeditor/ckeditor5-utils/src/keystrokehandler.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_focustracker__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/focustracker */ "./node_modules/@ckeditor/ckeditor5-utils/src/focustracker.js");
/* harmony import */ var _theme_icons_dropdown_arrow_svg__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../../../theme/icons/dropdown-arrow.svg */ "./node_modules/@ckeditor/ckeditor5-ui/theme/icons/dropdown-arrow.svg");
/* harmony import */ var _theme_components_dropdown_splitbutton_css__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../../../theme/components/dropdown/splitbutton.css */ "./node_modules/@ckeditor/ckeditor5-ui/theme/components/dropdown/splitbutton.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module ui/dropdown/button/splitbuttonview
*/
/**
* The split button view class.
*
* const view = new SplitButtonView();
*
* view.set( {
* label: 'A button',
* keystroke: 'Ctrl+B',
* tooltip: true
* } );
*
* view.render();
*
* document.body.append( view.element );
*
* Also see the {@link module:ui/dropdown/utils~createDropdown `createDropdown()` util}.
*
* @implements module:ui/dropdown/button/dropdownbutton~DropdownButton
* @extends module:ui/view~View
*/
class SplitButtonView extends _view__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* @inheritDoc
*/
constructor( locale ) {
super( locale );
const bind = this.bindTemplate;
// Implement the Button interface.
this.set( 'class' );
this.set( 'icon' );
this.set( 'isEnabled', true );
this.set( 'isOn', false );
this.set( 'isToggleable', false );
this.set( 'isVisible', true );
this.set( 'keystroke' );
this.set( 'label' );
this.set( 'tabindex', -1 );
this.set( 'tooltip' );
this.set( 'tooltipPosition', 's' );
this.set( 'type', 'button' );
this.set( 'withText', false );
/**
* Collection of the child views inside of the split button {@link #element}.
*
* @readonly
* @member {module:ui/viewcollection~ViewCollection}
*/
this.children = this.createCollection();
/**
* A main button of split button.
*
* @readonly
* @member {module:ui/button/buttonview~ButtonView}
*/
this.actionView = this._createActionView();
/**
* A secondary button of split button that opens dropdown.
*
* @readonly
* @member {module:ui/button/buttonview~ButtonView}
*/
this.arrowView = this._createArrowView();
/**
* Instance of the {@link module:utils/keystrokehandler~KeystrokeHandler}. It manages
* keystrokes of the split button:
*
* * <kbd>▶</kbd> moves focus to arrow view when action view is focused,
* * <kbd>◀</kbd> moves focus to action view when arrow view is focused.
*
* @readonly
* @member {module:utils/keystrokehandler~KeystrokeHandler}
*/
this.keystrokes = new _ckeditor_ckeditor5_utils_src_keystrokehandler__WEBPACK_IMPORTED_MODULE_2__["default"]();
/**
* Tracks information about DOM focus in the dropdown.
*
* @readonly
* @member {module:utils/focustracker~FocusTracker}
*/
this.focusTracker = new _ckeditor_ckeditor5_utils_src_focustracker__WEBPACK_IMPORTED_MODULE_3__["default"]();
this.setTemplate( {
tag: 'div',
attributes: {
class: [
'ck',
'ck-splitbutton',
bind.to( 'class' ),
bind.if( 'isVisible', 'ck-hidden', value => !value ),
this.arrowView.bindTemplate.if( 'isOn', 'ck-splitbutton_open' )
]
},
children: this.children
} );
}
/**
* @inheritDoc
*/
render() {
super.render();
this.children.add( this.actionView );
this.children.add( this.arrowView );
this.focusTracker.add( this.actionView.element );
this.focusTracker.add( this.arrowView.element );
this.keystrokes.listenTo( this.element );
// Overrides toolbar focus cycling behavior.
this.keystrokes.set( 'arrowright', ( evt, cancel ) => {
if ( this.focusTracker.focusedElement === this.actionView.element ) {
this.arrowView.focus();
cancel();
}
} );
// Overrides toolbar focus cycling behavior.
this.keystrokes.set( 'arrowleft', ( evt, cancel ) => {
if ( this.focusTracker.focusedElement === this.arrowView.element ) {
this.actionView.focus();
cancel();
}
} );
}
/**
* @inheritDoc
*/
destroy() {
super.destroy();
this.focusTracker.destroy();
this.keystrokes.destroy();
}
/**
* Focuses the {@link #actionView#element} of the action part of split button.
*/
focus() {
this.actionView.focus();
}
/**
* Creates a {@link module:ui/button/buttonview~ButtonView} instance as {@link #actionView} and binds it with main split button
* attributes.
*
* @private
* @returns {module:ui/button/buttonview~ButtonView}
*/
_createActionView() {
const actionView = new _button_buttonview__WEBPACK_IMPORTED_MODULE_1__["default"]();
actionView.bind(
'icon',
'isEnabled',
'isOn',
'isToggleable',
'keystroke',
'label',
'tabindex',
'tooltip',
'tooltipPosition',
'type',
'withText'
).to( this );
actionView.extendTemplate( {
attributes: {
class: 'ck-splitbutton__action'
}
} );
actionView.delegate( 'execute' ).to( this );
return actionView;
}
/**
* Creates a {@link module:ui/button/buttonview~ButtonView} instance as {@link #arrowView} and binds it with main split button
* attributes.
*
* @private
* @returns {module:ui/button/buttonview~ButtonView}
*/
_createArrowView() {
const arrowView = new _button_buttonview__WEBPACK_IMPORTED_MODULE_1__["default"]();
const bind = arrowView.bindTemplate;
arrowView.icon = _theme_icons_dropdown_arrow_svg__WEBPACK_IMPORTED_MODULE_4__["default"];
arrowView.extendTemplate( {
attributes: {
class: 'ck-splitbutton__arrow',
'aria-haspopup': true,
'aria-expanded': bind.to( 'isOn', value => String( value ) )
}
} );
arrowView.bind( 'isEnabled' ).to( this );
arrowView.delegate( 'execute' ).to( this, 'open' );
return arrowView;
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/src/dropdown/dropdownpanelview.js":
/*!*******************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/src/dropdown/dropdownpanelview.js ***!
\*******************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ DropdownPanelView)
/* harmony export */ });
/* harmony import */ var _view__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../view */ "./node_modules/@ckeditor/ckeditor5-ui/src/view.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module ui/dropdown/dropdownpanelview
*/
/**
* The dropdown panel view class.
*
* See {@link module:ui/dropdown/dropdownview~DropdownView} to learn about the common usage.
*
* @extends module:ui/view~View
*/
class DropdownPanelView extends _view__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* @inheritDoc
*/
constructor( locale ) {
super( locale );
const bind = this.bindTemplate;
/**
* Controls whether the panel is visible.
*
* @observable
* @member {Boolean} #isVisible
*/
this.set( 'isVisible', false );
/**
* The position of the panel, relative to the parent.
*
* This property is reflected in the CSS class set to {@link #element} that controls
* the position of the panel.
*
* @observable
* @default 'se'
* @member {'s'|'se'|'sw'|'sme'|'smw'|'n'|'ne'|'nw'|'nme'|'nmw'} #position
*/
this.set( 'position', 'se' );
/**
* Collection of the child views in this panel.
*
* A common child type is the {@link module:ui/list/listview~ListView} and {@link module:ui/toolbar/toolbarview~ToolbarView}.
* See {@link module:ui/dropdown/utils~addListToDropdown} and
* {@link module:ui/dropdown/utils~addToolbarToDropdown} to learn more about child views of dropdowns.
*
* @readonly
* @member {module:ui/viewcollection~ViewCollection}
*/
this.children = this.createCollection();
this.setTemplate( {
tag: 'div',
attributes: {
class: [
'ck',
'ck-reset',
'ck-dropdown__panel',
bind.to( 'position', value => `ck-dropdown__panel_${ value }` ),
bind.if( 'isVisible', 'ck-dropdown__panel-visible' )
]
},
children: this.children,
on: {
// Drag and drop in the panel should not break the selection in the editor.
// https://github.com/ckeditor/ckeditor5-ui/issues/228
selectstart: bind.to( evt => evt.preventDefault() )
}
} );
}
/**
* Focuses the view element or first item in view collection on opening dropdown's panel.
*
* See also {@link module:ui/dropdown/dropdownpanelfocusable~DropdownPanelFocusable}.
*/
focus() {
if ( this.children.length ) {
this.children.first.focus();
}
}
/**
* Focuses the view element or last item in view collection on opening dropdown's panel.
*
* See also {@link module:ui/dropdown/dropdownpanelfocusable~DropdownPanelFocusable}.
*/
focusLast() {
if ( this.children.length ) {
const lastChild = this.children.last;
if ( typeof lastChild.focusLast === 'function' ) {
lastChild.focusLast();
} else {
lastChild.focus();
}
}
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/src/dropdown/dropdownview.js":
/*!**************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/src/dropdown/dropdownview.js ***!
\**************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ DropdownView)
/* harmony export */ });
/* harmony import */ var _view__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../view */ "./node_modules/@ckeditor/ckeditor5-ui/src/view.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_keystrokehandler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/keystrokehandler */ "./node_modules/@ckeditor/ckeditor5-utils/src/keystrokehandler.js");
/* harmony import */ var _theme_components_dropdown_dropdown_css__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../theme/components/dropdown/dropdown.css */ "./node_modules/@ckeditor/ckeditor5-ui/theme/components/dropdown/dropdown.css");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_dom_position__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/dom/position */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/position.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module ui/dropdown/dropdownview
*/
/**
* The dropdown view class. It manages the dropdown button and dropdown panel.
*
* In most cases, the easiest way to create a dropdown is by using the {@link module:ui/dropdown/utils~createDropdown}
* util:
*
* const dropdown = createDropdown( locale );
*
* // Configure dropdown's button properties:
* dropdown.buttonView.set( {
* label: 'A dropdown',
* withText: true
* } );
*
* dropdown.render();
*
* dropdown.panelView.element.textContent = 'Content of the panel';
*
* // Will render a dropdown with a panel containing a "Content of the panel" text.
* document.body.appendChild( dropdown.element );
*
* If you want to add a richer content to the dropdown panel, you can use the {@link module:ui/dropdown/utils~addListToDropdown}
* and {@link module:ui/dropdown/utils~addToolbarToDropdown} helpers. See more examples in
* {@link module:ui/dropdown/utils~createDropdown} documentation.
*
* If you want to create a completely custom dropdown, then you can compose it manually:
*
* const button = new DropdownButtonView( locale );
* const panel = new DropdownPanelView( locale );
* const dropdown = new DropdownView( locale, button, panel );
*
* button.set( {
* label: 'A dropdown',
* withText: true
* } );
*
* dropdown.render();
*
* panel.element.textContent = 'Content of the panel';
*
* // Will render a dropdown with a panel containing a "Content of the panel" text.
* document.body.appendChild( dropdown.element );
*
* However, dropdown created this way will contain little behavior. You will need to implement handlers for actions
* such as {@link module:ui/bindings/clickoutsidehandler~clickOutsideHandler clicking outside an open dropdown}
* (which should close it) and support for arrow keys inside the panel. Therefore, unless you really know what
* you do and you really need to do it, it is recommended to use the {@link module:ui/dropdown/utils~createDropdown} helper.
*
* @extends module:ui/view~View
*/
class DropdownView extends _view__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* Creates an instance of the dropdown.
*
* Also see {@link #render}.
*
* @param {module:utils/locale~Locale} [locale] The localization services instance.
* @param {module:ui/dropdown/button/dropdownbutton~DropdownButton} buttonView
* @param {module:ui/dropdown/dropdownpanelview~DropdownPanelView} panelView
*/
constructor( locale, buttonView, panelView ) {
super( locale );
const bind = this.bindTemplate;
/**
* Button of the dropdown view. Clicking the button opens the {@link #panelView}.
*
* @readonly
* @member {module:ui/button/buttonview~ButtonView} #buttonView
*/
this.buttonView = buttonView;
/**
* Panel of the dropdown. It opens when the {@link #buttonView} is
* {@link module:ui/button/buttonview~ButtonView#event:execute executed} (i.e. clicked).
*
* Child views can be added to the panel's `children` collection:
*
* dropdown.panelView.children.add( childView );
*
* See {@link module:ui/dropdown/dropdownpanelview~DropdownPanelView#children} and
* {@link module:ui/viewcollection~ViewCollection#add}.
*
* @readonly
* @member {module:ui/dropdown/dropdownpanelview~DropdownPanelView} #panelView
*/
this.panelView = panelView;
/**
* Controls whether the dropdown view is open, i.e. shows or hides the {@link #panelView panel}.
*
* @observable
* @member {Boolean} #isOpen
*/
this.set( 'isOpen', false );
/**
* Controls whether the dropdown is enabled, i.e. it can be clicked and execute an action.
*
* See {@link module:ui/button/buttonview~ButtonView#isEnabled}.
*
* @observable
* @member {Boolean} #isEnabled
*/
this.set( 'isEnabled', true );
/**
* (Optional) The additional CSS class set on the dropdown {@link #element}.
*
* @observable
* @member {String} #class
*/
this.set( 'class' );
/**
* (Optional) The `id` attribute of the dropdown (i.e. to pair with a `<label>` element).
*
* @observable
* @member {String} #id
*/
this.set( 'id' );
/**
* The position of the panel, relative to the dropdown.
*
* **Note**: When `'auto'`, the panel will use one of the remaining positions to stay
* in the viewport, visible to the user. The positions correspond directly to
* {@link module:ui/dropdown/dropdownview~DropdownView.defaultPanelPositions default panel positions}.
*
* **Note**: This value has an impact on the
* {@link module:ui/dropdown/dropdownpanelview~DropdownPanelView#position} property
* each time the panel becomes {@link #isOpen open}.
*
* @observable
* @default 'auto'
* @member {'auto'|'s'|'se'|'sw'|'sme'|'smw'|'n'|'ne'|'nw'|'nme'|'nmw'} #panelPosition
*/
this.set( 'panelPosition', 'auto' );
/**
* Instance of the {@link module:utils/keystrokehandler~KeystrokeHandler}. It manages
* keystrokes of the dropdown:
*
* * <kbd>▼</kbd> opens the dropdown,
* * <kbd>◀</kbd> and <kbd>Esc</kbd> closes the dropdown.
*
* @readonly
* @member {module:utils/keystrokehandler~KeystrokeHandler}
*/
this.keystrokes = new _ckeditor_ckeditor5_utils_src_keystrokehandler__WEBPACK_IMPORTED_MODULE_1__["default"]();
this.setTemplate( {
tag: 'div',
attributes: {
class: [
'ck',
'ck-dropdown',
bind.to( 'class' ),
bind.if( 'isEnabled', 'ck-disabled', value => !value )
],
id: bind.to( 'id' ),
'aria-describedby': bind.to( 'ariaDescribedById' )
},
children: [
buttonView,
panelView
]
} );
buttonView.extendTemplate( {
attributes: {
class: [
'ck-dropdown__button'
]
}
} );
/**
* A child {@link module:ui/list/listview~ListView list view} of the dropdown located
* in its {@link module:ui/dropdown/dropdownview~DropdownView#panelView panel}.
*
* **Note**: Only supported when dropdown has list view added using {@link module:ui/dropdown/utils~addListToDropdown}.
*
* @readonly
* @member {module:ui/list/listview~ListView} #listView
*/
/**
* A child toolbar of the dropdown located in the
* {@link module:ui/dropdown/dropdownview~DropdownView#panelView panel}.
*
* **Note**: Only supported when dropdown has list view added using {@link module:ui/dropdown/utils~addToolbarToDropdown}.
*
* @readonly
* @member {module:ui/toolbar/toolbarview~ToolbarView} #toolbarView
*/
/**
* Fired when the toolbar button or list item is executed.
*
* For {@link #listView} It fires when a child of some {@link module:ui/list/listitemview~ListItemView}
* fired `execute`.
*
* For {@link #toolbarView} It fires when one of the buttons has been
* {@link module:ui/button/buttonview~ButtonView#event:execute executed}.
*
* **Note**: Only supported when dropdown has list view added using {@link module:ui/dropdown/utils~addListToDropdown}
* or {@link module:ui/dropdown/utils~addToolbarToDropdown}.
*
* @event execute
*/
}
/**
* @inheritDoc
*/
render() {
super.render();
// Toggle the dropdown when its button has been clicked.
this.listenTo( this.buttonView, 'open', () => {
this.isOpen = !this.isOpen;
} );
// Toggle the visibility of the panel when the dropdown becomes open.
this.panelView.bind( 'isVisible' ).to( this, 'isOpen' );
// Let the dropdown control the position of the panel. The position must
// be updated every time the dropdown is open.
this.on( 'change:isOpen', () => {
if ( !this.isOpen ) {
return;
}
// If "auto", find the best position of the panel to fit into the viewport.
// Otherwise, simply assign the static position.
if ( this.panelPosition === 'auto' ) {
this.panelView.position = DropdownView._getOptimalPosition( {
element: this.panelView.element,
target: this.buttonView.element,
fitInViewport: true,
positions: this._panelPositions
} ).name;
} else {
this.panelView.position = this.panelPosition;
}
} );
// Listen for keystrokes coming from within #element.
this.keystrokes.listenTo( this.element );
const closeDropdown = ( data, cancel ) => {
if ( this.isOpen ) {
this.buttonView.focus();
this.isOpen = false;
cancel();
}
};
// Open the dropdown panel using the arrow down key, just like with return or space.
this.keystrokes.set( 'arrowdown', ( data, cancel ) => {
// Don't open if the dropdown is disabled or already open.
if ( this.buttonView.isEnabled && !this.isOpen ) {
this.isOpen = true;
cancel();
}
} );
// Block the right arrow key (until nested dropdowns are implemented).
this.keystrokes.set( 'arrowright', ( data, cancel ) => {
if ( this.isOpen ) {
cancel();
}
} );
// Close the dropdown using the arrow left/escape key.
this.keystrokes.set( 'arrowleft', closeDropdown );
this.keystrokes.set( 'esc', closeDropdown );
}
/**
* Focuses the {@link #buttonView}.
*/
focus() {
this.buttonView.focus();
}
/**
* Returns {@link #panelView panel} positions to be used by the
* {@link module:utils/dom/position~getOptimalPosition `getOptimalPosition()`}
* utility considering the direction of the language the UI of the editor is displayed in.
*
* @type {module:utils/dom/position~Options#positions}
* @private
*/
get _panelPositions() {
const {
south, north,
southEast, southWest,
northEast, northWest,
southMiddleEast, southMiddleWest,
northMiddleEast, northMiddleWest
} = DropdownView.defaultPanelPositions;
if ( this.locale.uiLanguageDirection !== 'rtl' ) {
return [
southEast, southWest, southMiddleEast, southMiddleWest, south,
northEast, northWest, northMiddleEast, northMiddleWest, north
];
} else {
return [
southWest, southEast, southMiddleWest, southMiddleEast, south,
northWest, northEast, northMiddleWest, northMiddleEast, north
];
}
}
}
/**
* A set of positioning functions used by the dropdown view to determine
* the optimal position (i.e. fitting into the browser viewport) of its
* {@link module:ui/dropdown/dropdownview~DropdownView#panelView panel} when
* {@link module:ui/dropdown/dropdownview~DropdownView#panelPosition} is set to 'auto'`.
*
* The available positioning functions are as follow:
*
* **South**
*
* * `south`
*
* [ Button ]
* +-----------------+
* | Panel |
* +-----------------+
*
* * `southEast`
*
* [ Button ]
* +-----------------+
* | Panel |
* +-----------------+
*
* * `southWest`
*
* [ Button ]
* +-----------------+
* | Panel |
* +-----------------+
*
* * `southMiddleEast`
*
* [ Button ]
* +-----------------+
* | Panel |
* +-----------------+
*
* * `southMiddleWest`
*
* [ Button ]
* +-----------------+
* | Panel |
* +-----------------+
*
* **North**
*
* * `north`
*
* +-----------------+
* | Panel |
* +-----------------+
* [ Button ]
*
* * `northEast`
*
* +-----------------+
* | Panel |
* +-----------------+
* [ Button ]
*
* * `northWest`
*
* +-----------------+
* | Panel |
* +-----------------+
* [ Button ]
*
* * `northMiddleEast`
*
* +-----------------+
* | Panel |
* +-----------------+
* [ Button ]
*
* * `northMiddleWest`
*
* +-----------------+
* | Panel |
* +-----------------+
* [ Button ]
*
* Positioning functions are compatible with {@link module:utils/dom/position~Position}.
*
* The name that position function returns will be reflected in dropdown panel's class that
* controls its placement. See {@link module:ui/dropdown/dropdownview~DropdownView#panelPosition}
* to learn more.
*
* @member {Object} module:ui/dropdown/dropdownview~DropdownView.defaultPanelPositions
*/
DropdownView.defaultPanelPositions = {
south: ( buttonRect, panelRect ) => {
return {
top: buttonRect.bottom,
left: buttonRect.left - ( panelRect.width - buttonRect.width ) / 2,
name: 's'
};
},
southEast: buttonRect => {
return {
top: buttonRect.bottom,
left: buttonRect.left,
name: 'se'
};
},
southWest: ( buttonRect, panelRect ) => {
return {
top: buttonRect.bottom,
left: buttonRect.left - panelRect.width + buttonRect.width,
name: 'sw'
};
},
southMiddleEast: ( buttonRect, panelRect ) => {
return {
top: buttonRect.bottom,
left: buttonRect.left - ( panelRect.width - buttonRect.width ) / 4,
name: 'sme'
};
},
southMiddleWest: ( buttonRect, panelRect ) => {
return {
top: buttonRect.bottom,
left: buttonRect.left - ( panelRect.width - buttonRect.width ) * 3 / 4,
name: 'smw'
};
},
north: ( buttonRect, panelRect ) => {
return {
top: buttonRect.top - panelRect.height,
left: buttonRect.left - ( panelRect.width - buttonRect.width ) / 2,
name: 'n'
};
},
northEast: ( buttonRect, panelRect ) => {
return {
top: buttonRect.top - panelRect.height,
left: buttonRect.left,
name: 'ne'
};
},
northWest: ( buttonRect, panelRect ) => {
return {
top: buttonRect.top - panelRect.height,
left: buttonRect.left - panelRect.width + buttonRect.width,
name: 'nw'
};
},
northMiddleEast: ( buttonRect, panelRect ) => {
return {
top: buttonRect.top - panelRect.height,
left: buttonRect.left - ( panelRect.width - buttonRect.width ) / 4,
name: 'nme'
};
},
northMiddleWest: ( buttonRect, panelRect ) => {
return {
top: buttonRect.top - panelRect.height,
left: buttonRect.left - ( panelRect.width - buttonRect.width ) * 3 / 4,
name: 'nmw'
};
}
};
/**
* A function used to calculate the optimal position for the dropdown panel.
*
* @protected
* @member {Function} module:ui/dropdown/dropdownview~DropdownView._getOptimalPosition
*/
DropdownView._getOptimalPosition = _ckeditor_ckeditor5_utils_src_dom_position__WEBPACK_IMPORTED_MODULE_3__.getOptimalPosition;
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/src/dropdown/utils.js":
/*!*******************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/src/dropdown/utils.js ***!
\*******************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "addListToDropdown": () => (/* binding */ addListToDropdown),
/* harmony export */ "addToolbarToDropdown": () => (/* binding */ addToolbarToDropdown),
/* harmony export */ "createDropdown": () => (/* binding */ createDropdown)
/* harmony export */ });
/* harmony import */ var _dropdownpanelview__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./dropdownpanelview */ "./node_modules/@ckeditor/ckeditor5-ui/src/dropdown/dropdownpanelview.js");
/* harmony import */ var _dropdownview__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./dropdownview */ "./node_modules/@ckeditor/ckeditor5-ui/src/dropdown/dropdownview.js");
/* harmony import */ var _button_dropdownbuttonview__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./button/dropdownbuttonview */ "./node_modules/@ckeditor/ckeditor5-ui/src/dropdown/button/dropdownbuttonview.js");
/* harmony import */ var _toolbar_toolbarview__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../toolbar/toolbarview */ "./node_modules/@ckeditor/ckeditor5-ui/src/toolbar/toolbarview.js");
/* harmony import */ var _list_listview__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../list/listview */ "./node_modules/@ckeditor/ckeditor5-ui/src/list/listview.js");
/* harmony import */ var _list_listitemview__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../list/listitemview */ "./node_modules/@ckeditor/ckeditor5-ui/src/list/listitemview.js");
/* harmony import */ var _list_listseparatorview__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ../list/listseparatorview */ "./node_modules/@ckeditor/ckeditor5-ui/src/list/listseparatorview.js");
/* harmony import */ var _button_buttonview__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ../button/buttonview */ "./node_modules/@ckeditor/ckeditor5-ui/src/button/buttonview.js");
/* harmony import */ var _button_switchbuttonview__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ../button/switchbuttonview */ "./node_modules/@ckeditor/ckeditor5-ui/src/button/switchbuttonview.js");
/* harmony import */ var _bindings_clickoutsidehandler__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ../bindings/clickoutsidehandler */ "./node_modules/@ckeditor/ckeditor5-ui/src/bindings/clickoutsidehandler.js");
/* harmony import */ var _theme_components_dropdown_toolbardropdown_css__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ../../theme/components/dropdown/toolbardropdown.css */ "./node_modules/@ckeditor/ckeditor5-ui/theme/components/dropdown/toolbardropdown.css");
/* harmony import */ var _theme_components_dropdown_listdropdown_css__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ../../theme/components/dropdown/listdropdown.css */ "./node_modules/@ckeditor/ckeditor5-ui/theme/components/dropdown/listdropdown.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module ui/dropdown/utils
*/
/**
* A helper for creating dropdowns. It creates an instance of a {@link module:ui/dropdown/dropdownview~DropdownView dropdown},
* with a {@link module:ui/dropdown/button/dropdownbutton~DropdownButton button},
* {@link module:ui/dropdown/dropdownpanelview~DropdownPanelView panel} and all standard dropdown's behaviors.
*
* # Creating dropdowns
*
* By default, the default {@link module:ui/dropdown/button/dropdownbuttonview~DropdownButtonView} class is used as
* definition of the button:
*
* const dropdown = createDropdown( model );
*
* // Configure dropdown's button properties:
* dropdown.buttonView.set( {
* label: 'A dropdown',
* withText: true
* } );
*
* dropdown.render();
*
* // Will render a dropdown labeled "A dropdown" with an empty panel.
* document.body.appendChild( dropdown.element );
*
* You can also provide other button views (they need to implement the
* {@link module:ui/dropdown/button/dropdownbutton~DropdownButton} interface). For instance, you can use
* {@link module:ui/dropdown/button/splitbuttonview~SplitButtonView} to create a dropdown with a split button.
*
* const dropdown = createDropdown( model, SplitButtonView );
*
* // Configure dropdown's button properties:
* dropdown.buttonView.set( {
* label: 'A dropdown',
* withText: true
* } );
*
* dropdown.buttonView.on( 'execute', () => {
* // Add the behavior of the "action part" of the split button.
* // Split button consists of the "action part" and "arrow part".
* // The arrow opens the dropdown while the action part can have some other behavior.
* } );
*
* dropdown.render();
*
* // Will render a dropdown labeled "A dropdown" with an empty panel.
* document.body.appendChild( dropdown.element );
*
* # Adding content to the dropdown's panel
*
* The content of the panel can be inserted directly into the `dropdown.panelView.element`:
*
* dropdown.panelView.element.textContent = 'Content of the panel';
*
* However, most of the time you will want to add there either a {@link module:ui/list/listview~ListView list of options}
* or a list of buttons (i.e. a {@link module:ui/toolbar/toolbarview~ToolbarView toolbar}).
* To simplify the task, you can use, respectively, {@link module:ui/dropdown/utils~addListToDropdown} or
* {@link module:ui/dropdown/utils~addToolbarToDropdown} utils.
*
* @param {module:utils/locale~Locale} locale The locale instance.
* @param {Function} ButtonClass The dropdown button view class. Needs to implement the
* {@link module:ui/dropdown/button/dropdownbutton~DropdownButton} interface.
* @returns {module:ui/dropdown/dropdownview~DropdownView} The dropdown view instance.
*/
function createDropdown( locale, ButtonClass = _button_dropdownbuttonview__WEBPACK_IMPORTED_MODULE_2__["default"] ) {
const buttonView = new ButtonClass( locale );
const panelView = new _dropdownpanelview__WEBPACK_IMPORTED_MODULE_0__["default"]( locale );
const dropdownView = new _dropdownview__WEBPACK_IMPORTED_MODULE_1__["default"]( locale, buttonView, panelView );
buttonView.bind( 'isEnabled' ).to( dropdownView );
if ( buttonView instanceof _button_dropdownbuttonview__WEBPACK_IMPORTED_MODULE_2__["default"] ) {
buttonView.bind( 'isOn' ).to( dropdownView, 'isOpen' );
} else {
buttonView.arrowView.bind( 'isOn' ).to( dropdownView, 'isOpen' );
}
addDefaultBehavior( dropdownView );
return dropdownView;
}
/**
* Adds an instance of {@link module:ui/toolbar/toolbarview~ToolbarView} to a dropdown.
*
* const buttons = [];
*
* // Either create a new ButtonView instance or create existing.
* buttons.push( new ButtonView() );
* buttons.push( editor.ui.componentFactory.create( 'someButton' ) );
*
* const dropdown = createDropdown( locale );
*
* addToolbarToDropdown( dropdown, buttons );
*
* dropdown.toolbarView.isVertical = true;
*
* // Will render a vertical button dropdown labeled "A button dropdown"
* // with a button group in the panel containing two buttons.
* dropdown.render()
* document.body.appendChild( dropdown.element );
*
* See {@link module:ui/dropdown/utils~createDropdown} and {@link module:ui/toolbar/toolbarview~ToolbarView}.
*
* @param {module:ui/dropdown/dropdownview~DropdownView} dropdownView A dropdown instance to which `ToolbarView` will be added.
* @param {Iterable.<module:ui/button/buttonview~ButtonView>} buttons
*/
function addToolbarToDropdown( dropdownView, buttons ) {
const locale = dropdownView.locale;
const t = locale.t;
const toolbarView = dropdownView.toolbarView = new _toolbar_toolbarview__WEBPACK_IMPORTED_MODULE_3__["default"]( locale );
toolbarView.set( 'ariaLabel', t( 'Dropdown toolbar' ) );
dropdownView.extendTemplate( {
attributes: {
class: [ 'ck-toolbar-dropdown' ]
}
} );
buttons.map( view => toolbarView.items.add( view ) );
dropdownView.panelView.children.add( toolbarView );
toolbarView.items.delegate( 'execute' ).to( dropdownView );
}
/**
* Adds an instance of {@link module:ui/list/listview~ListView} to a dropdown.
*
* const items = new Collection();
*
* items.add( {
* type: 'button',
* model: new Model( {
* withText: true,
* label: 'First item',
* labelStyle: 'color: red'
* } )
* } );
*
* items.add( {
* type: 'button',
* model: new Model( {
* withText: true,
* label: 'Second item',
* labelStyle: 'color: green',
* class: 'foo'
* } )
* } );
*
* const dropdown = createDropdown( locale );
*
* addListToDropdown( dropdown, items );
*
* // Will render a dropdown with a list in the panel containing two items.
* dropdown.render()
* document.body.appendChild( dropdown.element );
*
* The `items` collection passed to this methods controls the presence and attributes of respective
* {@link module:ui/list/listitemview~ListItemView list items}.
*
*
* See {@link module:ui/dropdown/utils~createDropdown} and {@link module:list/list~List}.
*
* @param {module:ui/dropdown/dropdownview~DropdownView} dropdownView A dropdown instance to which `ListVIew` will be added.
* @param {Iterable.<module:ui/dropdown/utils~ListDropdownItemDefinition>} items
* A collection of the list item definitions to populate the list.
*/
function addListToDropdown( dropdownView, items ) {
const locale = dropdownView.locale;
const listView = dropdownView.listView = new _list_listview__WEBPACK_IMPORTED_MODULE_4__["default"]( locale );
listView.items.bindTo( items ).using( ( { type, model } ) => {
if ( type === 'separator' ) {
return new _list_listseparatorview__WEBPACK_IMPORTED_MODULE_6__["default"]( locale );
} else if ( type === 'button' || type === 'switchbutton' ) {
const listItemView = new _list_listitemview__WEBPACK_IMPORTED_MODULE_5__["default"]( locale );
let buttonView;
if ( type === 'button' ) {
buttonView = new _button_buttonview__WEBPACK_IMPORTED_MODULE_7__["default"]( locale );
} else {
buttonView = new _button_switchbuttonview__WEBPACK_IMPORTED_MODULE_8__["default"]( locale );
}
// Bind all model properties to the button view.
buttonView.bind( ...Object.keys( model ) ).to( model );
buttonView.delegate( 'execute' ).to( listItemView );
listItemView.children.add( buttonView );
return listItemView;
}
} );
dropdownView.panelView.children.add( listView );
listView.items.delegate( 'execute' ).to( dropdownView );
}
// Add a set of default behaviors to dropdown view.
//
// @param {module:ui/dropdown/dropdownview~DropdownView} dropdownView
function addDefaultBehavior( dropdownView ) {
closeDropdownOnBlur( dropdownView );
closeDropdownOnExecute( dropdownView );
focusDropdownContentsOnArrows( dropdownView );
}
// Adds a behavior to a dropdownView that closes opened dropdown when user clicks outside the dropdown.
//
// @param {module:ui/dropdown/dropdownview~DropdownView} dropdownView
function closeDropdownOnBlur( dropdownView ) {
dropdownView.on( 'render', () => {
(0,_bindings_clickoutsidehandler__WEBPACK_IMPORTED_MODULE_9__["default"])( {
emitter: dropdownView,
activator: () => dropdownView.isOpen,
callback: () => {
dropdownView.isOpen = false;
},
contextElements: [ dropdownView.element ]
} );
} );
}
// Adds a behavior to a dropdownView that closes the dropdown view on "execute" event.
//
// @param {module:ui/dropdown/dropdownview~DropdownView} dropdownView
function closeDropdownOnExecute( dropdownView ) {
// Close the dropdown when one of the list items has been executed.
dropdownView.on( 'execute', evt => {
// Toggling a switch button view should not close the dropdown.
if ( evt.source instanceof _button_switchbuttonview__WEBPACK_IMPORTED_MODULE_8__["default"] ) {
return;
}
dropdownView.isOpen = false;
} );
}
// Adds a behavior to a dropdownView that focuses the dropdown's panel view contents on keystrokes.
//
// @param {module:ui/dropdown/dropdownview~DropdownView} dropdownView
function focusDropdownContentsOnArrows( dropdownView ) {
// If the dropdown panel is already open, the arrow down key should focus the first child of the #panelView.
dropdownView.keystrokes.set( 'arrowdown', ( data, cancel ) => {
if ( dropdownView.isOpen ) {
dropdownView.panelView.focus();
cancel();
}
} );
// If the dropdown panel is already open, the arrow up key should focus the last child of the #panelView.
dropdownView.keystrokes.set( 'arrowup', ( data, cancel ) => {
if ( dropdownView.isOpen ) {
dropdownView.panelView.focusLast();
cancel();
}
} );
}
/**
* A definition of the list item used by the {@link module:ui/dropdown/utils~addListToDropdown}
* utility.
*
* @typedef {Object} module:ui/dropdown/utils~ListDropdownItemDefinition
*
* @property {String} type Either `'separator'`, `'button'` or `'switchbutton'`.
* @property {module:ui/model~Model} [model] Model of the item (when **not** `'separator'`).
* Its properties fuel the newly created list item (or its children, depending on the `type`).
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/src/editableui/editableuiview.js":
/*!******************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/src/editableui/editableuiview.js ***!
\******************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ EditableUIView)
/* harmony export */ });
/* harmony import */ var _view__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../view */ "./node_modules/@ckeditor/ckeditor5-ui/src/view.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module ui/editableui/editableuiview
*/
/**
* The editable UI view class.
*
* @extends module:ui/view~View
*/
class EditableUIView extends _view__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* Creates an instance of EditableUIView class.
*
* @param {module:utils/locale~Locale} [locale] The locale instance.
* @param {module:engine/view/view~View} editingView The editing view instance the editable is related to.
* @param {HTMLElement} [editableElement] The editable element. If not specified, this view
* should create it. Otherwise, the existing element should be used.
*/
constructor( locale, editingView, editableElement ) {
super( locale );
this.setTemplate( {
tag: 'div',
attributes: {
class: [
'ck',
'ck-content',
'ck-editor__editable',
'ck-rounded-corners'
],
lang: locale.contentLanguage,
dir: locale.contentLanguageDirection
}
} );
/**
* The name of the editable UI view.
*
* @member {String} #name
*/
this.name = null;
/**
* Controls whether the editable is focused, i.e. the user is typing in it.
*
* @observable
* @member {Boolean} #isFocused
*/
this.set( 'isFocused', false );
/**
* The element which is the main editable element (usually the one with `contentEditable="true"`).
*
* @private
* @type {HTMLElement}
*/
this._editableElement = editableElement;
/**
* Whether an external {@link #_editableElement} was passed into the constructor, which also means
* the view will not render its {@link #template}.
*
* @private
* @type {Boolean}
*/
this._hasExternalElement = !!this._editableElement;
/**
* The editing view instance the editable is related to. Editable uses the editing
* view to dynamically modify its certain DOM attributes after {@link #render rendering}.
*
* **Note**: The DOM attributes are performed by the editing view and not UI
* {@link module:ui/view~View#bindTemplate template bindings} because once rendered,
* the editable DOM element must remain under the full control of the engine to work properly.
*
* @protected
* @type {module:engine/view/view~View}
*/
this._editingView = editingView;
}
/**
* Renders the view by either applying the {@link #template} to the existing
* {@link #_editableElement} or assigning {@link #element} as {@link #_editableElement}.
*/
render() {
super.render();
if ( this._hasExternalElement ) {
this.template.apply( this.element = this._editableElement );
} else {
this._editableElement = this.element;
}
this.on( 'change:isFocused', () => this._updateIsFocusedClasses() );
this._updateIsFocusedClasses();
}
/**
* @inheritDoc
*/
destroy() {
if ( this._hasExternalElement ) {
this.template.revert( this._editableElement );
}
super.destroy();
}
/**
* Updates the `ck-focused` and `ck-blurred` CSS classes on the {@link #element} according to
* the {@link #isFocused} property value using the {@link #_editingView editing view} API.
*
* @private
*/
_updateIsFocusedClasses() {
const editingView = this._editingView;
if ( editingView.isRenderingInProgress ) {
updateAfterRender( this );
} else {
update( this );
}
function update( view ) {
editingView.change( writer => {
const viewRoot = editingView.document.getRoot( view.name );
writer.addClass( view.isFocused ? 'ck-focused' : 'ck-blurred', viewRoot );
writer.removeClass( view.isFocused ? 'ck-blurred' : 'ck-focused', viewRoot );
} );
}
// In a case of a multi-root editor, a callback will be attached more than once (one callback for each root).
// While executing one callback the `isRenderingInProgress` observable is changing what causes executing another
// callback and render is called inside the already pending render.
// We need to be sure that callback is executed only when the value has changed from `true` to `false`.
// See https://github.com/ckeditor/ckeditor5/issues/1676.
function updateAfterRender( view ) {
editingView.once( 'change:isRenderingInProgress', ( evt, name, value ) => {
if ( !value ) {
update( view );
} else {
updateAfterRender( view );
}
} );
}
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/src/editableui/inline/inlineeditableuiview.js":
/*!*******************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/src/editableui/inline/inlineeditableuiview.js ***!
\*******************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ InlineEditableUIView)
/* harmony export */ });
/* harmony import */ var _editableui_editableuiview__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../../editableui/editableuiview */ "./node_modules/@ckeditor/ckeditor5-ui/src/editableui/editableuiview.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module ui/editableui/inline/inlineeditableuiview
*/
/**
* The inline editable UI class implementing an inline {@link module:ui/editableui/editableuiview~EditableUIView}.
*
* @extends module:ui/editableui/editableuiview~EditableUIView
*/
class InlineEditableUIView extends _editableui_editableuiview__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* Creates an instance of the InlineEditableUIView class.
*
* @param {module:utils/locale~Locale} [locale] The locale instance.
* @param {module:engine/view/view~View} editingView The editing view instance the editable is related to.
* @param {HTMLElement} [editableElement] The editable element. If not specified, the
* {@link module:ui/editableui/editableuiview~EditableUIView}
* will create it. Otherwise, the existing element will be used.
*/
constructor( locale, editingView, editableElement ) {
super( locale, editingView, editableElement );
this.extendTemplate( {
attributes: {
role: 'textbox',
class: 'ck-editor__editable_inline'
}
} );
}
/**
* @inheritDoc
*/
render() {
super.render();
const editingView = this._editingView;
const t = this.t;
editingView.change( writer => {
const viewRoot = editingView.document.getRoot( this.name );
writer.setAttribute( 'aria-label', t( 'Rich Text Editor, %0', this.name ), viewRoot );
} );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/src/editorui/bodycollection.js":
/*!****************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/src/editorui/bodycollection.js ***!
\****************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ BodyCollection)
/* harmony export */ });
/* harmony import */ var _template__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../template */ "./node_modules/@ckeditor/ckeditor5-ui/src/template.js");
/* harmony import */ var _viewcollection__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../viewcollection */ "./node_modules/@ckeditor/ckeditor5-ui/src/viewcollection.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_dom_createelement__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/dom/createelement */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/createelement.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module ui/editorui/bodycollection
*/
/* globals document */
/**
* This is a special {@link module:ui/viewcollection~ViewCollection} dedicated to elements that are detached
* from the DOM structure of the editor, like panels, icons, etc.
*
* The body collection is available in the {@link module:ui/editorui/editoruiview~EditorUIView#body `editor.ui.view.body`} property.
* Any plugin can add a {@link module:ui/view~View view} to this collection.
* These views will render in a container placed directly in the `<body>` element.
* The editor will detach and destroy this collection when the editor will be {@link module:core/editor/editor~Editor#destroy destroyed}.
*
* If you need to control the life cycle of the body collection on your own, you can create your own instance of this class.
*
* A body collection will render itself automatically in the DOM body element as soon as you call {@link ~BodyCollection#attachToDom}.
* If you create multiple body collections, this class will create a special wrapper element in the DOM to limit the number of
* elements created directly in the body and remove it when the last body collection will be
* {@link ~BodyCollection#detachFromDom detached}.
*
* @extends module:ui/viewcollection~ViewCollection
*/
class BodyCollection extends _viewcollection__WEBPACK_IMPORTED_MODULE_1__["default"] {
/**
* Creates a new instance of the {@link module:ui/editorui/bodycollection~BodyCollection}.
*
* @param {module:utils/locale~Locale} locale The {@link module:core/editor/editor~Editor editor's locale} instance.
* @param {Iterable.<module:ui/view~View>} [initialItems] The initial items of the collection.
*/
constructor( locale, initialItems = [] ) {
super( initialItems );
/**
* The {@link module:core/editor/editor~Editor#locale editor's locale} instance.
* See the view {@link module:ui/view~View#locale locale} property.
*
* @member {module:utils/locale~Locale}
*/
this.locale = locale;
}
/**
* Attaches the body collection to the DOM body element. You need to execute this method to render the content of
* the body collection.
*/
attachToDom() {
/**
* The element holding elements of the body region.
*
* @protected
* @member {HTMLElement} #_bodyCollectionContainer
*/
this._bodyCollectionContainer = new _template__WEBPACK_IMPORTED_MODULE_0__["default"]( {
tag: 'div',
attributes: {
class: [
'ck',
'ck-reset_all',
'ck-body',
'ck-rounded-corners'
],
dir: this.locale.uiLanguageDirection
},
children: this
} ).render();
let wrapper = document.querySelector( '.ck-body-wrapper' );
if ( !wrapper ) {
wrapper = (0,_ckeditor_ckeditor5_utils_src_dom_createelement__WEBPACK_IMPORTED_MODULE_2__["default"])( document, 'div', { class: 'ck-body-wrapper' } );
document.body.appendChild( wrapper );
}
wrapper.appendChild( this._bodyCollectionContainer );
}
/**
* Detaches the collection from the DOM structure. Use this method when you do not need to use the body collection
* anymore to clean-up the DOM structure.
*/
detachFromDom() {
super.destroy();
if ( this._bodyCollectionContainer ) {
this._bodyCollectionContainer.remove();
}
const wrapper = document.querySelector( '.ck-body-wrapper' );
if ( wrapper && wrapper.childElementCount == 0 ) {
wrapper.remove();
}
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/src/editorui/boxed/boxededitoruiview.js":
/*!*************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/src/editorui/boxed/boxededitoruiview.js ***!
\*************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ BoxedEditorUIView)
/* harmony export */ });
/* harmony import */ var _editorui_editoruiview__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../../editorui/editoruiview */ "./node_modules/@ckeditor/ckeditor5-ui/src/editorui/editoruiview.js");
/* harmony import */ var _label_labelview__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../../label/labelview */ "./node_modules/@ckeditor/ckeditor5-ui/src/label/labelview.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module ui/editorui/boxed/boxededitoruiview
*/
/**
* The boxed editor UI view class. This class represents an editor interface
* consisting of a toolbar and an editable area, enclosed within a box.
*
* @extends module:ui/editorui/editoruiview~EditorUIView
*/
class BoxedEditorUIView extends _editorui_editoruiview__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* Creates an instance of the boxed editor UI view class.
*
* @param {module:utils/locale~Locale} locale The locale instance..
*/
constructor( locale ) {
super( locale );
/**
* Collection of the child views located in the top (`.ck-editor__top`)
* area of the UI.
*
* @readonly
* @member {module:ui/viewcollection~ViewCollection}
*/
this.top = this.createCollection();
/**
* Collection of the child views located in the main (`.ck-editor__main`)
* area of the UI.
*
* @readonly
* @member {module:ui/viewcollection~ViewCollection}
*/
this.main = this.createCollection();
/**
* Voice label of the UI.
*
* @protected
* @readonly
* @member {module:ui/view~View} #_voiceLabelView
*/
this._voiceLabelView = this._createVoiceLabel();
this.setTemplate( {
tag: 'div',
attributes: {
class: [
'ck',
'ck-reset',
'ck-editor',
'ck-rounded-corners'
],
role: 'application',
dir: locale.uiLanguageDirection,
lang: locale.uiLanguage,
'aria-labelledby': this._voiceLabelView.id
},
children: [
this._voiceLabelView,
{
tag: 'div',
attributes: {
class: [
'ck',
'ck-editor__top',
'ck-reset_all'
],
role: 'presentation'
},
children: this.top
},
{
tag: 'div',
attributes: {
class: [
'ck',
'ck-editor__main'
],
role: 'presentation'
},
children: this.main
}
]
} );
}
/**
* Creates a voice label view instance.
*
* @private
* @returns {module:ui/label/labelview~LabelView}
*/
_createVoiceLabel() {
const t = this.t;
const voiceLabel = new _label_labelview__WEBPACK_IMPORTED_MODULE_1__["default"]();
voiceLabel.text = t( 'Rich Text Editor' );
voiceLabel.extendTemplate( {
attributes: {
class: 'ck-voice-label'
}
} );
return voiceLabel;
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/src/editorui/editoruiview.js":
/*!**************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/src/editorui/editoruiview.js ***!
\**************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ EditorUIView)
/* harmony export */ });
/* harmony import */ var _view__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../view */ "./node_modules/@ckeditor/ckeditor5-ui/src/view.js");
/* harmony import */ var _bodycollection__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./bodycollection */ "./node_modules/@ckeditor/ckeditor5-ui/src/editorui/bodycollection.js");
/* harmony import */ var _theme_components_editorui_editorui_css__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../theme/components/editorui/editorui.css */ "./node_modules/@ckeditor/ckeditor5-ui/theme/components/editorui/editorui.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module ui/editorui/editoruiview
*/
/**
* The editor UI view class. Base class for the editor main views.
*
* @extends module:ui/view~View
*/
class EditorUIView extends _view__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* Creates an instance of the editor UI view class.
*
* @param {module:utils/locale~Locale} [locale] The locale instance.
*/
constructor( locale ) {
super( locale );
/**
* Collection of the child views, detached from the DOM
* structure of the editor, like panels, icons etc.
*
* @readonly
* @member {module:ui/viewcollection~ViewCollection} #body
*/
this.body = new _bodycollection__WEBPACK_IMPORTED_MODULE_1__["default"]( locale );
}
/**
* @inheritDoc
*/
render() {
super.render();
this.body.attachToDom();
}
/**
* @inheritDoc
*/
destroy() {
this.body.detachFromDom();
return super.destroy();
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/src/focuscycler.js":
/*!****************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/src/focuscycler.js ***!
\****************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ FocusCycler)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_utils_src_dom_isvisible__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/dom/isvisible */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/isvisible.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module ui/focuscycler
*/
/**
* A utility class that helps cycling over focusable {@link module:ui/view~View views} in a
* {@link module:ui/viewcollection~ViewCollection} when the focus is tracked by the
* {@link module:utils/focustracker~FocusTracker} instance. It helps implementing keyboard
* navigation in HTML forms, toolbars, lists and the like.
*
* To work properly it requires:
* * a collection of focusable (HTML `tabindex` attribute) views that implement the `focus()` method,
* * an associated focus tracker to determine which view is focused.
*
* A simple cycler setup can look like this:
*
* const focusables = new ViewCollection();
* const focusTracker = new FocusTracker();
*
* // Add focusable views to the focus tracker.
* focusTracker.add( ... );
*
* Then, the cycler can be used manually:
*
* const cycler = new FocusCycler( { focusables, focusTracker } );
*
* // Will focus the first focusable view in #focusables.
* cycler.focusFirst();
*
* // Will log the next focusable item in #focusables.
* console.log( cycler.next );
*
* Alternatively, it can work side by side with the {@link module:utils/keystrokehandler~KeystrokeHandler}:
*
* const keystrokeHandler = new KeystrokeHandler();
*
* // Activate the keystroke handler.
* keystrokeHandler.listenTo( sourceOfEvents );
*
* const cycler = new FocusCycler( {
* focusables, focusTracker, keystrokeHandler,
* actions: {
* // When arrowup of arrowleft is detected by the #keystrokeHandler,
* // focusPrevious() will be called on the cycler.
* focusPrevious: [ 'arrowup', 'arrowleft' ],
* }
* } );
*
* Check out the {@glink framework/guides/deep-dive/ui/focus-tracking "Deep dive into focus tracking" guide} to learn more.
*/
class FocusCycler {
/**
* Creates an instance of the focus cycler utility.
*
* @param {Object} options Configuration options.
* @param {module:utils/collection~Collection|Object} options.focusables
* @param {module:utils/focustracker~FocusTracker} options.focusTracker
* @param {module:utils/keystrokehandler~KeystrokeHandler} [options.keystrokeHandler]
* @param {Object} [options.actions]
*/
constructor( options ) {
Object.assign( this, options );
/**
* A {@link module:ui/view~View view} collection that the cycler operates on.
*
* @readonly
* @member {module:utils/collection~Collection} #focusables
*/
/**
* A focus tracker instance that the cycler uses to determine the current focus
* state in {@link #focusables}.
*
* @readonly
* @member {module:utils/focustracker~FocusTracker} #focusTracker
*/
/**
* An instance of the {@link module:utils/keystrokehandler~KeystrokeHandler}
* which can respond to certain keystrokes and cycle the focus.
*
* @readonly
* @member {module:utils/keystrokehandler~KeystrokeHandler} #keystrokeHandler
*/
/**
* Actions that the cycler can take when a keystroke is pressed. Requires
* `options.keystrokeHandler` to be passed and working. When an action is
* performed, `preventDefault` and `stopPropagation` will be called on the event
* the keystroke fired in the DOM.
*
* actions: {
* // Will call #focusPrevious() when arrowleft or arrowup is pressed.
* focusPrevious: [ 'arrowleft', 'arrowup' ],
*
* // Will call #focusNext() when arrowdown is pressed.
* focusNext: 'arrowdown'
* }
*
* @readonly
* @member {Object} #actions
*/
if ( options.actions && options.keystrokeHandler ) {
for ( const methodName in options.actions ) {
let actions = options.actions[ methodName ];
if ( typeof actions == 'string' ) {
actions = [ actions ];
}
for ( const keystroke of actions ) {
options.keystrokeHandler.set( keystroke, ( data, cancel ) => {
this[ methodName ]();
cancel();
} );
}
}
}
}
/**
* Returns the first focusable view in {@link #focusables}.
* Returns `null` if there is none.
*
* **Note**: Hidden views (e.g. with `display: none`) are ignored.
*
* @readonly
* @member {module:ui/view~View|null} #first
*/
get first() {
return this.focusables.find( isFocusable ) || null;
}
/**
* Returns the last focusable view in {@link #focusables}.
* Returns `null` if there is none.
*
* **Note**: Hidden views (e.g. with `display: none`) are ignored.
*
* @readonly
* @member {module:ui/view~View|null} #last
*/
get last() {
return this.focusables.filter( isFocusable ).slice( -1 )[ 0 ] || null;
}
/**
* Returns the next focusable view in {@link #focusables} based on {@link #current}.
* Returns `null` if there is none.
*
* **Note**: Hidden views (e.g. with `display: none`) are ignored.
*
* @readonly
* @member {module:ui/view~View|null} #next
*/
get next() {
return this._getFocusableItem( 1 );
}
/**
* Returns the previous focusable view in {@link #focusables} based on {@link #current}.
* Returns `null` if there is none.
*
* **Note**: Hidden views (e.g. with `display: none`) are ignored.
*
* @readonly
* @member {module:ui/view~View|null} #previous
*/
get previous() {
return this._getFocusableItem( -1 );
}
/**
* An index of the view in the {@link #focusables} which is focused according
* to {@link #focusTracker}. Returns `null` when there is no such view.
*
* @readonly
* @member {Number|null} #current
*/
get current() {
let index = null;
// There's no focused view in the focusables.
if ( this.focusTracker.focusedElement === null ) {
return null;
}
this.focusables.find( ( view, viewIndex ) => {
const focused = view.element === this.focusTracker.focusedElement;
if ( focused ) {
index = viewIndex;
}
return focused;
} );
return index;
}
/**
* Focuses the {@link #first} item in {@link #focusables}.
*
* **Note**: Hidden views (e.g. with `display: none`) are ignored.
*/
focusFirst() {
this._focus( this.first );
}
/**
* Focuses the {@link #last} item in {@link #focusables}.
*
* **Note**: Hidden views (e.g. with `display: none`) are ignored.
*/
focusLast() {
this._focus( this.last );
}
/**
* Focuses the {@link #next} item in {@link #focusables}.
*
* **Note**: Hidden views (e.g. with `display: none`) are ignored.
*/
focusNext() {
this._focus( this.next );
}
/**
* Focuses the {@link #previous} item in {@link #focusables}.
*
* **Note**: Hidden views (e.g. with `display: none`) are ignored.
*/
focusPrevious() {
this._focus( this.previous );
}
/**
* Focuses the given view if it exists.
*
* @protected
* @param {module:ui/view~View} view
*/
_focus( view ) {
if ( view ) {
view.focus();
}
}
/**
* Returns the next or previous focusable view in {@link #focusables} with respect
* to {@link #current}.
*
* @protected
* @param {Number} step Either `1` for checking forward from {@link #current} or
* `-1` for checking backwards.
* @returns {module:ui/view~View|null}
*/
_getFocusableItem( step ) {
// Cache for speed.
const current = this.current;
const collectionLength = this.focusables.length;
if ( !collectionLength ) {
return null;
}
// Start from the beginning if no view is focused.
// https://github.com/ckeditor/ckeditor5-ui/issues/206
if ( current === null ) {
return this[ step === 1 ? 'first' : 'last' ];
}
// Cycle in both directions.
let index = ( current + collectionLength + step ) % collectionLength;
do {
const view = this.focusables.get( index );
if ( isFocusable( view ) ) {
return view;
}
// Cycle in both directions.
index = ( index + collectionLength + step ) % collectionLength;
} while ( index !== current );
return null;
}
}
// Checks whether a view is focusable.
//
// @private
// @param {module:ui/view~View} view A view to be checked.
// @returns {Boolean}
function isFocusable( view ) {
return !!( view.focus && (0,_ckeditor_ckeditor5_utils_src_dom_isvisible__WEBPACK_IMPORTED_MODULE_0__["default"])( view.element ) );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/src/formheader/formheaderview.js":
/*!******************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/src/formheader/formheaderview.js ***!
\******************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ FormHeaderView)
/* harmony export */ });
/* harmony import */ var _view__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../view */ "./node_modules/@ckeditor/ckeditor5-ui/src/view.js");
/* harmony import */ var _theme_components_formheader_formheader_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../../theme/components/formheader/formheader.css */ "./node_modules/@ckeditor/ckeditor5-ui/theme/components/formheader/formheader.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module ui/formheader/formheaderview
*/
/**
* The class component representing a form header view. It should be used in more advanced forms to
* describe the main purpose of the form.
*
* By default the component contains a bolded label view that has to be set. The label is usually a short (at most 3-word) string.
* The component can also be extended by any other elements, like: icons, dropdowns, etc.
*
* It is used i.a.
* by {@link module:table/tablecellproperties/ui/tablecellpropertiesview~TableCellPropertiesView}
* and {@link module:special-characters/ui/specialcharactersnavigationview~SpecialCharactersNavigationView}.
*
* The latter is an example, where the component has been extended by {@link module:ui/dropdown/dropdownview~DropdownView} view.
*
* @extends module:ui/view~View
*/
class FormHeaderView extends _view__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* Creates an instance of the form header class.
*
* @param {module:utils/locale~Locale} locale The locale instance.
* @param {Object} options
* @param {String} options.label A label.
* @param {String} [options.class] An additional class.
*/
constructor( locale, options = {} ) {
super( locale );
const bind = this.bindTemplate;
/**
* The label of the header.
*
* @observable
* @member {String} #label
*/
this.set( 'label', options.label || '' );
/**
* An additional CSS class added to the {@link #element}.
*
* @observable
* @member {String} #class
*/
this.set( 'class', options.class || null );
/**
* A collection of header items.
*
* @readonly
* @member {module:ui/viewcollection~ViewCollection}
*/
this.children = this.createCollection();
this.setTemplate( {
tag: 'div',
attributes: {
class: [
'ck',
'ck-form__header',
bind.to( 'class' )
]
},
children: this.children
} );
const label = new _view__WEBPACK_IMPORTED_MODULE_0__["default"]( locale );
label.setTemplate( {
tag: 'span',
attributes: {
class: [
'ck',
'ck-form__header__label'
]
},
children: [
{ text: bind.to( 'label' ) }
]
} );
this.children.add( label );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/src/icon/iconview.js":
/*!******************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/src/icon/iconview.js ***!
\******************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ IconView)
/* harmony export */ });
/* harmony import */ var _view__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../view */ "./node_modules/@ckeditor/ckeditor5-ui/src/view.js");
/* harmony import */ var _theme_components_icon_icon_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../../theme/components/icon/icon.css */ "./node_modules/@ckeditor/ckeditor5-ui/theme/components/icon/icon.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/* global DOMParser */
/**
* @module ui/icon/iconview
*/
/**
* The icon view class.
*
* @extends module:ui/view~View
*/
class IconView extends _view__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* @inheritDoc
*/
constructor() {
super();
const bind = this.bindTemplate;
/**
* The SVG source of the icon.
*
* @observable
* @member {String} #content
*/
this.set( 'content', '' );
/**
* This attribute specifies the boundaries to which the
* icon content should stretch.
*
* @observable
* @default '0 0 20 20'
* @member {String} #viewBox
*/
this.set( 'viewBox', '0 0 20 20' );
/**
* The fill color of the child `path.ck-icon__fill`.
*
* @observable
* @default ''
* @member {String} #fillColor
*/
this.set( 'fillColor', '' );
this.setTemplate( {
tag: 'svg',
ns: 'http://www.w3.org/2000/svg',
attributes: {
class: [
'ck',
'ck-icon'
],
viewBox: bind.to( 'viewBox' )
}
} );
}
/**
* @inheritDoc
*/
render() {
super.render();
this._updateXMLContent();
this._colorFillPaths();
// This is a hack for lack of innerHTML binding.
// See: https://github.com/ckeditor/ckeditor5-ui/issues/99.
this.on( 'change:content', () => {
this._updateXMLContent();
this._colorFillPaths();
} );
this.on( 'change:fillColor', () => {
this._colorFillPaths();
} );
}
/**
* Updates the {@link #element} with the value of {@link #content}.
*
* @private
*/
_updateXMLContent() {
if ( this.content ) {
const parsed = new DOMParser().parseFromString( this.content.trim(), 'image/svg+xml' );
const svg = parsed.querySelector( 'svg' );
const viewBox = svg.getAttribute( 'viewBox' );
if ( viewBox ) {
this.viewBox = viewBox;
}
this.element.innerHTML = '';
while ( svg.childNodes.length > 0 ) {
this.element.appendChild( svg.childNodes[ 0 ] );
}
}
}
/**
* Fills all child `path.ck-icon__fill` with the `#fillColor`.
*
* @private
*/
_colorFillPaths() {
if ( this.fillColor ) {
this.element.querySelectorAll( '.ck-icon__fill' ).forEach( path => {
path.style.fill = this.fillColor;
} );
}
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/src/iframe/iframeview.js":
/*!**********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/src/iframe/iframeview.js ***!
\**********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ IframeView)
/* harmony export */ });
/* harmony import */ var _view__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../view */ "./node_modules/@ckeditor/ckeditor5-ui/src/view.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module ui/iframe/iframeview
*/
/**
* The iframe view class.
*
* const iframe = new IframeView();
*
* iframe.render();
* document.body.appendChild( iframe.element );
*
* iframe.on( 'loaded', () => {
* console.log( 'The iframe has loaded', iframe.element.contentWindow );
* } );
*
* iframe.element.src = 'https://ckeditor.com';
*
* @extends module:ui/view~View
*/
class IframeView extends _view__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* Creates a new instance of the iframe view.
*
* @param {module:utils/locale~Locale} [locale] The locale instance.
*/
constructor( locale ) {
super( locale );
const bind = this.bindTemplate;
this.setTemplate( {
tag: 'iframe',
attributes: {
class: [
'ck',
'ck-reset_all'
],
// It seems that we need to allow scripts in order to be able to listen to events.
// TODO: Research that. Perhaps the src must be set?
sandbox: 'allow-same-origin allow-scripts'
},
on: {
load: bind.to( 'loaded' )
}
} );
}
/**
* Renders the iframe's {@link #element} and returns a `Promise` for asynchronous
* child `contentDocument` loading process.
*
* @returns {Promise} A promise which resolves once the iframe `contentDocument` has
* been {@link #event:loaded}.
*/
render() {
return new Promise( resolve => {
this.on( 'loaded', resolve );
super.render();
} );
}
}
/**
* Fired when the DOM iframe's `contentDocument` finished loading.
*
* @event loaded
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/src/index.js":
/*!**********************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/src/index.js ***!
\**********************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "BalloonPanelView": () => (/* reexport safe */ _panel_balloon_balloonpanelview__WEBPACK_IMPORTED_MODULE_29__["default"]),
/* harmony export */ "BalloonToolbar": () => (/* reexport safe */ _toolbar_balloon_balloontoolbar__WEBPACK_IMPORTED_MODULE_38__["default"]),
/* harmony export */ "BlockToolbar": () => (/* reexport safe */ _toolbar_block_blocktoolbar__WEBPACK_IMPORTED_MODULE_39__["default"]),
/* harmony export */ "BodyCollection": () => (/* reexport safe */ _editorui_bodycollection__WEBPACK_IMPORTED_MODULE_3__["default"]),
/* harmony export */ "BoxedEditorUIView": () => (/* reexport safe */ _editorui_boxed_boxededitoruiview__WEBPACK_IMPORTED_MODULE_13__["default"]),
/* harmony export */ "ButtonView": () => (/* reexport safe */ _button_buttonview__WEBPACK_IMPORTED_MODULE_4__["default"]),
/* harmony export */ "ColorGridView": () => (/* reexport safe */ _colorgrid_colorgridview__WEBPACK_IMPORTED_MODULE_7__["default"]),
/* harmony export */ "ColorTileView": () => (/* reexport safe */ _colorgrid_colortileview__WEBPACK_IMPORTED_MODULE_8__["default"]),
/* harmony export */ "ContextualBalloon": () => (/* reexport safe */ _panel_balloon_contextualballoon__WEBPACK_IMPORTED_MODULE_30__["default"]),
/* harmony export */ "DropdownButtonView": () => (/* reexport safe */ _dropdown_button_dropdownbuttonview__WEBPACK_IMPORTED_MODULE_9__["default"]),
/* harmony export */ "EditorUIView": () => (/* reexport safe */ _editorui_editoruiview__WEBPACK_IMPORTED_MODULE_12__["default"]),
/* harmony export */ "FocusCycler": () => (/* reexport safe */ _focuscycler__WEBPACK_IMPORTED_MODULE_16__["default"]),
/* harmony export */ "FormHeaderView": () => (/* reexport safe */ _formheader_formheaderview__WEBPACK_IMPORTED_MODULE_15__["default"]),
/* harmony export */ "IconView": () => (/* reexport safe */ _icon_iconview__WEBPACK_IMPORTED_MODULE_17__["default"]),
/* harmony export */ "IframeView": () => (/* reexport safe */ _iframe_iframeview__WEBPACK_IMPORTED_MODULE_21__["default"]),
/* harmony export */ "InlineEditableUIView": () => (/* reexport safe */ _editableui_inline_inlineeditableuiview__WEBPACK_IMPORTED_MODULE_14__["default"]),
/* harmony export */ "InputNumberView": () => (/* reexport safe */ _inputnumber_inputnumberview__WEBPACK_IMPORTED_MODULE_20__["default"]),
/* harmony export */ "InputTextView": () => (/* reexport safe */ _inputtext_inputtextview__WEBPACK_IMPORTED_MODULE_19__["default"]),
/* harmony export */ "InputView": () => (/* reexport safe */ _input_inputview__WEBPACK_IMPORTED_MODULE_18__["default"]),
/* harmony export */ "LabelView": () => (/* reexport safe */ _label_labelview__WEBPACK_IMPORTED_MODULE_22__["default"]),
/* harmony export */ "LabeledFieldView": () => (/* reexport safe */ _labeledfield_labeledfieldview__WEBPACK_IMPORTED_MODULE_23__["default"]),
/* harmony export */ "ListItemView": () => (/* reexport safe */ _list_listitemview__WEBPACK_IMPORTED_MODULE_25__["default"]),
/* harmony export */ "ListView": () => (/* reexport safe */ _list_listview__WEBPACK_IMPORTED_MODULE_26__["default"]),
/* harmony export */ "Model": () => (/* reexport safe */ _model__WEBPACK_IMPORTED_MODULE_28__["default"]),
/* harmony export */ "Notification": () => (/* reexport safe */ _notification_notification__WEBPACK_IMPORTED_MODULE_27__["default"]),
/* harmony export */ "SplitButtonView": () => (/* reexport safe */ _dropdown_button_splitbuttonview__WEBPACK_IMPORTED_MODULE_10__["default"]),
/* harmony export */ "StickyPanelView": () => (/* reexport safe */ _panel_sticky_stickypanelview__WEBPACK_IMPORTED_MODULE_31__["default"]),
/* harmony export */ "SwitchButtonView": () => (/* reexport safe */ _button_switchbuttonview__WEBPACK_IMPORTED_MODULE_5__["default"]),
/* harmony export */ "Template": () => (/* reexport safe */ _template__WEBPACK_IMPORTED_MODULE_33__["default"]),
/* harmony export */ "ToolbarSeparatorView": () => (/* reexport safe */ _toolbar_toolbarseparatorview__WEBPACK_IMPORTED_MODULE_35__["default"]),
/* harmony export */ "ToolbarView": () => (/* reexport safe */ _toolbar_toolbarview__WEBPACK_IMPORTED_MODULE_34__["default"]),
/* harmony export */ "TooltipView": () => (/* reexport safe */ _tooltip_tooltipview__WEBPACK_IMPORTED_MODULE_32__["default"]),
/* harmony export */ "View": () => (/* reexport safe */ _view__WEBPACK_IMPORTED_MODULE_40__["default"]),
/* harmony export */ "ViewCollection": () => (/* reexport safe */ _viewcollection__WEBPACK_IMPORTED_MODULE_41__["default"]),
/* harmony export */ "addListToDropdown": () => (/* reexport safe */ _dropdown_utils__WEBPACK_IMPORTED_MODULE_11__.addListToDropdown),
/* harmony export */ "addToolbarToDropdown": () => (/* reexport safe */ _dropdown_utils__WEBPACK_IMPORTED_MODULE_11__.addToolbarToDropdown),
/* harmony export */ "clickOutsideHandler": () => (/* reexport safe */ _bindings_clickoutsidehandler__WEBPACK_IMPORTED_MODULE_0__["default"]),
/* harmony export */ "createDropdown": () => (/* reexport safe */ _dropdown_utils__WEBPACK_IMPORTED_MODULE_11__.createDropdown),
/* harmony export */ "createLabeledDropdown": () => (/* reexport safe */ _labeledfield_utils__WEBPACK_IMPORTED_MODULE_24__.createLabeledDropdown),
/* harmony export */ "createLabeledInputNumber": () => (/* reexport safe */ _labeledfield_utils__WEBPACK_IMPORTED_MODULE_24__.createLabeledInputNumber),
/* harmony export */ "createLabeledInputText": () => (/* reexport safe */ _labeledfield_utils__WEBPACK_IMPORTED_MODULE_24__.createLabeledInputText),
/* harmony export */ "enableToolbarKeyboardFocus": () => (/* reexport safe */ _toolbar_enabletoolbarkeyboardfocus__WEBPACK_IMPORTED_MODULE_36__["default"]),
/* harmony export */ "getLocalizedColorOptions": () => (/* reexport safe */ _colorgrid_utils__WEBPACK_IMPORTED_MODULE_6__.getLocalizedColorOptions),
/* harmony export */ "injectCssTransitionDisabler": () => (/* reexport safe */ _bindings_injectcsstransitiondisabler__WEBPACK_IMPORTED_MODULE_1__["default"]),
/* harmony export */ "normalizeColorOptions": () => (/* reexport safe */ _colorgrid_utils__WEBPACK_IMPORTED_MODULE_6__.normalizeColorOptions),
/* harmony export */ "normalizeSingleColorDefinition": () => (/* reexport safe */ _colorgrid_utils__WEBPACK_IMPORTED_MODULE_6__.normalizeSingleColorDefinition),
/* harmony export */ "normalizeToolbarConfig": () => (/* reexport safe */ _toolbar_normalizetoolbarconfig__WEBPACK_IMPORTED_MODULE_37__["default"]),
/* harmony export */ "submitHandler": () => (/* reexport safe */ _bindings_submithandler__WEBPACK_IMPORTED_MODULE_2__["default"])
/* harmony export */ });
/* harmony import */ var _bindings_clickoutsidehandler__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./bindings/clickoutsidehandler */ "./node_modules/@ckeditor/ckeditor5-ui/src/bindings/clickoutsidehandler.js");
/* harmony import */ var _bindings_injectcsstransitiondisabler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./bindings/injectcsstransitiondisabler */ "./node_modules/@ckeditor/ckeditor5-ui/src/bindings/injectcsstransitiondisabler.js");
/* harmony import */ var _bindings_submithandler__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./bindings/submithandler */ "./node_modules/@ckeditor/ckeditor5-ui/src/bindings/submithandler.js");
/* harmony import */ var _editorui_bodycollection__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./editorui/bodycollection */ "./node_modules/@ckeditor/ckeditor5-ui/src/editorui/bodycollection.js");
/* harmony import */ var _button_buttonview__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./button/buttonview */ "./node_modules/@ckeditor/ckeditor5-ui/src/button/buttonview.js");
/* harmony import */ var _button_switchbuttonview__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./button/switchbuttonview */ "./node_modules/@ckeditor/ckeditor5-ui/src/button/switchbuttonview.js");
/* harmony import */ var _colorgrid_utils__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./colorgrid/utils */ "./node_modules/@ckeditor/ckeditor5-ui/src/colorgrid/utils.js");
/* harmony import */ var _colorgrid_colorgridview__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./colorgrid/colorgridview */ "./node_modules/@ckeditor/ckeditor5-ui/src/colorgrid/colorgridview.js");
/* harmony import */ var _colorgrid_colortileview__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./colorgrid/colortileview */ "./node_modules/@ckeditor/ckeditor5-ui/src/colorgrid/colortileview.js");
/* harmony import */ var _dropdown_button_dropdownbuttonview__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./dropdown/button/dropdownbuttonview */ "./node_modules/@ckeditor/ckeditor5-ui/src/dropdown/button/dropdownbuttonview.js");
/* harmony import */ var _dropdown_button_splitbuttonview__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./dropdown/button/splitbuttonview */ "./node_modules/@ckeditor/ckeditor5-ui/src/dropdown/button/splitbuttonview.js");
/* harmony import */ var _dropdown_utils__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ./dropdown/utils */ "./node_modules/@ckeditor/ckeditor5-ui/src/dropdown/utils.js");
/* harmony import */ var _editorui_editoruiview__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! ./editorui/editoruiview */ "./node_modules/@ckeditor/ckeditor5-ui/src/editorui/editoruiview.js");
/* harmony import */ var _editorui_boxed_boxededitoruiview__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(/*! ./editorui/boxed/boxededitoruiview */ "./node_modules/@ckeditor/ckeditor5-ui/src/editorui/boxed/boxededitoruiview.js");
/* harmony import */ var _editableui_inline_inlineeditableuiview__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(/*! ./editableui/inline/inlineeditableuiview */ "./node_modules/@ckeditor/ckeditor5-ui/src/editableui/inline/inlineeditableuiview.js");
/* harmony import */ var _formheader_formheaderview__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(/*! ./formheader/formheaderview */ "./node_modules/@ckeditor/ckeditor5-ui/src/formheader/formheaderview.js");
/* harmony import */ var _focuscycler__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(/*! ./focuscycler */ "./node_modules/@ckeditor/ckeditor5-ui/src/focuscycler.js");
/* harmony import */ var _icon_iconview__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(/*! ./icon/iconview */ "./node_modules/@ckeditor/ckeditor5-ui/src/icon/iconview.js");
/* harmony import */ var _input_inputview__WEBPACK_IMPORTED_MODULE_18__ = __webpack_require__(/*! ./input/inputview */ "./node_modules/@ckeditor/ckeditor5-ui/src/input/inputview.js");
/* harmony import */ var _inputtext_inputtextview__WEBPACK_IMPORTED_MODULE_19__ = __webpack_require__(/*! ./inputtext/inputtextview */ "./node_modules/@ckeditor/ckeditor5-ui/src/inputtext/inputtextview.js");
/* harmony import */ var _inputnumber_inputnumberview__WEBPACK_IMPORTED_MODULE_20__ = __webpack_require__(/*! ./inputnumber/inputnumberview */ "./node_modules/@ckeditor/ckeditor5-ui/src/inputnumber/inputnumberview.js");
/* harmony import */ var _iframe_iframeview__WEBPACK_IMPORTED_MODULE_21__ = __webpack_require__(/*! ./iframe/iframeview */ "./node_modules/@ckeditor/ckeditor5-ui/src/iframe/iframeview.js");
/* harmony import */ var _label_labelview__WEBPACK_IMPORTED_MODULE_22__ = __webpack_require__(/*! ./label/labelview */ "./node_modules/@ckeditor/ckeditor5-ui/src/label/labelview.js");
/* harmony import */ var _labeledfield_labeledfieldview__WEBPACK_IMPORTED_MODULE_23__ = __webpack_require__(/*! ./labeledfield/labeledfieldview */ "./node_modules/@ckeditor/ckeditor5-ui/src/labeledfield/labeledfieldview.js");
/* harmony import */ var _labeledfield_utils__WEBPACK_IMPORTED_MODULE_24__ = __webpack_require__(/*! ./labeledfield/utils */ "./node_modules/@ckeditor/ckeditor5-ui/src/labeledfield/utils.js");
/* harmony import */ var _list_listitemview__WEBPACK_IMPORTED_MODULE_25__ = __webpack_require__(/*! ./list/listitemview */ "./node_modules/@ckeditor/ckeditor5-ui/src/list/listitemview.js");
/* harmony import */ var _list_listview__WEBPACK_IMPORTED_MODULE_26__ = __webpack_require__(/*! ./list/listview */ "./node_modules/@ckeditor/ckeditor5-ui/src/list/listview.js");
/* harmony import */ var _notification_notification__WEBPACK_IMPORTED_MODULE_27__ = __webpack_require__(/*! ./notification/notification */ "./node_modules/@ckeditor/ckeditor5-ui/src/notification/notification.js");
/* harmony import */ var _model__WEBPACK_IMPORTED_MODULE_28__ = __webpack_require__(/*! ./model */ "./node_modules/@ckeditor/ckeditor5-ui/src/model.js");
/* harmony import */ var _panel_balloon_balloonpanelview__WEBPACK_IMPORTED_MODULE_29__ = __webpack_require__(/*! ./panel/balloon/balloonpanelview */ "./node_modules/@ckeditor/ckeditor5-ui/src/panel/balloon/balloonpanelview.js");
/* harmony import */ var _panel_balloon_contextualballoon__WEBPACK_IMPORTED_MODULE_30__ = __webpack_require__(/*! ./panel/balloon/contextualballoon */ "./node_modules/@ckeditor/ckeditor5-ui/src/panel/balloon/contextualballoon.js");
/* harmony import */ var _panel_sticky_stickypanelview__WEBPACK_IMPORTED_MODULE_31__ = __webpack_require__(/*! ./panel/sticky/stickypanelview */ "./node_modules/@ckeditor/ckeditor5-ui/src/panel/sticky/stickypanelview.js");
/* harmony import */ var _tooltip_tooltipview__WEBPACK_IMPORTED_MODULE_32__ = __webpack_require__(/*! ./tooltip/tooltipview */ "./node_modules/@ckeditor/ckeditor5-ui/src/tooltip/tooltipview.js");
/* harmony import */ var _template__WEBPACK_IMPORTED_MODULE_33__ = __webpack_require__(/*! ./template */ "./node_modules/@ckeditor/ckeditor5-ui/src/template.js");
/* harmony import */ var _toolbar_toolbarview__WEBPACK_IMPORTED_MODULE_34__ = __webpack_require__(/*! ./toolbar/toolbarview */ "./node_modules/@ckeditor/ckeditor5-ui/src/toolbar/toolbarview.js");
/* harmony import */ var _toolbar_toolbarseparatorview__WEBPACK_IMPORTED_MODULE_35__ = __webpack_require__(/*! ./toolbar/toolbarseparatorview */ "./node_modules/@ckeditor/ckeditor5-ui/src/toolbar/toolbarseparatorview.js");
/* harmony import */ var _toolbar_enabletoolbarkeyboardfocus__WEBPACK_IMPORTED_MODULE_36__ = __webpack_require__(/*! ./toolbar/enabletoolbarkeyboardfocus */ "./node_modules/@ckeditor/ckeditor5-ui/src/toolbar/enabletoolbarkeyboardfocus.js");
/* harmony import */ var _toolbar_normalizetoolbarconfig__WEBPACK_IMPORTED_MODULE_37__ = __webpack_require__(/*! ./toolbar/normalizetoolbarconfig */ "./node_modules/@ckeditor/ckeditor5-ui/src/toolbar/normalizetoolbarconfig.js");
/* harmony import */ var _toolbar_balloon_balloontoolbar__WEBPACK_IMPORTED_MODULE_38__ = __webpack_require__(/*! ./toolbar/balloon/balloontoolbar */ "./node_modules/@ckeditor/ckeditor5-ui/src/toolbar/balloon/balloontoolbar.js");
/* harmony import */ var _toolbar_block_blocktoolbar__WEBPACK_IMPORTED_MODULE_39__ = __webpack_require__(/*! ./toolbar/block/blocktoolbar */ "./node_modules/@ckeditor/ckeditor5-ui/src/toolbar/block/blocktoolbar.js");
/* harmony import */ var _view__WEBPACK_IMPORTED_MODULE_40__ = __webpack_require__(/*! ./view */ "./node_modules/@ckeditor/ckeditor5-ui/src/view.js");
/* harmony import */ var _viewcollection__WEBPACK_IMPORTED_MODULE_41__ = __webpack_require__(/*! ./viewcollection */ "./node_modules/@ckeditor/ckeditor5-ui/src/viewcollection.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module ui
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/src/input/inputview.js":
/*!********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/src/input/inputview.js ***!
\********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ InputView)
/* harmony export */ });
/* harmony import */ var _view__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../view */ "./node_modules/@ckeditor/ckeditor5-ui/src/view.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_focustracker__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/focustracker */ "./node_modules/@ckeditor/ckeditor5-utils/src/focustracker.js");
/* harmony import */ var _theme_components_input_input_css__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../theme/components/input/input.css */ "./node_modules/@ckeditor/ckeditor5-ui/theme/components/input/input.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module ui/input/inputview
*/
/**
* The base input view class.
*
* @extends module:ui/view~View
*/
class InputView extends _view__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* @inheritDoc
*/
constructor( locale ) {
super( locale );
/**
* The value of the input.
*
* @observable
* @member {String} #value
*/
this.set( 'value' );
/**
* The `id` attribute of the input (i.e. to pair with a `<label>` element).
*
* @observable
* @member {String} #id
*/
this.set( 'id' );
/**
* The `placeholder` attribute of the input.
*
* @observable
* @member {String} #placeholder
*/
this.set( 'placeholder' );
/**
* Controls whether the input view is in read-only mode.
*
* @observable
* @member {Boolean} #isReadOnly
*/
this.set( 'isReadOnly', false );
/**
* Set to `true` when the field has some error. Usually controlled via
* {@link module:ui/labeledinput/labeledinputview~LabeledInputView#errorText}.
*
* @observable
* @member {Boolean} #hasError
*/
this.set( 'hasError', false );
/**
* The `id` of the element describing this field, e.g. when it has
* some error; it helps screen readers read the error text.
*
* @observable
* @member {Boolean} #ariaDescribedById
*/
this.set( 'ariaDescribedById' );
/**
* Stores information about the editor UI focus and propagates it so various plugins and components
* are unified as a focus group.
*
* @readonly
* @member {module:utils/focustracker~FocusTracker} #focusTracker
*/
this.focusTracker = new _ckeditor_ckeditor5_utils_src_focustracker__WEBPACK_IMPORTED_MODULE_1__["default"]();
/**
* An observable flag set to `true` when the input is currently focused by the user.
* Set to `false` otherwise.
*
* @readonly
* @observable
* @member {Boolean} #isFocused
* @default false
*/
this.bind( 'isFocused' ).to( this.focusTracker );
/**
* An observable flag set to `true` when the input contains no text, i.e.
* when {@link #value} is `''`, `null`, or `false`.
*
* @readonly
* @observable
* @member {Boolean} #isEmpty
* @default true
*/
this.set( 'isEmpty', true );
/**
* Corresponds to the `inputmode` DOM attribute. Can be `text`, `numeric`, `decimal`, etc.
*
* @observable
* @member {Boolean} #inputMode
* @default 'text'
*/
this.set( 'inputMode', 'text' );
const bind = this.bindTemplate;
this.setTemplate( {
tag: 'input',
attributes: {
class: [
'ck',
'ck-input',
bind.if( 'isFocused', 'ck-input_focused' ),
bind.if( 'isEmpty', 'ck-input-text_empty' ),
bind.if( 'hasError', 'ck-error' )
],
id: bind.to( 'id' ),
placeholder: bind.to( 'placeholder' ),
readonly: bind.to( 'isReadOnly' ),
inputmode: bind.to( 'inputMode' ),
'aria-invalid': bind.if( 'hasError', true ),
'aria-describedby': bind.to( 'ariaDescribedById' )
},
on: {
input: bind.to( ( ...args ) => {
this.fire( 'input', ...args );
this._updateIsEmpty();
} ),
change: bind.to( this._updateIsEmpty.bind( this ) )
}
} );
/**
* Fired when the user types in the input. Corresponds to the native
* DOM `input` event.
*
* @event input
*/
}
/**
* @inheritDoc
*/
render() {
super.render();
this.focusTracker.add( this.element );
this._setDomElementValue( this.value );
this._updateIsEmpty();
// Bind `this.value` to the DOM element's value.
// We cannot use `value` DOM attribute because removing it on Edge does not clear the DOM element's value property.
this.on( 'change:value', ( evt, name, value ) => {
this._setDomElementValue( value );
this._updateIsEmpty();
} );
}
/**
* @inheritDoc
*/
destroy() {
super.destroy();
this.focusTracker.destroy();
}
/**
* Moves the focus to the input and selects the value.
*/
select() {
this.element.select();
}
/**
* Focuses the input.
*/
focus() {
this.element.focus();
}
/**
* Updates the {@link #isEmpty} property value on demand.
*
* @private
*/
_updateIsEmpty() {
this.isEmpty = isInputElementEmpty( this.element );
}
/**
* Sets the `value` property of the {@link #element DOM element} on demand.
*
* @private
*/
_setDomElementValue( value ) {
this.element.value = ( !value && value !== 0 ) ? '' : value;
}
}
function isInputElementEmpty( domElement ) {
return !domElement.value;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/src/inputnumber/inputnumberview.js":
/*!********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/src/inputnumber/inputnumberview.js ***!
\********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ InputNumberView)
/* harmony export */ });
/* harmony import */ var _input_inputview__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../input/inputview */ "./node_modules/@ckeditor/ckeditor5-ui/src/input/inputview.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module ui/inputnumber/inputnumberview
*/
/**
* The number input view class.
*
* @extends module:ui/input/inputview~InputView
*/
class InputNumberView extends _input_inputview__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* Creates an instance of the input number view.
*
* @param {module:utils/locale~Locale} locale The {@link module:core/editor/editor~Editor#locale} instance.
* @param {Object} [options] The options of the input.
* @param {Number} [options.min] The value of the `min` DOM attribute (the lowest accepted value).
* @param {Number} [options.max] The value of the `max` DOM attribute (the highest accepted value).
* @param {Number} [options.step] The value of the `step` DOM attribute.
*/
constructor( locale, { min, max, step } = {} ) {
super( locale );
const bind = this.bindTemplate;
/**
* The value of the `min` DOM attribute (the lowest accepted value) set on the {@link #element}.
*
* @observable
* @default undefined
* @member {Number} #min
*/
this.set( 'min', min );
/**
* The value of the `max` DOM attribute (the highest accepted value) set on the {@link #element}.
*
* @observable
* @default undefined
* @member {Number} #max
*/
this.set( 'max', max );
/**
* The value of the `step` DOM attribute set on the {@link #element}.
*
* @observable
* @default undefined
* @member {Number} #step
*/
this.set( 'step', step );
this.extendTemplate( {
attributes: {
type: 'number',
class: [
'ck-input-number'
],
min: bind.to( 'min' ),
max: bind.to( 'max' ),
step: bind.to( 'step' )
}
} );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/src/inputtext/inputtextview.js":
/*!****************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/src/inputtext/inputtextview.js ***!
\****************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ InputTextView)
/* harmony export */ });
/* harmony import */ var _input_inputview__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../input/inputview */ "./node_modules/@ckeditor/ckeditor5-ui/src/input/inputview.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module ui/inputtext/inputtextview
*/
/**
* The text input view class.
*
* @extends module:ui/input/inputview~InputView
*/
class InputTextView extends _input_inputview__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* @inheritDoc
*/
constructor( locale ) {
super( locale );
this.extendTemplate( {
attributes: {
type: 'text',
class: [
'ck-input-text'
]
}
} );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/src/label/labelview.js":
/*!********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/src/label/labelview.js ***!
\********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ LabelView)
/* harmony export */ });
/* harmony import */ var _view__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../view */ "./node_modules/@ckeditor/ckeditor5-ui/src/view.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_uid__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/uid */ "./node_modules/@ckeditor/ckeditor5-utils/src/uid.js");
/* harmony import */ var _theme_components_label_label_css__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../theme/components/label/label.css */ "./node_modules/@ckeditor/ckeditor5-ui/theme/components/label/label.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module ui/label/labelview
*/
/**
* The label view class.
*
* @extends module:ui/view~View
*/
class LabelView extends _view__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* @inheritDoc
*/
constructor( locale ) {
super( locale );
/**
* The text of the label.
*
* @observable
* @member {String} #text
*/
this.set( 'text' );
/**
* The `for` attribute of the label (i.e. to pair with an `<input>` element).
*
* @observable
* @member {String} #for
*/
this.set( 'for' );
/**
* An unique id of the label. It can be used by other UI components to reference
* the label, for instance, using the `aria-describedby` DOM attribute.
*
* @member {String} #id
*/
this.id = `ck-editor__label_${ (0,_ckeditor_ckeditor5_utils_src_uid__WEBPACK_IMPORTED_MODULE_1__["default"])() }`;
const bind = this.bindTemplate;
this.setTemplate( {
tag: 'label',
attributes: {
class: [
'ck',
'ck-label'
],
id: this.id,
for: bind.to( 'for' )
},
children: [
{
text: bind.to( 'text' )
}
]
} );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/src/labeledfield/labeledfieldview.js":
/*!**********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/src/labeledfield/labeledfieldview.js ***!
\**********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ LabeledFieldView)
/* harmony export */ });
/* harmony import */ var _view__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../view */ "./node_modules/@ckeditor/ckeditor5-ui/src/view.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_uid__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/uid */ "./node_modules/@ckeditor/ckeditor5-utils/src/uid.js");
/* harmony import */ var _label_labelview__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../label/labelview */ "./node_modules/@ckeditor/ckeditor5-ui/src/label/labelview.js");
/* harmony import */ var _theme_components_labeledfield_labeledfieldview_css__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../../theme/components/labeledfield/labeledfieldview.css */ "./node_modules/@ckeditor/ckeditor5-ui/theme/components/labeledfield/labeledfieldview.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module ui/labeledfield/labeledfieldview
*/
/**
* The labeled field view class. It can be used to enhance any view with the following features:
*
* * a label,
* * (optional) an error message,
* * (optional) an info (status) text,
*
* all bound logically by proper DOM attributes for UX and accessibility. It also provides an interface
* (e.g. observable properties) that allows controlling those additional features.
*
* The constructor of this class requires a callback that returns a view to be labeled. The callback
* is called with unique ids that allow binding of DOM properties:
*
* const labeledInputView = new LabeledFieldView( locale, ( labeledFieldView, viewUid, statusUid ) => {
* const inputView = new InputTextView( labeledFieldView.locale );
*
* inputView.set( {
* id: viewUid,
* ariaDescribedById: statusUid
* } );
*
* inputView.bind( 'isReadOnly' ).to( labeledFieldView, 'isEnabled', value => !value );
* inputView.bind( 'hasError' ).to( labeledFieldView, 'errorText', value => !!value );
*
* return inputView;
* } );
*
* labeledInputView.label = 'User name';
* labeledInputView.infoText = 'Full name like for instance, John Doe.';
* labeledInputView.render();
*
* document.body.append( labeledInputView.element );
*
* See {@link module:ui/labeledfield/utils} to discover ready–to–use labeled input helpers for common
* UI components.
*
* @extends module:ui/view~View
*/
class LabeledFieldView extends _view__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* Creates an instance of the labeled field view class using a provided creator function
* that provides the view to be labeled.
*
* @param {module:utils/locale~Locale} locale The locale instance.
* @param {Function} viewCreator A function that returns a {@link module:ui/view~View}
* that will be labeled. The following arguments are passed to the creator function:
*
* * an instance of the `LabeledFieldView` to allow binding observable properties,
* * an UID string that connects the {@link #labelView label} and the labeled field view in DOM,
* * an UID string that connects the {@link #statusView status} and the labeled field view in DOM.
*/
constructor( locale, viewCreator ) {
super( locale );
const viewUid = `ck-labeled-field-view-${ (0,_ckeditor_ckeditor5_utils_src_uid__WEBPACK_IMPORTED_MODULE_1__["default"])() }`;
const statusUid = `ck-labeled-field-view-status-${ (0,_ckeditor_ckeditor5_utils_src_uid__WEBPACK_IMPORTED_MODULE_1__["default"])() }`;
/**
* The field view that gets labeled.
*
* @member {module:ui/view~View} #fieldView
*/
this.fieldView = viewCreator( this, viewUid, statusUid );
/**
* The text of the label.
*
* @observable
* @member {String} #label
*/
this.set( 'label' );
/**
* Controls whether the component is in read-only mode.
*
* @observable
* @member {Boolean} #isEnabled
*/
this.set( 'isEnabled', true );
/**
* An observable flag set to `true` when {@link #fieldView} is empty (`false` otherwise).
*
* @readonly
* @observable
* @member {Boolean} #isEmpty
* @default true
*/
this.set( 'isEmpty', true );
/**
* An observable flag set to `true` when {@link #fieldView} is currently focused by
* the user (`false` otherwise).
*
* @readonly
* @observable
* @member {Boolean} #isFocused
* @default false
*/
this.set( 'isFocused', false );
/**
* The validation error text. When set, it will be displayed
* next to the {@link #fieldView} as a typical validation error message.
* Set it to `null` to hide the message.
*
* **Note:** Setting this property to anything but `null` will automatically
* make the `hasError` of the {@link #fieldView} `true`.
*
* @observable
* @member {String|null} #errorText
*/
this.set( 'errorText', null );
/**
* The additional information text displayed next to the {@link #fieldView} which can
* be used to inform the user about its purpose, provide help or hints.
*
* Set it to `null` to hide the message.
*
* **Note:** This text will be displayed in the same place as {@link #errorText} but the
* latter always takes precedence: if the {@link #errorText} is set, it replaces
* {@link #infoText}.
*
* @observable
* @member {String|null} #infoText
* @default null
*/
this.set( 'infoText', null );
/**
* (Optional) The additional CSS class set on the dropdown {@link #element}.
*
* @observable
* @member {String} #class
*/
this.set( 'class' );
/**
* The content of the `placeholder` attribute of the {@link #fieldView}.
*
* @observable
* @member {String} #placeholder
*/
this.set( 'placeholder' );
/**
* The label view instance that describes the entire view.
*
* @member {module:ui/label/labelview~LabelView} #labelView
*/
this.labelView = this._createLabelView( viewUid );
/**
* The status view for the {@link #fieldView}. It displays {@link #errorText} and
* {@link #infoText}.
*
* @member {module:ui/view~View} #statusView
*/
this.statusView = this._createStatusView( statusUid );
/**
* The combined status text made of {@link #errorText} and {@link #infoText}.
* Note that when present, {@link #errorText} always takes precedence in the
* status.
*
* @see #errorText
* @see #infoText
* @see #statusView
* @private
* @observable
* @member {String|null} #_statusText
*/
this.bind( '_statusText' ).to(
this, 'errorText',
this, 'infoText',
( errorText, infoText ) => errorText || infoText
);
const bind = this.bindTemplate;
this.setTemplate( {
tag: 'div',
attributes: {
class: [
'ck',
'ck-labeled-field-view',
bind.to( 'class' ),
bind.if( 'isEnabled', 'ck-disabled', value => !value ),
bind.if( 'isEmpty', 'ck-labeled-field-view_empty' ),
bind.if( 'isFocused', 'ck-labeled-field-view_focused' ),
bind.if( 'placeholder', 'ck-labeled-field-view_placeholder' ),
bind.if( 'errorText', 'ck-error' )
]
},
children: [
{
tag: 'div',
attributes: {
class: [
'ck',
'ck-labeled-field-view__input-wrapper'
]
},
children: [
this.fieldView,
this.labelView
]
},
this.statusView
]
} );
}
/**
* Creates label view class instance and bind with view.
*
* @private
* @param {String} id Unique id to set as labelView#for attribute.
* @returns {module:ui/label/labelview~LabelView}
*/
_createLabelView( id ) {
const labelView = new _label_labelview__WEBPACK_IMPORTED_MODULE_2__["default"]( this.locale );
labelView.for = id;
labelView.bind( 'text' ).to( this, 'label' );
return labelView;
}
/**
* Creates the status view instance. It displays {@link #errorText} and {@link #infoText}
* next to the {@link #fieldView}. See {@link #_statusText}.
*
* @private
* @param {String} statusUid Unique id of the status, shared with the {@link #fieldView view's}
* `aria-describedby` attribute.
* @returns {module:ui/view~View}
*/
_createStatusView( statusUid ) {
const statusView = new _view__WEBPACK_IMPORTED_MODULE_0__["default"]( this.locale );
const bind = this.bindTemplate;
statusView.setTemplate( {
tag: 'div',
attributes: {
class: [
'ck',
'ck-labeled-field-view__status',
bind.if( 'errorText', 'ck-labeled-field-view__status_error' ),
bind.if( '_statusText', 'ck-hidden', value => !value )
],
id: statusUid,
role: bind.if( 'errorText', 'alert' )
},
children: [
{
text: bind.to( '_statusText' )
}
]
} );
return statusView;
}
/**
* Focuses the {@link #fieldView}.
*/
focus() {
this.fieldView.focus();
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/src/labeledfield/utils.js":
/*!***********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/src/labeledfield/utils.js ***!
\***********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "createLabeledDropdown": () => (/* binding */ createLabeledDropdown),
/* harmony export */ "createLabeledInputNumber": () => (/* binding */ createLabeledInputNumber),
/* harmony export */ "createLabeledInputText": () => (/* binding */ createLabeledInputText)
/* harmony export */ });
/* harmony import */ var _inputtext_inputtextview__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../inputtext/inputtextview */ "./node_modules/@ckeditor/ckeditor5-ui/src/inputtext/inputtextview.js");
/* harmony import */ var _inputnumber_inputnumberview__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../inputnumber/inputnumberview */ "./node_modules/@ckeditor/ckeditor5-ui/src/inputnumber/inputnumberview.js");
/* harmony import */ var _dropdown_utils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../dropdown/utils */ "./node_modules/@ckeditor/ckeditor5-ui/src/dropdown/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module ui/labeledfield/utils
*/
/**
* A helper for creating labeled inputs.
*
* It creates an instance of a {@link module:ui/inputtext/inputtextview~InputTextView input text} that is
* logically related to a {@link module:ui/labeledfield/labeledfieldview~LabeledFieldView labeled view} in DOM.
*
* The helper does the following:
*
* * It sets input's `id` and `ariaDescribedById` attributes.
* * It binds input's `isReadOnly` to the labeled view.
* * It binds input's `hasError` to the labeled view.
* * It enables a logic that cleans up the error when user starts typing in the input.
*
* Usage:
*
* const labeledInputView = new LabeledFieldView( locale, createLabeledInputText );
* console.log( labeledInputView.fieldView ); // A text input instance.
*
* @param {module:ui/labeledfield/labeledfieldview~LabeledFieldView} labeledFieldView The instance of the labeled field view.
* @param {String} viewUid An UID string that allows DOM logical connection between the
* {@link module:ui/labeledfield/labeledfieldview~LabeledFieldView#labelView labeled view's label} and the input.
* @param {String} statusUid An UID string that allows DOM logical connection between the
* {@link module:ui/labeledfield/labeledfieldview~LabeledFieldView#statusView labeled view's status} and the input.
* @returns {module:ui/inputtext/inputtextview~InputTextView} The input text view instance.
*/
function createLabeledInputText( labeledFieldView, viewUid, statusUid ) {
const inputView = new _inputtext_inputtextview__WEBPACK_IMPORTED_MODULE_0__["default"]( labeledFieldView.locale );
inputView.set( {
id: viewUid,
ariaDescribedById: statusUid
} );
inputView.bind( 'isReadOnly' ).to( labeledFieldView, 'isEnabled', value => !value );
inputView.bind( 'hasError' ).to( labeledFieldView, 'errorText', value => !!value );
inputView.on( 'input', () => {
// UX: Make the error text disappear and disable the error indicator as the user
// starts fixing the errors.
labeledFieldView.errorText = null;
} );
labeledFieldView.bind( 'isEmpty', 'isFocused', 'placeholder' ).to( inputView );
return inputView;
}
/**
* A helper for creating labeled number inputs.
*
* It creates an instance of a {@link module:ui/inputnumber/inputnumberview~InputNumberView input number} that is
* logically related to a {@link module:ui/labeledfield/labeledfieldview~LabeledFieldView labeled view} in DOM.
*
* The helper does the following:
*
* * It sets input's `id` and `ariaDescribedById` attributes.
* * It binds input's `isReadOnly` to the labeled view.
* * It binds input's `hasError` to the labeled view.
* * It enables a logic that cleans up the error when user starts typing in the input.
*
* Usage:
*
* const labeledInputView = new LabeledFieldView( locale, createLabeledInputNumber );
* console.log( labeledInputView.fieldView ); // A number input instance.
*
* @param {module:ui/labeledfield/labeledfieldview~LabeledFieldView} labeledFieldView The instance of the labeled field view.
* @param {String} viewUid An UID string that allows DOM logical connection between the
* {@link module:ui/labeledfield/labeledfieldview~LabeledFieldView#labelView labeled view's label} and the input.
* @param {String} statusUid An UID string that allows DOM logical connection between the
* {@link module:ui/labeledfield/labeledfieldview~LabeledFieldView#statusView labeled view's status} and the input.
* @returns {module:ui/inputnumber/inputnumberview~InputNumberView} The input number view instance.
*/
function createLabeledInputNumber( labeledFieldView, viewUid, statusUid ) {
const inputView = new _inputnumber_inputnumberview__WEBPACK_IMPORTED_MODULE_1__["default"]( labeledFieldView.locale );
inputView.set( {
id: viewUid,
ariaDescribedById: statusUid,
inputMode: 'numeric'
} );
inputView.bind( 'isReadOnly' ).to( labeledFieldView, 'isEnabled', value => !value );
inputView.bind( 'hasError' ).to( labeledFieldView, 'errorText', value => !!value );
inputView.on( 'input', () => {
// UX: Make the error text disappear and disable the error indicator as the user
// starts fixing the errors.
labeledFieldView.errorText = null;
} );
labeledFieldView.bind( 'isEmpty', 'isFocused', 'placeholder' ).to( inputView );
return inputView;
}
/**
* A helper for creating labeled dropdowns.
*
* It creates an instance of a {@link module:ui/dropdown/dropdownview~DropdownView dropdown} that is
* logically related to a {@link module:ui/labeledfield/labeledfieldview~LabeledFieldView labeled field view}.
*
* The helper does the following:
*
* * It sets dropdown's `id` and `ariaDescribedById` attributes.
* * It binds input's `isEnabled` to the labeled view.
*
* Usage:
*
* const labeledInputView = new LabeledFieldView( locale, createLabeledDropdown );
* console.log( labeledInputView.fieldView ); // A dropdown instance.
*
* @param {module:ui/labeledfield/labeledfieldview~LabeledFieldView} labeledFieldView The instance of the labeled field view.
* @param {String} viewUid An UID string that allows DOM logical connection between the
* {@link module:ui/labeledfield/labeledfieldview~LabeledFieldView#labelView labeled view label} and the dropdown.
* @param {String} statusUid An UID string that allows DOM logical connection between the
* {@link module:ui/labeledfield/labeledfieldview~LabeledFieldView#statusView labeled view status} and the dropdown.
* @returns {module:ui/dropdown/dropdownview~DropdownView} The dropdown view instance.
*/
function createLabeledDropdown( labeledFieldView, viewUid, statusUid ) {
const dropdownView = (0,_dropdown_utils__WEBPACK_IMPORTED_MODULE_2__.createDropdown)( labeledFieldView.locale );
dropdownView.set( {
id: viewUid,
ariaDescribedById: statusUid
} );
dropdownView.bind( 'isEnabled' ).to( labeledFieldView );
return dropdownView;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/src/list/listitemview.js":
/*!**********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/src/list/listitemview.js ***!
\**********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ListItemView)
/* harmony export */ });
/* harmony import */ var _view__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../view */ "./node_modules/@ckeditor/ckeditor5-ui/src/view.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module ui/list/listitemview
*/
/**
* The list item view class.
*
* @extends module:ui/view~View
*/
class ListItemView extends _view__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* @inheritDoc
*/
constructor( locale ) {
super( locale );
/**
* Collection of the child views inside of the list item {@link #element}.
*
* @readonly
* @member {module:ui/viewcollection~ViewCollection}
*/
this.children = this.createCollection();
this.setTemplate( {
tag: 'li',
attributes: {
class: [
'ck',
'ck-list__item'
]
},
children: this.children
} );
}
/**
* Focuses the list item.
*/
focus() {
this.children.first.focus();
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/src/list/listseparatorview.js":
/*!***************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/src/list/listseparatorview.js ***!
\***************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ListSeparatorView)
/* harmony export */ });
/* harmony import */ var _view__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../view */ "./node_modules/@ckeditor/ckeditor5-ui/src/view.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module ui/list/listseparatorview
*/
/**
* The list separator view class.
*
* @extends module:ui/view~View
*/
class ListSeparatorView extends _view__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* @inheritDoc
*/
constructor( locale ) {
super( locale );
this.setTemplate( {
tag: 'li',
attributes: {
class: [
'ck',
'ck-list__separator'
]
}
} );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/src/list/listview.js":
/*!******************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/src/list/listview.js ***!
\******************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ListView)
/* harmony export */ });
/* harmony import */ var _view__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../view */ "./node_modules/@ckeditor/ckeditor5-ui/src/view.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_focustracker__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/focustracker */ "./node_modules/@ckeditor/ckeditor5-utils/src/focustracker.js");
/* harmony import */ var _focuscycler__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../focuscycler */ "./node_modules/@ckeditor/ckeditor5-ui/src/focuscycler.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_keystrokehandler__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/keystrokehandler */ "./node_modules/@ckeditor/ckeditor5-utils/src/keystrokehandler.js");
/* harmony import */ var _theme_components_list_list_css__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../../theme/components/list/list.css */ "./node_modules/@ckeditor/ckeditor5-ui/theme/components/list/list.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module ui/list/listview
*/
/**
* The list view class.
*
* @extends module:ui/view~View
* @implements module:ui/dropdown/dropdownpanelfocusable~DropdownPanelFocusable
*/
class ListView extends _view__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* @inheritDoc
*/
constructor() {
super();
/**
* Collection of the child list views.
*
* @readonly
* @member {module:ui/viewcollection~ViewCollection}
*/
this.items = this.createCollection();
/**
* Tracks information about DOM focus in the list.
*
* @readonly
* @member {module:utils/focustracker~FocusTracker}
*/
this.focusTracker = new _ckeditor_ckeditor5_utils_src_focustracker__WEBPACK_IMPORTED_MODULE_1__["default"]();
/**
* Instance of the {@link module:utils/keystrokehandler~KeystrokeHandler}.
*
* @readonly
* @member {module:utils/keystrokehandler~KeystrokeHandler}
*/
this.keystrokes = new _ckeditor_ckeditor5_utils_src_keystrokehandler__WEBPACK_IMPORTED_MODULE_3__["default"]();
/**
* Helps cycling over focusable {@link #items} in the list.
*
* @readonly
* @protected
* @member {module:ui/focuscycler~FocusCycler}
*/
this._focusCycler = new _focuscycler__WEBPACK_IMPORTED_MODULE_2__["default"]( {
focusables: this.items,
focusTracker: this.focusTracker,
keystrokeHandler: this.keystrokes,
actions: {
// Navigate list items backwards using the arrowup key.
focusPrevious: 'arrowup',
// Navigate toolbar items forwards using the arrowdown key.
focusNext: 'arrowdown'
}
} );
this.setTemplate( {
tag: 'ul',
attributes: {
class: [
'ck',
'ck-reset',
'ck-list'
]
},
children: this.items
} );
}
/**
* @inheritDoc
*/
render() {
super.render();
// Items added before rendering should be known to the #focusTracker.
for ( const item of this.items ) {
this.focusTracker.add( item.element );
}
this.items.on( 'add', ( evt, item ) => {
this.focusTracker.add( item.element );
} );
this.items.on( 'remove', ( evt, item ) => {
this.focusTracker.remove( item.element );
} );
// Start listening for the keystrokes coming from #element.
this.keystrokes.listenTo( this.element );
}
/**
* @inheritDoc
*/
destroy() {
super.destroy();
this.focusTracker.destroy();
this.keystrokes.destroy();
}
/**
* Focuses the first focusable in {@link #items}.
*/
focus() {
this._focusCycler.focusFirst();
}
/**
* Focuses the last focusable in {@link #items}.
*/
focusLast() {
this._focusCycler.focusLast();
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/src/model.js":
/*!**********************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/src/model.js ***!
\**********************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ Model)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/mix */ "./node_modules/@ckeditor/ckeditor5-utils/src/mix.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_observablemixin__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/observablemixin */ "./node_modules/@ckeditor/ckeditor5-utils/src/observablemixin.js");
/* harmony import */ var lodash_es__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! lodash-es */ "./node_modules/lodash-es/assignIn.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module ui/model
*/
/**
* The base MVC model class.
*
* @mixes module:utils/observablemixin~ObservableMixin
*/
class Model {
/**
* Creates a new Model instance.
*
* @param {Object} [attributes] The model state attributes to be defined during the instance creation.
* @param {Object} [properties] The (out of state) properties to be appended to the instance during creation.
*/
constructor( attributes, properties ) {
// Extend this instance with the additional (out of state) properties.
if ( properties ) {
(0,lodash_es__WEBPACK_IMPORTED_MODULE_2__["default"])( this, properties );
}
// Initialize the attributes.
if ( attributes ) {
this.set( attributes );
}
}
}
(0,_ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_0__["default"])( Model, _ckeditor_ckeditor5_utils_src_observablemixin__WEBPACK_IMPORTED_MODULE_1__["default"] );
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/src/notification/notification.js":
/*!******************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/src/notification/notification.js ***!
\******************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ Notification)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_core_src_contextplugin__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-core/src/contextplugin */ "./node_modules/@ckeditor/ckeditor5-core/src/contextplugin.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module ui/notification/notification
*/
/* globals window */
/**
* The Notification plugin.
*
* This plugin sends a few types of notifications: `success`, `info` and `warning`. The notifications need to be
* handled and displayed by a plugin responsible for showing the UI of the notifications. Using this plugin for dispatching
* notifications makes it possible to switch the notifications UI.
*
* Note that every unhandled and not stopped `warning` notification will be displayed as a system alert.
* See {@link module:ui/notification/notification~Notification#showWarning}.
*
* @extends module:core/contextplugin~ContextPlugin
*/
class Notification extends _ckeditor_ckeditor5_core_src_contextplugin__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* @inheritDoc
*/
static get pluginName() {
return 'Notification';
}
/**
* @inheritDoc
*/
init() {
// Each unhandled and not stopped `show:warning` event is displayed as a system alert.
this.on( 'show:warning', ( evt, data ) => {
window.alert( data.message ); // eslint-disable-line no-alert
}, { priority: 'lowest' } );
}
/**
* Shows a success notification.
*
* By default, it fires the {@link #event:show:success `show:success` event} with the given `data`. The event namespace can be extended
* using the `data.namespace` option. For example:
*
* showSuccess( 'Image is uploaded.', {
* namespace: 'upload:image'
* } );
*
* will fire the `show:success:upload:image` event.
*
* You can provide the title of the notification:
*
* showSuccess( 'Image is uploaded.', {
* title: 'Image upload success'
* } );
*
* @param {String} message The content of the notification.
* @param {Object} [data={}] Additional data.
* @param {String} [data.namespace] Additional event namespace.
* @param {String} [data.title] The title of the notification.
*/
showSuccess( message, data = {} ) {
this._showNotification( {
message,
type: 'success',
namespace: data.namespace,
title: data.title
} );
}
/**
* Shows an information notification.
*
* By default, it fires the {@link #event:show:info `show:info` event} with the given `data`. The event namespace can be extended
* using the `data.namespace` option. For example:
*
* showInfo( 'Editor is offline.', {
* namespace: 'editor:status'
* } );
*
* will fire the `show:info:editor:status` event.
*
* You can provide the title of the notification:
*
* showInfo( 'Editor is offline.', {
* title: 'Network information'
* } );
*
* @param {String} message The content of the notification.
* @param {Object} [data={}] Additional data.
* @param {String} [data.namespace] Additional event namespace.
* @param {String} [data.title] The title of the notification.
*/
showInfo( message, data = {} ) {
this._showNotification( {
message,
type: 'info',
namespace: data.namespace,
title: data.title
} );
}
/**
* Shows a warning notification.
*
* By default, it fires the {@link #event:show:warning `show:warning` event}
* with the given `data`. The event namespace can be extended using the `data.namespace` option. For example:
*
* showWarning( 'Image upload error.', {
* namespace: 'upload:image'
* } );
*
* will fire the `show:warning:upload:image` event.
*
* You can provide the title of the notification:
*
* showWarning( 'Image upload error.', {
* title: 'Upload failed'
* } );
*
* Note that each unhandled and not stopped `warning` notification will be displayed as a system alert.
* The plugin responsible for displaying warnings should `stop()` the event to prevent displaying it as an alert:
*
* notifications.on( 'show:warning', ( evt, data ) => {
* // Do something with the data.
*
* // Stop this event to prevent displaying it as an alert.
* evt.stop();
* } );
*
* You can attach many listeners to the same event and `stop()` this event in a listener with a low priority:
*
* notifications.on( 'show:warning', ( evt, data ) => {
* // Show the warning in the UI, but do not stop it.
* } );
*
* notifications.on( 'show:warning', ( evt, data ) => {
* // Log the warning to some error tracker.
*
* // Stop this event to prevent displaying it as an alert.
* evt.stop();
* }, { priority: 'low' } );
*
* @param {String} message The content of the notification.
* @param {Object} [data={}] Additional data.
* @param {String} [data.namespace] Additional event namespace.
* @param {String} [data.title] The title of the notification.
*/
showWarning( message, data = {} ) {
this._showNotification( {
message,
type: 'warning',
namespace: data.namespace,
title: data.title
} );
}
/**
* Fires the `show` event with the specified type, namespace and message.
*
* @private
* @param {Object} data The message data.
* @param {String} data.message The content of the notification.
* @param {'success'|'info'|'warning'} data.type The type of the message.
* @param {String} [data.namespace] Additional event namespace.
* @param {String} [data.title=''] The title of the notification.
*/
_showNotification( data ) {
const event = `show:${ data.type }` + ( data.namespace ? `:${ data.namespace }` : '' );
this.fire( event, {
message: data.message,
type: data.type,
title: data.title || ''
} );
}
/**
* Fired when one of the `showSuccess()`, `showInfo()`, `showWarning()` methods is called.
*
* @event show
* @param {Object} data The notification data.
* @param {String} data.message The content of the notification.
* @param {String} data.title The title of the notification.
* @param {'success'|'info'|'warning'} data.type The type of the notification.
*/
/**
* Fired when the `showSuccess()` method is called.
*
* @event show:success
* @param {Object} data The notification data.
* @param {String} data.message The content of the notification.
* @param {String} data.title The title of the notification.
* @param {'success'} data.type The type of the notification.
*/
/**
* Fired when the `showInfo()` method is called.
*
* @event show:info
* @param {Object} data The notification data.
* @param {String} data.message The content of the notification.
* @param {String} data.title The title of the notification.
* @param {'info'} data.type The type of the notification.
*/
/**
* Fired when the `showWarning()` method is called.
*
* When this event is not handled or stopped by `event.stop()`, the `data.message` of this event will
* be automatically displayed as a system alert.
*
* @event show:warning
* @param {Object} data The notification data.
* @param {String} data.message The content of the notification.
* @param {String} data.title The title of the notification.
* @param {'warning'} data.type The type of the notification.
*/
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/src/panel/balloon/balloonpanelview.js":
/*!***********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/src/panel/balloon/balloonpanelview.js ***!
\***********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ BalloonPanelView),
/* harmony export */ "generatePositions": () => (/* binding */ generatePositions)
/* harmony export */ });
/* harmony import */ var _view__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../../view */ "./node_modules/@ckeditor/ckeditor5-ui/src/view.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_dom_position__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/dom/position */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/position.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_dom_isrange__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/dom/isrange */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/isrange.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_dom_tounit__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/dom/tounit */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/tounit.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_dom_global__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/dom/global */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/global.js");
/* harmony import */ var lodash_es__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! lodash-es */ "./node_modules/lodash-es/isElement.js");
/* harmony import */ var _theme_components_panel_balloonpanel_css__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../../../theme/components/panel/balloonpanel.css */ "./node_modules/@ckeditor/ckeditor5-ui/theme/components/panel/balloonpanel.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md.
*/
/**
* @module ui/panel/balloon/balloonpanelview
*/
const toPx = (0,_ckeditor_ckeditor5_utils_src_dom_tounit__WEBPACK_IMPORTED_MODULE_3__["default"])( 'px' );
const defaultLimiterElement = _ckeditor_ckeditor5_utils_src_dom_global__WEBPACK_IMPORTED_MODULE_4__["default"].document.body;
/**
* The balloon panel view class.
*
* A floating container which can
* {@link module:ui/panel/balloon/balloonpanelview~BalloonPanelView#pin pin} to any
* {@link module:utils/dom/position~Options#target target} in the DOM and remain in that position
* e.g. when the web page is scrolled.
*
* The balloon panel can be used to display contextual, non-blocking UI like forms, toolbars and
* the like in its {@link module:ui/panel/balloon/balloonpanelview~BalloonPanelView#content} view
* collection.
*
* There is a number of {@link module:ui/panel/balloon/balloonpanelview~BalloonPanelView.defaultPositions}
* that the balloon can use, automatically switching from one to another when the viewport space becomes
* scarce to keep the balloon visible to the user as long as it is possible. The balloon will also
* accept any custom position set provided by the user compatible with the
* {@link module:utils/dom/position~Options options}.
*
* const panel = new BalloonPanelView( locale );
* const childView = new ChildView();
* const positions = BalloonPanelView.defaultPositions;
*
* panel.render();
*
* // Add a child view to the panel's content collection.
* panel.content.add( childView );
*
* // Start pinning the panel to an element with the "target" id DOM.
* // The balloon will remain pinned until unpin() is called.
* panel.pin( {
* target: document.querySelector( '#target' ),
* positions: [
* positions.northArrowSouth,
* positions.southArrowNorth
* ]
* } );
*
* @extends module:ui/view~View
*/
class BalloonPanelView extends _view__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* @inheritDoc
*/
constructor( locale ) {
super( locale );
const bind = this.bindTemplate;
/**
* The absolute top position of the balloon panel in pixels.
*
* @observable
* @default 0
* @member {Number} #top
*/
this.set( 'top', 0 );
/**
* The absolute left position of the balloon panel in pixels.
*
* @observable
* @default 0
* @member {Number} #left
*/
this.set( 'left', 0 );
/**
* The balloon panel's current position. The position name is reflected in the CSS class set
* to the balloon, i.e. `.ck-balloon-panel_arrow_nw` for the "arrow_nw" position. The class
* controls the minor aspects of the balloon's visual appearance like the placement
* of an {@link #withArrow arrow}. To support a new position, an additional CSS must be created.
*
* Default position names correspond with
* {@link module:ui/panel/balloon/balloonpanelview~BalloonPanelView.defaultPositions}.
*
* See the {@link #attachTo} and {@link #pin} methods to learn about custom balloon positions.
*
* @observable
* @default 'arrow_nw'
* @member {'arrow_nw'|'arrow_ne'|'arrow_sw'|'arrow_se'} #position
*/
this.set( 'position', 'arrow_nw' );
/**
* Controls whether the balloon panel is visible or not.
*
* @observable
* @default false
* @member {Boolean} #isVisible
*/
this.set( 'isVisible', false );
/**
* Controls whether the balloon panel has an arrow. The presence of the arrow
* is reflected in the `ck-balloon-panel_with-arrow` CSS class.
*
* @observable
* @default true
* @member {Boolean} #withArrow
*/
this.set( 'withArrow', true );
/**
* An additional CSS class added to the {@link #element}.
*
* @observable
* @member {String} #class
*/
this.set( 'class' );
/**
* A callback that starts pinning the panel when {@link #isVisible} gets
* `true`. Used by {@link #pin}.
*
* @private
* @member {Function} #_pinWhenIsVisibleCallback
*/
/**
* A collection of the child views that creates the balloon panel contents.
*
* @readonly
* @member {module:ui/viewcollection~ViewCollection}
*/
this.content = this.createCollection();
this.setTemplate( {
tag: 'div',
attributes: {
class: [
'ck',
'ck-balloon-panel',
bind.to( 'position', value => `ck-balloon-panel_${ value }` ),
bind.if( 'isVisible', 'ck-balloon-panel_visible' ),
bind.if( 'withArrow', 'ck-balloon-panel_with-arrow' ),
bind.to( 'class' )
],
style: {
top: bind.to( 'top', toPx ),
left: bind.to( 'left', toPx )
}
},
children: this.content
} );
}
/**
* Shows the panel.
*
* See {@link #isVisible}.
*/
show() {
this.isVisible = true;
}
/**
* Hides the panel.
*
* See {@link #isVisible}.
*/
hide() {
this.isVisible = false;
}
/**
* Attaches the panel to a specified {@link module:utils/dom/position~Options#target} with a
* smart positioning heuristics that chooses from available positions to make sure the panel
* is visible to the user i.e. within the limits of the viewport.
*
* This method accepts configuration {@link module:utils/dom/position~Options options}
* to set the `target`, optional `limiter` and `positions` the balloon should choose from.
*
* const panel = new BalloonPanelView( locale );
* const positions = BalloonPanelView.defaultPositions;
*
* panel.render();
*
* // Attach the panel to an element with the "target" id DOM.
* panel.attachTo( {
* target: document.querySelector( '#target' ),
* positions: [
* positions.northArrowSouth,
* positions.southArrowNorth
* ]
* } );
*
* **Note**: Attaching the panel will also automatically {@link #show} it.
*
* **Note**: An attached panel will not follow its target when the window is scrolled or resized.
* See the {@link #pin} method for a more permanent positioning strategy.
*
* @param {module:utils/dom/position~Options} options Positioning options compatible with
* {@link module:utils/dom/position~getOptimalPosition}. Default `positions` array is
* {@link module:ui/panel/balloon/balloonpanelview~BalloonPanelView.defaultPositions}.
*/
attachTo( options ) {
this.show();
const defaultPositions = BalloonPanelView.defaultPositions;
const positionOptions = Object.assign( {}, {
element: this.element,
positions: [
defaultPositions.southArrowNorth,
defaultPositions.southArrowNorthMiddleWest,
defaultPositions.southArrowNorthMiddleEast,
defaultPositions.southArrowNorthWest,
defaultPositions.southArrowNorthEast,
defaultPositions.northArrowSouth,
defaultPositions.northArrowSouthMiddleWest,
defaultPositions.northArrowSouthMiddleEast,
defaultPositions.northArrowSouthWest,
defaultPositions.northArrowSouthEast,
defaultPositions.viewportStickyNorth
],
limiter: defaultLimiterElement,
fitInViewport: true
}, options );
const optimalPosition = BalloonPanelView._getOptimalPosition( positionOptions );
// Usually browsers make some problems with super accurate values like 104.345px
// so it is better to use int values.
const left = parseInt( optimalPosition.left );
const top = parseInt( optimalPosition.top );
const { name: position, config = {} } = optimalPosition;
const { withArrow = true } = config;
Object.assign( this, { top, left, position, withArrow } );
}
/**
* Works the same way as the {@link #attachTo} method except that the position of the panel is
* continuously updated when:
*
* * any ancestor of the {@link module:utils/dom/position~Options#target}
* or {@link module:utils/dom/position~Options#limiter} is scrolled,
* * the browser window gets resized or scrolled.
*
* Thanks to that, the panel always sticks to the {@link module:utils/dom/position~Options#target}
* and is immune to the changing environment.
*
* const panel = new BalloonPanelView( locale );
* const positions = BalloonPanelView.defaultPositions;
*
* panel.render();
*
* // Pin the panel to an element with the "target" id DOM.
* panel.pin( {
* target: document.querySelector( '#target' ),
* positions: [
* positions.northArrowSouth,
* positions.southArrowNorth
* ]
* } );
*
* To leave the pinned state, use the {@link #unpin} method.
*
* **Note**: Pinning the panel will also automatically {@link #show} it.
*
* @param {module:utils/dom/position~Options} options Positioning options compatible with
* {@link module:utils/dom/position~getOptimalPosition}. Default `positions` array is
* {@link module:ui/panel/balloon/balloonpanelview~BalloonPanelView.defaultPositions}.
*/
pin( options ) {
this.unpin();
this._pinWhenIsVisibleCallback = () => {
if ( this.isVisible ) {
this._startPinning( options );
} else {
this._stopPinning();
}
};
this._startPinning( options );
// Control the state of the listeners depending on whether the panel is visible
// or not.
// TODO: Use on() (https://github.com/ckeditor/ckeditor5-utils/issues/144).
this.listenTo( this, 'change:isVisible', this._pinWhenIsVisibleCallback );
}
/**
* Stops pinning the panel, as set up by {@link #pin}.
*/
unpin() {
if ( this._pinWhenIsVisibleCallback ) {
// Deactivate listeners attached by pin().
this._stopPinning();
// Deactivate the panel pin() control logic.
// TODO: Use off() (https://github.com/ckeditor/ckeditor5-utils/issues/144).
this.stopListening( this, 'change:isVisible', this._pinWhenIsVisibleCallback );
this._pinWhenIsVisibleCallback = null;
this.hide();
}
}
/**
* Starts managing the pinned state of the panel. See {@link #pin}.
*
* @private
* @param {module:utils/dom/position~Options} options Positioning options compatible with
* {@link module:utils/dom/position~getOptimalPosition}.
*/
_startPinning( options ) {
this.attachTo( options );
const targetElement = getDomElement( options.target );
const limiterElement = options.limiter ? getDomElement( options.limiter ) : defaultLimiterElement;
// Then we need to listen on scroll event of eny element in the document.
this.listenTo( _ckeditor_ckeditor5_utils_src_dom_global__WEBPACK_IMPORTED_MODULE_4__["default"].document, 'scroll', ( evt, domEvt ) => {
const scrollTarget = domEvt.target;
// The position needs to be updated if the positioning target is within the scrolled element.
const isWithinScrollTarget = targetElement && scrollTarget.contains( targetElement );
// The position needs to be updated if the positioning limiter is within the scrolled element.
const isLimiterWithinScrollTarget = limiterElement && scrollTarget.contains( limiterElement );
// The positioning target and/or limiter can be a Rect, object etc..
// There's no way to optimize the listener then.
if ( isWithinScrollTarget || isLimiterWithinScrollTarget || !targetElement || !limiterElement ) {
this.attachTo( options );
}
}, { useCapture: true } );
// We need to listen on window resize event and update position.
this.listenTo( _ckeditor_ckeditor5_utils_src_dom_global__WEBPACK_IMPORTED_MODULE_4__["default"].window, 'resize', () => {
this.attachTo( options );
} );
}
/**
* Stops managing the pinned state of the panel. See {@link #pin}.
*
* @private
*/
_stopPinning() {
this.stopListening( _ckeditor_ckeditor5_utils_src_dom_global__WEBPACK_IMPORTED_MODULE_4__["default"].document, 'scroll' );
this.stopListening( _ckeditor_ckeditor5_utils_src_dom_global__WEBPACK_IMPORTED_MODULE_4__["default"].window, 'resize' );
}
}
// Returns the DOM element for given object or null, if there is none,
// e.g. when the passed object is a Rect instance or so.
//
// @private
// @param {*} object
// @returns {HTMLElement|null}
function getDomElement( object ) {
if ( (0,lodash_es__WEBPACK_IMPORTED_MODULE_6__["default"])( object ) ) {
return object;
}
if ( (0,_ckeditor_ckeditor5_utils_src_dom_isrange__WEBPACK_IMPORTED_MODULE_2__["default"])( object ) ) {
return object.commonAncestorContainer;
}
if ( typeof object == 'function' ) {
return getDomElement( object() );
}
return null;
}
/**
* A horizontal offset of the arrow tip from the edge of the balloon. Controlled by CSS.
*
* +-----|---------...
* | |
* | |
* | |
* | |
* +--+ | +------...
* \ | /
* \|/
* >|-----|<---------------- horizontal offset
*
* @default 25
* @member {Number} module:ui/panel/balloon/balloonpanelview~BalloonPanelView.arrowHorizontalOffset
*/
BalloonPanelView.arrowHorizontalOffset = 25;
/**
* A vertical offset of the arrow from the edge of the balloon. Controlled by CSS.
*
* +-------------...
* |
* |
* | /-- vertical offset
* | V
* +--+ +-----... ---------
* \ / |
* \/ |
* -------------------------------
* ^
*
* @default 10
* @member {Number} module:ui/panel/balloon/balloonpanelview~BalloonPanelView.arrowVerticalOffset
*/
BalloonPanelView.arrowVerticalOffset = 10;
/**
* A vertical offset of the balloon panel from the edge of the viewport if sticky.
* It helps in accessing toolbar buttons underneath the balloon panel.
*
* +---------------------------------------------------+
* | Target |
* | |
* | /-- vertical offset |
* +-----------------------------V-------------------------+
* | Toolbar +-------------+ |
* +--------------------| Balloon |--------------------+
* | | +-------------+ | |
* | | | |
* | | | |
* | | | |
* | +---------------------------------------------------+ |
* | Viewport |
* +-------------------------------------------------------+
*
* @default 20
* @member {Number} module:ui/panel/balloon/balloonpanelview~BalloonPanelView.stickyVerticalOffset
*/
BalloonPanelView.stickyVerticalOffset = 20;
/**
* Function used to calculate the optimal position for the balloon.
*
* @protected
* @member {Function} module:ui/panel/balloon/balloonpanelview~BalloonPanelView._getOptimalPosition
*/
BalloonPanelView._getOptimalPosition = _ckeditor_ckeditor5_utils_src_dom_position__WEBPACK_IMPORTED_MODULE_1__.getOptimalPosition;
/**
* A default set of positioning functions used by the balloon panel view
* when attaching using the {@link module:ui/panel/balloon/balloonpanelview~BalloonPanelView#attachTo} method.
*
* The available positioning functions are as follows:
*
*
*
* **North west**
*
* * `northWestArrowSouthWest`
*
* +-----------------+
* | Balloon |
* +-----------------+
* V
* [ Target ]
*
* * `northWestArrowSouthMiddleWest`
*
* +-----------------+
* | Balloon |
* +-----------------+
* V
* [ Target ]
*
* * `northWestArrowSouth`
*
* +-----------------+
* | Balloon |
* +-----------------+
* V
* [ Target ]
*
* * `northWestArrowSouthMiddleEast`
*
* +-----------------+
* | Balloon |
* +-----------------+
* V
* [ Target ]
*
* * `northWestArrowSouthEast`
*
* +-----------------+
* | Balloon |
* +-----------------+
* V
* [ Target ]
*
*
*
* **North**
*
* * `northArrowSouthWest`
*
* +-----------------+
* | Balloon |
* +-----------------+
* V
* [ Target ]
*
* * `northArrowSouthMiddleWest`
*
* +-----------------+
* | Balloon |
* +-----------------+
* V
* [ Target ]
*
* * `northArrowSouth`
*
* +-----------------+
* | Balloon |
* +-----------------+
* V
* [ Target ]
*
* * `northArrowSouthMiddleEast`
*
* +-----------------+
* | Balloon |
* +-----------------+
* V
* [ Target ]
*
* * `northArrowSouthEast`
*
* +-----------------+
* | Balloon |
* +-----------------+
* V
* [ Target ]
*
* **North east**
*
* * `northEastArrowSouthWest`
*
* +-----------------+
* | Balloon |
* +-----------------+
* V
* [ Target ]
*
*
* * `northEastArrowSouthMiddleWest`
*
* +-----------------+
* | Balloon |
* +-----------------+
* V
* [ Target ]
*
* * `northEastArrowSouth`
*
* +-----------------+
* | Balloon |
* +-----------------+
* V
* [ Target ]
*
* * `northEastArrowSouthMiddleEast`
*
* +-----------------+
* | Balloon |
* +-----------------+
* V
* [ Target ]
*
* * `northEastArrowSouthEast`
*
* +-----------------+
* | Balloon |
* +-----------------+
* V
* [ Target ]
*
*
*
* **South**
*
*
* * `southArrowNorthWest`
*
* [ Target ]
* ^
* +-----------------+
* | Balloon |
* +-----------------+
*
* * `southArrowNorthMiddleWest`
*
* [ Target ]
* ^
* +-----------------+
* | Balloon |
* +-----------------+
*
* * `southArrowNorth`
*
* [ Target ]
* ^
* +-----------------+
* | Balloon |
* +-----------------+
*
* * `southArrowNorthMiddleEast`
*
* [ Target ]
* ^
* +-----------------+
* | Balloon |
* +-----------------+
*
* * `southArrowNorthEast`
*
* [ Target ]
* ^
* +-----------------+
* | Balloon |
* +-----------------+
*
*
*
* **South west**
*
* * `southWestArrowNorthWest`
*
* [ Target ]
* ^
* +-----------------+
* | Balloon |
* +-----------------+
*
* * `southWestArrowNorthMiddleWest`
*
* [ Target ]
* ^
* +-----------------+
* | Balloon |
* +-----------------+
*
* * `southWestArrowNorth`
*
* [ Target ]
* ^
* +-----------------+
* | Balloon |
* +-----------------+
*
* * `southWestArrowNorthMiddleEast`
*
* [ Target ]
* ^
* +-----------------+
* | Balloon |
* +-----------------+
*
* * `southWestArrowNorthEast`
*
* [ Target ]
* ^
* +-----------------+
* | Balloon |
* +-----------------+
*
*
*
* **South east**
*
* * `southEastArrowNorthWest`
*
* [ Target ]
* ^
* +-----------------+
* | Balloon |
* +-----------------+
* * `southEastArrowNorthMiddleWest`
*
* [ Target ]
* ^
* +-----------------+
* | Balloon |
* +-----------------+
*
* * `southEastArrowNorth`
*
* [ Target ]
* ^
* +-----------------+
* | Balloon |
* +-----------------+
*
* * `southEastArrowNorthMiddleEast`
*
* [ Target ]
* ^
* +-----------------+
* | Balloon |
* +-----------------+
*
* * `southEastArrowNorthEast`
*
* [ Target ]
* ^
* +-----------------+
* | Balloon |
* +-----------------+
*
* * `viewportStickyNorth`
*
* +---------------------------+
* | [ Target ] |
* | |
* +-----------------------------------+
* | | +-----------------+ | |
* | | | Balloon | | |
* | | +-----------------+ | |
* | | | |
* | | | |
* | | | |
* | | | |
* | +---------------------------+ |
* | Viewport |
* +-----------------------------------+
*
* See {@link module:ui/panel/balloon/balloonpanelview~BalloonPanelView#attachTo}.
*
* Positioning functions must be compatible with {@link module:utils/dom/position~Position}.
*
* Default positioning functions with customized offsets can be generated using
* {@link module:ui/panel/balloon/balloonpanelview~generatePositions}.
*
* The name that the position function returns will be reflected in the balloon panel's class that
* controls the placement of the "arrow". See {@link #position} to learn more.
*
* @member {Object.<String,module:utils/dom/position~positioningFunction>}
* module:ui/panel/balloon/balloonpanelview~BalloonPanelView.defaultPositions
*/
BalloonPanelView.defaultPositions = generatePositions();
/**
* Returns available {@link module:ui/panel/balloon/balloonpanelview~BalloonPanelView}
* {@link module:utils/dom/position~positioningFunction positioning functions} adjusted by the specific offsets.
*
* @protected
* @param {Object} [options] Options to generate positions. If not specified, this helper will simply return
* {@link module:ui/panel/balloon/balloonpanelview~BalloonPanelView.defaultPositions}.
* @param {Number} [options.horizontalOffset] A custom horizontal offset (in pixels) of each position. If
* not specified, {@link module:ui/panel/balloon/balloonpanelview~BalloonPanelView.arrowHorizontalOffset the default value}
* will be used.
* @param {Number} [options.verticalOffset] A custom vertical offset (in pixels) of each position. If
* not specified, {@link module:ui/panel/balloon/balloonpanelview~BalloonPanelView.arrowVerticalOffset the default value}
* will be used.
* @param {Number} [options.stickyVerticalOffset] A custom offset (in pixels) of the `viewportStickyNorth` positioning function.
* If not specified, {@link module:ui/panel/balloon/balloonpanelview~BalloonPanelView.stickyVerticalOffset the default value}
* will be used.
* @param {Object} [options.config] Additional configuration of the balloon balloon panel view.
* Currently only {@link module:ui/panel/balloon/balloonpanelview~BalloonPanelView#withArrow} is supported. Learn more
* about {@link module:utils/dom/position~positioningFunction positioning functions}.
* @returns {Object.<String,module:utils/dom/position~positioningFunction>}
*/
function generatePositions( {
horizontalOffset = BalloonPanelView.arrowHorizontalOffset,
verticalOffset = BalloonPanelView.arrowVerticalOffset,
stickyVerticalOffset = BalloonPanelView.stickyVerticalOffset,
config
} = {} ) {
return {
// ------- North west
northWestArrowSouthWest: ( targetRect, balloonRect ) => ( {
top: getNorthTop( targetRect, balloonRect ),
left: targetRect.left - horizontalOffset,
name: 'arrow_sw',
...( config && { config } )
} ),
northWestArrowSouthMiddleWest: ( targetRect, balloonRect ) => ( {
top: getNorthTop( targetRect, balloonRect ),
left: targetRect.left - ( balloonRect.width * .25 ) - horizontalOffset,
name: 'arrow_smw',
...( config && { config } )
} ),
northWestArrowSouth: ( targetRect, balloonRect ) => ( {
top: getNorthTop( targetRect, balloonRect ),
left: targetRect.left - balloonRect.width / 2,
name: 'arrow_s',
...( config && { config } )
} ),
northWestArrowSouthMiddleEast: ( targetRect, balloonRect ) => ( {
top: getNorthTop( targetRect, balloonRect ),
left: targetRect.left - ( balloonRect.width * .75 ) + horizontalOffset,
name: 'arrow_sme',
...( config && { config } )
} ),
northWestArrowSouthEast: ( targetRect, balloonRect ) => ( {
top: getNorthTop( targetRect, balloonRect ),
left: targetRect.left - balloonRect.width + horizontalOffset,
name: 'arrow_se',
...( config && { config } )
} ),
// ------- North
northArrowSouthWest: ( targetRect, balloonRect ) => ( {
top: getNorthTop( targetRect, balloonRect ),
left: targetRect.left + targetRect.width / 2 - horizontalOffset,
name: 'arrow_sw',
...( config && { config } )
} ),
northArrowSouthMiddleWest: ( targetRect, balloonRect ) => ( {
top: getNorthTop( targetRect, balloonRect ),
left: targetRect.left + targetRect.width / 2 - ( balloonRect.width * .25 ) - horizontalOffset,
name: 'arrow_smw',
...( config && { config } )
} ),
northArrowSouth: ( targetRect, balloonRect ) => ( {
top: getNorthTop( targetRect, balloonRect ),
left: targetRect.left + targetRect.width / 2 - balloonRect.width / 2,
name: 'arrow_s',
...( config && { config } )
} ),
northArrowSouthMiddleEast: ( targetRect, balloonRect ) => ( {
top: getNorthTop( targetRect, balloonRect ),
left: targetRect.left + targetRect.width / 2 - ( balloonRect.width * .75 ) + horizontalOffset,
name: 'arrow_sme',
...( config && { config } )
} ),
northArrowSouthEast: ( targetRect, balloonRect ) => ( {
top: getNorthTop( targetRect, balloonRect ),
left: targetRect.left + targetRect.width / 2 - balloonRect.width + horizontalOffset,
name: 'arrow_se',
...( config && { config } )
} ),
// ------- North east
northEastArrowSouthWest: ( targetRect, balloonRect ) => ( {
top: getNorthTop( targetRect, balloonRect ),
left: targetRect.right - horizontalOffset,
name: 'arrow_sw',
...( config && { config } )
} ),
northEastArrowSouthMiddleWest: ( targetRect, balloonRect ) => ( {
top: getNorthTop( targetRect, balloonRect ),
left: targetRect.right - ( balloonRect.width * .25 ) - horizontalOffset,
name: 'arrow_smw',
...( config && { config } )
} ),
northEastArrowSouth: ( targetRect, balloonRect ) => ( {
top: getNorthTop( targetRect, balloonRect ),
left: targetRect.right - balloonRect.width / 2,
name: 'arrow_s',
...( config && { config } )
} ),
northEastArrowSouthMiddleEast: ( targetRect, balloonRect ) => ( {
top: getNorthTop( targetRect, balloonRect ),
left: targetRect.right - ( balloonRect.width * .75 ) + horizontalOffset,
name: 'arrow_sme',
...( config && { config } )
} ),
northEastArrowSouthEast: ( targetRect, balloonRect ) => ( {
top: getNorthTop( targetRect, balloonRect ),
left: targetRect.right - balloonRect.width + horizontalOffset,
name: 'arrow_se',
...( config && { config } )
} ),
// ------- South west
southWestArrowNorthWest: ( targetRect, balloonRect ) => ( {
top: getSouthTop( targetRect, balloonRect ),
left: targetRect.left - horizontalOffset,
name: 'arrow_nw',
...( config && { config } )
} ),
southWestArrowNorthMiddleWest: ( targetRect, balloonRect ) => ( {
top: getSouthTop( targetRect, balloonRect ),
left: targetRect.left - ( balloonRect.width * .25 ) - horizontalOffset,
name: 'arrow_nmw',
...( config && { config } )
} ),
southWestArrowNorth: ( targetRect, balloonRect ) => ( {
top: getSouthTop( targetRect, balloonRect ),
left: targetRect.left - balloonRect.width / 2,
name: 'arrow_n',
...( config && { config } )
} ),
southWestArrowNorthMiddleEast: ( targetRect, balloonRect ) => ( {
top: getSouthTop( targetRect, balloonRect ),
left: targetRect.left - ( balloonRect.width * .75 ) + horizontalOffset,
name: 'arrow_nme',
...( config && { config } )
} ),
southWestArrowNorthEast: ( targetRect, balloonRect ) => ( {
top: getSouthTop( targetRect, balloonRect ),
left: targetRect.left - balloonRect.width + horizontalOffset,
name: 'arrow_ne',
...( config && { config } )
} ),
// ------- South
southArrowNorthWest: ( targetRect, balloonRect ) => ( {
top: getSouthTop( targetRect, balloonRect ),
left: targetRect.left + targetRect.width / 2 - horizontalOffset,
name: 'arrow_nw',
...( config && { config } )
} ),
southArrowNorthMiddleWest: ( targetRect, balloonRect ) => ( {
top: getSouthTop( targetRect, balloonRect ),
left: targetRect.left + targetRect.width / 2 - ( balloonRect.width * 0.25 ) - horizontalOffset,
name: 'arrow_nmw',
...( config && { config } )
} ),
southArrowNorth: ( targetRect, balloonRect ) => ( {
top: getSouthTop( targetRect, balloonRect ),
left: targetRect.left + targetRect.width / 2 - balloonRect.width / 2,
name: 'arrow_n',
...( config && { config } )
} ),
southArrowNorthMiddleEast: ( targetRect, balloonRect ) => ( {
top: getSouthTop( targetRect, balloonRect ),
left: targetRect.left + targetRect.width / 2 - ( balloonRect.width * 0.75 ) + horizontalOffset,
name: 'arrow_nme',
...( config && { config } )
} ),
southArrowNorthEast: ( targetRect, balloonRect ) => ( {
top: getSouthTop( targetRect, balloonRect ),
left: targetRect.left + targetRect.width / 2 - balloonRect.width + horizontalOffset,
name: 'arrow_ne',
...( config && { config } )
} ),
// ------- South east
southEastArrowNorthWest: ( targetRect, balloonRect ) => ( {
top: getSouthTop( targetRect, balloonRect ),
left: targetRect.right - horizontalOffset,
name: 'arrow_nw',
...( config && { config } )
} ),
southEastArrowNorthMiddleWest: ( targetRect, balloonRect ) => ( {
top: getSouthTop( targetRect, balloonRect ),
left: targetRect.right - ( balloonRect.width * .25 ) - horizontalOffset,
name: 'arrow_nmw',
...( config && { config } )
} ),
southEastArrowNorth: ( targetRect, balloonRect ) => ( {
top: getSouthTop( targetRect, balloonRect ),
left: targetRect.right - balloonRect.width / 2,
name: 'arrow_n',
...( config && { config } )
} ),
southEastArrowNorthMiddleEast: ( targetRect, balloonRect ) => ( {
top: getSouthTop( targetRect, balloonRect ),
left: targetRect.right - ( balloonRect.width * .75 ) + horizontalOffset,
name: 'arrow_nme',
...( config && { config } )
} ),
southEastArrowNorthEast: ( targetRect, balloonRect ) => ( {
top: getSouthTop( targetRect, balloonRect ),
left: targetRect.right - balloonRect.width + horizontalOffset,
name: 'arrow_ne',
...( config && { config } )
} ),
// ------- Sticky
viewportStickyNorth: ( targetRect, balloonRect, viewportRect ) => {
if ( !targetRect.getIntersection( viewportRect ) ) {
return null;
}
return {
top: viewportRect.top + stickyVerticalOffset,
left: targetRect.left + targetRect.width / 2 - balloonRect.width / 2,
name: 'arrowless',
config: {
withArrow: false,
...config
}
};
}
};
// Returns the top coordinate for positions starting with `north*`.
//
// @private
// @param {utils/dom/rect~Rect} targetRect A rect of the target.
// @param {utils/dom/rect~Rect} elementRect A rect of the balloon.
// @returns {Number}
function getNorthTop( targetRect, balloonRect ) {
return targetRect.top - balloonRect.height - verticalOffset;
}
// Returns the top coordinate for positions starting with `south*`.
//
// @private
// @param {utils/dom/rect~Rect} targetRect A rect of the target.
// @param {utils/dom/rect~Rect} elementRect A rect of the balloon.
// @returns {Number}
function getSouthTop( targetRect ) {
return targetRect.bottom + verticalOffset;
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/src/panel/balloon/contextualballoon.js":
/*!************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/src/panel/balloon/contextualballoon.js ***!
\************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ContextualBalloon)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_core_src_plugin__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-core/src/plugin */ "./node_modules/@ckeditor/ckeditor5-core/src/plugin.js");
/* harmony import */ var _balloonpanelview__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./balloonpanelview */ "./node_modules/@ckeditor/ckeditor5-ui/src/panel/balloon/balloonpanelview.js");
/* harmony import */ var _view__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../view */ "./node_modules/@ckeditor/ckeditor5-ui/src/view.js");
/* harmony import */ var _button_buttonview__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../../button/buttonview */ "./node_modules/@ckeditor/ckeditor5-ui/src/button/buttonview.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_focustracker__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/focustracker */ "./node_modules/@ckeditor/ckeditor5-utils/src/focustracker.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_dom_tounit__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/dom/tounit */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/tounit.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_dom_rect__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/dom/rect */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/rect.js");
/* harmony import */ var _theme_icons_previous_arrow_svg__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ../../../theme/icons/previous-arrow.svg */ "./node_modules/@ckeditor/ckeditor5-ui/theme/icons/previous-arrow.svg");
/* harmony import */ var _theme_icons_next_arrow_svg__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ../../../theme/icons/next-arrow.svg */ "./node_modules/@ckeditor/ckeditor5-ui/theme/icons/next-arrow.svg");
/* harmony import */ var _theme_components_panel_balloonrotator_css__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ../../../theme/components/panel/balloonrotator.css */ "./node_modules/@ckeditor/ckeditor5-ui/theme/components/panel/balloonrotator.css");
/* harmony import */ var _theme_components_panel_fakepanel_css__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ../../../theme/components/panel/fakepanel.css */ "./node_modules/@ckeditor/ckeditor5-ui/theme/components/panel/fakepanel.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module ui/panel/balloon/contextualballoon
*/
const toPx = (0,_ckeditor_ckeditor5_utils_src_dom_tounit__WEBPACK_IMPORTED_MODULE_6__["default"])( 'px' );
/**
* Provides the common contextual balloon for the editor.
*
* The role of this plugin is to unify the contextual balloons logic, simplify views management and help
* avoid the unnecessary complexity of handling multiple {@link module:ui/panel/balloon/balloonpanelview~BalloonPanelView}
* instances in the editor.
*
* This plugin allows for creating single or multiple panel stacks.
*
* Each stack may have multiple views, with the one on the top being visible. When the visible view is removed from the stack,
* the previous view becomes visible.
*
* It might be useful to implement nested navigation in a balloon. For instance, a toolbar view may contain a link button.
* When you click it, a link view (which lets you set the URL) is created and put on top of the toolbar view, so the link panel
* is displayed. When you finish editing the link and close (remove) the link view, the toolbar view is visible again.
*
* However, there are cases when there are multiple independent balloons to be displayed, for instance, if the selection
* is inside two inline comments at the same time. For such cases, you can create two independent panel stacks.
* The contextual balloon plugin will create a navigation bar to let the users switch between these panel stacks using the "Next"
* and "Previous" buttons.
*
* If there are no views in the current stack, the balloon panel will try to switch to the next stack. If there are no
* panels in any stack, the balloon panel will be hidden.
*
* **Note**: To force the balloon panel to show only one view, even if there are other stacks, use the `singleViewMode=true` option
* when {@link module:ui/panel/balloon/contextualballoon~ContextualBalloon#add adding} a view to a panel.
*
* From the implementation point of view, the contextual ballon plugin is reusing a single
* {@link module:ui/panel/balloon/balloonpanelview~BalloonPanelView} instance to display multiple contextual balloon
* panels in the editor. It also creates a special {@link module:ui/panel/balloon/contextualballoon~RotatorView rotator view},
* used to manage multiple panel stacks. Rotator view is a child of the balloon panel view and the parent of the specific
* view you want to display. If there is more than one panel stack to be displayed, the rotator view will add a
* navigation bar. If there is only one stack, the rotator view is transparent (it does not add any UI elements).
*
* @extends module:core/plugin~Plugin
*/
class ContextualBalloon extends _ckeditor_ckeditor5_core_src_plugin__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* @inheritDoc
*/
static get pluginName() {
return 'ContextualBalloon';
}
/**
* @inheritDoc
*/
constructor( editor ) {
super( editor );
/**
* The {@link module:utils/dom/position~Options#limiter position limiter}
* for the {@link #view balloon}, used when no `limiter` has been passed into {@link #add}
* or {@link #updatePosition}.
*
* By default, a function that obtains the farthest DOM
* {@link module:engine/view/rooteditableelement~RootEditableElement}
* of the {@link module:engine/view/document~Document#selection}.
*
* @member {module:utils/dom/position~Options#limiter} #positionLimiter
*/
this.positionLimiter = () => {
const view = this.editor.editing.view;
const viewDocument = view.document;
const editableElement = viewDocument.selection.editableElement;
if ( editableElement ) {
return view.domConverter.mapViewToDom( editableElement.root );
}
return null;
};
/**
* The currently visible view or `null` when there are no views in any stack.
*
* @readonly
* @observable
* @member {module:ui/view~View|null} #visibleView
*/
this.set( 'visibleView', null );
/**
* The common balloon panel view.
*
* @readonly
* @member {module:ui/panel/balloon/balloonpanelview~BalloonPanelView} #view
*/
this.view = new _balloonpanelview__WEBPACK_IMPORTED_MODULE_1__["default"]( editor.locale );
editor.ui.view.body.add( this.view );
editor.ui.focusTracker.add( this.view.element );
/**
* The map of views and their stacks.
*
* @private
* @type {Map.<module:ui/view~View,Set>}
*/
this._viewToStack = new Map();
/**
* The map of IDs and stacks.
*
* @private
* @type {Map.<String,Set>}
*/
this._idToStack = new Map();
/**
* A total number of all stacks in the balloon.
*
* @private
* @readonly
* @observable
* @member {Number} #_numberOfStacks
*/
this.set( '_numberOfStacks', 0 );
/**
* A flag that controls the single view mode.
*
* @private
* @readonly
* @observable
* @member {Boolean} #_singleViewMode
*/
this.set( '_singleViewMode', false );
/**
* Rotator view embedded in the contextual balloon.
* Displays the currently visible view in the balloon and provides navigation for switching stacks.
*
* @private
* @type {module:ui/panel/balloon/contextualballoon~RotatorView}
*/
this._rotatorView = this._createRotatorView();
/**
* Displays fake panels under the balloon panel view when multiple stacks are added to the balloon.
*
* @private
* @type {module:ui/view~View}
*/
this._fakePanelsView = this._createFakePanelsView();
}
/**
* @inheritDoc
*/
destroy() {
super.destroy();
this.view.destroy();
this._rotatorView.destroy();
this._fakePanelsView.destroy();
}
/**
* Returns `true` when the given view is in one of the stacks. Otherwise returns `false`.
*
* @param {module:ui/view~View} view
* @returns {Boolean}
*/
hasView( view ) {
return Array.from( this._viewToStack.keys() ).includes( view );
}
/**
* Adds a new view to the stack and makes it visible if the current stack is visible
* or it is the first view in the balloon.
*
* @param {Object} data The configuration of the view.
* @param {String} [data.stackId='main'] The ID of the stack that the view is added to.
* @param {module:ui/view~View} [data.view] The content of the balloon.
* @param {module:utils/dom/position~Options} [data.position] Positioning options.
* @param {String} [data.balloonClassName] An additional CSS class added to the {@link #view balloon} when visible.
* @param {Boolean} [data.withArrow=true] Whether the {@link #view balloon} should be rendered with an arrow.
* @param {Boolean} [data.singleViewMode=false] Whether the view should be the only visible view even if other stacks were added.
*/
add( data ) {
if ( this.hasView( data.view ) ) {
/**
* Trying to add configuration of the same view more than once.
*
* @error contextualballoon-add-view-exist
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_4__["default"](
'contextualballoon-add-view-exist',
[ this, data ]
);
}
const stackId = data.stackId || 'main';
// If new stack is added, creates it and show view from this stack.
if ( !this._idToStack.has( stackId ) ) {
this._idToStack.set( stackId, new Map( [ [ data.view, data ] ] ) );
this._viewToStack.set( data.view, this._idToStack.get( stackId ) );
this._numberOfStacks = this._idToStack.size;
if ( !this._visibleStack || data.singleViewMode ) {
this.showStack( stackId );
}
return;
}
const stack = this._idToStack.get( stackId );
if ( data.singleViewMode ) {
this.showStack( stackId );
}
// Add new view to the stack.
stack.set( data.view, data );
this._viewToStack.set( data.view, stack );
// And display it if is added to the currently visible stack.
if ( stack === this._visibleStack ) {
this._showView( data );
}
}
/**
* Removes the given view from the stack. If the removed view was visible,
* the view preceding it in the stack will become visible instead.
* When there is no view in the stack, the next stack will be displayed.
* When there are no more stacks, the balloon will hide.
*
* @param {module:ui/view~View} view A view to be removed from the balloon.
*/
remove( view ) {
if ( !this.hasView( view ) ) {
/**
* Trying to remove the configuration of the view not defined in the stack.
*
* @error contextualballoon-remove-view-not-exist
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_4__["default"](
'contextualballoon-remove-view-not-exist',
[ this, view ]
);
}
const stack = this._viewToStack.get( view );
if ( this._singleViewMode && this.visibleView === view ) {
this._singleViewMode = false;
}
// When visible view will be removed we need to show a preceding view or next stack
// if a view is the only view in the stack.
if ( this.visibleView === view ) {
if ( stack.size === 1 ) {
if ( this._idToStack.size > 1 ) {
this._showNextStack();
} else {
this.view.hide();
this.visibleView = null;
this._rotatorView.hideView();
}
} else {
this._showView( Array.from( stack.values() )[ stack.size - 2 ] );
}
}
if ( stack.size === 1 ) {
this._idToStack.delete( this._getStackId( stack ) );
this._numberOfStacks = this._idToStack.size;
} else {
stack.delete( view );
}
this._viewToStack.delete( view );
}
/**
* Updates the position of the balloon using the position data of the first visible view in the stack.
* When new position data is given, the position data of the currently visible view will be updated.
*
* @param {module:utils/dom/position~Options} [position] position options.
*/
updatePosition( position ) {
if ( position ) {
this._visibleStack.get( this.visibleView ).position = position;
}
this.view.pin( this._getBalloonPosition() );
this._fakePanelsView.updatePosition();
}
/**
* Shows the last view from the stack of a given ID.
*
* @param {String} id
*/
showStack( id ) {
this.visibleStack = id;
const stack = this._idToStack.get( id );
if ( !stack ) {
/**
* Trying to show a stack that does not exist.
*
* @error contextualballoon-showstack-stack-not-exist
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_4__["default"](
'contextualballoon-showstack-stack-not-exist',
this
);
}
if ( this._visibleStack === stack ) {
return;
}
this._showView( Array.from( stack.values() ).pop() );
}
/**
* Returns the stack of the currently visible view.
*
* @private
* @type {Set}
*/
get _visibleStack() {
return this._viewToStack.get( this.visibleView );
}
/**
* Returns the ID of the given stack.
*
* @private
* @param {Set} stack
* @returns {String}
*/
_getStackId( stack ) {
const entry = Array.from( this._idToStack.entries() ).find( entry => entry[ 1 ] === stack );
return entry[ 0 ];
}
/**
* Shows the last view from the next stack.
*
* @private
*/
_showNextStack() {
const stacks = Array.from( this._idToStack.values() );
let nextIndex = stacks.indexOf( this._visibleStack ) + 1;
if ( !stacks[ nextIndex ] ) {
nextIndex = 0;
}
this.showStack( this._getStackId( stacks[ nextIndex ] ) );
}
/**
* Shows the last view from the previous stack.
*
* @private
*/
_showPrevStack() {
const stacks = Array.from( this._idToStack.values() );
let nextIndex = stacks.indexOf( this._visibleStack ) - 1;
if ( !stacks[ nextIndex ] ) {
nextIndex = stacks.length - 1;
}
this.showStack( this._getStackId( stacks[ nextIndex ] ) );
}
/**
* Creates a rotator view.
*
* @private
* @returns {module:ui/panel/balloon/contextualballoon~RotatorView}
*/
_createRotatorView() {
const view = new RotatorView( this.editor.locale );
const t = this.editor.locale.t;
this.view.content.add( view );
// Hide navigation when there is only a one stack & not in single view mode.
view.bind( 'isNavigationVisible' ).to( this, '_numberOfStacks', this, '_singleViewMode', ( value, isSingleViewMode ) => {
return !isSingleViewMode && value > 1;
} );
// Update balloon position after toggling navigation.
view.on( 'change:isNavigationVisible', () => ( this.updatePosition() ), { priority: 'low' } );
// Update stacks counter value.
view.bind( 'counter' ).to( this, 'visibleView', this, '_numberOfStacks', ( visibleView, numberOfStacks ) => {
if ( numberOfStacks < 2 ) {
return '';
}
const current = Array.from( this._idToStack.values() ).indexOf( this._visibleStack ) + 1;
return t( '%0 of %1', [ current, numberOfStacks ] );
} );
view.buttonNextView.on( 'execute', () => {
// When current view has a focus then move focus to the editable before removing it,
// otherwise editor will lost focus.
if ( view.focusTracker.isFocused ) {
this.editor.editing.view.focus();
}
this._showNextStack();
} );
view.buttonPrevView.on( 'execute', () => {
// When current view has a focus then move focus to the editable before removing it,
// otherwise editor will lost focus.
if ( view.focusTracker.isFocused ) {
this.editor.editing.view.focus();
}
this._showPrevStack();
} );
return view;
}
/**
* @private
* @returns {module:ui/view~View}
*/
_createFakePanelsView() {
const view = new FakePanelsView( this.editor.locale, this.view );
view.bind( 'numberOfPanels' ).to( this, '_numberOfStacks', this, '_singleViewMode', ( number, isSingleViewMode ) => {
const showPanels = !isSingleViewMode && number >= 2;
return showPanels ? Math.min( number - 1, 2 ) : 0;
} );
view.listenTo( this.view, 'change:top', () => view.updatePosition() );
view.listenTo( this.view, 'change:left', () => view.updatePosition() );
this.editor.ui.view.body.add( view );
return view;
}
/**
* Sets the view as the content of the balloon and attaches the balloon using position
* options of the first view.
*
* @private
* @param {Object} data Configuration.
* @param {module:ui/view~View} [data.view] The view to show in the balloon.
* @param {String} [data.balloonClassName=''] Additional class name which will be added to the {@link #view balloon}.
* @param {Boolean} [data.withArrow=true] Whether the {@link #view balloon} should be rendered with an arrow.
*/
_showView( { view, balloonClassName = '', withArrow = true, singleViewMode = false } ) {
this.view.class = balloonClassName;
this.view.withArrow = withArrow;
this._rotatorView.showView( view );
this.visibleView = view;
this.view.pin( this._getBalloonPosition() );
this._fakePanelsView.updatePosition();
if ( singleViewMode ) {
this._singleViewMode = true;
}
}
/**
* Returns position options of the last view in the stack.
* This keeps the balloon in the same position when the view is changed.
*
* @private
* @returns {module:utils/dom/position~Options}
*/
_getBalloonPosition() {
let position = Array.from( this._visibleStack.values() ).pop().position;
if ( position ) {
// Use the default limiter if none has been specified.
if ( !position.limiter ) {
// Don't modify the original options object.
position = Object.assign( {}, position, {
limiter: this.positionLimiter
} );
}
// Don't modify the original options object.
position = Object.assign( {}, position, {
viewportOffsetConfig: this.editor.ui.viewportOffset
} );
}
return position;
}
}
/**
* Rotator view is a helper class for the {@link module:ui/panel/balloon/contextualballoon~ContextualBalloon ContextualBalloon}.
* It is used for displaying the last view from the current stack and providing navigation buttons for switching stacks.
* See the {@link module:ui/panel/balloon/contextualballoon~ContextualBalloon ContextualBalloon} documentation to learn more.
*
* @extends module:ui/view~View
*/
class RotatorView extends _view__WEBPACK_IMPORTED_MODULE_2__["default"] {
/**
* @inheritDoc
*/
constructor( locale ) {
super( locale );
const t = locale.t;
const bind = this.bindTemplate;
/**
* Defines whether navigation is visible or not.
*
* @member {Boolean} #isNavigationVisible
*/
this.set( 'isNavigationVisible', true );
/**
* Used for checking if a view is focused or not.
*
* @type {module:utils/focustracker~FocusTracker}
*/
this.focusTracker = new _ckeditor_ckeditor5_utils_src_focustracker__WEBPACK_IMPORTED_MODULE_5__["default"]();
/**
* Navigation button for switching the stack to the previous one.
*
* @type {module:ui/button/buttonview~ButtonView}
*/
this.buttonPrevView = this._createButtonView( t( 'Previous' ), _theme_icons_previous_arrow_svg__WEBPACK_IMPORTED_MODULE_8__["default"] );
/**
* Navigation button for switching the stack to the next one.
*
* @type {module:ui/button/buttonview~ButtonView}
*/
this.buttonNextView = this._createButtonView( t( 'Next' ), _theme_icons_next_arrow_svg__WEBPACK_IMPORTED_MODULE_9__["default"] );
/**
* A collection of the child views that creates the rotator content.
*
* @readonly
* @type {module:ui/viewcollection~ViewCollection}
*/
this.content = this.createCollection();
this.setTemplate( {
tag: 'div',
attributes: {
class: [
'ck',
'ck-balloon-rotator'
],
'z-index': '-1'
},
children: [
{
tag: 'div',
attributes: {
class: [
'ck-balloon-rotator__navigation',
bind.to( 'isNavigationVisible', value => value ? '' : 'ck-hidden' )
]
},
children: [
this.buttonPrevView,
{
tag: 'span',
attributes: {
class: [
'ck-balloon-rotator__counter'
]
},
children: [
{
text: bind.to( 'counter' )
}
]
},
this.buttonNextView
]
},
{
tag: 'div',
attributes: {
class: 'ck-balloon-rotator__content'
},
children: this.content
}
]
} );
}
/**
* @inheritDoc
*/
render() {
super.render();
this.focusTracker.add( this.element );
}
/**
* @inheritDoc
*/
destroy() {
super.destroy();
this.focusTracker.destroy();
}
/**
* Shows a given view.
*
* @param {module:ui/view~View} view The view to show.
*/
showView( view ) {
this.hideView();
this.content.add( view );
}
/**
* Hides the currently displayed view.
*/
hideView() {
this.content.clear();
}
/**
* Creates a navigation button view.
*
* @private
* @param {String} label The button label.
* @param {String} icon The button icon.
* @returns {module:ui/button/buttonview~ButtonView}
*/
_createButtonView( label, icon ) {
const view = new _button_buttonview__WEBPACK_IMPORTED_MODULE_3__["default"]( this.locale );
view.set( {
label,
icon,
tooltip: true
} );
return view;
}
}
// Displays additional layers under the balloon when multiple stacks are added to the balloon.
//
// @private
// @extends module:ui/view~View
class FakePanelsView extends _view__WEBPACK_IMPORTED_MODULE_2__["default"] {
// @inheritDoc
constructor( locale, balloonPanelView ) {
super( locale );
const bind = this.bindTemplate;
// Fake panels top offset.
//
// @observable
// @member {Number} #top
this.set( 'top', 0 );
// Fake panels left offset.
//
// @observable
// @member {Number} #left
this.set( 'left', 0 );
// Fake panels height.
//
// @observable
// @member {Number} #height
this.set( 'height', 0 );
// Fake panels width.
//
// @observable
// @member {Number} #width
this.set( 'width', 0 );
// Number of rendered fake panels.
//
// @observable
// @member {Number} #numberOfPanels
this.set( 'numberOfPanels', 0 );
// Collection of the child views which creates fake panel content.
//
// @readonly
// @type {module:ui/viewcollection~ViewCollection}
this.content = this.createCollection();
// Context.
//
// @private
// @type {module:ui/panel/balloon/balloonpanelview~BalloonPanelView}
this._balloonPanelView = balloonPanelView;
this.setTemplate( {
tag: 'div',
attributes: {
class: [
'ck-fake-panel',
bind.to( 'numberOfPanels', number => number ? '' : 'ck-hidden' )
],
style: {
top: bind.to( 'top', toPx ),
left: bind.to( 'left', toPx ),
width: bind.to( 'width', toPx ),
height: bind.to( 'height', toPx )
}
},
children: this.content
} );
this.on( 'change:numberOfPanels', ( evt, name, next, prev ) => {
if ( next > prev ) {
this._addPanels( next - prev );
} else {
this._removePanels( prev - next );
}
this.updatePosition();
} );
}
// @private
// @param {Number} number
_addPanels( number ) {
while ( number-- ) {
const view = new _view__WEBPACK_IMPORTED_MODULE_2__["default"]();
view.setTemplate( { tag: 'div' } );
this.content.add( view );
this.registerChild( view );
}
}
// @private
// @param {Number} number
_removePanels( number ) {
while ( number-- ) {
const view = this.content.last;
this.content.remove( view );
this.deregisterChild( view );
view.destroy();
}
}
// Updates coordinates of fake panels.
updatePosition() {
if ( this.numberOfPanels ) {
const { top, left } = this._balloonPanelView;
const { width, height } = new _ckeditor_ckeditor5_utils_src_dom_rect__WEBPACK_IMPORTED_MODULE_7__["default"]( this._balloonPanelView.element );
Object.assign( this, { top, left, width, height } );
}
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/src/panel/sticky/stickypanelview.js":
/*!*********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/src/panel/sticky/stickypanelview.js ***!
\*********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ StickyPanelView)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_utils_src_dom_global__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/dom/global */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/global.js");
/* harmony import */ var _view__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../../view */ "./node_modules/@ckeditor/ckeditor5-ui/src/view.js");
/* harmony import */ var _template__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../template */ "./node_modules/@ckeditor/ckeditor5-ui/src/template.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_dom_tounit__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/dom/tounit */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/tounit.js");
/* harmony import */ var _theme_components_panel_stickypanel_css__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../../../theme/components/panel/stickypanel.css */ "./node_modules/@ckeditor/ckeditor5-ui/theme/components/panel/stickypanel.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module ui/panel/sticky/stickypanelview
*/
const toPx = (0,_ckeditor_ckeditor5_utils_src_dom_tounit__WEBPACK_IMPORTED_MODULE_3__["default"])( 'px' );
/**
* The sticky panel view class.
*/
class StickyPanelView extends _view__WEBPACK_IMPORTED_MODULE_1__["default"] {
/**
* @inheritDoc
*/
constructor( locale ) {
super( locale );
const bind = this.bindTemplate;
/**
* Controls whether the sticky panel should be active.
*
* @readonly
* @observable
* @member {Boolean} #isActive
*/
this.set( 'isActive', false );
/**
* Controls whether the sticky panel is in the "sticky" state.
*
* @readonly
* @observable
* @member {Boolean} #isSticky
*/
this.set( 'isSticky', false );
/**
* The limiter element for the sticky panel instance. Its bounding rect limits
* the "stickyness" of the panel, i.e. when the panel reaches the bottom
* edge of the limiter, it becomes sticky to that edge and does not float
* off the limiter. It is mandatory for the panel to work properly and once
* set, it cannot be changed.
*
* @readonly
* @observable
* @member {HTMLElement} #limiterElement
*/
this.set( 'limiterElement', null );
/**
* The offset from the bottom edge of {@link #limiterElement}
* which stops the panel from stickying any further to prevent limiter's content
* from being completely covered.
*
* @readonly
* @observable
* @default 50
* @member {Number} #limiterBottomOffset
*/
this.set( 'limiterBottomOffset', 50 );
/**
* The offset from the top edge of the web browser's viewport which makes the
* panel become sticky. The default value is `0`, which means the panel becomes
* sticky when it's upper edge touches the top of the page viewport.
*
* This attribute is useful when the web page has UI elements positioned to the top
* either using `position: fixed` or `position: sticky`, which would cover the
* sticky panel or vice–versa (depending on the `z-index` hierarchy).
*
* Bound to {@link module:core/editor/editorui~EditorUI#viewportOffset `EditorUI#viewportOffset`}.
*
* If {@link module:core/editor/editorconfig~EditorConfig#ui `EditorConfig#ui.viewportOffset.top`} is defined, then
* it will override the default value.
*
* @observable
* @default 0
* @member {Number} #viewportTopOffset
*/
this.set( 'viewportTopOffset', 0 );
/**
* Controls the `margin-left` CSS style of the panel.
*
* @protected
* @readonly
* @observable
* @member {String} #_marginLeft
*/
this.set( '_marginLeft', null );
/**
* Set `true` if the sticky panel reached the bottom edge of the
* {@link #limiterElement}.
*
* @protected
* @readonly
* @observable
* @member {Boolean} #_isStickyToTheLimiter
*/
this.set( '_isStickyToTheLimiter', false );
/**
* Set `true` if the sticky panel uses the {@link #viewportTopOffset},
* i.e. not {@link #_isStickyToTheLimiter} and the {@link #viewportTopOffset}
* is not `0`.
*
* @protected
* @readonly
* @observable
* @member {Boolean} #_hasViewportTopOffset
*/
this.set( '_hasViewportTopOffset', false );
/**
* Collection of the child views which creates balloon panel contents.
*
* @readonly
* @member {module:ui/viewcollection~ViewCollection}
*/
this.content = this.createCollection();
/**
* The DOM bounding client rect of the {@link module:ui/view~View#element} of the panel.
*
* @protected
* @member {Object} #_panelRect
*/
/**
* The DOM bounding client rect of the {@link #limiterElement}
* of the panel.
*
* @protected
* @member {Object} #_limiterRect
*/
/**
* A dummy element which visually fills the space as long as the
* actual panel is sticky. It prevents flickering of the UI.
*
* @protected
* @property {HTMLElement}
*/
this._contentPanelPlaceholder = new _template__WEBPACK_IMPORTED_MODULE_2__["default"]( {
tag: 'div',
attributes: {
class: [
'ck',
'ck-sticky-panel__placeholder'
],
style: {
display: bind.to( 'isSticky', isSticky => isSticky ? 'block' : 'none' ),
height: bind.to( 'isSticky', isSticky => {
return isSticky ? toPx( this._panelRect.height ) : null;
} )
}
}
} ).render();
/**
* The panel which accepts children into {@link #content} collection.
* Also an element which is positioned when {@link #isSticky}.
*
* @protected
* @property {HTMLElement}
*/
this._contentPanel = new _template__WEBPACK_IMPORTED_MODULE_2__["default"]( {
tag: 'div',
attributes: {
class: [
'ck',
'ck-sticky-panel__content',
// Toggle class of the panel when "sticky" state changes in the view.
bind.if( 'isSticky', 'ck-sticky-panel__content_sticky' ),
bind.if( '_isStickyToTheLimiter', 'ck-sticky-panel__content_sticky_bottom-limit' )
],
style: {
width: bind.to( 'isSticky', isSticky => {
return isSticky ? toPx( this._contentPanelPlaceholder.getBoundingClientRect().width ) : null;
} ),
top: bind.to( '_hasViewportTopOffset', _hasViewportTopOffset => {
return _hasViewportTopOffset ? toPx( this.viewportTopOffset ) : null;
} ),
bottom: bind.to( '_isStickyToTheLimiter', _isStickyToTheLimiter => {
return _isStickyToTheLimiter ? toPx( this.limiterBottomOffset ) : null;
} ),
marginLeft: bind.to( '_marginLeft' )
}
},
children: this.content
} ).render();
this.setTemplate( {
tag: 'div',
attributes: {
class: [
'ck',
'ck-sticky-panel'
]
},
children: [
this._contentPanelPlaceholder,
this._contentPanel
]
} );
}
/**
* @inheritDoc
*/
render() {
super.render();
// Check if the panel should go into the sticky state immediately.
this._checkIfShouldBeSticky();
// Update sticky state of the panel as the window is being scrolled.
this.listenTo( _ckeditor_ckeditor5_utils_src_dom_global__WEBPACK_IMPORTED_MODULE_0__["default"].window, 'scroll', () => {
this._checkIfShouldBeSticky();
} );
// Synchronize with `model.isActive` because sticking an inactive panel is pointless.
this.listenTo( this, 'change:isActive', () => {
this._checkIfShouldBeSticky();
} );
}
/**
* Analyzes the environment to decide whether the panel should
* be sticky or not.
*
* @protected
*/
_checkIfShouldBeSticky() {
const panelRect = this._panelRect = this._contentPanel.getBoundingClientRect();
let limiterRect;
if ( !this.limiterElement ) {
this.isSticky = false;
} else {
limiterRect = this._limiterRect = this.limiterElement.getBoundingClientRect();
// The panel must be active to become sticky.
this.isSticky = this.isActive &&
// The limiter's top edge must be beyond the upper edge of the visible viewport (+the viewportTopOffset).
limiterRect.top < this.viewportTopOffset &&
// The model#limiterElement's height mustn't be smaller than the panel's height and model#limiterBottomOffset.
// There's no point in entering the sticky mode if the model#limiterElement is very, very small, because
// it would immediately set model#_isStickyToTheLimiter true and, given model#limiterBottomOffset, the panel
// would be positioned before the model#limiterElement.
this._panelRect.height + this.limiterBottomOffset < limiterRect.height;
}
// Stick the panel to the top edge of the viewport simulating CSS position:sticky.
// TODO: Possibly replaced by CSS in the future http://caniuse.com/#feat=css-sticky
if ( this.isSticky ) {
this._isStickyToTheLimiter =
limiterRect.bottom < panelRect.height + this.limiterBottomOffset + this.viewportTopOffset;
this._hasViewportTopOffset = !this._isStickyToTheLimiter && !!this.viewportTopOffset;
this._marginLeft = this._isStickyToTheLimiter ? null : toPx( -_ckeditor_ckeditor5_utils_src_dom_global__WEBPACK_IMPORTED_MODULE_0__["default"].window.scrollX );
}
// Detach the panel from the top edge of the viewport.
else {
this._isStickyToTheLimiter = false;
this._hasViewportTopOffset = false;
this._marginLeft = null;
}
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/src/template.js":
/*!*************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/src/template.js ***!
\*************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "TemplateBinding": () => (/* binding */ TemplateBinding),
/* harmony export */ "TemplateIfBinding": () => (/* binding */ TemplateIfBinding),
/* harmony export */ "TemplateToBinding": () => (/* binding */ TemplateToBinding),
/* harmony export */ "default": () => (/* binding */ Template)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/mix */ "./node_modules/@ckeditor/ckeditor5-utils/src/mix.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_emittermixin__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/emittermixin */ "./node_modules/@ckeditor/ckeditor5-utils/src/emittermixin.js");
/* harmony import */ var _view__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./view */ "./node_modules/@ckeditor/ckeditor5-ui/src/view.js");
/* harmony import */ var _viewcollection__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./viewcollection */ "./node_modules/@ckeditor/ckeditor5-ui/src/viewcollection.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_dom_isnode__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/dom/isnode */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/isnode.js");
/* harmony import */ var lodash_es__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! lodash-es */ "./node_modules/lodash-es/isObject.js");
/* harmony import */ var lodash_es__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! lodash-es */ "./node_modules/lodash-es/cloneDeepWith.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_toarray__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/toarray */ "./node_modules/@ckeditor/ckeditor5-utils/src/toarray.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module ui/template
*/
/* global document */
const xhtmlNs = 'http://www.w3.org/1999/xhtml';
/**
* A basic Template class. It renders a DOM HTML element or text from a
* {@link module:ui/template~TemplateDefinition definition} and supports element attributes, children,
* bindings to {@link module:utils/observablemixin~Observable observables} and DOM event propagation.
*
* A simple template can look like this:
*
* const bind = Template.bind( observable, emitter );
*
* new Template( {
* tag: 'p',
* attributes: {
* class: 'foo',
* style: {
* backgroundColor: 'yellow'
* }
* },
* on: {
* click: bind.to( 'clicked' )
* },
* children: [
* 'A paragraph.'
* ]
* } ).render();
*
* and it will render the following HTML element:
*
* <p class="foo" style="background-color: yellow;">A paragraph.</p>
*
* Additionally, the `observable` will always fire `clicked` upon clicking `<p>` in the DOM.
*
* See {@link module:ui/template~TemplateDefinition} to know more about templates and complex
* template definitions.
*
* @mixes module:utils/emittermixin~EmitterMixin
*/
class Template {
/**
* Creates an instance of the {@link ~Template} class.
*
* @param {module:ui/template~TemplateDefinition} def The definition of the template.
*/
constructor( def ) {
Object.assign( this, normalize( clone( def ) ) );
/**
* Indicates whether this particular Template instance has been
* {@link #render rendered}.
*
* @readonly
* @protected
* @member {Boolean}
*/
this._isRendered = false;
/**
* The tag (`tagName`) of this template, e.g. `div`. It also indicates that the template
* renders to an HTML element.
*
* @member {String} #tag
*/
/**
* The text of the template. It also indicates that the template renders to a DOM text node.
*
* @member {Array.<String|module:ui/template~TemplateValueSchema>} #text
*/
/**
* The attributes of the template, e.g. `{ id: [ 'ck-id' ] }`, corresponding with
* the attributes of an HTML element.
*
* **Note**: This property only makes sense when {@link #tag} is defined.
*
* @member {Object} #attributes
*/
/**
* The children of the template. They can be either:
* * independent instances of {@link ~Template} (sub–templates),
* * native DOM Nodes.
*
* **Note**: This property only makes sense when {@link #tag} is defined.
*
* @member {Array.<module:ui/template~Template|Node>} #children
*/
/**
* The DOM event listeners of the template.
*
* @member {Object} #eventListeners
*/
/**
* The data used by the {@link #revert} method to restore a node to its original state.
*
* See: {@link #apply}.
*
* @readonly
* @protected
* @member {module:ui/template~RenderData}
*/
this._revertData = null;
}
/**
* Renders a DOM Node (an HTML element or text) out of the template.
*
* const domNode = new Template( { ... } ).render();
*
* See: {@link #apply}.
*
* @returns {HTMLElement|Text}
*/
render() {
const node = this._renderNode( {
intoFragment: true
} );
this._isRendered = true;
return node;
}
/**
* Applies the template to an existing DOM Node, either HTML element or text.
*
* **Note:** No new DOM nodes will be created. Applying extends:
*
* {@link module:ui/template~TemplateDefinition attributes},
* {@link module:ui/template~TemplateDefinition event listeners}, and
* `textContent` of {@link module:ui/template~TemplateDefinition children} only.
*
* **Note:** Existing `class` and `style` attributes are extended when a template
* is applied to an HTML element, while other attributes and `textContent` are overridden.
*
* **Note:** The process of applying a template can be easily reverted using the
* {@link module:ui/template~Template#revert} method.
*
* const element = document.createElement( 'div' );
* const observable = new Model( { divClass: 'my-div' } );
* const emitter = Object.create( EmitterMixin );
* const bind = Template.bind( observable, emitter );
*
* new Template( {
* attributes: {
* id: 'first-div',
* class: bind.to( 'divClass' )
* },
* on: {
* click: bind( 'elementClicked' ) // Will be fired by the observable.
* },
* children: [
* 'Div text.'
* ]
* } ).apply( element );
*
* console.log( element.outerHTML ); // -> '<div id="first-div" class="my-div"></div>'
*
* @see module:ui/template~Template#render
* @see module:ui/template~Template#revert
* @param {Node} node Root node for the template to apply.
*/
apply( node ) {
this._revertData = getEmptyRevertData();
this._renderNode( {
node,
isApplying: true,
revertData: this._revertData
} );
return node;
}
/**
* Reverts a template {@link module:ui/template~Template#apply applied} to a DOM node.
*
* @param {Node} node The root node for the template to revert. In most of the cases, it is the
* same node used by {@link module:ui/template~Template#apply}.
*/
revert( node ) {
if ( !this._revertData ) {
/**
* Attempting to revert a template which has not been applied yet.
*
* @error ui-template-revert-not-applied
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_0__["default"](
'ui-template-revert-not-applied',
[ this, node ]
);
}
this._revertTemplateFromNode( node, this._revertData );
}
/**
* Returns an iterator which traverses the template in search of {@link module:ui/view~View}
* instances and returns them one by one.
*
* const viewFoo = new View();
* const viewBar = new View();
* const viewBaz = new View();
* const template = new Template( {
* tag: 'div',
* children: [
* viewFoo,
* {
* tag: 'div',
* children: [
* viewBar
* ]
* },
* viewBaz
* ]
* } );
*
* // Logs: viewFoo, viewBar, viewBaz
* for ( const view of template.getViews() ) {
* console.log( view );
* }
*
* @returns {Iterable.<module:ui/view~View>}
*/
* getViews() {
function* search( def ) {
if ( def.children ) {
for ( const child of def.children ) {
if ( isView( child ) ) {
yield child;
} else if ( isTemplate( child ) ) {
yield* search( child );
}
}
}
}
yield* search( this );
}
/**
* An entry point to the interface which binds DOM nodes to
* {@link module:utils/observablemixin~Observable observables}.
* There are two types of bindings:
*
* * HTML element attributes or text `textContent` synchronized with attributes of an
* {@link module:utils/observablemixin~Observable}. Learn more about {@link module:ui/template~BindChain#to}
* and {@link module:ui/template~BindChain#if}.
*
* const bind = Template.bind( observable, emitter );
*
* new Template( {
* attributes: {
* // Binds the element "class" attribute to observable#classAttribute.
* class: bind.to( 'classAttribute' )
* }
* } ).render();
*
* * DOM events fired on HTML element propagated through
* {@link module:utils/observablemixin~Observable}. Learn more about {@link module:ui/template~BindChain#to}.
*
* const bind = Template.bind( observable, emitter );
*
* new Template( {
* on: {
* // Will be fired by the observable.
* click: bind( 'elementClicked' )
* }
* } ).render();
*
* Also see {@link module:ui/view~View#bindTemplate}.
*
* @param {module:utils/observablemixin~Observable} observable An observable which provides boundable attributes.
* @param {module:utils/emittermixin~Emitter} emitter An emitter that listens to observable attribute
* changes or DOM Events (depending on the kind of the binding). Usually, a {@link module:ui/view~View} instance.
* @returns {module:ui/template~BindChain}
*/
static bind( observable, emitter ) {
return {
to( eventNameOrFunctionOrAttribute, callback ) {
return new TemplateToBinding( {
eventNameOrFunction: eventNameOrFunctionOrAttribute,
attribute: eventNameOrFunctionOrAttribute,
observable, emitter, callback
} );
},
if( attribute, valueIfTrue, callback ) {
return new TemplateIfBinding( {
observable, emitter, attribute, valueIfTrue, callback
} );
}
};
}
/**
* Extends an existing {@link module:ui/template~Template} instance with some additional content
* from another {@link module:ui/template~TemplateDefinition}.
*
* const bind = Template.bind( observable, emitter );
*
* const template = new Template( {
* tag: 'p',
* attributes: {
* class: 'a',
* data-x: bind.to( 'foo' )
* },
* children: [
* {
* tag: 'span',
* attributes: {
* class: 'b'
* },
* children: [
* 'Span'
* ]
* }
* ]
* } );
*
* // Instance-level extension.
* Template.extend( template, {
* attributes: {
* class: 'b',
* data-x: bind.to( 'bar' )
* },
* children: [
* {
* attributes: {
* class: 'c'
* }
* }
* ]
* } );
*
* // Child extension.
* Template.extend( template.children[ 0 ], {
* attributes: {
* class: 'd'
* }
* } );
*
* the `outerHTML` of `template.render()` is:
*
* <p class="a b" data-x="{ observable.foo } { observable.bar }">
* <span class="b c d">Span</span>
* </p>
*
* @param {module:ui/template~Template} template An existing template instance to be extended.
* @param {module:ui/template~TemplateDefinition} def Additional definition to be applied to a template.
*/
static extend( template, def ) {
if ( template._isRendered ) {
/**
* Extending a template after rendering may not work as expected. To make sure
* the {@link module:ui/template~Template.extend extending} works for an element,
* make sure it happens before {@link #render} is called.
*
* @error template-extend-render
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_0__["default"](
'template-extend-render',
[ this, template ]
);
}
extendTemplate( template, normalize( clone( def ) ) );
}
/**
* Renders a DOM Node (either an HTML element or text) out of the template.
*
* @protected
* @param {module:ui/template~RenderData} data Rendering data.
*/
_renderNode( data ) {
let isInvalid;
if ( data.node ) {
// When applying, a definition cannot have "tag" and "text" at the same time.
isInvalid = this.tag && this.text;
} else {
// When rendering, a definition must have either "tag" or "text": XOR( this.tag, this.text ).
isInvalid = this.tag ? this.text : !this.text;
}
if ( isInvalid ) {
/**
* Node definition cannot have the "tag" and "text" properties at the same time.
* Node definition must have either "tag" or "text" when rendering a new Node.
*
* @error ui-template-wrong-syntax
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_0__["default"](
'ui-template-wrong-syntax',
this
);
}
if ( this.text ) {
return this._renderText( data );
} else {
return this._renderElement( data );
}
}
/**
* Renders an HTML element out of the template.
*
* @protected
* @param {module:ui/template~RenderData} data Rendering data.
*/
_renderElement( data ) {
let node = data.node;
if ( !node ) {
node = data.node = document.createElementNS( this.ns || xhtmlNs, this.tag );
}
this._renderAttributes( data );
this._renderElementChildren( data );
this._setUpListeners( data );
return node;
}
/**
* Renders a text node out of {@link module:ui/template~Template#text}.
*
* @protected
* @param {module:ui/template~RenderData} data Rendering data.
*/
_renderText( data ) {
let node = data.node;
// Save the original textContent to revert it in #revert().
if ( node ) {
data.revertData.text = node.textContent;
} else {
node = data.node = document.createTextNode( '' );
}
// Check if this Text Node is bound to Observable. Cases:
//
// text: [ Template.bind( ... ).to( ... ) ]
//
// text: [
// 'foo',
// Template.bind( ... ).to( ... ),
// ...
// ]
//
if ( hasTemplateBinding( this.text ) ) {
this._bindToObservable( {
schema: this.text,
updater: getTextUpdater( node ),
data
} );
}
// Simply set text. Cases:
//
// text: [ 'all', 'are', 'static' ]
//
// text: [ 'foo' ]
//
else {
node.textContent = this.text.join( '' );
}
return node;
}
/**
* Renders HTML element attributes out of {@link module:ui/template~Template#attributes}.
*
* @protected
* @param {module:ui/template~RenderData} data Rendering data.
*/
_renderAttributes( data ) {
let attrName, attrValue, domAttrValue, attrNs;
if ( !this.attributes ) {
return;
}
const node = data.node;
const revertData = data.revertData;
for ( attrName in this.attributes ) {
// Current attribute value in DOM.
domAttrValue = node.getAttribute( attrName );
// The value to be set.
attrValue = this.attributes[ attrName ];
// Save revert data.
if ( revertData ) {
revertData.attributes[ attrName ] = domAttrValue;
}
// Detect custom namespace:
//
// class: {
// ns: 'abc',
// value: Template.bind( ... ).to( ... )
// }
//
attrNs = ( (0,lodash_es__WEBPACK_IMPORTED_MODULE_7__["default"])( attrValue[ 0 ] ) && attrValue[ 0 ].ns ) ? attrValue[ 0 ].ns : null;
// Activate binding if one is found. Cases:
//
// class: [
// Template.bind( ... ).to( ... )
// ]
//
// class: [
// 'bar',
// Template.bind( ... ).to( ... ),
// 'baz'
// ]
//
// class: {
// ns: 'abc',
// value: Template.bind( ... ).to( ... )
// }
//
if ( hasTemplateBinding( attrValue ) ) {
// Normalize attributes with additional data like namespace:
//
// class: {
// ns: 'abc',
// value: [ ... ]
// }
//
const valueToBind = attrNs ? attrValue[ 0 ].value : attrValue;
// Extend the original value of attributes like "style" and "class",
// don't override them.
if ( revertData && shouldExtend( attrName ) ) {
valueToBind.unshift( domAttrValue );
}
this._bindToObservable( {
schema: valueToBind,
updater: getAttributeUpdater( node, attrName, attrNs ),
data
} );
}
// Style attribute could be an Object so it needs to be parsed in a specific way.
//
// style: {
// width: '100px',
// height: Template.bind( ... ).to( ... )
// }
//
else if ( attrName == 'style' && typeof attrValue[ 0 ] !== 'string' ) {
this._renderStyleAttribute( attrValue[ 0 ], data );
}
// Otherwise simply set the static attribute:
//
// class: [ 'foo' ]
//
// class: [ 'all', 'are', 'static' ]
//
// class: [
// {
// ns: 'abc',
// value: [ 'foo' ]
// }
// ]
//
else {
// Extend the original value of attributes like "style" and "class",
// don't override them.
if ( revertData && domAttrValue && shouldExtend( attrName ) ) {
attrValue.unshift( domAttrValue );
}
attrValue = attrValue
// Retrieve "values" from:
//
// class: [
// {
// ns: 'abc',
// value: [ ... ]
// }
// ]
//
.map( val => val ? ( val.value || val ) : val )
// Flatten the array.
.reduce( ( prev, next ) => prev.concat( next ), [] )
// Convert into string.
.reduce( arrayValueReducer, '' );
if ( !isFalsy( attrValue ) ) {
node.setAttributeNS( attrNs, attrName, attrValue );
}
}
}
}
/**
* Renders the `style` attribute of an HTML element based on
* {@link module:ui/template~Template#attributes}.
*
* A style attribute is an {Object} with static values:
*
* attributes: {
* style: {
* color: 'red'
* }
* }
*
* or values bound to {@link module:ui/model~Model} properties:
*
* attributes: {
* style: {
* color: bind.to( ... )
* }
* }
*
* Note: The `style` attribute is rendered without setting the namespace. It does not seem to be
* needed.
*
* @private
* @param {Object} styles Styles located in `attributes.style` of {@link module:ui/template~TemplateDefinition}.
* @param {module:ui/template~RenderData} data Rendering data.
*/
_renderStyleAttribute( styles, data ) {
const node = data.node;
for ( const styleName in styles ) {
const styleValue = styles[ styleName ];
// Cases:
//
// style: {
// color: bind.to( 'attribute' )
// }
//
if ( hasTemplateBinding( styleValue ) ) {
this._bindToObservable( {
schema: [ styleValue ],
updater: getStyleUpdater( node, styleName ),
data
} );
}
// Cases:
//
// style: {
// color: 'red'
// }
//
else {
node.style[ styleName ] = styleValue;
}
}
}
/**
* Recursively renders HTML element's children from {@link module:ui/template~Template#children}.
*
* @protected
* @param {module:ui/template~RenderData} data Rendering data.
*/
_renderElementChildren( data ) {
const node = data.node;
const container = data.intoFragment ? document.createDocumentFragment() : node;
const isApplying = data.isApplying;
let childIndex = 0;
for ( const child of this.children ) {
if ( isViewCollection( child ) ) {
if ( !isApplying ) {
child.setParent( node );
// Note: ViewCollection renders its children.
for ( const view of child ) {
container.appendChild( view.element );
}
}
} else if ( isView( child ) ) {
if ( !isApplying ) {
if ( !child.isRendered ) {
child.render();
}
container.appendChild( child.element );
}
} else if ( (0,_ckeditor_ckeditor5_utils_src_dom_isnode__WEBPACK_IMPORTED_MODULE_5__["default"])( child ) ) {
container.appendChild( child );
} else {
if ( isApplying ) {
const revertData = data.revertData;
const childRevertData = getEmptyRevertData();
revertData.children.push( childRevertData );
child._renderNode( {
node: container.childNodes[ childIndex++ ],
isApplying: true,
revertData: childRevertData
} );
} else {
container.appendChild( child.render() );
}
}
}
if ( data.intoFragment ) {
node.appendChild( container );
}
}
/**
* Activates `on` event listeners from the {@link module:ui/template~TemplateDefinition}
* on an HTML element.
*
* @protected
* @param {module:ui/template~RenderData} data Rendering data.
*/
_setUpListeners( data ) {
if ( !this.eventListeners ) {
return;
}
for ( const key in this.eventListeners ) {
const revertBindings = this.eventListeners[ key ].map( schemaItem => {
const [ domEvtName, domSelector ] = key.split( '@' );
return schemaItem.activateDomEventListener( domEvtName, domSelector, data );
} );
if ( data.revertData ) {
data.revertData.bindings.push( revertBindings );
}
}
}
/**
* For a given {@link module:ui/template~TemplateValueSchema} containing {@link module:ui/template~TemplateBinding}
* activates the binding and sets its initial value.
*
* Note: {@link module:ui/template~TemplateValueSchema} can be for HTML element attributes or
* text node `textContent`.
*
* @protected
* @param {Object} options Binding options.
* @param {module:ui/template~TemplateValueSchema} options.schema
* @param {Function} options.updater A function which updates the DOM (like attribute or text).
* @param {module:ui/template~RenderData} options.data Rendering data.
*/
_bindToObservable( { schema, updater, data } ) {
const revertData = data.revertData;
// Set initial values.
syncValueSchemaValue( schema, updater, data );
const revertBindings = schema
// Filter "falsy" (false, undefined, null, '') value schema components out.
.filter( item => !isFalsy( item ) )
// Filter inactive bindings from schema, like static strings ('foo'), numbers (42), etc.
.filter( item => item.observable )
// Once only the actual binding are left, let the emitter listen to observable change:attribute event.
// TODO: Reduce the number of listeners attached as many bindings may listen
// to the same observable attribute.
.map( templateBinding => templateBinding.activateAttributeListener( schema, updater, data ) );
if ( revertData ) {
revertData.bindings.push( revertBindings );
}
}
/**
* Reverts {@link module:ui/template~RenderData#revertData template data} from a node to
* return it to the original state.
*
* @protected
* @param {HTMLElement|Text} node A node to be reverted.
* @param {Object} revertData An object that stores information about what changes have been made by
* {@link #apply} to the node. See {@link module:ui/template~RenderData#revertData} for more information.
*/
_revertTemplateFromNode( node, revertData ) {
for ( const binding of revertData.bindings ) {
// Each binding may consist of several observable+observable#attribute.
// like the following has 2:
//
// class: [
// 'x',
// bind.to( 'foo' ),
// 'y',
// bind.to( 'bar' )
// ]
//
for ( const revertBinding of binding ) {
revertBinding();
}
}
if ( revertData.text ) {
node.textContent = revertData.text;
return;
}
for ( const attrName in revertData.attributes ) {
const attrValue = revertData.attributes[ attrName ];
// When the attribute has **not** been set before #apply().
if ( attrValue === null ) {
node.removeAttribute( attrName );
} else {
node.setAttribute( attrName, attrValue );
}
}
for ( let i = 0; i < revertData.children.length; ++i ) {
this._revertTemplateFromNode( node.childNodes[ i ], revertData.children[ i ] );
}
}
}
(0,_ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_1__["default"])( Template, _ckeditor_ckeditor5_utils_src_emittermixin__WEBPACK_IMPORTED_MODULE_2__["default"] );
/**
* Describes a binding created by the {@link module:ui/template~Template.bind} interface.
*
* @protected
*/
class TemplateBinding {
/**
* Creates an instance of the {@link module:ui/template~TemplateBinding} class.
*
* @param {module:ui/template~TemplateDefinition} def The definition of the binding.
*/
constructor( def ) {
Object.assign( this, def );
/**
* An observable instance of the binding. It either:
*
* * provides the attribute with the value,
* * or passes the event when a corresponding DOM event is fired.
*
* @member {module:utils/observablemixin~ObservableMixin} module:ui/template~TemplateBinding#observable
*/
/**
* An {@link module:utils/emittermixin~Emitter} used by the binding to:
*
* * listen to the attribute change in the {@link module:ui/template~TemplateBinding#observable},
* * or listen to the event in the DOM.
*
* @member {module:utils/emittermixin~EmitterMixin} module:ui/template~TemplateBinding#emitter
*/
/**
* The name of the {@link module:ui/template~TemplateBinding#observable observed attribute}.
*
* @member {String} module:ui/template~TemplateBinding#attribute
*/
/**
* A custom function to process the value of the {@link module:ui/template~TemplateBinding#attribute}.
*
* @member {Function} [module:ui/template~TemplateBinding#callback]
*/
}
/**
* Returns the value of the binding. It is the value of the {@link module:ui/template~TemplateBinding#attribute} in
* {@link module:ui/template~TemplateBinding#observable}. The value may be processed by the
* {@link module:ui/template~TemplateBinding#callback}, if such has been passed to the binding.
*
* @param {Node} [node] A native DOM node, passed to the custom {@link module:ui/template~TemplateBinding#callback}.
* @returns {*} The value of {@link module:ui/template~TemplateBinding#attribute} in
* {@link module:ui/template~TemplateBinding#observable}.
*/
getValue( node ) {
const value = this.observable[ this.attribute ];
return this.callback ? this.callback( value, node ) : value;
}
/**
* Activates the listener which waits for changes of the {@link module:ui/template~TemplateBinding#attribute} in
* {@link module:ui/template~TemplateBinding#observable}, then updates the DOM with the aggregated
* value of {@link module:ui/template~TemplateValueSchema}.
*
* @param {module:ui/template~TemplateValueSchema} schema A full schema to generate an attribute or text in the DOM.
* @param {Function} updater A DOM updater function used to update the native DOM attribute or text.
* @param {module:ui/template~RenderData} data Rendering data.
* @returns {Function} A function to sever the listener binding.
*/
activateAttributeListener( schema, updater, data ) {
const callback = () => syncValueSchemaValue( schema, updater, data );
this.emitter.listenTo( this.observable, 'change:' + this.attribute, callback );
// Allows revert of the listener.
return () => {
this.emitter.stopListening( this.observable, 'change:' + this.attribute, callback );
};
}
}
/**
* Describes either:
*
* * a binding to an {@link module:utils/observablemixin~Observable},
* * or a native DOM event binding.
*
* It is created by the {@link module:ui/template~BindChain#to} method.
*
* @protected
*/
class TemplateToBinding extends TemplateBinding {
/**
* Activates the listener for the native DOM event, which when fired, is propagated by
* the {@link module:ui/template~TemplateBinding#emitter}.
*
* @param {String} domEvtName The name of the native DOM event.
* @param {String} domSelector The selector in the DOM to filter delegated events.
* @param {module:ui/template~RenderData} data Rendering data.
* @returns {Function} A function to sever the listener binding.
*/
activateDomEventListener( domEvtName, domSelector, data ) {
const callback = ( evt, domEvt ) => {
if ( !domSelector || domEvt.target.matches( domSelector ) ) {
if ( typeof this.eventNameOrFunction == 'function' ) {
this.eventNameOrFunction( domEvt );
} else {
this.observable.fire( this.eventNameOrFunction, domEvt );
}
}
};
this.emitter.listenTo( data.node, domEvtName, callback );
// Allows revert of the listener.
return () => {
this.emitter.stopListening( data.node, domEvtName, callback );
};
}
}
/**
* Describes a binding to {@link module:utils/observablemixin~ObservableMixin} created by the {@link module:ui/template~BindChain#if}
* method.
*
* @protected
*/
class TemplateIfBinding extends TemplateBinding {
/**
* @inheritDoc
*/
getValue( node ) {
const value = super.getValue( node );
return isFalsy( value ) ? false : ( this.valueIfTrue || true );
}
/**
* The value of the DOM attribute or text to be set if the {@link module:ui/template~TemplateBinding#attribute} in
* {@link module:ui/template~TemplateBinding#observable} is `true`.
*
* @member {String} [module:ui/template~TemplateIfBinding#valueIfTrue]
*/
}
// Checks whether given {@link module:ui/template~TemplateValueSchema} contains a
// {@link module:ui/template~TemplateBinding}.
//
// @param {module:ui/template~TemplateValueSchema} schema
// @returns {Boolean}
function hasTemplateBinding( schema ) {
if ( !schema ) {
return false;
}
// Normalize attributes with additional data like namespace:
//
// class: {
// ns: 'abc',
// value: [ ... ]
// }
//
if ( schema.value ) {
schema = schema.value;
}
if ( Array.isArray( schema ) ) {
return schema.some( hasTemplateBinding );
} else if ( schema instanceof TemplateBinding ) {
return true;
}
return false;
}
// Assembles the value using {@link module:ui/template~TemplateValueSchema} and stores it in a form of
// an Array. Each entry of the Array corresponds to one of {@link module:ui/template~TemplateValueSchema}
// items.
//
// @param {module:ui/template~TemplateValueSchema} schema
// @param {Node} node DOM Node updated when {@link module:utils/observablemixin~ObservableMixin} changes.
// @returns {Array}
function getValueSchemaValue( schema, node ) {
return schema.map( schemaItem => {
// Process {@link module:ui/template~TemplateBinding} bindings.
if ( schemaItem instanceof TemplateBinding ) {
return schemaItem.getValue( node );
}
// All static values like strings, numbers, and "falsy" values (false, null, undefined, '', etc.) just pass.
return schemaItem;
} );
}
// A function executed each time the bound Observable attribute changes, which updates the DOM with a value
// constructed from {@link module:ui/template~TemplateValueSchema}.
//
// @param {module:ui/template~TemplateValueSchema} schema
// @param {Function} updater A function which updates the DOM (like attribute or text).
// @param {Node} node DOM Node updated when {@link module:utils/observablemixin~ObservableMixin} changes.
function syncValueSchemaValue( schema, updater, { node } ) {
let value = getValueSchemaValue( schema, node );
// Check if schema is a single Template.bind.if, like:
//
// class: Template.bind.if( 'foo' )
//
if ( schema.length == 1 && schema[ 0 ] instanceof TemplateIfBinding ) {
value = value[ 0 ];
} else {
value = value.reduce( arrayValueReducer, '' );
}
if ( isFalsy( value ) ) {
updater.remove();
} else {
updater.set( value );
}
}
// Returns an object consisting of `set` and `remove` functions, which
// can be used in the context of DOM Node to set or reset `textContent`.
// @see module:ui/view~View#_bindToObservable
//
// @param {Node} node DOM Node to be modified.
// @returns {Object}
function getTextUpdater( node ) {
return {
set( value ) {
node.textContent = value;
},
remove() {
node.textContent = '';
}
};
}
// Returns an object consisting of `set` and `remove` functions, which
// can be used in the context of DOM Node to set or reset an attribute.
// @see module:ui/view~View#_bindToObservable
//
// @param {Node} node DOM Node to be modified.
// @param {String} attrName Name of the attribute to be modified.
// @param {String} [ns=null] Namespace to use.
// @returns {Object}
function getAttributeUpdater( el, attrName, ns ) {
return {
set( value ) {
el.setAttributeNS( ns, attrName, value );
},
remove() {
el.removeAttributeNS( ns, attrName );
}
};
}
// Returns an object consisting of `set` and `remove` functions, which
// can be used in the context of CSSStyleDeclaration to set or remove a style.
// @see module:ui/view~View#_bindToObservable
//
// @param {Node} node DOM Node to be modified.
// @param {String} styleName Name of the style to be modified.
// @returns {Object}
function getStyleUpdater( el, styleName ) {
return {
set( value ) {
el.style[ styleName ] = value;
},
remove() {
el.style[ styleName ] = null;
}
};
}
// Clones definition of the template.
//
// @param {module:ui/template~TemplateDefinition} def
// @returns {module:ui/template~TemplateDefinition}
function clone( def ) {
const clone = (0,lodash_es__WEBPACK_IMPORTED_MODULE_8__["default"])( def, value => {
// Don't clone the `Template.bind`* bindings because of the references to Observable
// and DomEmitterMixin instances inside, which would also be traversed and cloned by greedy
// cloneDeepWith algorithm. There's no point in cloning Observable/DomEmitterMixins
// along with the definition.
//
// Don't clone Template instances if provided as a child. They're simply #render()ed
// and nothing should interfere.
//
// Also don't clone View instances if provided as a child of the Template. The template
// instance will be extracted from the View during the normalization and there's no need
// to clone it.
if ( value && ( value instanceof TemplateBinding || isTemplate( value ) || isView( value ) || isViewCollection( value ) ) ) {
return value;
}
} );
return clone;
}
// Normalizes given {@link module:ui/template~TemplateDefinition}.
//
// See:
// * {@link normalizeAttributes}
// * {@link normalizeListeners}
// * {@link normalizePlainTextDefinition}
// * {@link normalizeTextDefinition}
//
// @param {module:ui/template~TemplateDefinition} def
// @returns {module:ui/template~TemplateDefinition} Normalized definition.
function normalize( def ) {
if ( typeof def == 'string' ) {
def = normalizePlainTextDefinition( def );
} else if ( def.text ) {
normalizeTextDefinition( def );
}
if ( def.on ) {
def.eventListeners = normalizeListeners( def.on );
// Template mixes EmitterMixin, so delete #on to avoid collision.
delete def.on;
}
if ( !def.text ) {
if ( def.attributes ) {
normalizeAttributes( def.attributes );
}
const children = [];
if ( def.children ) {
if ( isViewCollection( def.children ) ) {
children.push( def.children );
} else {
for ( const child of def.children ) {
if ( isTemplate( child ) || isView( child ) || (0,_ckeditor_ckeditor5_utils_src_dom_isnode__WEBPACK_IMPORTED_MODULE_5__["default"])( child ) ) {
children.push( child );
} else {
children.push( new Template( child ) );
}
}
}
}
def.children = children;
}
return def;
}
// Normalizes "attributes" section of {@link module:ui/template~TemplateDefinition}.
//
// attributes: {
// a: 'bar',
// b: {@link module:ui/template~TemplateBinding},
// c: {
// value: 'bar'
// }
// }
//
// becomes
//
// attributes: {
// a: [ 'bar' ],
// b: [ {@link module:ui/template~TemplateBinding} ],
// c: {
// value: [ 'bar' ]
// }
// }
//
// @param {Object} attributes
function normalizeAttributes( attributes ) {
for ( const a in attributes ) {
if ( attributes[ a ].value ) {
attributes[ a ].value = (0,_ckeditor_ckeditor5_utils_src_toarray__WEBPACK_IMPORTED_MODULE_6__["default"])( attributes[ a ].value );
}
arrayify( attributes, a );
}
}
// Normalizes "on" section of {@link module:ui/template~TemplateDefinition}.
//
// on: {
// a: 'bar',
// b: {@link module:ui/template~TemplateBinding},
// c: [ {@link module:ui/template~TemplateBinding}, () => { ... } ]
// }
//
// becomes
//
// on: {
// a: [ 'bar' ],
// b: [ {@link module:ui/template~TemplateBinding} ],
// c: [ {@link module:ui/template~TemplateBinding}, () => { ... } ]
// }
//
// @param {Object} listeners
// @returns {Object} Object containing normalized listeners.
function normalizeListeners( listeners ) {
for ( const l in listeners ) {
arrayify( listeners, l );
}
return listeners;
}
// Normalizes "string" {@link module:ui/template~TemplateDefinition}.
//
// "foo"
//
// becomes
//
// { text: [ 'foo' ] },
//
// @param {String} def
// @returns {module:ui/template~TemplateDefinition} Normalized template definition.
function normalizePlainTextDefinition( def ) {
return {
text: [ def ]
};
}
// Normalizes text {@link module:ui/template~TemplateDefinition}.
//
// children: [
// { text: 'def' },
// { text: {@link module:ui/template~TemplateBinding} }
// ]
//
// becomes
//
// children: [
// { text: [ 'def' ] },
// { text: [ {@link module:ui/template~TemplateBinding} ] }
// ]
//
// @param {module:ui/template~TemplateDefinition} def
function normalizeTextDefinition( def ) {
def.text = (0,_ckeditor_ckeditor5_utils_src_toarray__WEBPACK_IMPORTED_MODULE_6__["default"])( def.text );
}
// Wraps an entry in Object in an Array, if not already one.
//
// {
// x: 'y',
// a: [ 'b' ]
// }
//
// becomes
//
// {
// x: [ 'y' ],
// a: [ 'b' ]
// }
//
// @param {Object} obj
// @param {String} key
function arrayify( obj, key ) {
obj[ key ] = (0,_ckeditor_ckeditor5_utils_src_toarray__WEBPACK_IMPORTED_MODULE_6__["default"])( obj[ key ] );
}
// A helper which concatenates the value avoiding unwanted
// leading white spaces.
//
// @param {String} prev
// @param {String} cur
// @returns {String}
function arrayValueReducer( prev, cur ) {
if ( isFalsy( cur ) ) {
return prev;
} else if ( isFalsy( prev ) ) {
return cur;
} else {
return `${ prev } ${ cur }`;
}
}
// Extends one object defined in the following format:
//
// {
// key1: [Array1],
// key2: [Array2],
// ...
// keyN: [ArrayN]
// }
//
// with another object of the same data format.
//
// @param {Object} obj Base object.
// @param {Object} ext Object extending base.
// @returns {String}
function extendObjectValueArray( obj, ext ) {
for ( const a in ext ) {
if ( obj[ a ] ) {
obj[ a ].push( ...ext[ a ] );
} else {
obj[ a ] = ext[ a ];
}
}
}
// A helper for {@link module:ui/template~Template#extend}. Recursively extends {@link module:ui/template~Template} instance
// with content from {@link module:ui/template~TemplateDefinition}. See {@link module:ui/template~Template#extend} to learn more.
//
// @param {module:ui/template~Template} def A template instance to be extended.
// @param {module:ui/template~TemplateDefinition} def A definition which is to extend the template instance.
// @param {Object} Error context.
function extendTemplate( template, def ) {
if ( def.attributes ) {
if ( !template.attributes ) {
template.attributes = {};
}
extendObjectValueArray( template.attributes, def.attributes );
}
if ( def.eventListeners ) {
if ( !template.eventListeners ) {
template.eventListeners = {};
}
extendObjectValueArray( template.eventListeners, def.eventListeners );
}
if ( def.text ) {
template.text.push( ...def.text );
}
if ( def.children && def.children.length ) {
if ( template.children.length != def.children.length ) {
/**
* The number of children in extended definition does not match.
*
* @error ui-template-extend-children-mismatch
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_0__["default"](
'ui-template-extend-children-mismatch',
template
);
}
let childIndex = 0;
for ( const childDef of def.children ) {
extendTemplate( template.children[ childIndex++ ], childDef );
}
}
}
// Checks if value is "falsy".
// Note: 0 (Number) is not "falsy" in this context.
//
// @private
// @param {*} value Value to be checked.
function isFalsy( value ) {
return !value && value !== 0;
}
// Checks if the item is an instance of {@link module:ui/view~View}
//
// @private
// @param {*} value Value to be checked.
function isView( item ) {
return item instanceof _view__WEBPACK_IMPORTED_MODULE_3__["default"];
}
// Checks if the item is an instance of {@link module:ui/template~Template}
//
// @private
// @param {*} value Value to be checked.
function isTemplate( item ) {
return item instanceof Template;
}
// Checks if the item is an instance of {@link module:ui/viewcollection~ViewCollection}
//
// @private
// @param {*} value Value to be checked.
function isViewCollection( item ) {
return item instanceof _viewcollection__WEBPACK_IMPORTED_MODULE_4__["default"];
}
// Creates an empty skeleton for {@link module:ui/template~Template#revert}
// data.
//
// @private
function getEmptyRevertData() {
return {
children: [],
bindings: [],
attributes: {}
};
}
// Checks whether an attribute should be extended when
// {@link module:ui/template~Template#apply} is called.
//
// @private
// @param {String} attrName Attribute name to check.
function shouldExtend( attrName ) {
return attrName == 'class' || attrName == 'style';
}
/**
* A definition of the {@link module:ui/template~Template}. It describes what kind of
* node a template will render (HTML element or text), attributes of an element, DOM event
* listeners and children.
*
* Also see:
* * {@link module:ui/template~TemplateValueSchema} to learn about HTML element attributes,
* * {@link module:ui/template~TemplateListenerSchema} to learn about DOM event listeners.
*
* A sample definition on an HTML element can look like this:
*
* new Template( {
* tag: 'p',
* children: [
* {
* tag: 'span',
* attributes: { ... },
* children: [ ... ],
* },
* {
* text: 'static–text'
* },
* 'also-static–text',
* ],
* attributes: {
* class: {@link module:ui/template~TemplateValueSchema},
* id: {@link module:ui/template~TemplateValueSchema},
* style: {@link module:ui/template~TemplateValueSchema}
*
* // ...
* },
* on: {
* 'click': {@link module:ui/template~TemplateListenerSchema}
*
* // Document.querySelector format is also accepted.
* 'keyup@a.some-class': {@link module:ui/template~TemplateListenerSchema}
*
* // ...
* }
* } );
*
* A {@link module:ui/view~View}, another {@link module:ui/template~Template} or a native DOM node
* can also become a child of a template. When a view is passed, its {@link module:ui/view~View#element} is used:
*
* const view = new SomeView();
* const childTemplate = new Template( { ... } );
* const childNode = document.createElement( 'b' );
*
* new Template( {
* tag: 'p',
*
* children: [
* // view#element will be added as a child of this <p>.
* view,
*
* // The output of childTemplate.render() will be added here.
* childTemplate,
*
* // Native DOM nodes are included directly in the rendered output.
* childNode
* ]
* } );
*
* An entire {@link module:ui/viewcollection~ViewCollection} can be used as a child in the definition:
*
* const collection = new ViewCollection();
* collection.add( someView );
*
* new Template( {
* tag: 'p',
*
* children: collection
* } );
*
* @typedef module:ui/template~TemplateDefinition
* @type Object
*
* @property {String} tag See the template {@link module:ui/template~Template#tag} property.
*
* @property {Array.<module:ui/template~TemplateDefinition>} [children]
* See the template {@link module:ui/template~Template#children} property.
*
* @property {Object.<String, module:ui/template~TemplateValueSchema>} [attributes]
* See the template {@link module:ui/template~Template#attributes} property.
*
* @property {String|module:ui/template~TemplateValueSchema|Array.<String|module:ui/template~TemplateValueSchema>} [text]
* See the template {@link module:ui/template~Template#text} property.
*
* @property {Object.<String, module:ui/template~TemplateListenerSchema>} [on]
* See the template {@link module:ui/template~Template#eventListeners} property.
*/
/**
* Describes a value of an HTML element attribute or `textContent`. It allows combining multiple
* data sources like static values and {@link module:utils/observablemixin~Observable} attributes.
*
* Also see:
* * {@link module:ui/template~TemplateDefinition} to learn where to use it,
* * {@link module:ui/template~Template.bind} to learn how to configure
* {@link module:utils/observablemixin~Observable} attribute bindings,
* * {@link module:ui/template~Template#render} to learn how to render a template,
* * {@link module:ui/template~BindChain#to `to()`} and {@link module:ui/template~BindChain#if `if()`}
* methods to learn more about bindings.
*
* Attribute values can be described in many different ways:
*
* // Bind helper will create bindings to attributes of the observable.
* const bind = Template.bind( observable, emitter );
*
* new Template( {
* tag: 'p',
* attributes: {
* // A plain string schema.
* 'class': 'static-text',
*
* // An object schema, binds to the "foo" attribute of the
* // observable and follows its value.
* 'class': bind.to( 'foo' ),
*
* // An array schema, combines the above.
* 'class': [
* 'static-text',
* bind.to( 'bar', () => { ... } ),
*
* // Bindings can also be conditional.
* bind.if( 'baz', 'class-when-baz-is-true' )
* ],
*
* // An array schema, with a custom namespace, e.g. useful for creating SVGs.
* 'class': {
* ns: 'http://ns.url',
* value: [
* bind.if( 'baz', 'value-when-true' ),
* 'static-text'
* ]
* },
*
* // An object schema, specific for styles.
* style: {
* color: 'red',
* backgroundColor: bind.to( 'qux', () => { ... } )
* }
* }
* } );
*
* Text nodes can also have complex values:
*
* const bind = Template.bind( observable, emitter );
*
* // Will render a "foo" text node.
* new Template( {
* text: 'foo'
* } );
*
* // Will render a "static text: {observable.foo}" text node.
* // The text of the node will be updated as the "foo" attribute changes.
* new Template( {
* text: [
* 'static text: ',
* bind.to( 'foo', () => { ... } )
* ]
* } );
*
* @typedef module:ui/template~TemplateValueSchema
* @type {Object|String|Array}
*/
/**
* Describes an event listener attached to an HTML element. Such listener can propagate DOM events
* through an {@link module:utils/observablemixin~Observable} instance, execute custom callbacks
* or both, if necessary.
*
* Also see:
* * {@link module:ui/template~TemplateDefinition} to learn more about template definitions,
* * {@link module:ui/template~BindChain#to `to()`} method to learn more about bindings.
*
* Check out different ways of attaching event listeners below:
*
* // Bind helper will propagate events through the observable.
* const bind = Template.bind( observable, emitter );
*
* new Template( {
* tag: 'p',
* on: {
* // An object schema. The observable will fire the "clicked" event upon DOM "click".
* click: bind.to( 'clicked' )
*
* // An object schema. It will work for "click" event on "a.foo" children only.
* 'click@a.foo': bind.to( 'clicked' )
*
* // An array schema, makes the observable propagate multiple events.
* click: [
* bind.to( 'clicked' ),
* bind.to( 'executed' )
* ],
*
* // An array schema with a custom callback.
* 'click@a.foo': {
* bind.to( 'clicked' ),
* bind.to( evt => {
* console.log( `${ evt.target } has been clicked!` );
* } }
* }
* }
* } );
*
* @typedef module:ui/template~TemplateListenerSchema
* @type {Object|String|Array}
*/
/**
* The return value of {@link ~Template.bind `Template.bind()`}. It provides `to()` and `if()`
* methods to create the {@link module:utils/observablemixin~Observable observable} attribute and event bindings.
*
* @interface module:ui/template~BindChain
*/
/**
* Binds an {@link module:utils/observablemixin~Observable observable} to either:
*
* * an HTML element attribute or a text node `textContent`, so it remains in sync with the observable
* attribute as it changes,
* * or an HTML element DOM event, so the DOM events are propagated through an observable.
*
* Some common use cases of `to()` bindings are presented below:
*
* const bind = Template.bind( observable, emitter );
*
* new Template( {
* tag: 'p',
* attributes: {
* // class="..." attribute gets bound to `observable#a`
* class: bind.to( 'a' )
* },
* children: [
* // <p>...</p> gets bound to observable#b; always `toUpperCase()`.
* {
* text: bind.to( 'b', ( value, node ) => value.toUpperCase() )
* }
* ],
* on: {
* click: [
* // An observable will fire "clicked" upon "click" in the DOM.
* bind.to( 'clicked' ),
*
* // A custom callback will be executed upon "click" in the DOM.
* bind.to( () => {
* ...
* } )
* ]
* }
* } ).render();
*
* Learn more about using `to()` in the {@link module:ui/template~TemplateValueSchema} and
* {@link module:ui/template~TemplateListenerSchema}.
*
* @method #to
* @param {String|Function} eventNameOrFunctionOrAttribute An attribute name of
* {@link module:utils/observablemixin~Observable} or a DOM event name or an event callback.
* @param {Function} [callback] Allows for processing of the value. Accepts `Node` and `value` as arguments.
* @returns {module:ui/template~TemplateBinding}
*/
/**
* Binds an {@link module:utils/observablemixin~Observable observable} to an HTML element attribute or a text
* node `textContent` so it remains in sync with the observable attribute as it changes.
*
* Unlike {@link module:ui/template~BindChain#to}, it controls the presence of the attribute or `textContent`
* depending on the "falseness" of an {@link module:utils/observablemixin~Observable} attribute.
*
* const bind = Template.bind( observable, emitter );
*
* new Template( {
* tag: 'input',
* attributes: {
* // <input checked> when `observable#a` is not undefined/null/false/''
* // <input> when `observable#a` is undefined/null/false
* checked: bind.if( 'a' )
* },
* children: [
* {
* // <input>"b-is-not-set"</input> when `observable#b` is undefined/null/false/''
* // <input></input> when `observable#b` is not "falsy"
* text: bind.if( 'b', 'b-is-not-set', ( value, node ) => !value )
* }
* ]
* } ).render();
*
* Learn more about using `if()` in the {@link module:ui/template~TemplateValueSchema}.
*
* @method #if
* @param {String} attribute An attribute name of {@link module:utils/observablemixin~Observable} used in the binding.
* @param {String} [valueIfTrue] Value set when the {@link module:utils/observablemixin~Observable} attribute is not
* undefined/null/false/'' (empty string).
* @param {Function} [callback] Allows for processing of the value. Accepts `Node` and `value` as arguments.
* @returns {module:ui/template~TemplateBinding}
*/
/**
* The {@link module:ui/template~Template#_renderNode} configuration.
*
* @private
* @interface module:ui/template~RenderData
*/
/**
* Tells {@link module:ui/template~Template#_renderNode} to render
* children into `DocumentFragment` first and then append the fragment
* to the parent element. It is a speed optimization.
*
* @member {Boolean} #intoFragment
*/
/**
* A node which is being rendered.
*
* @member {HTMLElement|Text} #node
*/
/**
* Indicates whether the {@module:ui/template~RenderNodeOptions#node} has
* been provided by {@module:ui/template~Template#apply}.
*
* @member {Boolean} #isApplying
*/
/**
* An object storing the data that helps {@module:ui/template~Template#revert}
* bringing back an element to its initial state, i.e. before
* {@module:ui/template~Template#apply} was called.
*
* @member {Object} #revertData
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/src/toolbar/balloon/balloontoolbar.js":
/*!***********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/src/toolbar/balloon/balloontoolbar.js ***!
\***********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ BalloonToolbar)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_core_src_plugin__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-core/src/plugin */ "./node_modules/@ckeditor/ckeditor5-core/src/plugin.js");
/* harmony import */ var _panel_balloon_contextualballoon__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../../panel/balloon/contextualballoon */ "./node_modules/@ckeditor/ckeditor5-ui/src/panel/balloon/contextualballoon.js");
/* harmony import */ var _toolbarview__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../toolbarview */ "./node_modules/@ckeditor/ckeditor5-ui/src/toolbar/toolbarview.js");
/* harmony import */ var _panel_balloon_balloonpanelview_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../../panel/balloon/balloonpanelview.js */ "./node_modules/@ckeditor/ckeditor5-ui/src/panel/balloon/balloonpanelview.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_focustracker__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/focustracker */ "./node_modules/@ckeditor/ckeditor5-utils/src/focustracker.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_dom_rect__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/dom/rect */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/rect.js");
/* harmony import */ var _normalizetoolbarconfig__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ../normalizetoolbarconfig */ "./node_modules/@ckeditor/ckeditor5-ui/src/toolbar/normalizetoolbarconfig.js");
/* harmony import */ var lodash_es__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! lodash-es */ "./node_modules/lodash-es/debounce.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_dom_resizeobserver__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/dom/resizeobserver */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/resizeobserver.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_dom_tounit__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/dom/tounit */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/tounit.js");
/* harmony import */ var _ckeditor_ckeditor5_utils__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils */ "./node_modules/@ckeditor/ckeditor5-utils/src/index.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module ui/toolbar/balloon/balloontoolbar
*/
const toPx = (0,_ckeditor_ckeditor5_utils_src_dom_tounit__WEBPACK_IMPORTED_MODULE_8__["default"])( 'px' );
/**
* The contextual toolbar.
*
* It uses the {@link module:ui/panel/balloon/contextualballoon~ContextualBalloon contextual balloon plugin}.
*
* @extends module:core/plugin~Plugin
*/
class BalloonToolbar extends _ckeditor_ckeditor5_core_src_plugin__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* @inheritDoc
*/
static get pluginName() {
return 'BalloonToolbar';
}
/**
* @inheritDoc
*/
static get requires() {
return [ _panel_balloon_contextualballoon__WEBPACK_IMPORTED_MODULE_1__["default"] ];
}
/**
* @inheritDoc
*/
constructor( editor ) {
super( editor );
/**
* A cached and normalized `config.balloonToolbar` object.
*
* @type {module:core/editor/editorconfig~EditorConfig#balloonToolbar}
* @private
*/
this._balloonConfig = (0,_normalizetoolbarconfig__WEBPACK_IMPORTED_MODULE_6__["default"])( editor.config.get( 'balloonToolbar' ) );
/**
* The toolbar view displayed in the balloon.
*
* @type {module:ui/toolbar/toolbarview~ToolbarView}
*/
this.toolbarView = this._createToolbarView();
/**
* Tracks the focus of the {@link module:core/editor/editorui~EditorUI#getEditableElement editable element}
* and the {@link #toolbarView}. When both are blurred then the toolbar should hide.
*
* @readonly
* @type {module:utils:focustracker~FocusTracker}
*/
this.focusTracker = new _ckeditor_ckeditor5_utils_src_focustracker__WEBPACK_IMPORTED_MODULE_4__["default"]();
// Wait for the EditorUI#init. EditableElement is not available before.
editor.ui.once( 'ready', () => {
this.focusTracker.add( editor.ui.getEditableElement() );
this.focusTracker.add( this.toolbarView.element );
} );
/**
* An instance of the resize observer that allows to respond to changes in editable's geometry
* so the toolbar can stay within its boundaries (and group toolbar items that do not fit).
*
* **Note**: Used only when `shouldNotGroupWhenFull` was **not** set in the
* {@link module:core/editor/editorconfig~EditorConfig#balloonToolbar configuration}.
*
* **Note:** Created in {@link #init}.
*
* @protected
* @member {module:utils/dom/resizeobserver~ResizeObserver}
*/
this._resizeObserver = null;
/**
* The contextual balloon plugin instance.
*
* @private
* @type {module:ui/panel/balloon/contextualballoon~ContextualBalloon}
*/
this._balloon = editor.plugins.get( _panel_balloon_contextualballoon__WEBPACK_IMPORTED_MODULE_1__["default"] );
/**
* Fires {@link #event:_selectionChangeDebounced} event using `lodash#debounce`.
*
* This function is stored as a plugin property to make possible to cancel
* trailing debounced invocation on destroy.
*
* @private
* @type {Function}
*/
this._fireSelectionChangeDebounced = (0,lodash_es__WEBPACK_IMPORTED_MODULE_10__["default"])( () => this.fire( '_selectionChangeDebounced' ), 200 );
// The appearance of the BalloonToolbar method is event–driven.
// It is possible to stop the #show event and this prevent the toolbar from showing up.
this.decorate( 'show' );
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const selection = editor.model.document.selection;
// Show/hide the toolbar on editable focus/blur.
this.listenTo( this.focusTracker, 'change:isFocused', ( evt, name, isFocused ) => {
const isToolbarVisible = this._balloon.visibleView === this.toolbarView;
if ( !isFocused && isToolbarVisible ) {
this.hide();
} else if ( isFocused ) {
this.show();
}
} );
// Hide the toolbar when the selection is changed by a direct change or has changed to collapsed.
this.listenTo( selection, 'change:range', ( evt, data ) => {
if ( data.directChange || selection.isCollapsed ) {
this.hide();
}
// Fire internal `_selectionChangeDebounced` event to use it for showing
// the toolbar after the selection stops changing.
this._fireSelectionChangeDebounced();
} );
// Show the toolbar when the selection stops changing.
this.listenTo( this, '_selectionChangeDebounced', () => {
if ( this.editor.editing.view.document.isFocused ) {
this.show();
}
} );
if ( !this._balloonConfig.shouldNotGroupWhenFull ) {
this.listenTo( editor, 'ready', () => {
const editableElement = editor.ui.view.editable.element;
// Set #toolbarView's max-width on the initialization and update it on the editable resize.
this._resizeObserver = new _ckeditor_ckeditor5_utils_src_dom_resizeobserver__WEBPACK_IMPORTED_MODULE_7__["default"]( editableElement, () => {
// The max-width equals 90% of the editable's width for the best user experience.
// The value keeps the balloon very close to the boundaries of the editable and limits the cases
// when the balloon juts out from the editable element it belongs to.
this.toolbarView.maxWidth = toPx( new _ckeditor_ckeditor5_utils_src_dom_rect__WEBPACK_IMPORTED_MODULE_5__["default"]( editableElement ).width * .9 );
} );
} );
}
// Listen to the toolbar view and whenever it changes its geometry due to some items being
// grouped or ungrouped, update the position of the balloon because a shorter/longer toolbar
// means the balloon could be pointing at the wrong place. Once updated, the balloon will point
// at the right selection in the content again.
// https://github.com/ckeditor/ckeditor5/issues/6444
this.listenTo( this.toolbarView, 'groupedItemsUpdate', () => {
this._updatePosition();
} );
}
/**
* Creates toolbar components based on given configuration.
* This needs to be done when all plugins are ready.
*
* @inheritDoc
*/
afterInit() {
const factory = this.editor.ui.componentFactory;
this.toolbarView.fillFromConfig( this._balloonConfig, factory );
}
/**
* Creates the toolbar view instance.
*
* @private
* @returns {module:ui/toolbar/toolbarview~ToolbarView}
*/
_createToolbarView() {
const shouldGroupWhenFull = !this._balloonConfig.shouldNotGroupWhenFull;
const toolbarView = new _toolbarview__WEBPACK_IMPORTED_MODULE_2__["default"]( this.editor.locale, {
shouldGroupWhenFull,
isFloating: true
} );
toolbarView.render();
return toolbarView;
}
/**
* Shows the toolbar and attaches it to the selection.
*
* Fires {@link #event:show} event which can be stopped to prevent the toolbar from showing up.
*/
show() {
const editor = this.editor;
const selection = editor.model.document.selection;
const schema = editor.model.schema;
// Do not add the toolbar to the balloon stack twice.
if ( this._balloon.hasView( this.toolbarView ) ) {
return;
}
// Do not show the toolbar when the selection is collapsed.
if ( selection.isCollapsed ) {
return;
}
// Do not show the toolbar when there is more than one range in the selection and they fully contain selectable elements.
// See https://github.com/ckeditor/ckeditor5/issues/6443.
if ( selectionContainsOnlyMultipleSelectables( selection, schema ) ) {
return;
}
// Don not show the toolbar when all components inside are disabled
// see https://github.com/ckeditor/ckeditor5-ui/issues/269.
if ( Array.from( this.toolbarView.items ).every( item => item.isEnabled !== undefined && !item.isEnabled ) ) {
return;
}
// Update the toolbar position when the editor ui should be refreshed.
this.listenTo( this.editor.ui, 'update', () => {
this._updatePosition();
} );
// Add the toolbar to the common editor contextual balloon.
this._balloon.add( {
view: this.toolbarView,
position: this._getBalloonPositionData(),
balloonClassName: 'ck-toolbar-container'
} );
}
/**
* Hides the toolbar.
*/
hide() {
if ( this._balloon.hasView( this.toolbarView ) ) {
this.stopListening( this.editor.ui, 'update' );
this._balloon.remove( this.toolbarView );
}
}
/**
* Returns positioning options for the {@link #_balloon}. They control the way balloon is attached
* to the selection.
*
* @private
* @returns {module:utils/dom/position~Options}
*/
_getBalloonPositionData() {
const editor = this.editor;
const view = editor.editing.view;
const viewDocument = view.document;
const viewSelection = viewDocument.selection;
// Get direction of the selection.
const isBackward = viewDocument.selection.isBackward;
return {
// Because the target for BalloonPanelView is a Rect (not DOMRange), it's geometry will stay fixed
// as the window scrolls. To let the BalloonPanelView follow such Rect, is must be continuously
// computed and hence, the target is defined as a function instead of a static value.
// https://github.com/ckeditor/ckeditor5-ui/issues/195
target: () => {
const range = isBackward ? viewSelection.getFirstRange() : viewSelection.getLastRange();
const rangeRects = _ckeditor_ckeditor5_utils_src_dom_rect__WEBPACK_IMPORTED_MODULE_5__["default"].getDomRangeRects( view.domConverter.viewRangeToDom( range ) );
// Select the proper range rect depending on the direction of the selection.
if ( isBackward ) {
return rangeRects[ 0 ];
} else {
// Ditch the zero-width "orphan" rect in the next line for the forward selection if there's
// another one preceding it. It is not rendered as a selection by the web browser anyway.
// https://github.com/ckeditor/ckeditor5-ui/issues/308
if ( rangeRects.length > 1 && rangeRects[ rangeRects.length - 1 ].width === 0 ) {
rangeRects.pop();
}
return rangeRects[ rangeRects.length - 1 ];
}
},
positions: this._getBalloonPositions( isBackward )
};
}
/**
* Updates the position of the {@link #_balloon} to make up for changes:
*
* * in the geometry of the selection it is attached to (e.g. the selection moved in the viewport or expanded or shrunk),
* * or the geometry of the balloon toolbar itself (e.g. the toolbar has grouped or ungrouped some items and it is shorter or longer).
*
* @private
*/
_updatePosition() {
this._balloon.updatePosition( this._getBalloonPositionData() );
}
/**
* @inheritDoc
*/
destroy() {
super.destroy();
this.stopListening();
this._fireSelectionChangeDebounced.cancel();
this.toolbarView.destroy();
this.focusTracker.destroy();
if ( this._resizeObserver ) {
this._resizeObserver.destroy();
}
}
/**
* This event is fired just before the toolbar shows up. Stopping this event will prevent this.
*
* @event show
*/
/**
* This is internal plugin event which is fired 200 ms after model selection last change.
* This is to makes easy test debounced action without need to use `setTimeout`.
*
* @protected
* @event _selectionChangeDebounced
*/
/**
* Returns toolbar positions for the given direction of the selection.
*
* @private
* @param {Boolean} isBackward
* @returns {Array.<module:utils/dom/position~Position>}
*/
_getBalloonPositions( isBackward ) {
const isSafariIniOS = _ckeditor_ckeditor5_utils__WEBPACK_IMPORTED_MODULE_9__.env.isSafari && _ckeditor_ckeditor5_utils__WEBPACK_IMPORTED_MODULE_9__.env.isiOS;
// https://github.com/ckeditor/ckeditor5/issues/7707
const positions = isSafariIniOS ? (0,_panel_balloon_balloonpanelview_js__WEBPACK_IMPORTED_MODULE_3__.generatePositions)( {
// 20px when zoomed out. Less then 20px when zoomed in; the "radius" of the native selection handle gets
// smaller as the user zooms in. No less than the default v-offset, though.
verticalOffset: Math.max(
_panel_balloon_balloonpanelview_js__WEBPACK_IMPORTED_MODULE_3__["default"].arrowVerticalOffset,
Math.round( 20 / _ckeditor_ckeditor5_utils__WEBPACK_IMPORTED_MODULE_9__.global.window.visualViewport.scale )
)
} ) : _panel_balloon_balloonpanelview_js__WEBPACK_IMPORTED_MODULE_3__["default"].defaultPositions;
return isBackward ? [
positions.northWestArrowSouth,
positions.northWestArrowSouthWest,
positions.northWestArrowSouthEast,
positions.northWestArrowSouthMiddleEast,
positions.northWestArrowSouthMiddleWest,
positions.southWestArrowNorth,
positions.southWestArrowNorthWest,
positions.southWestArrowNorthEast,
positions.southWestArrowNorthMiddleWest,
positions.southWestArrowNorthMiddleEast
] : [
positions.southEastArrowNorth,
positions.southEastArrowNorthEast,
positions.southEastArrowNorthWest,
positions.southEastArrowNorthMiddleEast,
positions.southEastArrowNorthMiddleWest,
positions.northEastArrowSouth,
positions.northEastArrowSouthEast,
positions.northEastArrowSouthWest,
positions.northEastArrowSouthMiddleEast,
positions.northEastArrowSouthMiddleWest
];
}
}
// Returns "true" when the selection has multiple ranges and each range contains a selectable element
// and nothing else.
//
// @private
// @param {module:engine/model/selection~Selection} selection
// @param {module:engine/model/schema~Schema} schema
// @returns {Boolean}
function selectionContainsOnlyMultipleSelectables( selection, schema ) {
// It doesn't contain multiple objects if there is only one range.
if ( selection.rangeCount === 1 ) {
return false;
}
return [ ...selection.getRanges() ].every( range => {
const element = range.getContainedElement();
return element && schema.isSelectable( element );
} );
}
/**
* Contextual toolbar configuration. Used by the {@link module:ui/toolbar/balloon/balloontoolbar~BalloonToolbar}
* feature.
*
* ## Configuring toolbar items
*
* const config = {
* balloonToolbar: [ 'bold', 'italic', 'undo', 'redo' ]
* };
*
* You can also use `'|'` to create a separator between groups of items:
*
* const config = {
* balloonToolbar: [ 'bold', 'italic', | 'undo', 'redo' ]
* };
*
* Read also about configuring the main editor toolbar in {@link module:core/editor/editorconfig~EditorConfig#toolbar}.
*
* ## Configuring items grouping
*
* You can prevent automatic items grouping by setting the `shouldNotGroupWhenFull` option:
*
* const config = {
* balloonToolbar: {
* items: [ 'bold', 'italic', 'undo', 'redo' ],
* shouldNotGroupWhenFull: true
* },
* };
*
* @member {Array.<String>|Object} module:core/editor/editorconfig~EditorConfig#balloonToolbar
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/src/toolbar/block/blockbuttonview.js":
/*!**********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/src/toolbar/block/blockbuttonview.js ***!
\**********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ BlockButtonView)
/* harmony export */ });
/* harmony import */ var _button_buttonview__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../../button/buttonview */ "./node_modules/@ckeditor/ckeditor5-ui/src/button/buttonview.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_dom_tounit__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/dom/tounit */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/tounit.js");
/* harmony import */ var _theme_components_toolbar_blocktoolbar_css__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../../theme/components/toolbar/blocktoolbar.css */ "./node_modules/@ckeditor/ckeditor5-ui/theme/components/toolbar/blocktoolbar.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module ui/toolbar/block/blockbuttonview
*/
const toPx = (0,_ckeditor_ckeditor5_utils_src_dom_tounit__WEBPACK_IMPORTED_MODULE_1__["default"])( 'px' );
/**
* The block button view class.
*
* This view represents a button attached next to block element where the selection is anchored.
*
* See {@link module:ui/toolbar/block/blocktoolbar~BlockToolbar}.
*
* @extends {module:ui/button/buttonview~ButtonView}
*/
class BlockButtonView extends _button_buttonview__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* @inheritDoc
*/
constructor( locale ) {
super( locale );
const bind = this.bindTemplate;
// Hide button on init.
this.isVisible = false;
this.isToggleable = true;
/**
* Top offset.
*
* @member {Number} #top
*/
this.set( 'top', 0 );
/**
* Left offset.
*
* @member {Number} #left
*/
this.set( 'left', 0 );
this.extendTemplate( {
attributes: {
class: 'ck-block-toolbar-button',
style: {
top: bind.to( 'top', val => toPx( val ) ),
left: bind.to( 'left', val => toPx( val ) )
}
}
} );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/src/toolbar/block/blocktoolbar.js":
/*!*******************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/src/toolbar/block/blocktoolbar.js ***!
\*******************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ BlockToolbar)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_core_src_plugin__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-core/src/plugin */ "./node_modules/@ckeditor/ckeditor5-core/src/plugin.js");
/* harmony import */ var _ckeditor_ckeditor5_core_theme_icons_pilcrow_svg__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-core/theme/icons/pilcrow.svg */ "./node_modules/@ckeditor/ckeditor5-core/theme/icons/pilcrow.svg");
/* harmony import */ var _blockbuttonview__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./blockbuttonview */ "./node_modules/@ckeditor/ckeditor5-ui/src/toolbar/block/blockbuttonview.js");
/* harmony import */ var _panel_balloon_balloonpanelview__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../../panel/balloon/balloonpanelview */ "./node_modules/@ckeditor/ckeditor5-ui/src/panel/balloon/balloonpanelview.js");
/* harmony import */ var _toolbarview__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../toolbarview */ "./node_modules/@ckeditor/ckeditor5-ui/src/toolbar/toolbarview.js");
/* harmony import */ var _bindings_clickoutsidehandler__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../../bindings/clickoutsidehandler */ "./node_modules/@ckeditor/ckeditor5-ui/src/bindings/clickoutsidehandler.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_dom_position__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/dom/position */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/position.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_dom_rect__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/dom/rect */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/rect.js");
/* harmony import */ var _normalizetoolbarconfig__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ../normalizetoolbarconfig */ "./node_modules/@ckeditor/ckeditor5-ui/src/toolbar/normalizetoolbarconfig.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_dom_resizeobserver__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/dom/resizeobserver */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/resizeobserver.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_dom_tounit__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/dom/tounit */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/tounit.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module ui/toolbar/block/blocktoolbar
*/
/* global window */
const toPx = (0,_ckeditor_ckeditor5_utils_src_dom_tounit__WEBPACK_IMPORTED_MODULE_10__["default"])( 'px' );
/**
* The block toolbar plugin.
*
* This plugin provides a button positioned next to the block of content where the selection is anchored.
* Upon clicking the button, a dropdown providing access to editor features shows up, as configured in
* {@link module:core/editor/editorconfig~EditorConfig#blockToolbar}.
*
* By default, the button is displayed next to all elements marked in {@link module:engine/model/schema~Schema}
* as `$block` for which the toolbar provides at least one option.
*
* By default, the button is attached so its right boundary is touching the
* {@link module:engine/view/editableelement~EditableElement}:
*
* __ |
* | || This is a block of content that the
* ¯¯ | button is attached to. This is a
* | block of content that the button is
* | attached to.
*
* The position of the button can be adjusted using the CSS `transform` property:
*
* .ck-block-toolbar-button {
* transform: translateX( -10px );
* }
*
* __ |
* | | | This is a block of content that the
* ¯¯ | button is attached to. This is a
* | block of content that the button is
* | attached to.
*
* **Note**: If you plan to run the editor in a right–to–left (RTL) language, keep in mind the button
* will be attached to the **right** boundary of the editable area. In that case, make sure the
* CSS position adjustment works properly by adding the following styles:
*
* .ck[dir="rtl"] .ck-block-toolbar-button {
* transform: translateX( 10px );
* }
*
* @extends module:core/plugin~Plugin
*/
class BlockToolbar extends _ckeditor_ckeditor5_core_src_plugin__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* @inheritDoc
*/
static get pluginName() {
return 'BlockToolbar';
}
/**
* @inheritDoc
*/
constructor( editor ) {
super( editor );
/**
* A cached and normalized `config.blockToolbar` object.
*
* @type {module:core/editor/editorconfig~EditorConfig#blockToolbar}
* @private
*/
this._blockToolbarConfig = (0,_normalizetoolbarconfig__WEBPACK_IMPORTED_MODULE_8__["default"])( this.editor.config.get( 'blockToolbar' ) );
/**
* The toolbar view.
*
* @type {module:ui/toolbar/toolbarview~ToolbarView}
*/
this.toolbarView = this._createToolbarView();
/**
* The balloon panel view, containing the {@link #toolbarView}.
*
* @type {module:ui/panel/balloon/balloonpanelview~BalloonPanelView}
*/
this.panelView = this._createPanelView();
/**
* The button view that opens the {@link #toolbarView}.
*
* @type {module:ui/toolbar/block/blockbuttonview~BlockButtonView}
*/
this.buttonView = this._createButtonView();
/**
* An instance of the resize observer that allows to respond to changes in editable's geometry
* so the toolbar can stay within its boundaries (and group toolbar items that do not fit).
*
* **Note**: Used only when `shouldNotGroupWhenFull` was **not** set in the
* {@link module:core/editor/editorconfig~EditorConfig#blockToolbar configuration}.
*
* **Note:** Created in {@link #afterInit}.
*
* @protected
* @member {module:utils/dom/resizeobserver~ResizeObserver}
*/
this._resizeObserver = null;
// Close the #panelView upon clicking outside of the plugin UI.
(0,_bindings_clickoutsidehandler__WEBPACK_IMPORTED_MODULE_5__["default"])( {
emitter: this.panelView,
contextElements: [ this.panelView.element, this.buttonView.element ],
activator: () => this.panelView.isVisible,
callback: () => this._hidePanel()
} );
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
// Hides panel on a direct selection change.
this.listenTo( editor.model.document.selection, 'change:range', ( evt, data ) => {
if ( data.directChange ) {
this._hidePanel();
}
} );
this.listenTo( editor.ui, 'update', () => this._updateButton() );
// `low` priority is used because of https://github.com/ckeditor/ckeditor5-core/issues/133.
this.listenTo( editor, 'change:isReadOnly', () => this._updateButton(), { priority: 'low' } );
this.listenTo( editor.ui.focusTracker, 'change:isFocused', () => this._updateButton() );
// Reposition button on resize.
this.listenTo( this.buttonView, 'change:isVisible', ( evt, name, isVisible ) => {
if ( isVisible ) {
// Keep correct position of button and panel on window#resize.
this.buttonView.listenTo( window, 'resize', () => this._updateButton() );
} else {
// Stop repositioning button when is hidden.
this.buttonView.stopListening( window, 'resize' );
// Hide the panel when the button disappears.
this._hidePanel();
}
} );
}
/**
* Fills the toolbar with its items based on the configuration.
*
* **Note:** This needs to be done after all plugins are ready.
*
* @inheritDoc
*/
afterInit() {
const factory = this.editor.ui.componentFactory;
const config = this._blockToolbarConfig;
this.toolbarView.fillFromConfig( config, factory );
// Hide panel before executing each button in the panel.
for ( const item of this.toolbarView.items ) {
item.on( 'execute', () => this._hidePanel( true ), { priority: 'high' } );
}
if ( !config.shouldNotGroupWhenFull ) {
this.listenTo( this.editor, 'ready', () => {
const editableElement = this.editor.ui.view.editable.element;
// Set #toolbarView's max-width just after the initialization and update it on the editable resize.
this._resizeObserver = new _ckeditor_ckeditor5_utils_src_dom_resizeobserver__WEBPACK_IMPORTED_MODULE_9__["default"]( editableElement, () => {
this.toolbarView.maxWidth = this._getToolbarMaxWidth();
} );
} );
}
}
/**
* @inheritDoc
*/
destroy() {
super.destroy();
// Destroy created UI components as they are not automatically destroyed (see ckeditor5#1341).
this.panelView.destroy();
this.buttonView.destroy();
this.toolbarView.destroy();
if ( this._resizeObserver ) {
this._resizeObserver.destroy();
}
}
/**
* Creates the {@link #toolbarView}.
*
* @private
* @returns {module:ui/toolbar/toolbarview~ToolbarView}
*/
_createToolbarView() {
const shouldGroupWhenFull = !this._blockToolbarConfig.shouldNotGroupWhenFull;
const toolbarView = new _toolbarview__WEBPACK_IMPORTED_MODULE_4__["default"]( this.editor.locale, {
shouldGroupWhenFull,
isFloating: true
} );
// When toolbar lost focus then panel should hide.
toolbarView.focusTracker.on( 'change:isFocused', ( evt, name, is ) => {
if ( !is ) {
this._hidePanel();
}
} );
return toolbarView;
}
/**
* Creates the {@link #panelView}.
*
* @private
* @returns {module:ui/panel/balloon/balloonpanelview~BalloonPanelView}
*/
_createPanelView() {
const editor = this.editor;
const panelView = new _panel_balloon_balloonpanelview__WEBPACK_IMPORTED_MODULE_3__["default"]( editor.locale );
panelView.content.add( this.toolbarView );
panelView.class = 'ck-toolbar-container';
editor.ui.view.body.add( panelView );
editor.ui.focusTracker.add( panelView.element );
// Close #panelView on `Esc` press.
this.toolbarView.keystrokes.set( 'Esc', ( evt, cancel ) => {
this._hidePanel( true );
cancel();
} );
return panelView;
}
/**
* Creates the {@link #buttonView}.
*
* @private
* @returns {module:ui/toolbar/block/blockbuttonview~BlockButtonView}
*/
_createButtonView() {
const editor = this.editor;
const t = editor.t;
const buttonView = new _blockbuttonview__WEBPACK_IMPORTED_MODULE_2__["default"]( editor.locale );
buttonView.set( {
label: t( 'Edit block' ),
icon: _ckeditor_ckeditor5_core_theme_icons_pilcrow_svg__WEBPACK_IMPORTED_MODULE_1__["default"],
withText: false
} );
// Bind the panelView observable properties to the buttonView.
buttonView.bind( 'isOn' ).to( this.panelView, 'isVisible' );
buttonView.bind( 'tooltip' ).to( this.panelView, 'isVisible', isVisible => !isVisible );
// Toggle the panelView upon buttonView#execute.
this.listenTo( buttonView, 'execute', () => {
if ( !this.panelView.isVisible ) {
this._showPanel();
} else {
this._hidePanel( true );
}
} );
editor.ui.view.body.add( buttonView );
editor.ui.focusTracker.add( buttonView.element );
return buttonView;
}
/**
* Shows or hides the button.
* When all the conditions for displaying the button are matched, it shows the button. Hides otherwise.
*
* @private
*/
_updateButton() {
const editor = this.editor;
const model = editor.model;
const view = editor.editing.view;
// Hides the button when the editor is not focused.
if ( !editor.ui.focusTracker.isFocused ) {
this._hideButton();
return;
}
// Hides the button when the editor switches to the read-only mode.
if ( editor.isReadOnly ) {
this._hideButton();
return;
}
// Get the first selected block, button will be attached to this element.
const modelTarget = Array.from( model.document.selection.getSelectedBlocks() )[ 0 ];
// Hides the button when there is no enabled item in toolbar for the current block element.
if ( !modelTarget || Array.from( this.toolbarView.items ).every( item => !item.isEnabled ) ) {
this._hideButton();
return;
}
// Get DOM target element.
const domTarget = view.domConverter.mapViewToDom( editor.editing.mapper.toViewElement( modelTarget ) );
// Show block button.
this.buttonView.isVisible = true;
// Attach block button to target DOM element.
this._attachButtonToElement( domTarget );
// When panel is opened then refresh it position to be properly aligned with block button.
if ( this.panelView.isVisible ) {
this._showPanel();
}
}
/**
* Hides the button.
*
* @private
*/
_hideButton() {
this.buttonView.isVisible = false;
}
/**
* Shows the {@link #toolbarView} attached to the {@link #buttonView}.
* If the toolbar is already visible, then it simply repositions it.
*
* @private
*/
_showPanel() {
const wasVisible = this.panelView.isVisible;
// So here's the thing: If there was no initial panelView#show() or these two were in different order, the toolbar
// positioning will break in RTL editors. Weird, right? What you show know is that the toolbar
// grouping works thanks to:
//
// * the ResizeObserver, which kicks in as soon as the toolbar shows up in DOM (becomes visible again).
// * the observable ToolbarView#maxWidth, which triggers re-grouping when changed.
//
// Here are the possible scenarios:
//
// 1. (WRONG ❌) If the #maxWidth is set when the toolbar is invisible, it won't affect item grouping (no DOMRects, no grouping).
// Then, when panelView.pin() is called, the position of the toolbar will be calculated for the old
// items grouping state, and when finally ResizeObserver kicks in (hey, the toolbar is visible now, right?)
// it will group/ungroup some items and the length of the toolbar will change. But since in RTL the toolbar
// is attached on the right side and the positioning uses CSS "left", it will result in the toolbar shifting
// to the left and being displayed in the wrong place.
// 2. (WRONG ❌) If the panelView.pin() is called first and #maxWidth set next, then basically the story repeats. The balloon
// calculates the position for the old toolbar grouping state, then the toolbar re-groups items and because
// it is positioned using CSS "left" it will move.
// 3. (RIGHT ✅) We show the panel first (the toolbar does re-grouping but it does not matter), then the #maxWidth
// is set allowing the toolbar to re-group again and finally panelView.pin() does the positioning when the
// items grouping state is stable and final.
//
// https://github.com/ckeditor/ckeditor5/issues/6449, https://github.com/ckeditor/ckeditor5/issues/6575
this.panelView.show();
this.toolbarView.maxWidth = this._getToolbarMaxWidth();
this.panelView.pin( {
target: this.buttonView.element,
limiter: this.editor.ui.getEditableElement()
} );
if ( !wasVisible ) {
this.toolbarView.items.get( 0 ).focus();
}
}
/**
* Hides the {@link #toolbarView}.
*
* @private
* @param {Boolean} [focusEditable=false] When `true`, the editable will be focused after hiding the panel.
*/
_hidePanel( focusEditable ) {
this.panelView.isVisible = false;
if ( focusEditable ) {
this.editor.editing.view.focus();
}
}
/**
* Attaches the {@link #buttonView} to the target block of content.
*
* @protected
* @param {HTMLElement} targetElement Target element.
*/
_attachButtonToElement( targetElement ) {
const contentStyles = window.getComputedStyle( targetElement );
const editableRect = new _ckeditor_ckeditor5_utils_src_dom_rect__WEBPACK_IMPORTED_MODULE_7__["default"]( this.editor.ui.getEditableElement() );
const contentPaddingTop = parseInt( contentStyles.paddingTop, 10 );
// When line height is not an integer then thread it as "normal".
// MDN says that 'normal' == ~1.2 on desktop browsers.
const contentLineHeight = parseInt( contentStyles.lineHeight, 10 ) || parseInt( contentStyles.fontSize, 10 ) * 1.2;
const position = (0,_ckeditor_ckeditor5_utils_src_dom_position__WEBPACK_IMPORTED_MODULE_6__.getOptimalPosition)( {
element: this.buttonView.element,
target: targetElement,
positions: [
( contentRect, buttonRect ) => {
let left;
if ( this.editor.locale.uiLanguageDirection === 'ltr' ) {
left = editableRect.left - buttonRect.width;
} else {
left = editableRect.right;
}
return {
top: contentRect.top + contentPaddingTop + ( contentLineHeight - buttonRect.height ) / 2,
left
};
}
]
} );
this.buttonView.top = position.top;
this.buttonView.left = position.left;
}
/**
* Gets the {@link #toolbarView} max-width, based on
* editable width plus distance between farthest edge of the {@link #buttonView} and the editable.
*
* @private
* @returns {String} maxWidth A maximum width that toolbar can have, in pixels.
*/
_getToolbarMaxWidth() {
const editableElement = this.editor.ui.view.editable.element;
const editableRect = new _ckeditor_ckeditor5_utils_src_dom_rect__WEBPACK_IMPORTED_MODULE_7__["default"]( editableElement );
const buttonRect = new _ckeditor_ckeditor5_utils_src_dom_rect__WEBPACK_IMPORTED_MODULE_7__["default"]( this.buttonView.element );
const isRTL = this.editor.locale.uiLanguageDirection === 'rtl';
const offset = isRTL ? ( buttonRect.left - editableRect.right ) + buttonRect.width : editableRect.left - buttonRect.left;
return toPx( editableRect.width + offset );
}
}
/**
* The block toolbar configuration. Used by the {@link module:ui/toolbar/block/blocktoolbar~BlockToolbar}
* feature.
*
* const config = {
* blockToolbar: [ 'paragraph', 'heading1', 'heading2', 'bulletedList', 'numberedList' ]
* };
*
* You can also use `'|'` to create a separator between groups of items:
*
* const config = {
* blockToolbar: [ 'paragraph', 'heading1', 'heading2', '|', 'bulletedList', 'numberedList' ]
* };
*
* ## Configuring items grouping
*
* You can prevent automatic items grouping by setting the `shouldNotGroupWhenFull` option:
*
* const config = {
* blockToolbar: {
* items: [ 'paragraph', 'heading1', 'heading2', '|', 'bulletedList', 'numberedList' ],
* shouldNotGroupWhenFull: true
* },
* };
*
* Read more about configuring the main editor toolbar in {@link module:core/editor/editorconfig~EditorConfig#toolbar}.
*
* @member {Array.<String>|Object} module:core/editor/editorconfig~EditorConfig#blockToolbar
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/src/toolbar/enabletoolbarkeyboardfocus.js":
/*!***************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/src/toolbar/enabletoolbarkeyboardfocus.js ***!
\***************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ enableToolbarKeyboardFocus)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module ui/toolbar/enabletoolbarkeyboardfocus
*/
/**
* Enables focus/blur toolbar navigation using `Alt+F10` and `Esc` keystrokes.
*
* @param {Object} options Options of the utility.
* @param {*} options.origin A view to which the focus will return when `Esc` is pressed and
* `options.toolbar` is focused.
* @param {module:utils/keystrokehandler~KeystrokeHandler} options.originKeystrokeHandler A keystroke
* handler to register `Alt+F10` keystroke.
* @param {module:utils/focustracker~FocusTracker} options.originFocusTracker A focus tracker
* for `options.origin`.
* @param {module:ui/toolbar/toolbarview~ToolbarView} options.toolbar A toolbar which is to gain
* focus when `Alt+F10` is pressed.
* @param {Function} [options.beforeFocus] A callback executed before the `options.toolbar` gains focus
* upon the `Alt+F10` keystroke.
* @param {Function} [options.afterBlur] A callback executed after `options.toolbar` loses focus upon
* `Esc` keystroke but before the focus goes back to `options.origin`.
*/
function enableToolbarKeyboardFocus( {
origin,
originKeystrokeHandler,
originFocusTracker,
toolbar,
beforeFocus,
afterBlur
} ) {
// Because toolbar items can get focus, the overall state of the toolbar must
// also be tracked.
originFocusTracker.add( toolbar.element );
// Focus the toolbar on the keystroke, if not already focused.
originKeystrokeHandler.set( 'Alt+F10', ( data, cancel ) => {
if ( originFocusTracker.isFocused && !toolbar.focusTracker.isFocused ) {
if ( beforeFocus ) {
beforeFocus();
}
toolbar.focus();
cancel();
}
} );
// Blur the toolbar and bring the focus back to origin.
toolbar.keystrokes.set( 'Esc', ( data, cancel ) => {
if ( toolbar.focusTracker.isFocused ) {
origin.focus();
if ( afterBlur ) {
afterBlur();
}
cancel();
}
} );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/src/toolbar/normalizetoolbarconfig.js":
/*!***********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/src/toolbar/normalizetoolbarconfig.js ***!
\***********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ normalizeToolbarConfig)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module ui/toolbar/normalizetoolbarconfig
*/
/**
* Normalizes the toolbar configuration (`config.toolbar`), which:
*
* * may be defined as an `Array`:
*
* toolbar: [ 'heading', 'bold', 'italic', 'link', ... ]
*
* * or an `Object`:
*
* toolbar: {
* items: [ 'heading', 'bold', 'italic', 'link', ... ],
* removeItems: [ 'bold' ],
* ...
* }
*
* * or may not be defined at all (`undefined`)
*
* and returns it in the object form.
*
* @param {Array|Object|undefined} config The value of `config.toolbar`.
* @returns {Object} A normalized toolbar config object.
*/
function normalizeToolbarConfig( config ) {
if ( Array.isArray( config ) ) {
return {
items: config,
removeItems: []
};
}
if ( !config ) {
return {
items: [],
removeItems: []
};
}
return Object.assign( {
items: [],
removeItems: []
}, config );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/src/toolbar/toolbarlinebreakview.js":
/*!*********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/src/toolbar/toolbarlinebreakview.js ***!
\*********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ToolbarLineBreakView)
/* harmony export */ });
/* harmony import */ var _view__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../view */ "./node_modules/@ckeditor/ckeditor5-ui/src/view.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module ui/toolbar/toolbarlinebreakview
*/
/**
* The toolbar line break view class.
*
* @extends module:ui/view~View
*/
class ToolbarLineBreakView extends _view__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* @inheritDoc
*/
constructor( locale ) {
super( locale );
this.setTemplate( {
tag: 'span',
attributes: {
class: [
'ck',
'ck-toolbar__line-break'
]
}
} );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/src/toolbar/toolbarseparatorview.js":
/*!*********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/src/toolbar/toolbarseparatorview.js ***!
\*********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ToolbarSeparatorView)
/* harmony export */ });
/* harmony import */ var _view__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../view */ "./node_modules/@ckeditor/ckeditor5-ui/src/view.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module ui/toolbar/toolbarseparatorview
*/
/**
* The toolbar separator view class.
*
* @extends module:ui/view~View
*/
class ToolbarSeparatorView extends _view__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* @inheritDoc
*/
constructor( locale ) {
super( locale );
this.setTemplate( {
tag: 'span',
attributes: {
class: [
'ck',
'ck-toolbar__separator'
]
}
} );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/src/toolbar/toolbarview.js":
/*!************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/src/toolbar/toolbarview.js ***!
\************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ToolbarView)
/* harmony export */ });
/* harmony import */ var _view__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../view */ "./node_modules/@ckeditor/ckeditor5-ui/src/view.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_focustracker__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/focustracker */ "./node_modules/@ckeditor/ckeditor5-utils/src/focustracker.js");
/* harmony import */ var _focuscycler__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../focuscycler */ "./node_modules/@ckeditor/ckeditor5-ui/src/focuscycler.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_keystrokehandler__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/keystrokehandler */ "./node_modules/@ckeditor/ckeditor5-utils/src/keystrokehandler.js");
/* harmony import */ var _toolbarseparatorview__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./toolbarseparatorview */ "./node_modules/@ckeditor/ckeditor5-ui/src/toolbar/toolbarseparatorview.js");
/* harmony import */ var _toolbarlinebreakview__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./toolbarlinebreakview */ "./node_modules/@ckeditor/ckeditor5-ui/src/toolbar/toolbarlinebreakview.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_dom_resizeobserver__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/dom/resizeobserver */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/resizeobserver.js");
/* harmony import */ var _bindings_preventdefault_js__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ../bindings/preventdefault.js */ "./node_modules/@ckeditor/ckeditor5-ui/src/bindings/preventdefault.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_dom_rect__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/dom/rect */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/rect.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_dom_isvisible__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/dom/isvisible */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/isvisible.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_dom_global__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/dom/global */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/global.js");
/* harmony import */ var _dropdown_utils__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ../dropdown/utils */ "./node_modules/@ckeditor/ckeditor5-ui/src/dropdown/utils.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/* harmony import */ var _normalizetoolbarconfig__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(/*! ./normalizetoolbarconfig */ "./node_modules/@ckeditor/ckeditor5-ui/src/toolbar/normalizetoolbarconfig.js");
/* harmony import */ var _ckeditor_ckeditor5_core_theme_icons_three_vertical_dots_svg__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(/*! @ckeditor/ckeditor5-core/theme/icons/three-vertical-dots.svg */ "./node_modules/@ckeditor/ckeditor5-core/theme/icons/three-vertical-dots.svg");
/* harmony import */ var _theme_components_toolbar_toolbar_css__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(/*! ../../theme/components/toolbar/toolbar.css */ "./node_modules/@ckeditor/ckeditor5-ui/theme/components/toolbar/toolbar.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module ui/toolbar/toolbarview
*/
/**
* The toolbar view class.
*
* @extends module:ui/view~View
* @implements module:ui/dropdown/dropdownpanelfocusable~DropdownPanelFocusable
*/
class ToolbarView extends _view__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* Creates an instance of the {@link module:ui/toolbar/toolbarview~ToolbarView} class.
*
* Also see {@link #render}.
*
* @param {module:utils/locale~Locale} locale The localization services instance.
* @param {module:ui/toolbar/toolbarview~ToolbarOptions} [options] Configuration options of the toolbar.
*/
constructor( locale, options ) {
super( locale );
const bind = this.bindTemplate;
const t = this.t;
/**
* A reference to the options object passed to the constructor.
*
* @readonly
* @member {module:ui/toolbar/toolbarview~ToolbarOptions}
*/
this.options = options || {};
/**
* Label used by assistive technologies to describe this toolbar element.
*
* @default 'Editor toolbar'
* @member {String} #ariaLabel
*/
this.set( 'ariaLabel', t( 'Editor toolbar' ) );
/**
* The maximum width of the toolbar element.
*
* **Note**: When set to a specific value (e.g. `'200px'`), the value will affect the behavior of the
* {@link module:ui/toolbar/toolbarview~ToolbarOptions#shouldGroupWhenFull}
* option by changing the number of {@link #items} that will be displayed in the toolbar at a time.
*
* @observable
* @default 'auto'
* @member {String} #maxWidth
*/
this.set( 'maxWidth', 'auto' );
/**
* A collection of toolbar items (buttons, dropdowns, etc.).
*
* @readonly
* @member {module:ui/viewcollection~ViewCollection}
*/
this.items = this.createCollection();
/**
* Tracks information about the DOM focus in the toolbar.
*
* @readonly
* @member {module:utils/focustracker~FocusTracker}
*/
this.focusTracker = new _ckeditor_ckeditor5_utils_src_focustracker__WEBPACK_IMPORTED_MODULE_1__["default"]();
/**
* An instance of the {@link module:utils/keystrokehandler~KeystrokeHandler}
* to handle keyboard navigation in the toolbar.
*
* @readonly
* @member {module:utils/keystrokehandler~KeystrokeHandler}
*/
this.keystrokes = new _ckeditor_ckeditor5_utils_src_keystrokehandler__WEBPACK_IMPORTED_MODULE_3__["default"]();
/**
* An additional CSS class added to the {@link #element}.
*
* @observable
* @member {String} #class
*/
this.set( 'class' );
/**
* When set true, makes the toolbar look compact with {@link #element}.
*
* @observable
* @default false
* @member {String} #isCompact
*/
this.set( 'isCompact', false );
/**
* A (child) view containing {@link #items toolbar items}.
*
* @readonly
* @member {module:ui/toolbar/toolbarview~ItemsView}
*/
this.itemsView = new ItemsView( locale );
/**
* A top–level collection aggregating building blocks of the toolbar.
*
* ┌───────────────── ToolbarView ─────────────────┐
* | ┌──────────────── #children ────────────────┐ |
* | | ┌──────────── #itemsView ───────────┐ | |
* | | | [ item1 ] [ item2 ] ... [ itemN ] | | |
* | | └──────────────────────────────────-┘ | |
* | └───────────────────────────────────────────┘ |
* └───────────────────────────────────────────────┘
*
* By default, it contains the {@link #itemsView} but it can be extended with additional
* UI elements when necessary.
*
* @readonly
* @member {module:ui/viewcollection~ViewCollection}
*/
this.children = this.createCollection();
this.children.add( this.itemsView );
/**
* A collection of {@link #items} that take part in the focus cycling
* (i.e. navigation using the keyboard). Usually, it contains a subset of {@link #items} with
* some optional UI elements that also belong to the toolbar and should be focusable
* by the user.
*
* @readonly
* @member {module:ui/viewcollection~ViewCollection}
*/
this.focusables = this.createCollection();
/**
* Controls the orientation of toolbar items. Only available when
* {@link module:ui/toolbar/toolbarview~ToolbarOptions#shouldGroupWhenFull dynamic items grouping}
* is **disabled**.
*
* @observable
* @member {Boolean} #isVertical
*/
/**
* Helps cycling over {@link #focusables focusable items} in the toolbar.
*
* @readonly
* @protected
* @member {module:ui/focuscycler~FocusCycler}
*/
const isRtl = locale.uiLanguageDirection === 'rtl';
this._focusCycler = new _focuscycler__WEBPACK_IMPORTED_MODULE_2__["default"]( {
focusables: this.focusables,
focusTracker: this.focusTracker,
keystrokeHandler: this.keystrokes,
actions: {
// Navigate toolbar items backwards using the arrow[left,up] keys.
focusPrevious: [ isRtl ? 'arrowright' : 'arrowleft', 'arrowup' ],
// Navigate toolbar items forwards using the arrow[right,down] keys.
focusNext: [ isRtl ? 'arrowleft' : 'arrowright', 'arrowdown' ]
}
} );
const classes = [
'ck',
'ck-toolbar',
bind.to( 'class' ),
bind.if( 'isCompact', 'ck-toolbar_compact' )
];
if ( this.options.shouldGroupWhenFull && this.options.isFloating ) {
classes.push( 'ck-toolbar_floating' );
}
this.setTemplate( {
tag: 'div',
attributes: {
class: classes,
role: 'toolbar',
'aria-label': bind.to( 'ariaLabel' ),
style: {
maxWidth: bind.to( 'maxWidth' )
}
},
children: this.children,
on: {
// https://github.com/ckeditor/ckeditor5-ui/issues/206
mousedown: (0,_bindings_preventdefault_js__WEBPACK_IMPORTED_MODULE_7__["default"])( this )
}
} );
/**
* An instance of the active toolbar behavior that shapes its look and functionality.
*
* See {@link module:ui/toolbar/toolbarview~ToolbarBehavior} to learn more.
*
* @protected
* @readonly
* @member {module:ui/toolbar/toolbarview~ToolbarBehavior}
*/
this._behavior = this.options.shouldGroupWhenFull ? new DynamicGrouping( this ) : new StaticLayout( this );
}
/**
* @inheritDoc
*/
render() {
super.render();
// Children added before rendering should be known to the #focusTracker.
for ( const item of this.items ) {
this.focusTracker.add( item.element );
}
this.items.on( 'add', ( evt, item ) => {
this.focusTracker.add( item.element );
} );
this.items.on( 'remove', ( evt, item ) => {
this.focusTracker.remove( item.element );
} );
// Start listening for the keystrokes coming from #element.
this.keystrokes.listenTo( this.element );
this._behavior.render( this );
}
/**
* @inheritDoc
*/
destroy() {
this._behavior.destroy();
this.focusTracker.destroy();
this.keystrokes.destroy();
return super.destroy();
}
/**
* Focuses the first focusable in {@link #focusables}.
*/
focus() {
this._focusCycler.focusFirst();
}
/**
* Focuses the last focusable in {@link #focusables}.
*/
focusLast() {
this._focusCycler.focusLast();
}
/**
* A utility that expands the plain toolbar configuration into
* {@link module:ui/toolbar/toolbarview~ToolbarView#items} using a given component factory.
*
* @param {Array.<String>|Object} itemsOrConfig The toolbar items or the entire toolbar configuration object.
* @param {module:ui/componentfactory~ComponentFactory} factory A factory producing toolbar items.
*/
fillFromConfig( itemsOrConfig, factory ) {
const config = (0,_normalizetoolbarconfig__WEBPACK_IMPORTED_MODULE_13__["default"])( itemsOrConfig );
const itemsToClean = config.items
.filter( ( name, idx, items ) => {
if ( name === '|' ) {
return true;
}
// Items listed in `config.removeItems` should not be added to the toolbar.
if ( config.removeItems.indexOf( name ) !== -1 ) {
return false;
}
if ( name === '-' ) {
// The toolbar line breaks must not be rendered when toolbar grouping is enabled.
// (https://github.com/ckeditor/ckeditor5/issues/8582)
if ( this.options.shouldGroupWhenFull ) {
/**
* The toolbar multiline breaks (`-` items) only work when the automatic button grouping
* is disabled in the toolbar configuration.
* To do this, set the `shouldNotGroupWhenFull` option to `true` in the editor configuration:
*
* const config = {
* toolbar: {
* items: [ ... ],
* shouldNotGroupWhenFull: true
* }
* }
*
* Learn more about {@link module:core/editor/editorconfig~EditorConfig#toolbar toolbar configuration}.
*
* @error toolbarview-line-break-ignored-when-grouping-items
*/
(0,_ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_12__.logWarning)( 'toolbarview-line-break-ignored-when-grouping-items', items );
return false;
}
return true;
}
// For the items that cannot be instantiated we are sending warning message. We also filter them out.
if ( !factory.has( name ) ) {
/**
* There was a problem processing the configuration of the toolbar. The item with the given
* name does not exist so it was omitted when rendering the toolbar.
*
* This warning usually shows up when the {@link module:core/plugin~Plugin} which is supposed
* to provide a toolbar item has not been loaded or there is a typo in the configuration.
*
* Make sure the plugin responsible for this toolbar item is loaded and the toolbar configuration
* is correct, e.g. {@link module:basic-styles/bold~Bold} is loaded for the `'bold'` toolbar item.
*
* You can use the following snippet to retrieve all available toolbar items:
*
* Array.from( editor.ui.componentFactory.names() );
*
* @error toolbarview-item-unavailable
* @param {String} name The name of the component.
*/
(0,_ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_12__.logWarning)( 'toolbarview-item-unavailable', { name } );
return false;
}
return true;
} );
const itemsToAdd = this._cleanSeparators( itemsToClean )
// Instantiate toolbar items.
.map( name => {
if ( name === '|' ) {
return new _toolbarseparatorview__WEBPACK_IMPORTED_MODULE_4__["default"]();
} else if ( name === '-' ) {
return new _toolbarlinebreakview__WEBPACK_IMPORTED_MODULE_5__["default"]();
}
return factory.create( name );
} );
this.items.addMany( itemsToAdd );
}
/**
* Remove leading, trailing, and duplicated separators (`-` and `|`).
*
* @private
* @param {Array.<String>} items
*/
_cleanSeparators( items ) {
const nonSeparatorPredicate = item => ( item !== '-' && item !== '|' );
const count = items.length;
// Find an index of the first item that is not a separator.
const firstCommandItem = items.findIndex( nonSeparatorPredicate );
// Search from the end of the list, then convert found index back to the original direction.
const lastCommandItem = count - items
.slice()
.reverse()
.findIndex( nonSeparatorPredicate );
return items
// Return items without the leading and trailing separators.
.slice( firstCommandItem, lastCommandItem )
// Remove duplicated separators.
.filter( ( name, idx, items ) => {
// Filter only separators.
if ( nonSeparatorPredicate( name ) ) {
return true;
}
const isDuplicated = idx > 0 && items[ idx - 1 ] === name;
return !isDuplicated;
} );
}
/**
* Fired when some toolbar {@link #items} were grouped or ungrouped as a result of some change
* in the toolbar geometry.
*
* **Note**: This event is always fired **once** regardless of the number of items that were be
* grouped or ungrouped at a time.
*
* **Note**: This event is fired only if the items grouping functionality was enabled in
* the first place (see {@link module:ui/toolbar/toolbarview~ToolbarOptions#shouldGroupWhenFull}).
*
* @event groupedItemsUpdate
*/
}
/**
* An inner block of the {@link module:ui/toolbar/toolbarview~ToolbarView} hosting its
* {@link module:ui/toolbar/toolbarview~ToolbarView#items}.
*
* @private
* @extends module:ui/view~View
*/
class ItemsView extends _view__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* @inheritDoc
*/
constructor( locale ) {
super( locale );
/**
* A collection of items (buttons, dropdowns, etc.).
*
* @readonly
* @member {module:ui/viewcollection~ViewCollection}
*/
this.children = this.createCollection();
this.setTemplate( {
tag: 'div',
attributes: {
class: [
'ck',
'ck-toolbar__items'
]
},
children: this.children
} );
}
}
/**
* A toolbar behavior that makes it static and unresponsive to the changes of the environment.
* At the same time, it also makes it possible to display a toolbar with a vertical layout
* using the {@link module:ui/toolbar/toolbarview~ToolbarView#isVertical} property.
*
* @private
* @implements module:ui/toolbar/toolbarview~ToolbarBehavior
*/
class StaticLayout {
/**
* Creates an instance of the {@link module:ui/toolbar/toolbarview~StaticLayout} toolbar
* behavior.
*
* @param {module:ui/toolbar/toolbarview~ToolbarView} view An instance of the toolbar that this behavior
* is added to.
*/
constructor( view ) {
const bind = view.bindTemplate;
// Static toolbar can be vertical when needed.
view.set( 'isVertical', false );
// 1:1 pass–through binding, all ToolbarView#items are visible.
view.itemsView.children.bindTo( view.items ).using( item => item );
// 1:1 pass–through binding, all ToolbarView#items are focusable.
view.focusables.bindTo( view.items ).using( item => item );
view.extendTemplate( {
attributes: {
class: [
// When vertical, the toolbar has an additional CSS class.
bind.if( 'isVertical', 'ck-toolbar_vertical' )
]
}
} );
}
/**
* @inheritDoc
*/
render() {}
/**
* @inheritDoc
*/
destroy() {}
}
/**
* A toolbar behavior that makes the items respond to changes in the geometry.
*
* In a nutshell, it groups {@link module:ui/toolbar/toolbarview~ToolbarView#items}
* that do not fit visually into a single row of the toolbar (due to limited space).
* Items that do not fit are aggregated in a dropdown displayed at the end of the toolbar.
*
* ┌──────────────────────────────────────── ToolbarView ──────────────────────────────────────────┐
* | ┌─────────────────────────────────────── #children ─────────────────────────────────────────┐ |
* | | ┌─────── #itemsView ────────┐ ┌──────────────────────┐ ┌── #groupedItemsDropdown ───┐ | |
* | | | #ungroupedItems | | ToolbarSeparatorView | | #groupedItems | | |
* | | └──────────────────────────-┘ └──────────────────────┘ └────────────────────────────┘ | |
* | | \---------- only when toolbar items overflow --------/ | |
* | └───────────────────────────────────────────────────────────────────────────────────────────┘ |
* └───────────────────────────────────────────────────────────────────────────────────────────────┘
*
* @private
* @implements module:ui/toolbar/toolbarview~ToolbarBehavior
*/
class DynamicGrouping {
/**
* Creates an instance of the {@link module:ui/toolbar/toolbarview~DynamicGrouping} toolbar
* behavior.
*
* @param {module:ui/toolbar/toolbarview~ToolbarView} view An instance of the toolbar that this behavior
* is added to.
*/
constructor( view ) {
/**
* A toolbar view this behavior belongs to.
*
* @readonly
* @member {module:ui/toolbar~ToolbarView}
*/
this.view = view;
/**
* A collection of toolbar children.
*
* @readonly
* @member {module:ui/viewcollection~ViewCollection}
*/
this.viewChildren = view.children;
/**
* A collection of focusable toolbar elements.
*
* @readonly
* @member {module:ui/viewcollection~ViewCollection}
*/
this.viewFocusables = view.focusables;
/**
* A view containing toolbar items.
*
* @readonly
* @member {module:ui/toolbar/toolbarview~ItemsView}
*/
this.viewItemsView = view.itemsView;
/**
* Toolbar focus tracker.
*
* @readonly
* @member {module:utils/focustracker~FocusTracker}
*/
this.viewFocusTracker = view.focusTracker;
/**
* Toolbar locale.
*
* @readonly
* @member {module:utils/locale~Locale}
*/
this.viewLocale = view.locale;
/**
* Toolbar element.
*
* @readonly
* @member {HTMLElement} #viewElement
*/
/**
* A subset of toolbar {@link module:ui/toolbar/toolbarview~ToolbarView#items}.
* Aggregates items that fit into a single row of the toolbar and were not {@link #groupedItems grouped}
* into a {@link #groupedItemsDropdown dropdown}. Items of this collection are displayed in the
* {@link module:ui/toolbar/toolbarview~ToolbarView#itemsView}.
*
* When none of the {@link module:ui/toolbar/toolbarview~ToolbarView#items} were grouped, it
* matches the {@link module:ui/toolbar/toolbarview~ToolbarView#items} collection in size and order.
*
* @readonly
* @member {module:ui/viewcollection~ViewCollection}
*/
this.ungroupedItems = view.createCollection();
/**
* A subset of toolbar {@link module:ui/toolbar/toolbarview~ToolbarView#items}.
* A collection of the toolbar items that do not fit into a single row of the toolbar.
* Grouped items are displayed in a dedicated {@link #groupedItemsDropdown dropdown}.
*
* When none of the {@link module:ui/toolbar/toolbarview~ToolbarView#items} were grouped,
* this collection is empty.
*
* @readonly
* @member {module:ui/viewcollection~ViewCollection}
*/
this.groupedItems = view.createCollection();
/**
* The dropdown that aggregates {@link #groupedItems grouped items} that do not fit into a single
* row of the toolbar. It is displayed on demand as the last of
* {@link module:ui/toolbar/toolbarview~ToolbarView#children toolbar children} and offers another
* (nested) toolbar which displays items that would normally overflow.
*
* @readonly
* @member {module:ui/dropdown/dropdownview~DropdownView}
*/
this.groupedItemsDropdown = this._createGroupedItemsDropdown();
/**
* An instance of the resize observer that helps dynamically determine the geometry of the toolbar
* and manage items that do not fit into a single row.
*
* **Note:** Created in {@link #_enableGroupingOnResize}.
*
* @readonly
* @member {module:utils/dom/resizeobserver~ResizeObserver}
*/
this.resizeObserver = null;
/**
* A cached value of the horizontal padding style used by {@link #_updateGrouping}
* to manage the {@link module:ui/toolbar/toolbarview~ToolbarView#items} that do not fit into
* a single toolbar line. This value can be reused between updates because it is unlikely that
* the padding will change and re–using `Window.getComputedStyle()` is expensive.
*
* @readonly
* @member {Number}
*/
this.cachedPadding = null;
/**
* A flag indicating that an items grouping update has been queued (e.g. due to the toolbar being visible)
* and should be executed immediately the next time the toolbar shows up.
*
* @readonly
* @member {Boolean}
*/
this.shouldUpdateGroupingOnNextResize = false;
// Only those items that were not grouped are visible to the user.
view.itemsView.children.bindTo( this.ungroupedItems ).using( item => item );
// Make sure all #items visible in the main space of the toolbar are "focuscycleable".
this.ungroupedItems.on( 'add', this._updateFocusCycleableItems.bind( this ) );
this.ungroupedItems.on( 'remove', this._updateFocusCycleableItems.bind( this ) );
// Make sure the #groupedItemsDropdown is also included in cycling when it appears.
view.children.on( 'add', this._updateFocusCycleableItems.bind( this ) );
view.children.on( 'remove', this._updateFocusCycleableItems.bind( this ) );
// ToolbarView#items is dynamic. When an item is added or removed, it should be automatically
// represented in either grouped or ungrouped items at the right index.
// In other words #items == concat( #ungroupedItems, #groupedItems )
// (in length and order).
view.items.on( 'change', ( evt, changeData ) => {
const index = changeData.index;
// Removing.
for ( const removedItem of changeData.removed ) {
if ( index >= this.ungroupedItems.length ) {
this.groupedItems.remove( removedItem );
} else {
this.ungroupedItems.remove( removedItem );
}
}
// Adding.
for ( let currentIndex = index; currentIndex < index + changeData.added.length; currentIndex++ ) {
const addedItem = changeData.added[ currentIndex - index ];
if ( currentIndex > this.ungroupedItems.length ) {
this.groupedItems.add( addedItem, currentIndex - this.ungroupedItems.length );
} else {
this.ungroupedItems.add( addedItem, currentIndex );
}
}
// When new ungrouped items join in and land in #ungroupedItems, there's a chance it causes
// the toolbar to overflow.
// Consequently if removed from grouped or ungrouped items, there is a chance
// some new space is available and we could do some ungrouping.
this._updateGrouping();
} );
view.extendTemplate( {
attributes: {
class: [
// To group items dynamically, the toolbar needs a dedicated CSS class.
'ck-toolbar_grouping'
]
}
} );
}
/**
* Enables dynamic items grouping based on the dimensions of the toolbar.
*
* @param {module:ui/toolbar/toolbarview~ToolbarView} view An instance of the toolbar that this behavior
* is added to.
*/
render( view ) {
this.viewElement = view.element;
this._enableGroupingOnResize();
this._enableGroupingOnMaxWidthChange( view );
}
/**
* Cleans up the internals used by this behavior.
*/
destroy() {
// The dropdown may not be in ToolbarView#children at the moment of toolbar destruction
// so let's make sure it's actually destroyed along with the toolbar.
this.groupedItemsDropdown.destroy();
this.resizeObserver.destroy();
}
/**
* When called, it will check if any of the {@link #ungroupedItems} do not fit into a single row of the toolbar,
* and it will move them to the {@link #groupedItems} when it happens.
*
* At the same time, it will also check if there is enough space in the toolbar for the first of the
* {@link #groupedItems} to be returned back to {@link #ungroupedItems} and still fit into a single row
* without the toolbar wrapping.
*
* @protected
*/
_updateGrouping() {
// Do no grouping–related geometry analysis when the toolbar is detached from visible DOM,
// for instance before #render(), or after render but without a parent or a parent detached
// from DOM. DOMRects won't work anyway and there will be tons of warning in the console and
// nothing else. This happens, for instance, when the toolbar is detached from DOM and
// some logic adds or removes its #items.
if ( !this.viewElement.ownerDocument.body.contains( this.viewElement ) ) {
return;
}
// Do not update grouping when the element is invisible. Such toolbar has DOMRect filled with zeros
// and that would cause all items to be grouped. Instead, queue the grouping so it runs next time
// the toolbar is visible (the next ResizeObserver callback execution). This is handy because
// the grouping could be caused by increasing the #maxWidth when the toolbar was invisible and the next
// time it shows up, some items could actually be ungrouped (https://github.com/ckeditor/ckeditor5/issues/6575).
if ( !(0,_ckeditor_ckeditor5_utils_src_dom_isvisible__WEBPACK_IMPORTED_MODULE_9__["default"])( this.viewElement ) ) {
this.shouldUpdateGroupingOnNextResize = true;
return;
}
// Remember how many items were initially grouped so at the it is possible to figure out if the number
// of grouped items has changed. If the number has changed, geometry of the toolbar has also changed.
const initialGroupedItemsCount = this.groupedItems.length;
let wereItemsGrouped;
// Group #items as long as some wrap to the next row. This will happen, for instance,
// when the toolbar is getting narrow and there is not enough space to display all items in
// a single row.
while ( this._areItemsOverflowing ) {
this._groupLastItem();
wereItemsGrouped = true;
}
// If none were grouped now but there were some items already grouped before,
// then, what the hell, maybe let's see if some of them can be ungrouped. This happens when,
// for instance, the toolbar is stretching and there's more space in it than before.
if ( !wereItemsGrouped && this.groupedItems.length ) {
// Ungroup items as long as none are overflowing or there are none to ungroup left.
while ( this.groupedItems.length && !this._areItemsOverflowing ) {
this._ungroupFirstItem();
}
// If the ungrouping ended up with some item wrapping to the next row,
// put it back to the group toolbar ("undo the last ungroup"). We don't know whether
// an item will wrap or not until we ungroup it (that's a DOM/CSS thing) so this
// clean–up is vital for the algorithm.
if ( this._areItemsOverflowing ) {
this._groupLastItem();
}
}
if ( this.groupedItems.length !== initialGroupedItemsCount ) {
this.view.fire( 'groupedItemsUpdate' );
}
}
/**
* Returns `true` when {@link module:ui/toolbar/toolbarview~ToolbarView#element} children visually overflow,
* for instance if the toolbar is narrower than its members. Returns `false` otherwise.
*
* @private
* @type {Boolean}
*/
get _areItemsOverflowing() {
// An empty toolbar cannot overflow.
if ( !this.ungroupedItems.length ) {
return false;
}
const element = this.viewElement;
const uiLanguageDirection = this.viewLocale.uiLanguageDirection;
const lastChildRect = new _ckeditor_ckeditor5_utils_src_dom_rect__WEBPACK_IMPORTED_MODULE_8__["default"]( element.lastChild );
const toolbarRect = new _ckeditor_ckeditor5_utils_src_dom_rect__WEBPACK_IMPORTED_MODULE_8__["default"]( element );
if ( !this.cachedPadding ) {
const computedStyle = _ckeditor_ckeditor5_utils_src_dom_global__WEBPACK_IMPORTED_MODULE_10__["default"].window.getComputedStyle( element );
const paddingProperty = uiLanguageDirection === 'ltr' ? 'paddingRight' : 'paddingLeft';
// parseInt() is essential because of quirky floating point numbers logic and DOM.
// If the padding turned out too big because of that, the grouped items dropdown would
// always look (from the Rect perspective) like it overflows (while it's not).
this.cachedPadding = Number.parseInt( computedStyle[ paddingProperty ] );
}
if ( uiLanguageDirection === 'ltr' ) {
return lastChildRect.right > toolbarRect.right - this.cachedPadding;
} else {
return lastChildRect.left < toolbarRect.left + this.cachedPadding;
}
}
/**
* Enables the functionality that prevents {@link #ungroupedItems} from overflowing (wrapping to the next row)
* upon resize when there is little space available. Instead, the toolbar items are moved to the
* {@link #groupedItems} collection and displayed in a dropdown at the end of the row (which has its own nested toolbar).
*
* When called, the toolbar will automatically analyze the location of its {@link #ungroupedItems} and "group"
* them in the dropdown if necessary. It will also observe the browser window for size changes in
* the future and respond to them by grouping more items or reverting already grouped back, depending
* on the visual space available.
*
* @private
*/
_enableGroupingOnResize() {
let previousWidth;
// TODO: Consider debounce.
this.resizeObserver = new _ckeditor_ckeditor5_utils_src_dom_resizeobserver__WEBPACK_IMPORTED_MODULE_6__["default"]( this.viewElement, entry => {
if ( !previousWidth || previousWidth !== entry.contentRect.width || this.shouldUpdateGroupingOnNextResize ) {
this.shouldUpdateGroupingOnNextResize = false;
this._updateGrouping();
previousWidth = entry.contentRect.width;
}
} );
this._updateGrouping();
}
/**
* Enables the grouping functionality, just like {@link #_enableGroupingOnResize} but the difference is that
* it listens to the changes of {@link module:ui/toolbar/toolbarview~ToolbarView#maxWidth} instead.
*
* @private
*/
_enableGroupingOnMaxWidthChange( view ) {
view.on( 'change:maxWidth', () => {
this._updateGrouping();
} );
}
/**
* When called, it will remove the last item from {@link #ungroupedItems} and move it back
* to the {@link #groupedItems} collection.
*
* The opposite of {@link #_ungroupFirstItem}.
*
* @private
*/
_groupLastItem() {
if ( !this.groupedItems.length ) {
this.viewChildren.add( new _toolbarseparatorview__WEBPACK_IMPORTED_MODULE_4__["default"]() );
this.viewChildren.add( this.groupedItemsDropdown );
this.viewFocusTracker.add( this.groupedItemsDropdown.element );
}
this.groupedItems.add( this.ungroupedItems.remove( this.ungroupedItems.last ), 0 );
}
/**
* Moves the very first item belonging to {@link #groupedItems} back
* to the {@link #ungroupedItems} collection.
*
* The opposite of {@link #_groupLastItem}.
*
* @private
*/
_ungroupFirstItem() {
this.ungroupedItems.add( this.groupedItems.remove( this.groupedItems.first ) );
if ( !this.groupedItems.length ) {
this.viewChildren.remove( this.groupedItemsDropdown );
this.viewChildren.remove( this.viewChildren.last );
this.viewFocusTracker.remove( this.groupedItemsDropdown.element );
}
}
/**
* Creates the {@link #groupedItemsDropdown} that hosts the members of the {@link #groupedItems}
* collection when there is not enough space in the toolbar to display all items in a single row.
*
* @private
* @returns {module:ui/dropdown/dropdownview~DropdownView}
*/
_createGroupedItemsDropdown() {
const locale = this.viewLocale;
const t = locale.t;
const dropdown = (0,_dropdown_utils__WEBPACK_IMPORTED_MODULE_11__.createDropdown)( locale );
dropdown.class = 'ck-toolbar__grouped-dropdown';
// Make sure the dropdown never sticks out to the left/right. It should be under the main toolbar.
// (https://github.com/ckeditor/ckeditor5/issues/5608)
dropdown.panelPosition = locale.uiLanguageDirection === 'ltr' ? 'sw' : 'se';
(0,_dropdown_utils__WEBPACK_IMPORTED_MODULE_11__.addToolbarToDropdown)( dropdown, [] );
dropdown.buttonView.set( {
label: t( 'Show more items' ),
tooltip: true,
tooltipPosition: locale.uiLanguageDirection === 'rtl' ? 'se' : 'sw',
icon: _ckeditor_ckeditor5_core_theme_icons_three_vertical_dots_svg__WEBPACK_IMPORTED_MODULE_14__["default"]
} );
// 1:1 pass–through binding.
dropdown.toolbarView.items.bindTo( this.groupedItems ).using( item => item );
return dropdown;
}
/**
* Updates the {@link module:ui/toolbar/toolbarview~ToolbarView#focusables focus–cycleable items}
* collection so it represents the up–to–date state of the UI from the perspective of the user.
*
* For instance, the {@link #groupedItemsDropdown} can show up and hide but when it is visible,
* it must be subject to focus cycling in the toolbar.
*
* See the {@link module:ui/toolbar/toolbarview~ToolbarView#focusables collection} documentation
* to learn more about the purpose of this method.
*
* @private
*/
_updateFocusCycleableItems() {
this.viewFocusables.clear();
this.ungroupedItems.map( item => {
this.viewFocusables.add( item );
} );
if ( this.groupedItems.length ) {
this.viewFocusables.add( this.groupedItemsDropdown );
}
}
}
/**
* Options passed to the {@link module:ui/toolbar/toolbarview~ToolbarView#constructor} of the toolbar.
*
* @interface module:ui/toolbar/toolbarview~ToolbarOptions
*/
/**
* When set to `true`, the toolbar will automatically group {@link module:ui/toolbar/toolbarview~ToolbarView#items} that
* would normally wrap to the next line when there is not enough space to display them in a single row, for
* instance, if the parent container of the toolbar is narrow. For toolbars in absolutely positioned containers
* without width restrictions also the {@link module:ui/toolbar/toolbarview~ToolbarOptions#isFloating} option is required to be `true`.
*
* See also: {@link module:ui/toolbar/toolbarview~ToolbarView#maxWidth}.
*
* @member {Boolean} module:ui/toolbar/toolbarview~ToolbarOptions#shouldGroupWhenFull
*/
/**
* This option should be enabled for toolbars in absolutely positioned containers without width restrictions
* to enable automatic {@link module:ui/toolbar/toolbarview~ToolbarView#items} grouping.
* When this option is set to `true`, the items will stop wrapping to the next line
* and together with {@link module:ui/toolbar/toolbarview~ToolbarOptions#shouldGroupWhenFull},
* this will allow grouping them when there is not enough space in a single row.
*
* @member {Boolean} module:ui/toolbar/toolbarview~ToolbarOptions#isFloating
*/
/**
* A class interface defining the behavior of the {@link module:ui/toolbar/toolbarview~ToolbarView}.
*
* Toolbar behaviors extend its look and functionality and have an impact on the
* {@link module:ui/toolbar/toolbarview~ToolbarView#element} template or
* {@link module:ui/toolbar/toolbarview~ToolbarView#render rendering}. They can be enabled
* conditionally, e.g. depending on the configuration of the toolbar.
*
* @private
* @interface module:ui/toolbar/toolbarview~ToolbarBehavior
*/
/**
* Creates a new toolbar behavior instance.
*
* The instance is created in the {@link module:ui/toolbar/toolbarview~ToolbarView#constructor} of the toolbar.
* This is the right place to extend the {@link module:ui/toolbar/toolbarview~ToolbarView#template} of
* the toolbar, define extra toolbar properties, etc.
*
* @method #constructor
* @param {module:ui/toolbar/toolbarview~ToolbarView} view An instance of the toolbar that this behavior is added to.
*/
/**
* A method called after the toolbar has been {@link module:ui/toolbar/toolbarview~ToolbarView#render rendered}.
* It can be used to, for example, customize the behavior of the toolbar when its {@link module:ui/toolbar/toolbarview~ToolbarView#element}
* is available.
*
* @readonly
* @member {Function} #render
* @param {module:ui/toolbar/toolbarview~ToolbarView} view An instance of the toolbar being rendered.
*/
/**
* A method called after the toolbar has been {@link module:ui/toolbar/toolbarview~ToolbarView#destroy destroyed}.
* It allows cleaning up after the toolbar behavior, for instance, this is the right place to detach
* event listeners, free up references, etc.
*
* @readonly
* @member {Function} #destroy
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/src/tooltip/tooltipview.js":
/*!************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/src/tooltip/tooltipview.js ***!
\************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ TooltipView)
/* harmony export */ });
/* harmony import */ var _view__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../view */ "./node_modules/@ckeditor/ckeditor5-ui/src/view.js");
/* harmony import */ var _theme_components_tooltip_tooltip_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../../theme/components/tooltip/tooltip.css */ "./node_modules/@ckeditor/ckeditor5-ui/theme/components/tooltip/tooltip.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module ui/tooltip/tooltipview
*/
/**
* The tooltip view class.
*
* @extends module:ui/view~View
*/
class TooltipView extends _view__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* @inheritDoc
*/
constructor( locale ) {
super( locale );
/**
* The text of the tooltip visible to the user.
*
* @observable
* @member {String} #text
*/
this.set( 'text', '' );
/**
* The position of the tooltip (south, south-west, south-east, or north).
*
* +-----------+
* | north |
* +-----------+
* V
* [element]
*
* [element]
* ^
* +-----------+
* | south |
* +-----------+
*
* +----------+
* [element] < | east |
* +----------+
*
* +----------+
* | west | > [element]
* +----------+
*
* [element]
* ^
* +--------------+
* | south west |
* +--------------+
*
* [element]
* ^
* +--------------+
* | south east |
* +--------------+
* @observable
* @default 's'
* @member {'s'|'n'|'e'|'w'|'sw'|'se'} #position
*/
this.set( 'position', 's' );
const bind = this.bindTemplate;
this.setTemplate( {
tag: 'span',
attributes: {
class: [
'ck',
'ck-tooltip',
bind.to( 'position', position => 'ck-tooltip_' + position ),
bind.if( 'text', 'ck-hidden', value => !value.trim() )
]
},
children: [
{
tag: 'span',
attributes: {
class: [
'ck',
'ck-tooltip__text'
]
},
children: [
{
text: bind.to( 'text' )
}
]
}
]
} );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/src/view.js":
/*!*********************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/src/view.js ***!
\*********************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ View)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/* harmony import */ var _viewcollection__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./viewcollection */ "./node_modules/@ckeditor/ckeditor5-ui/src/viewcollection.js");
/* harmony import */ var _template__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./template */ "./node_modules/@ckeditor/ckeditor5-ui/src/template.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_dom_emittermixin__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/dom/emittermixin */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/emittermixin.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_observablemixin__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/observablemixin */ "./node_modules/@ckeditor/ckeditor5-utils/src/observablemixin.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_collection__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/collection */ "./node_modules/@ckeditor/ckeditor5-utils/src/collection.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/mix */ "./node_modules/@ckeditor/ckeditor5-utils/src/mix.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_isiterable__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/isiterable */ "./node_modules/@ckeditor/ckeditor5-utils/src/isiterable.js");
/* harmony import */ var _theme_globals_globals_css__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ../theme/globals/globals.css */ "./node_modules/@ckeditor/ckeditor5-ui/theme/globals/globals.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module ui/view
*/
/**
* The basic view class, which represents an HTML element created out of a
* {@link module:ui/view~View#template}. Views are building blocks of the user interface and handle
* interaction
*
* Views {@link module:ui/view~View#registerChild aggregate} children in
* {@link module:ui/view~View#createCollection collections} and manage the life cycle of DOM
* listeners e.g. by handling rendering and destruction.
*
* See the {@link module:ui/template~TemplateDefinition} syntax to learn more about shaping view
* elements, attributes and listeners.
*
* class SampleView extends View {
* constructor( locale ) {
* super( locale );
*
* const bind = this.bindTemplate;
*
* // Views define their interface (state) using observable attributes.
* this.set( 'elementClass', 'bar' );
*
* this.setTemplate( {
* tag: 'p',
*
* // The element of the view can be defined with its children.
* children: [
* 'Hello',
* {
* tag: 'b',
* children: [ 'world!' ]
* }
* ],
* attributes: {
* class: [
* 'foo',
*
* // Observable attributes control the state of the view in DOM.
* bind.to( 'elementClass' )
* ]
* },
* on: {
* // Views listen to DOM events and propagate them.
* click: bind.to( 'clicked' )
* }
* } );
* }
* }
*
* const view = new SampleView( locale );
*
* view.render();
*
* // Append <p class="foo bar">Hello<b>world</b></p> to the <body>
* document.body.appendChild( view.element );
*
* // Change the class attribute to <p class="foo baz">Hello<b>world</b></p>
* view.elementClass = 'baz';
*
* // Respond to the "click" event in DOM by executing a custom action.
* view.on( 'clicked', () => {
* console.log( 'The view has been clicked!' );
* } );
*
* @mixes module:utils/observablemixin~ObservableMixin
*/
class View {
/**
* Creates an instance of the {@link module:ui/view~View} class.
*
* Also see {@link #render}.
*
* @param {module:utils/locale~Locale} [locale] The localization services instance.
*/
constructor( locale ) {
/**
* An HTML element of the view. `null` until {@link #render rendered}
* from the {@link #template}.
*
* class SampleView extends View {
* constructor() {
* super();
*
* // A template instance the #element will be created from.
* this.setTemplate( {
* tag: 'p'
*
* // ...
* } );
* }
* }
*
* const view = new SampleView();
*
* // Renders the #template.
* view.render();
*
* // Append the HTML element of the view to <body>.
* document.body.appendChild( view.element );
*
* **Note**: The element of the view can also be assigned directly:
*
* view.element = document.querySelector( '#my-container' );
*
* @member {HTMLElement}
*/
this.element = null;
/**
* Set `true` when the view has already been {@link module:ui/view~View#render rendered}.
*
* @readonly
* @member {Boolean} #isRendered
*/
this.isRendered = false;
/**
* A set of tools to localize the user interface.
*
* Also see {@link module:core/editor/editor~Editor#locale}.
*
* @readonly
* @member {module:utils/locale~Locale}
*/
this.locale = locale;
/**
* Shorthand for {@link module:utils/locale~Locale#t}.
*
* Note: If {@link #locale} instance hasn't been passed to the view this method may not
* be available.
*
* @see module:utils/locale~Locale#t
* @method
*/
this.t = locale && locale.t;
/**
* Collections registered with {@link #createCollection}.
*
* @protected
* @member {Set.<module:ui/viewcollection~ViewCollection>}
*/
this._viewCollections = new _ckeditor_ckeditor5_utils_src_collection__WEBPACK_IMPORTED_MODULE_5__["default"]();
/**
* A collection of view instances, which have been added directly
* into the {@link module:ui/template~Template#children}.
*
* @protected
* @member {module:ui/viewcollection~ViewCollection}
*/
this._unboundChildren = this.createCollection();
// Pass parent locale to its children.
this._viewCollections.on( 'add', ( evt, collection ) => {
collection.locale = locale;
} );
/**
* Template of this view. It provides the {@link #element} representing
* the view in DOM, which is {@link #render rendered}.
*
* @member {module:ui/template~Template} #template
*/
/**
* Cached {@link module:ui/template~BindChain bind chain} object created by the
* {@link #template}. See {@link #bindTemplate}.
*
* @private
* @member {Object} #_bindTemplate
*/
this.decorate( 'render' );
}
/**
* Shorthand for {@link module:ui/template~Template.bind}, a binding
* {@link module:ui/template~BindChain interface} pre–configured for the view instance.
*
* It provides {@link module:ui/template~BindChain#to `to()`} and
* {@link module:ui/template~BindChain#if `if()`} methods that initialize bindings with
* observable attributes and attach DOM listeners.
*
* class SampleView extends View {
* constructor( locale ) {
* super( locale );
*
* const bind = this.bindTemplate;
*
* // These {@link module:utils/observablemixin~Observable observable} attributes will control
* // the state of the view in DOM.
* this.set( {
* elementClass: 'foo',
* isEnabled: true
* } );
*
* this.setTemplate( {
* tag: 'p',
*
* attributes: {
* // The class HTML attribute will follow elementClass
* // and isEnabled view attributes.
* class: [
* bind.to( 'elementClass' )
* bind.if( 'isEnabled', 'present-when-enabled' )
* ]
* },
*
* on: {
* // The view will fire the "clicked" event upon clicking <p> in DOM.
* click: bind.to( 'clicked' )
* }
* } );
* }
* }
*
* @method #bindTemplate
*/
get bindTemplate() {
if ( this._bindTemplate ) {
return this._bindTemplate;
}
return ( this._bindTemplate = _template__WEBPACK_IMPORTED_MODULE_2__["default"].bind( this, this ) );
}
/**
* Creates a new collection of views, which can be used as
* {@link module:ui/template~Template#children} of this view.
*
* class SampleView extends View {
* constructor( locale ) {
* super( locale );
*
* const child = new ChildView( locale );
* this.items = this.createCollection( [ child ] );
*
* this.setTemplate( {
* tag: 'p',
*
* // `items` collection will render here.
* children: this.items
* } );
* }
* }
*
* const view = new SampleView( locale );
* view.render();
*
* // It will append <p><child#element></p> to the <body>.
* document.body.appendChild( view.element );
*
* @param {Iterable.<module:ui/view~View>} [views] Initial views of the collection.
* @returns {module:ui/viewcollection~ViewCollection} A new collection of view instances.
*/
createCollection( views ) {
const collection = new _viewcollection__WEBPACK_IMPORTED_MODULE_1__["default"]( views );
this._viewCollections.add( collection );
return collection;
}
/**
* Registers a new child view under the view instance. Once registered, a child
* view is managed by its parent, including {@link #render rendering}
* and {@link #destroy destruction}.
*
* To revert this, use {@link #deregisterChild}.
*
* class SampleView extends View {
* constructor( locale ) {
* super( locale );
*
* this.childA = new SomeChildView( locale );
* this.childB = new SomeChildView( locale );
*
* this.setTemplate( { tag: 'p' } );
*
* // Register the children.
* this.registerChild( [ this.childA, this.childB ] );
* }
*
* render() {
* super.render();
*
* this.element.appendChild( this.childA.element );
* this.element.appendChild( this.childB.element );
* }
* }
*
* const view = new SampleView( locale );
*
* view.render();
*
* // Will append <p><childA#element><b></b><childB#element></p>.
* document.body.appendChild( view.element );
*
* **Note**: There's no need to add child views if they're already referenced in the
* {@link #template}:
*
* class SampleView extends View {
* constructor( locale ) {
* super( locale );
*
* this.childA = new SomeChildView( locale );
* this.childB = new SomeChildView( locale );
*
* this.setTemplate( {
* tag: 'p',
*
* // These children will be added automatically. There's no
* // need to call {@link #registerChild} for any of them.
* children: [ this.childA, this.childB ]
* } );
* }
*
* // ...
* }
*
* @param {module:ui/view~View|Iterable.<module:ui/view~View>} children Children views to be registered.
*/
registerChild( children ) {
if ( !(0,_ckeditor_ckeditor5_utils_src_isiterable__WEBPACK_IMPORTED_MODULE_7__["default"])( children ) ) {
children = [ children ];
}
for ( const child of children ) {
this._unboundChildren.add( child );
}
}
/**
* The opposite of {@link #registerChild}. Removes a child view from this view instance.
* Once removed, the child is no longer managed by its parent, e.g. it can safely
* become a child of another parent view.
*
* @see #registerChild
* @param {module:ui/view~View|Iterable.<module:ui/view~View>} children Child views to be removed.
*/
deregisterChild( children ) {
if ( !(0,_ckeditor_ckeditor5_utils_src_isiterable__WEBPACK_IMPORTED_MODULE_7__["default"])( children ) ) {
children = [ children ];
}
for ( const child of children ) {
this._unboundChildren.remove( child );
}
}
/**
* Sets the {@link #template} of the view with with given definition.
*
* A shorthand for:
*
* view.setTemplate( definition );
*
* @param {module:ui/template~TemplateDefinition} definition Definition of view's template.
*/
setTemplate( definition ) {
this.template = new _template__WEBPACK_IMPORTED_MODULE_2__["default"]( definition );
}
/**
* {@link module:ui/template~Template.extend Extends} the {@link #template} of the view with
* with given definition.
*
* A shorthand for:
*
* Template.extend( view.template, definition );
*
* **Note**: Is requires the {@link #template} to be already set. See {@link #setTemplate}.
*
* @param {module:ui/template~TemplateDefinition} definition Definition which
* extends the {@link #template}.
*/
extendTemplate( definition ) {
_template__WEBPACK_IMPORTED_MODULE_2__["default"].extend( this.template, definition );
}
/**
* Recursively renders the view.
*
* Once the view is rendered:
* * the {@link #element} becomes an HTML element out of {@link #template},
* * the {@link #isRendered} flag is set `true`.
*
* **Note**: The children of the view:
* * defined directly in the {@link #template}
* * residing in collections created by the {@link #createCollection} method,
* * and added by {@link #registerChild}
* are also rendered in the process.
*
* In general, `render()` method is the right place to keep the code which refers to the
* {@link #element} and should be executed at the very beginning of the view's life cycle.
*
* It is possible to {@link module:ui/template~Template.extend} the {@link #template} before
* the view is rendered. To allow an early customization of the view (e.g. by its parent),
* such references should be done in `render()`.
*
* class SampleView extends View {
* constructor() {
* this.setTemplate( {
* // ...
* } );
* },
*
* render() {
* // View#element becomes available.
* super.render();
*
* // The "scroll" listener depends on #element.
* this.listenTo( window, 'scroll', () => {
* // A reference to #element would render the #template and make it non-extendable.
* if ( window.scrollY > 0 ) {
* this.element.scrollLeft = 100;
* } else {
* this.element.scrollLeft = 0;
* }
* } );
* }
* }
*
* const view = new SampleView();
*
* // Let's customize the view before it gets rendered.
* view.extendTemplate( {
* attributes: {
* class: [
* 'additional-class'
* ]
* }
* } );
*
* // Late rendering allows customization of the view.
* view.render();
*/
render() {
if ( this.isRendered ) {
/**
* This View has already been rendered.
*
* @error ui-view-render-already-rendered
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_0__["default"]( 'ui-view-render-already-rendered', this );
}
// Render #element of the view.
if ( this.template ) {
this.element = this.template.render();
// Auto–register view children from #template.
this.registerChild( this.template.getViews() );
}
this.isRendered = true;
}
/**
* Recursively destroys the view instance and child views added by {@link #registerChild} and
* residing in collections created by the {@link #createCollection}.
*
* Destruction disables all event listeners:
* * created on the view, e.g. `view.on( 'event', () => {} )`,
* * defined in the {@link #template} for DOM events.
*/
destroy() {
this.stopListening();
this._viewCollections.map( c => c.destroy() );
// Template isn't obligatory for views.
if ( this.template && this.template._revertData ) {
this.template.revert( this.element );
}
}
/**
* Event fired by the {@link #render} method. Actual rendering is executed as a listener to
* this event with the default priority.
*
* See {@link module:utils/observablemixin~ObservableMixin#decorate} for more information and samples.
*
* @event render
*/
}
(0,_ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_6__["default"])( View, _ckeditor_ckeditor5_utils_src_dom_emittermixin__WEBPACK_IMPORTED_MODULE_3__["default"] );
(0,_ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_6__["default"])( View, _ckeditor_ckeditor5_utils_src_observablemixin__WEBPACK_IMPORTED_MODULE_4__["default"] );
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/src/viewcollection.js":
/*!*******************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/src/viewcollection.js ***!
\*******************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ViewCollection)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_collection__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/collection */ "./node_modules/@ckeditor/ckeditor5-utils/src/collection.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module ui/viewcollection
*/
/**
* Collects {@link module:ui/view~View} instances.
*
* const parentView = new ParentView( locale );
* const collection = new ViewCollection( locale );
*
* collection.setParent( parentView.element );
*
* const viewA = new ChildView( locale );
* const viewB = new ChildView( locale );
*
* View collection renders and manages view {@link module:ui/view~View#element elements}:
*
* collection.add( viewA );
* collection.add( viewB );
*
* console.log( parentView.element.firsChild ); // -> viewA.element
* console.log( parentView.element.lastChild ); // -> viewB.element
*
* It {@link module:ui/viewcollection~ViewCollection#delegate propagates} DOM events too:
*
* // Delegate #click and #keydown events from viewA and viewB to the parentView.
* collection.delegate( 'click' ).to( parentView );
*
* parentView.on( 'click', ( evt ) => {
* console.log( `${ evt.source } has been clicked.` );
* } );
*
* // This event will be delegated to the parentView.
* viewB.fire( 'click' );
*
* **Note**: A view collection can be used directly in the {@link module:ui/template~TemplateDefinition definition}
* of a {@link module:ui/template~Template template}.
*
* @extends module:utils/collection~Collection
* @mixes module:utils/observablemixin~ObservableMixin
*/
class ViewCollection extends _ckeditor_ckeditor5_utils_src_collection__WEBPACK_IMPORTED_MODULE_1__["default"] {
/**
* Creates a new instance of the {@link module:ui/viewcollection~ViewCollection}.
*
* @param {Iterable.<module:ui/view~View>} [initialItems] The initial items of the collection.
*/
constructor( initialItems = [] ) {
super( initialItems, {
// An #id Number attribute should be legal and not break the `ViewCollection` instance.
// https://github.com/ckeditor/ckeditor5-ui/issues/93
idProperty: 'viewUid'
} );
// Handle {@link module:ui/view~View#element} in DOM when a new view is added to the collection.
this.on( 'add', ( evt, view, index ) => {
this._renderViewIntoCollectionParent( view, index );
} );
// Handle {@link module:ui/view~View#element} in DOM when a view is removed from the collection.
this.on( 'remove', ( evt, view ) => {
if ( view.element && this._parentElement ) {
view.element.remove();
}
} );
/**
* A parent element within which child views are rendered and managed in DOM.
*
* @protected
* @member {HTMLElement}
*/
this._parentElement = null;
}
/**
* Destroys the view collection along with child views.
* See the view {@link module:ui/view~View#destroy} method.
*/
destroy() {
this.map( view => view.destroy() );
}
/**
* Sets the parent HTML element of this collection. When parent is set, {@link #add adding} and
* {@link #remove removing} views in the collection synchronizes their
* {@link module:ui/view~View#element elements} in the parent element.
*
* @param {HTMLElement} element A new parent element.
*/
setParent( elementOrDocFragment ) {
this._parentElement = elementOrDocFragment;
// Take care of the initial collection items passed to the constructor.
for ( const view of this ) {
this._renderViewIntoCollectionParent( view );
}
}
/**
* Delegates selected events coming from within views in the collection to any
* {@link module:utils/emittermixin~Emitter}.
*
* For the following views and collection:
*
* const viewA = new View();
* const viewB = new View();
* const viewC = new View();
*
* const views = parentView.createCollection();
*
* views.delegate( 'eventX' ).to( viewB );
* views.delegate( 'eventX', 'eventY' ).to( viewC );
*
* views.add( viewA );
*
* the `eventX` is delegated (fired by) `viewB` and `viewC` along with `customData`:
*
* viewA.fire( 'eventX', customData );
*
* and `eventY` is delegated (fired by) `viewC` along with `customData`:
*
* viewA.fire( 'eventY', customData );
*
* See {@link module:utils/emittermixin~Emitter#delegate}.
*
* @param {...String} events {@link module:ui/view~View} event names to be delegated to another
* {@link module:utils/emittermixin~Emitter}.
* @returns {Object}
* @returns {Function} return.to A function which accepts the destination of
* {@link module:utils/emittermixin~Emitter#delegate delegated} events.
*/
delegate( ...events ) {
if ( !events.length || !isStringArray( events ) ) {
/**
* All event names must be strings.
*
* @error ui-viewcollection-delegate-wrong-events
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_0__["default"](
'ui-viewcollection-delegate-wrong-events',
this
);
}
return {
/**
* Selects destination for {@link module:utils/emittermixin~Emitter#delegate} events.
*
* @memberOf module:ui/viewcollection~ViewCollection#delegate
* @function module:ui/viewcollection~ViewCollection#delegate.to
* @param {module:utils/emittermixin~Emitter} dest An `Emitter` instance which is
* the destination for delegated events.
*/
to: dest => {
// Activate delegating on existing views in this collection.
for ( const view of this ) {
for ( const evtName of events ) {
view.delegate( evtName ).to( dest );
}
}
// Activate delegating on future views in this collection.
this.on( 'add', ( evt, view ) => {
for ( const evtName of events ) {
view.delegate( evtName ).to( dest );
}
} );
// Deactivate delegating when view is removed from this collection.
this.on( 'remove', ( evt, view ) => {
for ( const evtName of events ) {
view.stopDelegating( evtName, dest );
}
} );
}
};
}
/**
* This method {@link module:ui/view~View#render renders} a new view added to the collection.
*
* If the {@link #_parentElement parent element} of the collection is set, this method also adds
* the view's {@link module:ui/view~View#element} as a child of the parent in DOM at a specified index.
*
* **Note**: If index is not specified, the view's element is pushed as the last child
* of the parent element.
*
* @private
* @param {module:ui/view~View} view A new view added to the collection.
* @param {Number} [index] An index the view holds in the collection. When not specified,
* the view is added at the end.
*/
_renderViewIntoCollectionParent( view, index ) {
if ( !view.isRendered ) {
view.render();
}
if ( view.element && this._parentElement ) {
this._parentElement.insertBefore( view.element, this._parentElement.children[ index ] );
}
}
/**
* Removes a child view from the collection. If the {@link #setParent parent element} of the
* collection has been set, the {@link module:ui/view~View#element element} of the view is also removed
* in DOM, reflecting the order of the collection.
*
* See the {@link #add} method.
*
* @method #remove
* @param {module:ui/view~View|Number|String} subject The view to remove, its id or index in the collection.
* @returns {Object} The removed view.
*/
}
// Check if all entries of the array are of `String` type.
//
// @private
// @param {Array} arr An array to be checked.
// @returns {Boolean}
function isStringArray( arr ) {
return arr.every( a => typeof a == 'string' );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-undo/src/basecommand.js":
/*!******************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-undo/src/basecommand.js ***!
\******************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ BaseCommand)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_core_src_command__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-core/src/command */ "./node_modules/@ckeditor/ckeditor5-core/src/command.js");
/* harmony import */ var _ckeditor_ckeditor5_engine_src_model_operation_transform__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-engine/src/model/operation/transform */ "./node_modules/@ckeditor/ckeditor5-engine/src/model/operation/transform.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module undo/basecommand
*/
/**
* Base class for undo feature commands: {@link module:undo/undocommand~UndoCommand} and {@link module:undo/redocommand~RedoCommand}.
*
* @protected
* @extends module:core/command~Command
*/
class BaseCommand extends _ckeditor_ckeditor5_core_src_command__WEBPACK_IMPORTED_MODULE_0__["default"] {
constructor( editor ) {
super( editor );
/**
* Stack of items stored by the command. These are pairs of:
*
* * {@link module:engine/model/batch~Batch batch} saved by the command,
* * {@link module:engine/model/selection~Selection selection} state at the moment of saving the batch.
*
* @protected
* @member {Array} #_stack
*/
this._stack = [];
/**
* Stores all batches that were created by this command.
*
* @protected
* @member {WeakSet.<module:engine/model/batch~Batch>} #_createdBatches
*/
this._createdBatches = new WeakSet();
// Refresh state, so the command is inactive right after initialization.
this.refresh();
// Set the transparent batch for the `editor.data.set()` call if the
// batch type is not set already.
this.listenTo( editor.data, 'set', ( evt, data ) => {
// Create a shallow copy of the options to not change the original args.
// And make sure that an object is assigned to data[ 1 ].
data[ 1 ] = { ...data[ 1 ] };
const options = data[ 1 ];
// If batch type is not set, default to non-undoable batch.
if ( !options.batchType ) {
options.batchType = { isUndoable: false };
}
}, { priority: 'high' } );
// Clear the stack for the `transparent` batches.
this.listenTo( editor.data, 'set', ( evt, data ) => {
// We can assume that the object exists and it has a `batchType` property.
// It was ensured with a higher priority listener before.
const options = data[ 1 ];
if ( !options.batchType.isUndoable ) {
this.clearStack();
}
} );
}
/**
* @inheritDoc
*/
refresh() {
this.isEnabled = this._stack.length > 0;
}
/**
* Stores a batch in the command, together with the selection state of the {@link module:engine/model/document~Document document}
* created by the editor which this command is registered to.
*
* @param {module:engine/model/batch~Batch} batch The batch to add.
*/
addBatch( batch ) {
const docSelection = this.editor.model.document.selection;
const selection = {
ranges: docSelection.hasOwnRange ? Array.from( docSelection.getRanges() ) : [],
isBackward: docSelection.isBackward
};
this._stack.push( { batch, selection } );
this.refresh();
}
/**
* Removes all items from the stack.
*/
clearStack() {
this._stack = [];
this.refresh();
}
/**
* Restores the {@link module:engine/model/document~Document#selection document selection} state after a batch was undone.
*
* @protected
* @param {Array.<module:engine/model/range~Range>} ranges Ranges to be restored.
* @param {Boolean} isBackward A flag describing whether the restored range was selected forward or backward.
* @param {Array.<module:engine/model/operation/operation~Operation>} operations Operations which has been applied
* since selection has been stored.
*/
_restoreSelection( ranges, isBackward, operations ) {
const model = this.editor.model;
const document = model.document;
// This will keep the transformed selection ranges.
const selectionRanges = [];
// Transform all ranges from the restored selection.
const transformedRangeGroups = ranges.map( range => range.getTransformedByOperations( operations ) );
const allRanges = transformedRangeGroups.flat();
for ( const rangeGroup of transformedRangeGroups ) {
// While transforming there could appear ranges that are contained by other ranges, we shall ignore them.
const transformed = rangeGroup
.filter( range => range.root != document.graveyard )
.filter( range => !isRangeContainedByAnyOtherRange( range, allRanges ) );
// All the transformed ranges ended up in graveyard.
if ( !transformed.length ) {
continue;
}
// After the range got transformed, we have an array of ranges. Some of those
// ranges may be "touching" -- they can be next to each other and could be merged.
normalizeRanges( transformed );
// For each `range` from `ranges`, we take only one transformed range.
// This is because we want to prevent situation where single-range selection
// got transformed to multi-range selection.
selectionRanges.push( transformed[ 0 ] );
}
// @if CK_DEBUG_ENGINE // console.log( `Restored selection by undo: ${ selectionRanges.join( ', ' ) }` );
// `selectionRanges` may be empty if all ranges ended up in graveyard. If that is the case, do not restore selection.
if ( selectionRanges.length ) {
model.change( writer => {
writer.setSelection( selectionRanges, { backward: isBackward } );
} );
}
}
/**
* Undoes a batch by reversing that batch, transforming reversed batch and finally applying it.
* This is a helper method for {@link #execute}.
*
* @protected
* @param {module:engine/model/batch~Batch} batchToUndo The batch to be undone.
* @param {module:engine/model/batch~Batch} undoingBatch The batch that will contain undoing changes.
*/
_undo( batchToUndo, undoingBatch ) {
const model = this.editor.model;
const document = model.document;
// All changes done by the command execution will be saved as one batch.
this._createdBatches.add( undoingBatch );
const operationsToUndo = batchToUndo.operations.slice().filter( operation => operation.isDocumentOperation );
operationsToUndo.reverse();
// We will process each operation from `batchToUndo`, in reverse order. If there were operations A, B and C in undone batch,
// we need to revert them in reverse order, so first C' (reversed C), then B', then A'.
for ( const operationToUndo of operationsToUndo ) {
const nextBaseVersion = operationToUndo.baseVersion + 1;
const historyOperations = Array.from( document.history.getOperations( nextBaseVersion ) );
const transformedSets = (0,_ckeditor_ckeditor5_engine_src_model_operation_transform__WEBPACK_IMPORTED_MODULE_1__.transformSets)(
[ operationToUndo.getReversed() ],
historyOperations,
{
useRelations: true,
document: this.editor.model.document,
padWithNoOps: false,
forceWeakRemove: true
}
);
const reversedOperations = transformedSets.operationsA;
// After reversed operation has been transformed by all history operations, apply it.
for ( const operation of reversedOperations ) {
// Before applying, add the operation to the `undoingBatch`.
undoingBatch.addOperation( operation );
model.applyOperation( operation );
document.history.setOperationAsUndone( operationToUndo, operation );
}
}
}
}
// Normalizes list of ranges by joining intersecting or "touching" ranges.
//
// @param {Array.<module:engine/model/range~Range>} ranges
//
function normalizeRanges( ranges ) {
ranges.sort( ( a, b ) => a.start.isBefore( b.start ) ? -1 : 1 );
for ( let i = 1; i < ranges.length; i++ ) {
const previousRange = ranges[ i - 1 ];
const joinedRange = previousRange.getJoined( ranges[ i ], true );
if ( joinedRange ) {
// Replace the ranges on the list with the new joined range.
i--;
ranges.splice( i, 2, joinedRange );
}
}
}
function isRangeContainedByAnyOtherRange( range, ranges ) {
return ranges.some( otherRange => otherRange !== range && otherRange.containsRange( range, true ) );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-undo/src/index.js":
/*!************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-undo/src/index.js ***!
\************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "Undo": () => (/* reexport safe */ _undo__WEBPACK_IMPORTED_MODULE_0__["default"]),
/* harmony export */ "UndoEditing": () => (/* reexport safe */ _undoediting__WEBPACK_IMPORTED_MODULE_1__["default"]),
/* harmony export */ "UndoUi": () => (/* reexport safe */ _undoui__WEBPACK_IMPORTED_MODULE_2__["default"])
/* harmony export */ });
/* harmony import */ var _undo__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./undo */ "./node_modules/@ckeditor/ckeditor5-undo/src/undo.js");
/* harmony import */ var _undoediting__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./undoediting */ "./node_modules/@ckeditor/ckeditor5-undo/src/undoediting.js");
/* harmony import */ var _undoui__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./undoui */ "./node_modules/@ckeditor/ckeditor5-undo/src/undoui.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module undo
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-undo/src/redocommand.js":
/*!******************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-undo/src/redocommand.js ***!
\******************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ RedoCommand)
/* harmony export */ });
/* harmony import */ var _basecommand__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./basecommand */ "./node_modules/@ckeditor/ckeditor5-undo/src/basecommand.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module undo/redocommand
*/
/**
* The redo command stores {@link module:engine/model/batch~Batch batches} that were used to undo a batch by
* {@link module:undo/undocommand~UndoCommand}. It is able to redo a previously undone batch by reversing the undoing
* batches created by `UndoCommand`. The reversed batch is transformed by all the batches from
* {@link module:engine/model/document~Document#history history} that happened after the reversed undo batch.
*
* The redo command also takes care of restoring the {@link module:engine/model/document~Document#selection document selection}.
*
* @extends module:undo/basecommand~BaseCommand
*/
class RedoCommand extends _basecommand__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* Executes the command. This method reverts the last {@link module:engine/model/batch~Batch batch} added to
* the command's stack, applies the reverted and transformed version on the
* {@link module:engine/model/document~Document document} and removes the batch from the stack.
* Then, it restores the {@link module:engine/model/document~Document#selection document selection}.
*
* @fires execute
*/
execute() {
const item = this._stack.pop();
const redoingBatch = this.editor.model.createBatch( { isUndo: true } );
// All changes have to be done in one `enqueueChange` callback so other listeners will not step between consecutive
// operations, or won't do changes to the document before selection is properly restored.
this.editor.model.enqueueChange( redoingBatch, () => {
const lastOperation = item.batch.operations[ item.batch.operations.length - 1 ];
const nextBaseVersion = lastOperation.baseVersion + 1;
const operations = this.editor.model.document.history.getOperations( nextBaseVersion );
this._restoreSelection( item.selection.ranges, item.selection.isBackward, operations );
this._undo( item.batch, redoingBatch );
} );
this.refresh();
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-undo/src/undo.js":
/*!***********************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-undo/src/undo.js ***!
\***********************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ Undo)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_core_src_plugin__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-core/src/plugin */ "./node_modules/@ckeditor/ckeditor5-core/src/plugin.js");
/* harmony import */ var _undoediting__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./undoediting */ "./node_modules/@ckeditor/ckeditor5-undo/src/undoediting.js");
/* harmony import */ var _undoui__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./undoui */ "./node_modules/@ckeditor/ckeditor5-undo/src/undoui.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module undo/undo
*/
/**
* The undo feature.
*
* This is a "glue" plugin which loads the {@link module:undo/undoediting~UndoEditing undo editing feature}
* and {@link module:undo/undoui~UndoUI undo UI feature}.
*
* Below is the explanation of the undo mechanism working together with {@link module:engine/model/history~History History}:
*
* Whenever a {@link module:engine/model/operation/operation~Operation operation} is applied to the
* {@link module:engine/model/document~Document document}, it is saved to `History` as is.
* The {@link module:engine/model/batch~Batch batch} that owns that operation is also saved, in
* {@link module:undo/undocommand~UndoCommand}, together with the selection that was present in the document before the
* operation was applied. A batch is saved instead of the operation because changes are undone batch-by-batch, not operation-by-operation
* and a batch is seen as one undo step.
*
* After some changes happen to the document, the `History` and `UndoCommand` stack can be represented as follows:
*
* History Undo stack
* ============== ==================================
* [operation A1] [batch A]
* [operation B1] [batch B]
* [operation B2] [batch C]
* [operation C1]
* [operation C2]
* [operation B3]
* [operation C3]
*
* Where operations starting with the same letter are from same batch.
*
* Undoing a batch means that a set of operations which will reverse the effects of that batch needs to be generated.
* For example, if a batch added several letters, undoing the batch should remove them. It is important to apply undoing
* operations in the reversed order, so if a batch has operation `X`, `Y`, `Z`, reversed operations `Zr`, `Yr` and `Xr`
* need to be applied. Otherwise reversed operation `Xr` would operate on a wrong document state, because operation `X`
* does not know that operations `Y` and `Z` happened.
*
* After operations from an undone batch got {@link module:engine/model/operation/operation~Operation#getReversed reversed},
* one needs to make sure if they are ready to be applied. In the scenario above, operation `C3` is the last operation and `C3r`
* bases on up-to-date document state, so it can be applied to the document.
*
* History Undo stack
* ================= ==================================
* [ operation A1 ] [ batch A ]
* [ operation B1 ] [ batch B ]
* [ operation B2 ] [ processing undoing batch C ]
* [ operation C1 ]
* [ operation C2 ]
* [ operation B3 ]
* [ operation C3 ]
* [ operation C3r ]
*
* Next is operation `C2`, reversed to `C2r`. `C2r` bases on `C2`, so it bases on the wrong document state. It needs to be
* transformed by operations from history that happened after it, so it "knows" about them. Let us assume that `C2' = C2r * B3 * C3 * C3r`,
* where `*` means "transformed by". Rest of operations from that batch are processed in the same fashion.
*
* History Undo stack Redo stack
* ================= ================================== ==================================
* [ operation A1 ] [ batch A ] [ batch Cr ]
* [ operation B1 ] [ batch B ]
* [ operation B2 ]
* [ operation C1 ]
* [ operation C2 ]
* [ operation B3 ]
* [ operation C3 ]
* [ operation C3r ]
* [ operation C2' ]
* [ operation C1' ]
*
* Selective undo works on the same basis, however, instead of undoing the last batch in the undo stack, any batch can be undone.
* The same algorithm applies: operations from a batch (i.e. `A1`) are reversed and then transformed by operations stored in history.
*
* Redo also is very similar to undo. It has its own stack that is filled with undoing (reversed batches). Operations from
* batch that is re-done are reversed-back, transformed in proper order and applied to the document.
*
* History Undo stack Redo stack
* ================= ================================== ==================================
* [ operation A1 ] [ batch A ]
* [ operation B1 ] [ batch B ]
* [ operation B2 ] [ batch Crr ]
* [ operation C1 ]
* [ operation C2 ]
* [ operation B3 ]
* [ operation C3 ]
* [ operation C3r ]
* [ operation C2' ]
* [ operation C1' ]
* [ operation C1'r]
* [ operation C2'r]
* [ operation C3rr]
*
* @extends module:core/plugin~Plugin
*/
class Undo extends _ckeditor_ckeditor5_core_src_plugin__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* @inheritDoc
*/
static get requires() {
return [ _undoediting__WEBPACK_IMPORTED_MODULE_1__["default"], _undoui__WEBPACK_IMPORTED_MODULE_2__["default"] ];
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'Undo';
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-undo/src/undocommand.js":
/*!******************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-undo/src/undocommand.js ***!
\******************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ UndoCommand)
/* harmony export */ });
/* harmony import */ var _basecommand__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./basecommand */ "./node_modules/@ckeditor/ckeditor5-undo/src/basecommand.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module undo/undocommand
*/
/**
* The undo command stores {@link module:engine/model/batch~Batch batches} applied to the
* {@link module:engine/model/document~Document document} and is able to undo a batch by reversing it and transforming by
* batches from {@link module:engine/model/document~Document#history history} that happened after the reversed batch.
*
* The undo command also takes care of restoring the {@link module:engine/model/document~Document#selection document selection}.
*
* @extends module:undo/basecommand~BaseCommand
*/
class UndoCommand extends _basecommand__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* Executes the command. This method reverts a {@link module:engine/model/batch~Batch batch} added to the command's stack, transforms
* and applies the reverted version on the {@link module:engine/model/document~Document document} and removes the batch from the stack.
* Then, it restores the {@link module:engine/model/document~Document#selection document selection}.
*
* @fires execute
* @fires revert
* @param {module:engine/model/batch~Batch} [batch] A batch that should be undone. If not set, the last added batch will be undone.
*/
execute( batch = null ) {
// If batch is not given, set `batchIndex` to the last index in command stack.
const batchIndex = batch ? this._stack.findIndex( a => a.batch == batch ) : this._stack.length - 1;
const item = this._stack.splice( batchIndex, 1 )[ 0 ];
const undoingBatch = this.editor.model.createBatch( { isUndo: true } );
// All changes has to be done in one `enqueueChange` callback so other listeners will not
// step between consecutive operations, or won't do changes to the document before selection is properly restored.
this.editor.model.enqueueChange( undoingBatch, () => {
this._undo( item.batch, undoingBatch );
const operations = this.editor.model.document.history.getOperations( item.batch.baseVersion );
this._restoreSelection( item.selection.ranges, item.selection.isBackward, operations );
this.fire( 'revert', item.batch, undoingBatch );
} );
this.refresh();
}
}
/**
* Fired when execution of the command reverts some batch.
*
* @event revert
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-undo/src/undoediting.js":
/*!******************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-undo/src/undoediting.js ***!
\******************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ UndoEditing)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_core_src_plugin__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-core/src/plugin */ "./node_modules/@ckeditor/ckeditor5-core/src/plugin.js");
/* harmony import */ var _undocommand__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./undocommand */ "./node_modules/@ckeditor/ckeditor5-undo/src/undocommand.js");
/* harmony import */ var _redocommand__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./redocommand */ "./node_modules/@ckeditor/ckeditor5-undo/src/redocommand.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module undo/undoediting
*/
/**
* The undo engine feature.
*
* It introduces the `'undo'` and `'redo'` commands to the editor.
*
* @extends module:core/plugin~Plugin
*/
class UndoEditing extends _ckeditor_ckeditor5_core_src_plugin__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* @inheritDoc
*/
static get pluginName() {
return 'UndoEditing';
}
/**
* @inheritDoc
*/
constructor( editor ) {
super( editor );
/**
* The command that manages undo {@link module:engine/model/batch~Batch batches} stack (history).
* Created and registered during the {@link #init feature initialization}.
*
* @private
* @member {module:undo/undocommand~UndoCommand} #_undoCommand
*/
/**
* The command that manages redo {@link module:engine/model/batch~Batch batches} stack (history).
* Created and registered during the {@link #init feature initialization}.
*
* @private
* @member {module:undo/undocommand~UndoCommand} #_redoCommand
*/
/**
* Keeps track of which batches were registered in undo.
*
* @private
* @member {WeakSet.<module:engine/model/batch~Batch>}
*/
this._batchRegistry = new WeakSet();
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
// Create commands.
this._undoCommand = new _undocommand__WEBPACK_IMPORTED_MODULE_1__["default"]( editor );
this._redoCommand = new _redocommand__WEBPACK_IMPORTED_MODULE_2__["default"]( editor );
// Register command to the editor.
editor.commands.add( 'undo', this._undoCommand );
editor.commands.add( 'redo', this._redoCommand );
this.listenTo( editor.model, 'applyOperation', ( evt, args ) => {
const operation = args[ 0 ];
// Do not register batch if the operation is not a document operation.
// This prevents from creating empty undo steps, where all operations where non-document operations.
// Non-document operations creates and alters content in detached tree fragments (for example, document fragments).
// Most of time this is preparing data before it is inserted into actual tree (for example during copy & paste).
// Such operations should not be reversed.
if ( !operation.isDocumentOperation ) {
return;
}
const batch = operation.batch;
const isRedoBatch = this._redoCommand._createdBatches.has( batch );
const isUndoBatch = this._undoCommand._createdBatches.has( batch );
const wasProcessed = this._batchRegistry.has( batch );
// Skip the batch if it was already processed.
if ( wasProcessed ) {
return;
}
// Add the batch to the registry so it will not be processed again.
this._batchRegistry.add( batch );
if ( !batch.isUndoable ) {
return;
}
if ( isRedoBatch ) {
// If this batch comes from `redoCommand`, add it to the `undoCommand` stack.
this._undoCommand.addBatch( batch );
} else if ( !isUndoBatch ) {
// If the batch comes neither from `redoCommand` nor from `undoCommand` then it is a new, regular batch.
// Add the batch to the `undoCommand` stack and clear the `redoCommand` stack.
this._undoCommand.addBatch( batch );
this._redoCommand.clearStack();
}
}, { priority: 'highest' } );
this.listenTo( this._undoCommand, 'revert', ( evt, undoneBatch, undoingBatch ) => {
this._redoCommand.addBatch( undoingBatch );
} );
editor.keystrokes.set( 'CTRL+Z', 'undo' );
editor.keystrokes.set( 'CTRL+Y', 'redo' );
editor.keystrokes.set( 'CTRL+SHIFT+Z', 'redo' );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-undo/src/undoui.js":
/*!*************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-undo/src/undoui.js ***!
\*************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ UndoUI)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_core_src_plugin__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-core/src/plugin */ "./node_modules/@ckeditor/ckeditor5-core/src/plugin.js");
/* harmony import */ var _ckeditor_ckeditor5_ui_src_button_buttonview__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-ui/src/button/buttonview */ "./node_modules/@ckeditor/ckeditor5-ui/src/button/buttonview.js");
/* harmony import */ var _theme_icons_undo_svg__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../theme/icons/undo.svg */ "./node_modules/@ckeditor/ckeditor5-undo/theme/icons/undo.svg");
/* harmony import */ var _theme_icons_redo_svg__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../theme/icons/redo.svg */ "./node_modules/@ckeditor/ckeditor5-undo/theme/icons/redo.svg");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module undo/undoui
*/
/**
* The undo UI feature. It introduces the `'undo'` and `'redo'` buttons to the editor.
*
* @extends module:core/plugin~Plugin
*/
class UndoUI extends _ckeditor_ckeditor5_core_src_plugin__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* @inheritDoc
*/
static get pluginName() {
return 'UndoUI';
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const locale = editor.locale;
const t = editor.t;
const localizedUndoIcon = locale.uiLanguageDirection == 'ltr' ? _theme_icons_undo_svg__WEBPACK_IMPORTED_MODULE_2__["default"] : _theme_icons_redo_svg__WEBPACK_IMPORTED_MODULE_3__["default"];
const localizedRedoIcon = locale.uiLanguageDirection == 'ltr' ? _theme_icons_redo_svg__WEBPACK_IMPORTED_MODULE_3__["default"] : _theme_icons_undo_svg__WEBPACK_IMPORTED_MODULE_2__["default"];
this._addButton( 'undo', t( 'Undo' ), 'CTRL+Z', localizedUndoIcon );
this._addButton( 'redo', t( 'Redo' ), 'CTRL+Y', localizedRedoIcon );
}
/**
* Creates a button for the specified command.
*
* @private
* @param {String} name Command name.
* @param {String} label Button label.
* @param {String} keystroke Command keystroke.
* @param {String} Icon Source of the icon.
*/
_addButton( name, label, keystroke, Icon ) {
const editor = this.editor;
editor.ui.componentFactory.add( name, locale => {
const command = editor.commands.get( name );
const view = new _ckeditor_ckeditor5_ui_src_button_buttonview__WEBPACK_IMPORTED_MODULE_1__["default"]( locale );
view.set( {
label,
icon: Icon,
keystroke,
tooltip: true
} );
view.bind( 'isEnabled' ).to( command, 'isEnabled' );
this.listenTo( view, 'execute', () => {
editor.execute( name );
editor.editing.view.focus();
} );
return view;
} );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-upload/src/adapters/base64uploadadapter.js":
/*!*************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-upload/src/adapters/base64uploadadapter.js ***!
\*************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ Base64UploadAdapter)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_core_src_plugin__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-core/src/plugin */ "./node_modules/@ckeditor/ckeditor5-core/src/plugin.js");
/* harmony import */ var _filerepository__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../filerepository */ "./node_modules/@ckeditor/ckeditor5-upload/src/filerepository.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module upload/adapters/base64uploadadapter
*/
/* globals window */
/**
* A plugin that converts images inserted into the editor into [Base64 strings](https://en.wikipedia.org/wiki/Base64)
* in the {@glink builds/guides/integration/saving-data editor output}.
*
* This kind of image upload does not require server processing – images are stored with the rest of the text and
* displayed by the web browser without additional requests.
*
* Check out the {@glink features/images/image-upload/image-upload comprehensive "Image upload overview"} to learn about
* other ways to upload images into CKEditor 5.
*
* @extends module:core/plugin~Plugin
*/
class Base64UploadAdapter extends _ckeditor_ckeditor5_core_src_plugin__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* @inheritDoc
*/
static get requires() {
return [ _filerepository__WEBPACK_IMPORTED_MODULE_1__["default"] ];
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'Base64UploadAdapter';
}
/**
* @inheritDoc
*/
init() {
this.editor.plugins.get( _filerepository__WEBPACK_IMPORTED_MODULE_1__["default"] ).createUploadAdapter = loader => new Adapter( loader );
}
}
/**
* The upload adapter that converts images inserted into the editor into Base64 strings.
*
* @private
* @implements module:upload/filerepository~UploadAdapter
*/
class Adapter {
/**
* Creates a new adapter instance.
*
* @param {module:upload/filerepository~FileLoader} loader
*/
constructor( loader ) {
/**
* `FileLoader` instance to use during the upload.
*
* @member {module:upload/filerepository~FileLoader} #loader
*/
this.loader = loader;
}
/**
* Starts the upload process.
*
* @see module:upload/filerepository~UploadAdapter#upload
* @returns {Promise}
*/
upload() {
return new Promise( ( resolve, reject ) => {
const reader = this.reader = new window.FileReader();
reader.addEventListener( 'load', () => {
resolve( { default: reader.result } );
} );
reader.addEventListener( 'error', err => {
reject( err );
} );
reader.addEventListener( 'abort', () => {
reject();
} );
this.loader.file.then( file => {
reader.readAsDataURL( file );
} );
} );
}
/**
* Aborts the upload process.
*
* @see module:upload/filerepository~UploadAdapter#abort
* @returns {Promise}
*/
abort() {
this.reader.abort();
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-upload/src/adapters/simpleuploadadapter.js":
/*!*************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-upload/src/adapters/simpleuploadadapter.js ***!
\*************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ SimpleUploadAdapter)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_core_src_plugin__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-core/src/plugin */ "./node_modules/@ckeditor/ckeditor5-core/src/plugin.js");
/* harmony import */ var _filerepository__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../filerepository */ "./node_modules/@ckeditor/ckeditor5-upload/src/filerepository.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module upload/adapters/simpleuploadadapter
*/
/* globals XMLHttpRequest, FormData */
/**
* The Simple upload adapter allows uploading images to an application running on your server using
* the [`XMLHttpRequest`](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest) API with a
* minimal {@link module:upload/adapters/simpleuploadadapter~SimpleUploadConfig editor configuration}.
*
* ClassicEditor
* .create( document.querySelector( '#editor' ), {
* simpleUpload: {
* uploadUrl: 'http://example.com',
* headers: {
* ...
* }
* }
* } )
* .then( ... )
* .catch( ... );
*
* See the {@glink features/images/image-upload/simple-upload-adapter "Simple upload adapter"} guide to learn how to
* learn more about the feature (configuration, server–side requirements, etc.).
*
* Check out the {@glink features/images/image-upload/image-upload comprehensive "Image upload overview"} to learn about
* other ways to upload images into CKEditor 5.
*
* @extends module:core/plugin~Plugin
*/
class SimpleUploadAdapter extends _ckeditor_ckeditor5_core_src_plugin__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* @inheritDoc
*/
static get requires() {
return [ _filerepository__WEBPACK_IMPORTED_MODULE_1__["default"] ];
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'SimpleUploadAdapter';
}
/**
* @inheritDoc
*/
init() {
const options = this.editor.config.get( 'simpleUpload' );
if ( !options ) {
return;
}
if ( !options.uploadUrl ) {
/**
* The {@link module:upload/adapters/simpleuploadadapter~SimpleUploadConfig#uploadUrl `config.simpleUpload.uploadUrl`}
* configuration required by the {@link module:upload/adapters/simpleuploadadapter~SimpleUploadAdapter `SimpleUploadAdapter`}
* is missing. Make sure the correct URL is specified for the image upload to work properly.
*
* @error simple-upload-adapter-missing-uploadurl
*/
(0,_ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_2__.logWarning)( 'simple-upload-adapter-missing-uploadurl' );
return;
}
this.editor.plugins.get( _filerepository__WEBPACK_IMPORTED_MODULE_1__["default"] ).createUploadAdapter = loader => {
return new Adapter( loader, options );
};
}
}
/**
* Upload adapter.
*
* @private
* @implements module:upload/filerepository~UploadAdapter
*/
class Adapter {
/**
* Creates a new adapter instance.
*
* @param {module:upload/filerepository~FileLoader} loader
* @param {module:upload/adapters/simpleuploadadapter~SimpleUploadConfig} options
*/
constructor( loader, options ) {
/**
* FileLoader instance to use during the upload.
*
* @member {module:upload/filerepository~FileLoader} #loader
*/
this.loader = loader;
/**
* The configuration of the adapter.
*
* @member {module:upload/adapters/simpleuploadadapter~SimpleUploadConfig} #options
*/
this.options = options;
}
/**
* Starts the upload process.
*
* @see module:upload/filerepository~UploadAdapter#upload
* @returns {Promise}
*/
upload() {
return this.loader.file
.then( file => new Promise( ( resolve, reject ) => {
this._initRequest();
this._initListeners( resolve, reject, file );
this._sendRequest( file );
} ) );
}
/**
* Aborts the upload process.
*
* @see module:upload/filerepository~UploadAdapter#abort
* @returns {Promise}
*/
abort() {
if ( this.xhr ) {
this.xhr.abort();
}
}
/**
* Initializes the `XMLHttpRequest` object using the URL specified as
* {@link module:upload/adapters/simpleuploadadapter~SimpleUploadConfig#uploadUrl `simpleUpload.uploadUrl`} in the editor's
* configuration.
*
* @private
*/
_initRequest() {
const xhr = this.xhr = new XMLHttpRequest();
xhr.open( 'POST', this.options.uploadUrl, true );
xhr.responseType = 'json';
}
/**
* Initializes XMLHttpRequest listeners
*
* @private
* @param {Function} resolve Callback function to be called when the request is successful.
* @param {Function} reject Callback function to be called when the request cannot be completed.
* @param {File} file Native File object.
*/
_initListeners( resolve, reject, file ) {
const xhr = this.xhr;
const loader = this.loader;
const genericErrorText = `Couldn't upload file: ${ file.name }.`;
xhr.addEventListener( 'error', () => reject( genericErrorText ) );
xhr.addEventListener( 'abort', () => reject() );
xhr.addEventListener( 'load', () => {
const response = xhr.response;
if ( !response || response.error ) {
return reject( response && response.error && response.error.message ? response.error.message : genericErrorText );
}
const urls = response.url ? { default: response.url } : response.urls;
// Resolve with the normalized `urls` property and pass the rest of the response
// to allow customizing the behavior of features relying on the upload adapters.
resolve( {
...response,
urls
} );
} );
// Upload progress when it is supported.
/* istanbul ignore else */
if ( xhr.upload ) {
xhr.upload.addEventListener( 'progress', evt => {
if ( evt.lengthComputable ) {
loader.uploadTotal = evt.total;
loader.uploaded = evt.loaded;
}
} );
}
}
/**
* Prepares the data and sends the request.
*
* @private
* @param {File} file File instance to be uploaded.
*/
_sendRequest( file ) {
// Set headers if specified.
const headers = this.options.headers || {};
// Use the withCredentials flag if specified.
const withCredentials = this.options.withCredentials || false;
for ( const headerName of Object.keys( headers ) ) {
this.xhr.setRequestHeader( headerName, headers[ headerName ] );
}
this.xhr.withCredentials = withCredentials;
// Prepare the form data.
const data = new FormData();
data.append( 'upload', file );
// Send the request.
this.xhr.send( data );
}
}
/**
* The configuration of the {@link module:upload/adapters/simpleuploadadapter~SimpleUploadAdapter simple upload adapter}.
*
* ClassicEditor
* .create( editorElement, {
* simpleUpload: {
* // The URL the images are uploaded to.
* uploadUrl: 'http://example.com',
*
* // Headers sent along with the XMLHttpRequest to the upload server.
* headers: {
* ...
* }
* }
* } );
* .then( ... )
* .catch( ... );
*
* See the {@glink features/images/image-upload/simple-upload-adapter "Simple upload adapter"} guide to learn more.
*
* See {@link module:core/editor/editorconfig~EditorConfig all editor configuration options}.
*
* @interface SimpleUploadConfig
*/
/**
* The configuration of the {@link module:upload/adapters/simpleuploadadapter~SimpleUploadAdapter simple upload adapter}.
*
* Read more in {@link module:upload/adapters/simpleuploadadapter~SimpleUploadConfig}.
*
* @member {module:upload/adapters/simpleuploadadapter~SimpleUploadConfig} module:core/editor/editorconfig~EditorConfig#simpleUpload
*/
/**
* The path (URL) to the server (application) which handles the file upload. When specified, enables the automatic
* upload of resources (images) inserted into the editor content.
*
* Learn more about the server application requirements in the
* {@glink features/images/image-upload/simple-upload-adapter#server-side-configuration "Server-side configuration"} section
* of the feature guide.
*
* @member {String} module:upload/adapters/simpleuploadadapter~SimpleUploadConfig#uploadUrl
*/
/**
* An object that defines additional [headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers) sent with
* the request to the server during the upload. This is the right place to implement security mechanisms like
* authentication and [CSRF](https://developer.mozilla.org/en-US/docs/Glossary/CSRF) protection.
*
* ClassicEditor
* .create( editorElement, {
* simpleUpload: {
* headers: {
* 'X-CSRF-TOKEN': 'CSRF-Token',
* Authorization: 'Bearer <JSON Web Token>'
* }
* }
* } );
* .then( ... )
* .catch( ... );
*
* Learn more about the server application requirements in the
* {@glink features/images/image-upload/simple-upload-adapter#server-side-configuration "Server-side configuration"} section
* of the feature guide.
*
* @member {Object.<String, String>} module:upload/adapters/simpleuploadadapter~SimpleUploadConfig#headers
*/
/**
* This flag enables the
* [`withCredentials`](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/withCredentials)
* property of the request sent to the server during the upload. It affects cross-site requests only and, for instance,
* allows credentials such as cookies to be sent along with the request.
*
* ClassicEditor
* .create( editorElement, {
* simpleUpload: {
* withCredentials: true
* }
* } );
* .then( ... )
* .catch( ... );
*
* Learn more about the server application requirements in the
* {@glink features/images/image-upload/simple-upload-adapter#server-side-configuration "Server-side configuration"} section
* of the feature guide.
*
* @member {Boolean} [module:upload/adapters/simpleuploadadapter~SimpleUploadConfig#withCredentials=false]
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-upload/src/filereader.js":
/*!*******************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-upload/src/filereader.js ***!
\*******************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ FileReader)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_utils_src_observablemixin__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/observablemixin */ "./node_modules/@ckeditor/ckeditor5-utils/src/observablemixin.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/mix */ "./node_modules/@ckeditor/ckeditor5-utils/src/mix.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module upload/filereader
*/
/* globals window */
/**
* Wrapper over the native `FileReader`.
*/
class FileReader {
/**
* Creates an instance of the FileReader.
*/
constructor() {
const reader = new window.FileReader();
/**
* Instance of native FileReader.
*
* @private
* @member {FileReader} #_reader
*/
this._reader = reader;
this._data = undefined;
/**
* Number of bytes loaded.
*
* @readonly
* @observable
* @member {Number} #loaded
*/
this.set( 'loaded', 0 );
reader.onprogress = evt => {
this.loaded = evt.loaded;
};
}
/**
* Returns error that occurred during file reading.
*
* @returns {Error}
*/
get error() {
return this._reader.error;
}
/**
* Holds the data of an already loaded file. The file must be first loaded
* by using {@link module:upload/filereader~FileReader#read `read()`}.
*
* @type {File|undefined}
*/
get data() {
return this._data;
}
/**
* Reads the provided file.
*
* @param {File} file Native File object.
* @returns {Promise.<String>} Returns a promise that will be resolved with file's content.
* The promise will be rejected in case of an error or when the reading process is aborted.
*/
read( file ) {
const reader = this._reader;
this.total = file.size;
return new Promise( ( resolve, reject ) => {
reader.onload = () => {
const result = reader.result;
this._data = result;
resolve( result );
};
reader.onerror = () => {
reject( 'error' );
};
reader.onabort = () => {
reject( 'aborted' );
};
this._reader.readAsDataURL( file );
} );
}
/**
* Aborts file reader.
*/
abort() {
this._reader.abort();
}
}
(0,_ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_1__["default"])( FileReader, _ckeditor_ckeditor5_utils_src_observablemixin__WEBPACK_IMPORTED_MODULE_0__["default"] );
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-upload/src/filerepository.js":
/*!***********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-upload/src/filerepository.js ***!
\***********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ FileRepository)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_core_src_plugin__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-core/src/plugin */ "./node_modules/@ckeditor/ckeditor5-core/src/plugin.js");
/* harmony import */ var _ckeditor_ckeditor5_core_src_pendingactions__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-core/src/pendingactions */ "./node_modules/@ckeditor/ckeditor5-core/src/pendingactions.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_observablemixin__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/observablemixin */ "./node_modules/@ckeditor/ckeditor5-utils/src/observablemixin.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_collection__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/collection */ "./node_modules/@ckeditor/ckeditor5-utils/src/collection.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/mix */ "./node_modules/@ckeditor/ckeditor5-utils/src/mix.js");
/* harmony import */ var _filereader_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./filereader.js */ "./node_modules/@ckeditor/ckeditor5-upload/src/filereader.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_uid_js__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/uid.js */ "./node_modules/@ckeditor/ckeditor5-utils/src/uid.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module upload/filerepository
*/
/**
* File repository plugin. A central point for managing file upload.
*
* To use it, first you need an upload adapter. Upload adapter's job is to handle communication with the server
* (sending the file and handling server's response). You can use one of the existing plugins introducing upload adapters
* (e.g. {@link module:easy-image/cloudservicesuploadadapter~CloudServicesUploadAdapter} or
* {@link module:adapter-ckfinder/uploadadapter~CKFinderUploadAdapter}) or write your own one – see
* the {@glink framework/guides/deep-dive/upload-adapter Custom image upload adapter deep dive guide}.
*
* Then, you can use {@link module:upload/filerepository~FileRepository#createLoader `createLoader()`} and the returned
* {@link module:upload/filerepository~FileLoader} instance to load and upload files.
*
* @extends module:core/plugin~Plugin
*/
class FileRepository extends _ckeditor_ckeditor5_core_src_plugin__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* @inheritDoc
*/
static get pluginName() {
return 'FileRepository';
}
/**
* @inheritDoc
*/
static get requires() {
return [ _ckeditor_ckeditor5_core_src_pendingactions__WEBPACK_IMPORTED_MODULE_1__["default"] ];
}
/**
* @inheritDoc
*/
init() {
/**
* Collection of loaders associated with this repository.
*
* @member {module:utils/collection~Collection} #loaders
*/
this.loaders = new _ckeditor_ckeditor5_utils_src_collection__WEBPACK_IMPORTED_MODULE_4__["default"]();
// Keeps upload in a sync with pending actions.
this.loaders.on( 'add', () => this._updatePendingAction() );
this.loaders.on( 'remove', () => this._updatePendingAction() );
/**
* Loaders mappings used to retrieve loaders references.
*
* @private
* @member {Map<File|Promise, FileLoader>} #_loadersMap
*/
this._loadersMap = new Map();
/**
* Reference to a pending action registered in a {@link module:core/pendingactions~PendingActions} plugin
* while upload is in progress. When there is no upload then value is `null`.
*
* @private
* @member {Object} #_pendingAction
*/
this._pendingAction = null;
/**
* A factory function which should be defined before using `FileRepository`.
*
* It should return a new instance of {@link module:upload/filerepository~UploadAdapter} that will be used to upload files.
* {@link module:upload/filerepository~FileLoader} instance associated with the adapter
* will be passed to that function.
*
* For more information and example see {@link module:upload/filerepository~UploadAdapter}.
*
* @member {Function} #createUploadAdapter
*/
/**
* Number of bytes uploaded.
*
* @readonly
* @observable
* @member {Number} #uploaded
*/
this.set( 'uploaded', 0 );
/**
* Number of total bytes to upload.
*
* It might be different than the file size because of headers and additional data.
* It contains `null` if value is not available yet, so it's better to use {@link #uploadedPercent} to monitor
* the progress.
*
* @readonly
* @observable
* @member {Number|null} #uploadTotal
*/
this.set( 'uploadTotal', null );
/**
* Upload progress in percents.
*
* @readonly
* @observable
* @member {Number} #uploadedPercent
*/
this.bind( 'uploadedPercent' ).to( this, 'uploaded', this, 'uploadTotal', ( uploaded, total ) => {
return total ? ( uploaded / total * 100 ) : 0;
} );
}
/**
* Returns the loader associated with specified file or promise.
*
* To get loader by id use `fileRepository.loaders.get( id )`.
*
* @param {File|Promise.<File>} fileOrPromise Native file or promise handle.
* @returns {module:upload/filerepository~FileLoader|null}
*/
getLoader( fileOrPromise ) {
return this._loadersMap.get( fileOrPromise ) || null;
}
/**
* Creates a loader instance for the given file.
*
* Requires {@link #createUploadAdapter} factory to be defined.
*
* @param {File|Promise.<File>} fileOrPromise Native File object or native Promise object which resolves to a File.
* @returns {module:upload/filerepository~FileLoader|null}
*/
createLoader( fileOrPromise ) {
if ( !this.createUploadAdapter ) {
/**
* You need to enable an upload adapter in order to be able to upload files.
*
* This warning shows up when {@link module:upload/filerepository~FileRepository} is being used
* without {@link #createUploadAdapter defining an upload adapter}.
*
* **If you see this warning when using one of the {@glink builds/index CKEditor 5 Builds}**
* it means that you did not configure any of the upload adapters available by default in those builds.
*
* See the {@glink features/images/image-upload/image-upload comprehensive "Image upload overview"} to learn which upload
* adapters are available in the builds and how to configure them.
*
* **If you see this warning when using a custom build** there is a chance that you enabled
* a feature like {@link module:image/imageupload~ImageUpload},
* or {@link module:image/imageupload/imageuploadui~ImageUploadUI} but you did not enable any upload adapter.
* You can choose one of the existing upload adapters listed in the
* {@glink features/images/image-upload/image-upload "Image upload overview"}.
*
* You can also implement your {@glink framework/guides/deep-dive/upload-adapter own image upload adapter}.
*
* @error filerepository-no-upload-adapter
*/
(0,_ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_2__.logWarning)( 'filerepository-no-upload-adapter' );
return null;
}
const loader = new FileLoader( Promise.resolve( fileOrPromise ), this.createUploadAdapter );
this.loaders.add( loader );
this._loadersMap.set( fileOrPromise, loader );
// Store also file => loader mapping so loader can be retrieved by file instance returned upon Promise resolution.
if ( fileOrPromise instanceof Promise ) {
loader.file
.then( file => {
this._loadersMap.set( file, loader );
} )
// Every then() must have a catch().
// File loader state (and rejections) are handled in read() and upload().
// Also, see the "does not swallow the file promise rejection" test.
.catch( () => {} );
}
loader.on( 'change:uploaded', () => {
let aggregatedUploaded = 0;
for ( const loader of this.loaders ) {
aggregatedUploaded += loader.uploaded;
}
this.uploaded = aggregatedUploaded;
} );
loader.on( 'change:uploadTotal', () => {
let aggregatedTotal = 0;
for ( const loader of this.loaders ) {
if ( loader.uploadTotal ) {
aggregatedTotal += loader.uploadTotal;
}
}
this.uploadTotal = aggregatedTotal;
} );
return loader;
}
/**
* Destroys the given loader.
*
* @param {File|Promise|module:upload/filerepository~FileLoader} fileOrPromiseOrLoader File or Promise associated
* with that loader or loader itself.
*/
destroyLoader( fileOrPromiseOrLoader ) {
const loader = fileOrPromiseOrLoader instanceof FileLoader ? fileOrPromiseOrLoader : this.getLoader( fileOrPromiseOrLoader );
loader._destroy();
this.loaders.remove( loader );
this._loadersMap.forEach( ( value, key ) => {
if ( value === loader ) {
this._loadersMap.delete( key );
}
} );
}
/**
* Registers or deregisters pending action bound with upload progress.
*
* @private
*/
_updatePendingAction() {
const pendingActions = this.editor.plugins.get( _ckeditor_ckeditor5_core_src_pendingactions__WEBPACK_IMPORTED_MODULE_1__["default"] );
if ( this.loaders.length ) {
if ( !this._pendingAction ) {
const t = this.editor.t;
const getMessage = value => `${ t( 'Upload in progress' ) } ${ parseInt( value ) }%.`;
this._pendingAction = pendingActions.add( getMessage( this.uploadedPercent ) );
this._pendingAction.bind( 'message' ).to( this, 'uploadedPercent', getMessage );
}
} else {
pendingActions.remove( this._pendingAction );
this._pendingAction = null;
}
}
}
(0,_ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_5__["default"])( FileRepository, _ckeditor_ckeditor5_utils_src_observablemixin__WEBPACK_IMPORTED_MODULE_3__["default"] );
/**
* File loader class.
*
* It is used to control the process of reading the file and uploading it using the specified upload adapter.
*/
class FileLoader {
/**
* Creates a new instance of `FileLoader`.
*
* @param {Promise.<File>} filePromise A promise which resolves to a file instance.
* @param {Function} uploadAdapterCreator The function which returns {@link module:upload/filerepository~UploadAdapter} instance.
*/
constructor( filePromise, uploadAdapterCreator ) {
/**
* Unique id of FileLoader instance.
*
* @readonly
* @member {Number}
*/
this.id = (0,_ckeditor_ckeditor5_utils_src_uid_js__WEBPACK_IMPORTED_MODULE_7__["default"])();
/**
* Additional wrapper over the initial file promise passed to this loader.
*
* @protected
* @member {module:upload/filerepository~FilePromiseWrapper}
*/
this._filePromiseWrapper = this._createFilePromiseWrapper( filePromise );
/**
* Adapter instance associated with this file loader.
*
* @private
* @member {module:upload/filerepository~UploadAdapter}
*/
this._adapter = uploadAdapterCreator( this );
/**
* FileReader used by FileLoader.
*
* @protected
* @member {module:upload/filereader~FileReader}
*/
this._reader = new _filereader_js__WEBPACK_IMPORTED_MODULE_6__["default"]();
/**
* Current status of FileLoader. It can be one of the following:
*
* * 'idle',
* * 'reading',
* * 'uploading',
* * 'aborted',
* * 'error'.
*
* When reading status can change in a following way:
*
* `idle` -> `reading` -> `idle`
* `idle` -> `reading -> `aborted`
* `idle` -> `reading -> `error`
*
* When uploading status can change in a following way:
*
* `idle` -> `uploading` -> `idle`
* `idle` -> `uploading` -> `aborted`
* `idle` -> `uploading` -> `error`
*
* @readonly
* @observable
* @member {String} #status
*/
this.set( 'status', 'idle' );
/**
* Number of bytes uploaded.
*
* @readonly
* @observable
* @member {Number} #uploaded
*/
this.set( 'uploaded', 0 );
/**
* Number of total bytes to upload.
*
* @readonly
* @observable
* @member {Number|null} #uploadTotal
*/
this.set( 'uploadTotal', null );
/**
* Upload progress in percents.
*
* @readonly
* @observable
* @member {Number} #uploadedPercent
*/
this.bind( 'uploadedPercent' ).to( this, 'uploaded', this, 'uploadTotal', ( uploaded, total ) => {
return total ? ( uploaded / total * 100 ) : 0;
} );
/**
* Response of the upload.
*
* @readonly
* @observable
* @member {Object|null} #uploadResponse
*/
this.set( 'uploadResponse', null );
}
/**
* A `Promise` which resolves to a `File` instance associated with this file loader.
*
* @type {Promise.<File|null>}
*/
get file() {
if ( !this._filePromiseWrapper ) {
// Loader was destroyed, return promise which resolves to null.
return Promise.resolve( null );
} else {
// The `this._filePromiseWrapper.promise` is chained and not simply returned to handle a case when:
//
// * The `loader.file.then( ... )` is called by external code (returned promise is pending).
// * Then `loader._destroy()` is called (call is synchronous) which destroys the `loader`.
// * Promise returned by the first `loader.file.then( ... )` call is resolved.
//
// Returning `this._filePromiseWrapper.promise` will still resolve to a `File` instance so there
// is an additional check needed in the chain to see if `loader` was destroyed in the meantime.
return this._filePromiseWrapper.promise.then( file => this._filePromiseWrapper ? file : null );
}
}
/**
* Returns the file data. To read its data, you need for first load the file
* by using the {@link module:upload/filerepository~FileLoader#read `read()`} method.
*
* @type {File|undefined}
*/
get data() {
return this._reader.data;
}
/**
* Reads file using {@link module:upload/filereader~FileReader}.
*
* Throws {@link module:utils/ckeditorerror~CKEditorError CKEditorError} `filerepository-read-wrong-status` when status
* is different than `idle`.
*
* Example usage:
*
* fileLoader.read()
* .then( data => { ... } )
* .catch( err => {
* if ( err === 'aborted' ) {
* console.log( 'Reading aborted.' );
* } else {
* console.log( 'Reading error.', err );
* }
* } );
*
* @returns {Promise.<String>} Returns promise that will be resolved with read data. Promise will be rejected if error
* occurs or if read process is aborted.
*/
read() {
if ( this.status != 'idle' ) {
/**
* You cannot call read if the status is different than idle.
*
* @error filerepository-read-wrong-status
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_2__["default"]( 'filerepository-read-wrong-status', this );
}
this.status = 'reading';
return this.file
.then( file => this._reader.read( file ) )
.then( data => {
// Edge case: reader was aborted after file was read - double check for proper status.
// It can happen when image was deleted during its upload.
if ( this.status !== 'reading' ) {
throw this.status;
}
this.status = 'idle';
return data;
} )
.catch( err => {
if ( err === 'aborted' ) {
this.status = 'aborted';
throw 'aborted';
}
this.status = 'error';
throw this._reader.error ? this._reader.error : err;
} );
}
/**
* Reads file using the provided {@link module:upload/filerepository~UploadAdapter}.
*
* Throws {@link module:utils/ckeditorerror~CKEditorError CKEditorError} `filerepository-upload-wrong-status` when status
* is different than `idle`.
* Example usage:
*
* fileLoader.upload()
* .then( data => { ... } )
* .catch( e => {
* if ( e === 'aborted' ) {
* console.log( 'Uploading aborted.' );
* } else {
* console.log( 'Uploading error.', e );
* }
* } );
*
* @returns {Promise.<Object>} Returns promise that will be resolved with response data. Promise will be rejected if error
* occurs or if read process is aborted.
*/
upload() {
if ( this.status != 'idle' ) {
/**
* You cannot call upload if the status is different than idle.
*
* @error filerepository-upload-wrong-status
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_2__["default"]( 'filerepository-upload-wrong-status', this );
}
this.status = 'uploading';
return this.file
.then( () => this._adapter.upload() )
.then( data => {
this.uploadResponse = data;
this.status = 'idle';
return data;
} )
.catch( err => {
if ( this.status === 'aborted' ) {
throw 'aborted';
}
this.status = 'error';
throw err;
} );
}
/**
* Aborts loading process.
*/
abort() {
const status = this.status;
this.status = 'aborted';
if ( !this._filePromiseWrapper.isFulfilled ) {
// Edge case: file loader is aborted before read() is called
// so it might happen that no one handled the rejection of this promise.
// See https://github.com/ckeditor/ckeditor5-upload/pull/100
this._filePromiseWrapper.promise.catch( () => {} );
this._filePromiseWrapper.rejecter( 'aborted' );
} else if ( status == 'reading' ) {
this._reader.abort();
} else if ( status == 'uploading' && this._adapter.abort ) {
this._adapter.abort();
}
this._destroy();
}
/**
* Performs cleanup.
*
* @private
*/
_destroy() {
this._filePromiseWrapper = undefined;
this._reader = undefined;
this._adapter = undefined;
this.uploadResponse = undefined;
}
/**
* Wraps a given file promise into another promise giving additional
* control (resolving, rejecting, checking if fulfilled) over it.
*
* @private
* @param filePromise The initial file promise to be wrapped.
* @returns {module:upload/filerepository~FilePromiseWrapper}
*/
_createFilePromiseWrapper( filePromise ) {
const wrapper = {};
wrapper.promise = new Promise( ( resolve, reject ) => {
wrapper.rejecter = reject;
wrapper.isFulfilled = false;
filePromise
.then( file => {
wrapper.isFulfilled = true;
resolve( file );
} )
.catch( err => {
wrapper.isFulfilled = true;
reject( err );
} );
} );
return wrapper;
}
}
(0,_ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_5__["default"])( FileLoader, _ckeditor_ckeditor5_utils_src_observablemixin__WEBPACK_IMPORTED_MODULE_3__["default"] );
/**
* Upload adapter interface used by the {@link module:upload/filerepository~FileRepository file repository}
* to handle file upload. An upload adapter is a bridge between the editor and server that handles file uploads.
* It should contain a logic necessary to initiate an upload process and monitor its progress.
*
* Learn how to develop your own upload adapter for CKEditor 5 in the
* {@glink framework/guides/deep-dive/upload-adapter "Custom upload adapter" guide}.
*
* @interface UploadAdapter
*/
/**
* Executes the upload process.
* This method should return a promise that will resolve when data will be uploaded to server. Promise should be
* resolved with an object containing information about uploaded file:
*
* {
* default: 'http://server/default-size.image.png'
* }
*
* Additionally, other image sizes can be provided:
*
* {
* default: 'http://server/default-size.image.png',
* '160': 'http://server/size-160.image.png',
* '500': 'http://server/size-500.image.png',
* '1000': 'http://server/size-1000.image.png',
* '1052': 'http://server/default-size.image.png'
* }
*
* You can also pass additional properties from the server. In this case you need to wrap URLs
* in the `urls` object and pass additional properties along the `urls` property.
*
* {
* myCustomProperty: 'foo',
* urls: {
* default: 'http://server/default-size.image.png',
* '160': 'http://server/size-160.image.png',
* '500': 'http://server/size-500.image.png',
* '1000': 'http://server/size-1000.image.png',
* '1052': 'http://server/default-size.image.png'
* }
* }
*
* NOTE: When returning multiple images, the widest returned one should equal the default one. It is essential to
* correctly set `width` attribute of the image. See this discussion:
* https://github.com/ckeditor/ckeditor5-easy-image/issues/4 for more information.
*
* Take a look at {@link module:upload/filerepository~UploadAdapter example Adapter implementation} and
* {@link module:upload/filerepository~FileRepository#createUploadAdapter createUploadAdapter method}.
*
* @method module:upload/filerepository~UploadAdapter#upload
* @returns {Promise.<Object>} Promise that should be resolved when data is uploaded.
*/
/**
* Aborts the upload process.
* After aborting it should reject promise returned from {@link #upload upload()}.
*
* Take a look at {@link module:upload/filerepository~UploadAdapter example Adapter implementation} and
* {@link module:upload/filerepository~FileRepository#createUploadAdapter createUploadAdapter method}.
*
* @method module:upload/filerepository~UploadAdapter#abort
*/
/**
* Object returned by {@link module:upload/filerepository~FileLoader#_createFilePromiseWrapper} method
* to add more control over the initial file promise passed to {@link module:upload/filerepository~FileLoader}.
*
* @protected
* @typedef {Object} module:upload/filerepository~FilePromiseWrapper
* @property {Promise.<File>} promise Wrapper promise which can be chained for further processing.
* @property {Function} rejecter Rejects the promise when called.
* @property {Boolean} isFulfilled Whether original promise is already fulfilled.
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-upload/src/index.js":
/*!**************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-upload/src/index.js ***!
\**************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "Base64UploadAdapter": () => (/* reexport safe */ _adapters_base64uploadadapter__WEBPACK_IMPORTED_MODULE_2__["default"]),
/* harmony export */ "FileDialogButtonView": () => (/* reexport safe */ _ui_filedialogbuttonview__WEBPACK_IMPORTED_MODULE_1__["default"]),
/* harmony export */ "FileRepository": () => (/* reexport safe */ _filerepository__WEBPACK_IMPORTED_MODULE_0__["default"]),
/* harmony export */ "SimpleUploadAdapter": () => (/* reexport safe */ _adapters_simpleuploadadapter__WEBPACK_IMPORTED_MODULE_3__["default"])
/* harmony export */ });
/* harmony import */ var _filerepository__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./filerepository */ "./node_modules/@ckeditor/ckeditor5-upload/src/filerepository.js");
/* harmony import */ var _ui_filedialogbuttonview__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./ui/filedialogbuttonview */ "./node_modules/@ckeditor/ckeditor5-upload/src/ui/filedialogbuttonview.js");
/* harmony import */ var _adapters_base64uploadadapter__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./adapters/base64uploadadapter */ "./node_modules/@ckeditor/ckeditor5-upload/src/adapters/base64uploadadapter.js");
/* harmony import */ var _adapters_simpleuploadadapter__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./adapters/simpleuploadadapter */ "./node_modules/@ckeditor/ckeditor5-upload/src/adapters/simpleuploadadapter.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module upload
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-upload/src/ui/filedialogbuttonview.js":
/*!********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-upload/src/ui/filedialogbuttonview.js ***!
\********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ FileDialogButtonView)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_ui_src_button_buttonview__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-ui/src/button/buttonview */ "./node_modules/@ckeditor/ckeditor5-ui/src/button/buttonview.js");
/* harmony import */ var _ckeditor_ckeditor5_ui_src_view__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-ui/src/view */ "./node_modules/@ckeditor/ckeditor5-ui/src/view.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module upload/ui/filedialogbuttonview
*/
/**
* The file dialog button view.
*
* This component provides a button that opens the native file selection dialog.
* It can be used to implement the UI of a file upload feature.
*
* const view = new FileDialogButtonView( locale );
*
* view.set( {
* acceptedType: 'image/*',
* allowMultipleFiles: true
* } );
*
* view.buttonView.set( {
* label: t( 'Insert image' ),
* icon: imageIcon,
* tooltip: true
* } );
*
* view.on( 'done', ( evt, files ) => {
* for ( const file of Array.from( files ) ) {
* console.log( 'Selected file', file );
* }
* } );
*
* @extends module:ui/view~View
*/
class FileDialogButtonView extends _ckeditor_ckeditor5_ui_src_view__WEBPACK_IMPORTED_MODULE_1__["default"] {
/**
* @inheritDoc
*/
constructor( locale ) {
super( locale );
/**
* The button view of the component.
*
* @member {module:ui/button/buttonview~ButtonView}
*/
this.buttonView = new _ckeditor_ckeditor5_ui_src_button_buttonview__WEBPACK_IMPORTED_MODULE_0__["default"]( locale );
/**
* A hidden `<input>` view used to execute file dialog.
*
* @protected
* @member {module:upload/ui/filedialogbuttonview~FileInputView}
*/
this._fileInputView = new FileInputView( locale );
/**
* Accepted file types. Can be provided in form of file extensions, media type or one of:
* * `audio/*`,
* * `video/*`,
* * `image/*`.
*
* @observable
* @member {String} #acceptedType
*/
this._fileInputView.bind( 'acceptedType' ).to( this );
/**
* Indicates if multiple files can be selected. Defaults to `true`.
*
* @observable
* @member {Boolean} #allowMultipleFiles
*/
this._fileInputView.bind( 'allowMultipleFiles' ).to( this );
/**
* Fired when file dialog is closed with file selected.
*
* view.on( 'done', ( evt, files ) => {
* for ( const file of files ) {
* console.log( 'Selected file', file );
* }
* }
*
* @event done
* @param {Array.<File>} files Array of selected files.
*/
this._fileInputView.delegate( 'done' ).to( this );
this.setTemplate( {
tag: 'span',
attributes: {
class: 'ck-file-dialog-button'
},
children: [
this.buttonView,
this._fileInputView
]
} );
this.buttonView.on( 'execute', () => {
this._fileInputView.open();
} );
}
/**
* Focuses the {@link #buttonView}.
*/
focus() {
this.buttonView.focus();
}
}
/**
* The hidden file input view class.
*
* @private
* @extends module:ui/view~View
*/
class FileInputView extends _ckeditor_ckeditor5_ui_src_view__WEBPACK_IMPORTED_MODULE_1__["default"] {
/**
* @inheritDoc
*/
constructor( locale ) {
super( locale );
/**
* Accepted file types. Can be provided in form of file extensions, media type or one of:
* * `audio/*`,
* * `video/*`,
* * `image/*`.
*
* @observable
* @member {String} #acceptedType
*/
this.set( 'acceptedType' );
/**
* Indicates if multiple files can be selected. Defaults to `false`.
*
* @observable
* @member {Boolean} #allowMultipleFiles
*/
this.set( 'allowMultipleFiles', false );
const bind = this.bindTemplate;
this.setTemplate( {
tag: 'input',
attributes: {
class: [
'ck-hidden'
],
type: 'file',
tabindex: '-1',
accept: bind.to( 'acceptedType' ),
multiple: bind.to( 'allowMultipleFiles' )
},
on: {
// Removing from code coverage since we cannot programmatically set input element files.
change: bind.to( /* istanbul ignore next */ () => {
if ( this.element && this.element.files && this.element.files.length ) {
this.fire( 'done', this.element.files );
}
this.element.value = '';
} )
}
} );
}
/**
* Opens file dialog.
*/
open() {
this.element.click();
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js":
/*!*********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js ***!
\*********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "DOCUMENTATION_URL": () => (/* binding */ DOCUMENTATION_URL),
/* harmony export */ "default": () => (/* binding */ CKEditorError),
/* harmony export */ "logError": () => (/* binding */ logError),
/* harmony export */ "logWarning": () => (/* binding */ logWarning)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module utils/ckeditorerror
*/
/* globals console */
/**
* URL to the documentation with error codes.
*/
const DOCUMENTATION_URL =
'https://ckeditor.com/docs/ckeditor5/latest/framework/guides/support/error-codes.html';
/**
* The CKEditor error class.
*
* You should throw `CKEditorError` when:
*
* * An unexpected situation occurred and the editor (most probably) will not work properly. Such exception will be handled
* by the {@link module:watchdog/watchdog~Watchdog watchdog} (if it is integrated),
* * If the editor is incorrectly integrated or the editor API is used in the wrong way. This way you will give
* feedback to the developer as soon as possible. Keep in mind that for common integration issues which should not
* stop editor initialization (like missing upload adapter, wrong name of a toolbar component) we use
* {@link module:utils/ckeditorerror~logWarning `logWarning()`} and
* {@link module:utils/ckeditorerror~logError `logError()`}
* to improve developers experience and let them see the a working editor as soon as possible.
*
* /**
* * Error thrown when a plugin cannot be loaded due to JavaScript errors, lack of plugins with a given name, etc.
* *
* * @error plugin-load
* * @param pluginName The name of the plugin that could not be loaded.
* * @param moduleName The name of the module which tried to load this plugin.
* * /
* throw new CKEditorError( 'plugin-load', {
* pluginName: 'foo',
* moduleName: 'bar'
* } );
*
* @extends Error
*/
class CKEditorError extends Error {
/**
* Creates an instance of the CKEditorError class.
*
* @param {String} errorName The error id in an `error-name` format. A link to this error documentation page will be added
* to the thrown error's `message`.
* @param {Object|null} context A context of the error by which the {@link module:watchdog/watchdog~Watchdog watchdog}
* is able to determine which editor crashed. It should be an editor instance or a property connected to it. It can be also
* a `null` value if the editor should not be restarted in case of the error (e.g. during the editor initialization).
* The error context should be checked using the `areConnectedThroughProperties( editor, context )` utility
* to check if the object works as the context.
* @param {Object} [data] Additional data describing the error. A stringified version of this object
* will be appended to the error message, so the data are quickly visible in the console. The original
* data object will also be later available under the {@link #data} property.
*/
constructor( errorName, context, data ) {
super( getErrorMessage( errorName, data ) );
/**
* @type {String}
*/
this.name = 'CKEditorError';
/**
* A context of the error by which the Watchdog is able to determine which editor crashed.
*
* @type {Object|null}
*/
this.context = context;
/**
* The additional error data passed to the constructor. Undefined if none was passed.
*
* @type {Object|undefined}
*/
this.data = data;
}
/**
* Checks if the error is of the `CKEditorError` type.
* @returns {Boolean}
*/
is( type ) {
return type === 'CKEditorError';
}
/**
* A utility that ensures that the thrown error is a {@link module:utils/ckeditorerror~CKEditorError} one.
* It is useful when combined with the {@link module:watchdog/watchdog~Watchdog} feature, which can restart the editor in case
* of a {@link module:utils/ckeditorerror~CKEditorError} error.
*
* @static
* @param {Error} err The error to rethrow.
* @param {Object} context An object connected through properties with the editor instance. This context will be used
* by the watchdog to verify which editor should be restarted.
*/
static rethrowUnexpectedError( err, context ) {
if ( err.is && err.is( 'CKEditorError' ) ) {
throw err;
}
/**
* An unexpected error occurred inside the CKEditor 5 codebase. This error will look like the original one
* to make the debugging easier.
*
* This error is only useful when the editor is initialized using the {@link module:watchdog/watchdog~Watchdog} feature.
* In case of such error (or any {@link module:utils/ckeditorerror~CKEditorError} error) the watchdog should restart the editor.
*
* @error unexpected-error
*/
const error = new CKEditorError( err.message, context );
// Restore the original stack trace to make the error look like the original one.
// See https://github.com/ckeditor/ckeditor5/issues/5595 for more details.
error.stack = err.stack;
throw error;
}
}
/**
* Logs a warning to the console with a properly formatted message and adds a link to the documentation.
* Use whenever you want to log a warning to the console.
*
* /**
* * There was a problem processing the configuration of the toolbar. The item with the given
* * name does not exist, so it was omitted when rendering the toolbar.
* *
* * @error toolbarview-item-unavailable
* * @param {String} name The name of the component.
* * /
* logWarning( 'toolbarview-item-unavailable', { name } );
*
* See also {@link module:utils/ckeditorerror~CKEditorError} for an explanation when to throw an error and when to log
* a warning or an error to the console.
*
* @param {String} errorName The error name to be logged.
* @param {Object} [data] Additional data to be logged.
*/
function logWarning( errorName, data ) {
console.warn( ...formatConsoleArguments( errorName, data ) );
}
/**
* Logs an error to the console with a properly formatted message and adds a link to the documentation.
* Use whenever you want to log an error to the console.
*
* /**
* * There was a problem processing the configuration of the toolbar. The item with the given
* * name does not exist, so it was omitted when rendering the toolbar.
* *
* * @error toolbarview-item-unavailable
* * @param {String} name The name of the component.
* * /
* logError( 'toolbarview-item-unavailable', { name } );
*
* **Note**: In most cases logging a warning using {@link module:utils/ckeditorerror~logWarning} is enough.
*
* See also {@link module:utils/ckeditorerror~CKEditorError} for an explanation when to use each method.
*
* @param {String} errorName The error name to be logged.
* @param {Object} [data] Additional data to be logged.
*/
function logError( errorName, data ) {
console.error( ...formatConsoleArguments( errorName, data ) );
}
// Returns formatted link to documentation message.
//
// @private
// @param {String} errorName
// @returns {string}
function getLinkToDocumentationMessage( errorName ) {
return `\nRead more: ${ DOCUMENTATION_URL }#error-${ errorName }`;
}
// Returns formatted error message.
//
// @private
// @param {String} errorName
// @param {Object} [data]
// @returns {string}
function getErrorMessage( errorName, data ) {
const processedObjects = new WeakSet();
const circularReferencesReplacer = ( key, value ) => {
if ( typeof value === 'object' && value !== null ) {
if ( processedObjects.has( value ) ) {
return `[object ${ value.constructor.name }]`;
}
processedObjects.add( value );
}
return value;
};
const stringifiedData = data ? ` ${ JSON.stringify( data, circularReferencesReplacer ) }` : '';
const documentationLink = getLinkToDocumentationMessage( errorName );
return errorName + stringifiedData + documentationLink;
}
// Returns formatted console error arguments.
//
// @private
// @param {String} errorName
// @param {Object} [data]
// @returns {Array}
function formatConsoleArguments( errorName, data ) {
const documentationMessage = getLinkToDocumentationMessage( errorName );
return data ? [ errorName, data, documentationMessage ] : [ errorName, documentationMessage ];
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-utils/src/collection.js":
/*!******************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-utils/src/collection.js ***!
\******************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ Collection)
/* harmony export */ });
/* harmony import */ var _emittermixin__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./emittermixin */ "./node_modules/@ckeditor/ckeditor5-utils/src/emittermixin.js");
/* harmony import */ var _ckeditorerror__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/* harmony import */ var _uid__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./uid */ "./node_modules/@ckeditor/ckeditor5-utils/src/uid.js");
/* harmony import */ var _isiterable__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./isiterable */ "./node_modules/@ckeditor/ckeditor5-utils/src/isiterable.js");
/* harmony import */ var _mix__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./mix */ "./node_modules/@ckeditor/ckeditor5-utils/src/mix.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module utils/collection
*/
/**
* Collections are ordered sets of objects. Items in the collection can be retrieved by their indexes
* in the collection (like in an array) or by their ids.
*
* If an object without an `id` property is being added to the collection, the `id` property will be generated
* automatically. Note that the automatically generated id is unique only within this single collection instance.
*
* By default an item in the collection is identified by its `id` property. The name of the identifier can be
* configured through the constructor of the collection.
*
* @mixes module:utils/emittermixin~EmitterMixin
*/
class Collection {
/**
* Creates a new Collection instance.
*
* You can provide an iterable of initial items the collection will be created with:
*
* const collection = new Collection( [ { id: 'John' }, { id: 'Mike' } ] );
*
* console.log( collection.get( 0 ) ); // -> { id: 'John' }
* console.log( collection.get( 1 ) ); // -> { id: 'Mike' }
* console.log( collection.get( 'Mike' ) ); // -> { id: 'Mike' }
*
* Or you can first create a collection and then add new items using the {@link #add} method:
*
* const collection = new Collection();
*
* collection.add( { id: 'John' } );
* console.log( collection.get( 0 ) ); // -> { id: 'John' }
*
* Whatever option you choose, you can always pass a configuration object as the last argument
* of the constructor:
*
* const emptyCollection = new Collection( { idProperty: 'name' } );
* emptyCollection.add( { name: 'John' } );
* console.log( collection.get( 'John' ) ); // -> { name: 'John' }
*
* const nonEmptyCollection = new Collection( [ { name: 'John' } ], { idProperty: 'name' } );
* nonEmptyCollection.add( { name: 'George' } );
* console.log( collection.get( 'George' ) ); // -> { name: 'George' }
* console.log( collection.get( 'John' ) ); // -> { name: 'John' }
*
* @param {Iterable.<Object>|Object} [initialItemsOrOptions] The initial items of the collection or
* the options object.
* @param {Object} [options={}] The options object, when the first argument is an array of initial items.
* @param {String} [options.idProperty='id'] The name of the property which is used to identify an item.
* Items that do not have such a property will be assigned one when added to the collection.
*/
constructor( initialItemsOrOptions = {}, options = {} ) {
const hasInitialItems = (0,_isiterable__WEBPACK_IMPORTED_MODULE_3__["default"])( initialItemsOrOptions );
if ( !hasInitialItems ) {
options = initialItemsOrOptions;
}
/**
* The internal list of items in the collection.
*
* @private
* @member {Object[]}
*/
this._items = [];
/**
* The internal map of items in the collection.
*
* @private
* @member {Map}
*/
this._itemMap = new Map();
/**
* The name of the property which is considered to identify an item.
*
* @private
* @member {String}
*/
this._idProperty = options.idProperty || 'id';
/**
* A helper mapping external items of a bound collection ({@link #bindTo})
* and actual items of this collection. It provides information
* necessary to properly remove items bound to another collection.
*
* See {@link #_bindToInternalToExternalMap}.
*
* @protected
* @member {WeakMap}
*/
this._bindToExternalToInternalMap = new WeakMap();
/**
* A helper mapping items of this collection to external items of a bound collection
* ({@link #bindTo}). It provides information necessary to manage the bindings, e.g.
* to avoid loops in two–way bindings.
*
* See {@link #_bindToExternalToInternalMap}.
*
* @protected
* @member {WeakMap}
*/
this._bindToInternalToExternalMap = new WeakMap();
/**
* Stores indexes of skipped items from bound external collection.
*
* @private
* @member {Array}
*/
this._skippedIndexesFromExternal = [];
// Set the initial content of the collection (if provided in the constructor).
if ( hasInitialItems ) {
for ( const item of initialItemsOrOptions ) {
this._items.push( item );
this._itemMap.set( this._getItemIdBeforeAdding( item ), item );
}
}
/**
* A collection instance this collection is bound to as a result
* of calling {@link #bindTo} method.
*
* @protected
* @member {module:utils/collection~Collection} #_bindToCollection
*/
}
/**
* The number of items available in the collection.
*
* @member {Number} #length
*/
get length() {
return this._items.length;
}
/**
* Returns the first item from the collection or null when collection is empty.
*
* @returns {Object|null} The first item or `null` if collection is empty.
*/
get first() {
return this._items[ 0 ] || null;
}
/**
* Returns the last item from the collection or null when collection is empty.
*
* @returns {Object|null} The last item or `null` if collection is empty.
*/
get last() {
return this._items[ this.length - 1 ] || null;
}
/**
* Adds an item into the collection.
*
* If the item does not have an id, then it will be automatically generated and set on the item.
*
* @chainable
* @param {Object} item
* @param {Number} [index] The position of the item in the collection. The item
* is pushed to the collection when `index` not specified.
* @fires add
* @fires change
*/
add( item, index ) {
return this.addMany( [ item ], index );
}
/**
* Adds multiple items into the collection.
*
* Any item not containing an id will get an automatically generated one.
*
* @chainable
* @param {Iterable.<Object>} item
* @param {Number} [index] The position of the insertion. Items will be appended if no `index` is specified.
* @fires add
* @fires change
*/
addMany( items, index ) {
if ( index === undefined ) {
index = this._items.length;
} else if ( index > this._items.length || index < 0 ) {
/**
* The `index` passed to {@link module:utils/collection~Collection#addMany `Collection#addMany()`}
* is invalid. It must be a number between 0 and the collection's length.
*
* @error collection-add-item-invalid-index
*/
throw new _ckeditorerror__WEBPACK_IMPORTED_MODULE_1__["default"]( 'collection-add-item-invalid-index', this );
}
for ( let offset = 0; offset < items.length; offset++ ) {
const item = items[ offset ];
const itemId = this._getItemIdBeforeAdding( item );
const currentItemIndex = index + offset;
this._items.splice( currentItemIndex, 0, item );
this._itemMap.set( itemId, item );
this.fire( 'add', item, currentItemIndex );
}
this.fire( 'change', {
added: items,
removed: [],
index
} );
return this;
}
/**
* Gets an item by its ID or index.
*
* @param {String|Number} idOrIndex The item ID or index in the collection.
* @returns {Object|null} The requested item or `null` if such item does not exist.
*/
get( idOrIndex ) {
let item;
if ( typeof idOrIndex == 'string' ) {
item = this._itemMap.get( idOrIndex );
} else if ( typeof idOrIndex == 'number' ) {
item = this._items[ idOrIndex ];
} else {
/**
* An index or ID must be given.
*
* @error collection-get-invalid-arg
*/
throw new _ckeditorerror__WEBPACK_IMPORTED_MODULE_1__["default"]( 'collection-get-invalid-arg', this );
}
return item || null;
}
/**
* Returns a Boolean indicating whether the collection contains an item.
*
* @param {Object|String} itemOrId The item or its ID in the collection.
* @returns {Boolean} `true` if the collection contains the item, `false` otherwise.
*/
has( itemOrId ) {
if ( typeof itemOrId == 'string' ) {
return this._itemMap.has( itemOrId );
} else { // Object
const idProperty = this._idProperty;
const id = itemOrId[ idProperty ];
return this._itemMap.has( id );
}
}
/**
* Gets an index of an item in the collection.
* When an item is not defined in the collection, the index will equal -1.
*
* @param {Object|String} itemOrId The item or its ID in the collection.
* @returns {Number} The index of a given item.
*/
getIndex( itemOrId ) {
let item;
if ( typeof itemOrId == 'string' ) {
item = this._itemMap.get( itemOrId );
} else {
item = itemOrId;
}
return this._items.indexOf( item );
}
/**
* Removes an item from the collection.
*
* @param {Object|Number|String} subject The item to remove, its ID or index in the collection.
* @returns {Object} The removed item.
* @fires remove
* @fires change
*/
remove( subject ) {
const [ item, index ] = this._remove( subject );
this.fire( 'change', {
added: [],
removed: [ item ],
index
} );
return item;
}
/**
* Executes the callback for each item in the collection and composes an array or values returned by this callback.
*
* @param {Function} callback
* @param {Object} callback.item
* @param {Number} callback.index
* @param {Object} ctx Context in which the `callback` will be called.
* @returns {Array} The result of mapping.
*/
map( callback, ctx ) {
return this._items.map( callback, ctx );
}
/**
* Finds the first item in the collection for which the `callback` returns a true value.
*
* @param {Function} callback
* @param {Object} callback.item
* @param {Number} callback.index
* @param {Object} ctx Context in which the `callback` will be called.
* @returns {Object} The item for which `callback` returned a true value.
*/
find( callback, ctx ) {
return this._items.find( callback, ctx );
}
/**
* Returns an array with items for which the `callback` returned a true value.
*
* @param {Function} callback
* @param {Object} callback.item
* @param {Number} callback.index
* @param {Object} ctx Context in which the `callback` will be called.
* @returns {Object[]} The array with matching items.
*/
filter( callback, ctx ) {
return this._items.filter( callback, ctx );
}
/**
* Removes all items from the collection and destroys the binding created using
* {@link #bindTo}.
*
* @fires remove
* @fires change
*/
clear() {
if ( this._bindToCollection ) {
this.stopListening( this._bindToCollection );
this._bindToCollection = null;
}
const removedItems = Array.from( this._items );
while ( this.length ) {
this._remove( 0 );
}
this.fire( 'change', {
added: [],
removed: removedItems,
index: 0
} );
}
/**
* Binds and synchronizes the collection with another one.
*
* The binding can be a simple factory:
*
* class FactoryClass {
* constructor( data ) {
* this.label = data.label;
* }
* }
*
* const source = new Collection( { idProperty: 'label' } );
* const target = new Collection();
*
* target.bindTo( source ).as( FactoryClass );
*
* source.add( { label: 'foo' } );
* source.add( { label: 'bar' } );
*
* console.log( target.length ); // 2
* console.log( target.get( 1 ).label ); // 'bar'
*
* source.remove( 0 );
* console.log( target.length ); // 1
* console.log( target.get( 0 ).label ); // 'bar'
*
* or the factory driven by a custom callback:
*
* class FooClass {
* constructor( data ) {
* this.label = data.label;
* }
* }
*
* class BarClass {
* constructor( data ) {
* this.label = data.label;
* }
* }
*
* const source = new Collection( { idProperty: 'label' } );
* const target = new Collection();
*
* target.bindTo( source ).using( ( item ) => {
* if ( item.label == 'foo' ) {
* return new FooClass( item );
* } else {
* return new BarClass( item );
* }
* } );
*
* source.add( { label: 'foo' } );
* source.add( { label: 'bar' } );
*
* console.log( target.length ); // 2
* console.log( target.get( 0 ) instanceof FooClass ); // true
* console.log( target.get( 1 ) instanceof BarClass ); // true
*
* or the factory out of property name:
*
* const source = new Collection( { idProperty: 'label' } );
* const target = new Collection();
*
* target.bindTo( source ).using( 'label' );
*
* source.add( { label: { value: 'foo' } } );
* source.add( { label: { value: 'bar' } } );
*
* console.log( target.length ); // 2
* console.log( target.get( 0 ).value ); // 'foo'
* console.log( target.get( 1 ).value ); // 'bar'
*
* It's possible to skip specified items by returning falsy value:
*
* const source = new Collection();
* const target = new Collection();
*
* target.bindTo( source ).using( item => {
* if ( item.hidden ) {
* return null;
* }
*
* return item;
* } );
*
* source.add( { hidden: true } );
* source.add( { hidden: false } );
*
* console.log( source.length ); // 2
* console.log( target.length ); // 1
*
* **Note**: {@link #clear} can be used to break the binding.
*
* @param {module:utils/collection~Collection} externalCollection A collection to be bound.
* @returns {Object}
* @returns {module:utils/collection~CollectionBindToChain} The binding chain object.
*/
bindTo( externalCollection ) {
if ( this._bindToCollection ) {
/**
* The collection cannot be bound more than once.
*
* @error collection-bind-to-rebind
*/
throw new _ckeditorerror__WEBPACK_IMPORTED_MODULE_1__["default"]( 'collection-bind-to-rebind', this );
}
this._bindToCollection = externalCollection;
return {
as: Class => {
this._setUpBindToBinding( item => new Class( item ) );
},
using: callbackOrProperty => {
if ( typeof callbackOrProperty == 'function' ) {
this._setUpBindToBinding( item => callbackOrProperty( item ) );
} else {
this._setUpBindToBinding( item => item[ callbackOrProperty ] );
}
}
};
}
/**
* Finalizes and activates a binding initiated by {#bindTo}.
*
* @protected
* @param {Function} factory A function which produces collection items.
*/
_setUpBindToBinding( factory ) {
const externalCollection = this._bindToCollection;
// Adds the item to the collection once a change has been done to the external collection.
//
// @private
const addItem = ( evt, externalItem, index ) => {
const isExternalBoundToThis = externalCollection._bindToCollection == this;
const externalItemBound = externalCollection._bindToInternalToExternalMap.get( externalItem );
// If an external collection is bound to this collection, which makes it a 2–way binding,
// and the particular external collection item is already bound, don't add it here.
// The external item has been created **out of this collection's item** and (re)adding it will
// cause a loop.
if ( isExternalBoundToThis && externalItemBound ) {
this._bindToExternalToInternalMap.set( externalItem, externalItemBound );
this._bindToInternalToExternalMap.set( externalItemBound, externalItem );
} else {
const item = factory( externalItem );
// When there is no item we need to remember skipped index first and then we can skip this item.
if ( !item ) {
this._skippedIndexesFromExternal.push( index );
return;
}
// Lets try to put item at the same index as index in external collection
// but when there are a skipped items in one or both collections we need to recalculate this index.
let finalIndex = index;
// When we try to insert item after some skipped items from external collection we need
// to include this skipped items and decrease index.
//
// For the following example:
// external -> [ 'A', 'B - skipped for internal', 'C - skipped for internal' ]
// internal -> [ A ]
//
// Another item is been added at the end of external collection:
// external.add( 'D' )
// external -> [ 'A', 'B - skipped for internal', 'C - skipped for internal', 'D' ]
//
// We can't just add 'D' to internal at the same index as index in external because
// this will produce empty indexes what is invalid:
// internal -> [ 'A', empty, empty, 'D' ]
//
// So we need to include skipped items and decrease index
// internal -> [ 'A', 'D' ]
for ( const skipped of this._skippedIndexesFromExternal ) {
if ( index > skipped ) {
finalIndex--;
}
}
// We need to take into consideration that external collection could skip some items from
// internal collection.
//
// For the following example:
// internal -> [ 'A', 'B - skipped for external', 'C - skipped for external' ]
// external -> [ A ]
//
// Another item is been added at the end of external collection:
// external.add( 'D' )
// external -> [ 'A', 'D' ]
//
// We need to include skipped items and place new item after them:
// internal -> [ 'A', 'B - skipped for external', 'C - skipped for external', 'D' ]
for ( const skipped of externalCollection._skippedIndexesFromExternal ) {
if ( finalIndex >= skipped ) {
finalIndex++;
}
}
this._bindToExternalToInternalMap.set( externalItem, item );
this._bindToInternalToExternalMap.set( item, externalItem );
this.add( item, finalIndex );
// After adding new element to internal collection we need update indexes
// of skipped items in external collection.
for ( let i = 0; i < externalCollection._skippedIndexesFromExternal.length; i++ ) {
if ( finalIndex <= externalCollection._skippedIndexesFromExternal[ i ] ) {
externalCollection._skippedIndexesFromExternal[ i ]++;
}
}
}
};
// Load the initial content of the collection.
for ( const externalItem of externalCollection ) {
addItem( null, externalItem, externalCollection.getIndex( externalItem ) );
}
// Synchronize the with collection as new items are added.
this.listenTo( externalCollection, 'add', addItem );
// Synchronize the with collection as new items are removed.
this.listenTo( externalCollection, 'remove', ( evt, externalItem, index ) => {
const item = this._bindToExternalToInternalMap.get( externalItem );
if ( item ) {
this.remove( item );
}
// After removing element from external collection we need update/remove indexes
// of skipped items in internal collection.
this._skippedIndexesFromExternal = this._skippedIndexesFromExternal.reduce( ( result, skipped ) => {
if ( index < skipped ) {
result.push( skipped - 1 );
}
if ( index > skipped ) {
result.push( skipped );
}
return result;
}, [] );
} );
}
/**
* Returns an unique id property for a given `item`.
*
* The method will generate new id and assign it to the `item` if it doesn't have any.
*
* @private
* @param {Object} item Item to be added.
* @returns {String}
*/
_getItemIdBeforeAdding( item ) {
const idProperty = this._idProperty;
let itemId;
if ( ( idProperty in item ) ) {
itemId = item[ idProperty ];
if ( typeof itemId != 'string' ) {
/**
* This item's ID should be a string.
*
* @error collection-add-invalid-id
*/
throw new _ckeditorerror__WEBPACK_IMPORTED_MODULE_1__["default"]( 'collection-add-invalid-id', this );
}
if ( this.get( itemId ) ) {
/**
* This item already exists in the collection.
*
* @error collection-add-item-already-exists
*/
throw new _ckeditorerror__WEBPACK_IMPORTED_MODULE_1__["default"]( 'collection-add-item-already-exists', this );
}
} else {
item[ idProperty ] = itemId = (0,_uid__WEBPACK_IMPORTED_MODULE_2__["default"])();
}
return itemId;
}
/**
* Core {@link #remove} method implementation shared in other functions.
*
* In contrast this method **does not** fire the {@link #event:change} event.
*
* @private
* @param {Object} subject The item to remove, its id or index in the collection.
* @returns {Array} Returns an array with the removed item and its index.
* @fires remove
*/
_remove( subject ) {
let index, id, item;
let itemDoesNotExist = false;
const idProperty = this._idProperty;
if ( typeof subject == 'string' ) {
id = subject;
item = this._itemMap.get( id );
itemDoesNotExist = !item;
if ( item ) {
index = this._items.indexOf( item );
}
} else if ( typeof subject == 'number' ) {
index = subject;
item = this._items[ index ];
itemDoesNotExist = !item;
if ( item ) {
id = item[ idProperty ];
}
} else {
item = subject;
id = item[ idProperty ];
index = this._items.indexOf( item );
itemDoesNotExist = ( index == -1 || !this._itemMap.get( id ) );
}
if ( itemDoesNotExist ) {
/**
* Item not found.
*
* @error collection-remove-404
*/
throw new _ckeditorerror__WEBPACK_IMPORTED_MODULE_1__["default"]( 'collection-remove-404', this );
}
this._items.splice( index, 1 );
this._itemMap.delete( id );
const externalItem = this._bindToInternalToExternalMap.get( item );
this._bindToInternalToExternalMap.delete( item );
this._bindToExternalToInternalMap.delete( externalItem );
this.fire( 'remove', item, index );
return [ item, index ];
}
/**
* Iterable interface.
*
* @returns {Iterable.<*>}
*/
[ Symbol.iterator ]() {
return this._items[ Symbol.iterator ]();
}
/**
* Fired when an item is added to the collection.
*
* @event add
* @param {Object} item The added item.
*/
/**
* Fired when the collection was changed due to adding or removing items.
*
* @event change
* @param {Iterable.<Object>} added A list of added items.
* @param {Iterable.<Object>} removed A list of removed items.
* @param {Number} index An index where the addition or removal occurred.
*/
/**
* Fired when an item is removed from the collection.
*
* @event remove
* @param {Object} item The removed item.
* @param {Number} index Index from which item was removed.
*/
}
(0,_mix__WEBPACK_IMPORTED_MODULE_4__["default"])( Collection, _emittermixin__WEBPACK_IMPORTED_MODULE_0__["default"] );
/**
* An object returned by the {@link module:utils/collection~Collection#bindTo `bindTo()`} method
* providing functions that specify the type of the binding.
*
* See the {@link module:utils/collection~Collection#bindTo `bindTo()`} documentation for examples.
*
* @interface module:utils/collection~CollectionBindToChain
*/
/**
* Creates a callback or a property binding.
*
* @method #using
* @param {Function|String} callbackOrProperty When the function is passed, it should return
* the collection items. When the string is provided, the property value is used to create the bound collection items.
*/
/**
* Creates the class factory binding in which items of the source collection are passed to
* the constructor of the specified class.
*
* @method #as
* @param {Function} Class The class constructor used to create instances in the factory.
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-utils/src/comparearrays.js":
/*!*********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-utils/src/comparearrays.js ***!
\*********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ compareArrays)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module utils/comparearrays
*/
/**
* Compares how given arrays relate to each other. One array can be: same as another array, prefix of another array
* or completely different. If arrays are different, first index at which they differ is returned. Otherwise,
* a flag specifying the relation is returned. Flags are negative numbers, so whenever a number >= 0 is returned
* it means that arrays differ.
*
* compareArrays( [ 0, 2 ], [ 0, 2 ] ); // 'same'
* compareArrays( [ 0, 2 ], [ 0, 2, 1 ] ); // 'prefix'
* compareArrays( [ 0, 2 ], [ 0 ] ); // 'extension'
* compareArrays( [ 0, 2 ], [ 1, 2 ] ); // 0
* compareArrays( [ 0, 2 ], [ 0, 1 ] ); // 1
*
* @param {Array} a Array that is compared.
* @param {Array} b Array to compare with.
* @returns {module:utils/comparearrays~ArrayRelation} How array `a` is related to `b`.
*/
function compareArrays( a, b ) {
const minLen = Math.min( a.length, b.length );
for ( let i = 0; i < minLen; i++ ) {
if ( a[ i ] != b[ i ] ) {
// The arrays are different.
return i;
}
}
// Both arrays were same at all points.
if ( a.length == b.length ) {
// If their length is also same, they are the same.
return 'same';
} else if ( a.length < b.length ) {
// Compared array is shorter so it is a prefix of the other array.
return 'prefix';
} else {
// Compared array is longer so it is an extension of the other array.
return 'extension';
}
}
/**
* @typedef {'extension'|'same'|'prefix'} module:utils/comparearrays~ArrayRelation
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-utils/src/config.js":
/*!**************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-utils/src/config.js ***!
\**************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ Config)
/* harmony export */ });
/* harmony import */ var lodash_es__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! lodash-es */ "./node_modules/lodash-es/isPlainObject.js");
/* harmony import */ var lodash_es__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! lodash-es */ "./node_modules/lodash-es/cloneDeepWith.js");
/* harmony import */ var lodash_es__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! lodash-es */ "./node_modules/lodash-es/isElement.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module utils/config
*/
/**
* Handles a configuration dictionary.
*/
class Config {
/**
* Creates an instance of the {@link ~Config} class.
*
* @param {Object} [configurations] The initial configurations to be set. Usually, provided by the user.
* @param {Object} [defaultConfigurations] The default configurations. Usually, provided by the system.
*/
constructor( configurations, defaultConfigurations ) {
/**
* Store for the whole configuration.
*
* @private
* @member {Object}
*/
this._config = {};
// Set default configuration.
if ( defaultConfigurations ) {
// Clone the configuration to make sure that the properties will not be shared
// between editors and make the watchdog feature work correctly.
this.define( cloneConfig( defaultConfigurations ) );
}
// Set initial configuration.
if ( configurations ) {
this._setObjectToTarget( this._config, configurations );
}
}
/**
* Set configuration values.
*
* It accepts both a name/value pair or an object, which properties and values will be used to set
* configurations.
*
* It also accepts setting a "deep configuration" by using dots in the name. For example, `'resize.width'` sets
* the value for the `width` configuration in the `resize` subset.
*
* config.set( 'width', 500 );
* config.set( 'toolbar.collapsed', true );
*
* // Equivalent to:
* config.set( {
* width: 500
* toolbar: {
* collapsed: true
* }
* } );
*
* Passing an object as the value will amend the configuration, not replace it.
*
* config.set( 'toolbar', {
* collapsed: true,
* } );
*
* config.set( 'toolbar', {
* color: 'red',
* } );
*
* config.get( 'toolbar.collapsed' ); // true
* config.get( 'toolbar.color' ); // 'red'
*
* @param {String|Object} name The configuration name or an object from which take properties as
* configuration entries. Configuration names are case-sensitive.
* @param {*} value The configuration value. Used if a name is passed.
*/
set( name, value ) {
this._setToTarget( this._config, name, value );
}
/**
* Does exactly the same as {@link #set} with one exception – passed configuration extends
* existing one, but does not overwrite already defined values.
*
* This method is supposed to be called by plugin developers to setup plugin's configurations. It would be
* rarely used for other needs.
*
* @param {String|Object} name The configuration name or an object from which take properties as
* configuration entries. Configuration names are case-sensitive.
* @param {*} value The configuration value. Used if a name is passed.
*/
define( name, value ) {
const isDefine = true;
this._setToTarget( this._config, name, value, isDefine );
}
/**
* Gets the value for a configuration entry.
*
* config.get( 'name' );
*
* Deep configurations can be retrieved by separating each part with a dot.
*
* config.get( 'toolbar.collapsed' );
*
* @param {String} name The configuration name. Configuration names are case-sensitive.
* @returns {*} The configuration value or `undefined` if the configuration entry was not found.
*/
get( name ) {
return this._getFromSource( this._config, name );
}
/**
* Iterates over all top level configuration names.
*
* @returns {Iterable.<String>}
*/
* names() {
for ( const name of Object.keys( this._config ) ) {
yield name;
}
}
/**
* Saves passed configuration to the specified target (nested object).
*
* @private
* @param {Object} target Nested config object.
* @param {String|Object} name The configuration name or an object from which take properties as
* configuration entries. Configuration names are case-sensitive.
* @param {*} value The configuration value. Used if a name is passed.
* @param {Boolean} [isDefine=false] Define if passed configuration should overwrite existing one.
*/
_setToTarget( target, name, value, isDefine = false ) {
// In case of an object, iterate through it and call `_setToTarget` again for each property.
if ( (0,lodash_es__WEBPACK_IMPORTED_MODULE_0__["default"])( name ) ) {
this._setObjectToTarget( target, name, isDefine );
return;
}
// The configuration name should be split into parts if it has dots. E.g. `resize.width` -> [`resize`, `width`].
const parts = name.split( '.' );
// Take the name of the configuration out of the parts. E.g. `resize.width` -> `width`.
name = parts.pop();
// Iterate over parts to check if currently stored configuration has proper structure.
for ( const part of parts ) {
// If there is no object for specified part then create one.
if ( !(0,lodash_es__WEBPACK_IMPORTED_MODULE_0__["default"])( target[ part ] ) ) {
target[ part ] = {};
}
// Nested object becomes a target.
target = target[ part ];
}
// In case of value is an object.
if ( (0,lodash_es__WEBPACK_IMPORTED_MODULE_0__["default"])( value ) ) {
// We take care of proper config structure.
if ( !(0,lodash_es__WEBPACK_IMPORTED_MODULE_0__["default"])( target[ name ] ) ) {
target[ name ] = {};
}
target = target[ name ];
// And iterate through this object calling `_setToTarget` again for each property.
this._setObjectToTarget( target, value, isDefine );
return;
}
// Do nothing if we are defining configuration for non empty name.
if ( isDefine && typeof target[ name ] != 'undefined' ) {
return;
}
target[ name ] = value;
}
/**
* Get specified configuration from specified source (nested object).
*
* @private
* @param {Object} source level of nested object.
* @param {String} name The configuration name. Configuration names are case-sensitive.
* @returns {*} The configuration value or `undefined` if the configuration entry was not found.
*/
_getFromSource( source, name ) {
// The configuration name should be split into parts if it has dots. E.g. `resize.width` -> [`resize`, `width`].
const parts = name.split( '.' );
// Take the name of the configuration out of the parts. E.g. `resize.width` -> `width`.
name = parts.pop();
// Iterate over parts to check if currently stored configuration has proper structure.
for ( const part of parts ) {
if ( !(0,lodash_es__WEBPACK_IMPORTED_MODULE_0__["default"])( source[ part ] ) ) {
source = null;
break;
}
// Nested object becomes a source.
source = source[ part ];
}
// Always returns undefined for non existing configuration.
return source ? cloneConfig( source[ name ] ) : undefined;
}
/**
* Iterates through passed object and calls {@link #_setToTarget} method with object key and value for each property.
*
* @private
* @param {Object} target Nested config object.
* @param {Object} configuration Configuration data set
* @param {Boolean} [isDefine] Defines if passed configuration is default configuration or not.
*/
_setObjectToTarget( target, configuration, isDefine ) {
Object.keys( configuration ).forEach( key => {
this._setToTarget( target, key, configuration[ key ], isDefine );
} );
}
}
// Clones configuration object or value.
// @param {*} source Source configuration
// @returns {*} Cloned configuration value.
function cloneConfig( source ) {
return (0,lodash_es__WEBPACK_IMPORTED_MODULE_1__["default"])( source, leaveDOMReferences );
}
// A customized function for cloneDeepWith.
// It will leave references to DOM Elements instead of cloning them.
//
// @param {*} value
// @returns {Element|undefined}
function leaveDOMReferences( value ) {
return (0,lodash_es__WEBPACK_IMPORTED_MODULE_2__["default"])( value ) ? value : undefined;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-utils/src/count.js":
/*!*************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-utils/src/count.js ***!
\*************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ count)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module utils/count
*/
/**
* Returns the number of items return by the iterator.
*
* count( [ 1, 2, 3, 4, 5 ] ); // 5;
*
* @param {Iterable.<*>} iterator Any iterator.
* @returns {Number} Number of items returned by that iterator.
*/
function count( iterator ) {
let count = 0;
for ( const _ of iterator ) { // eslint-disable-line no-unused-vars
count++;
}
return count;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-utils/src/diff.js":
/*!************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-utils/src/diff.js ***!
\************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ diff)
/* harmony export */ });
/* harmony import */ var _src_fastdiff__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../src/fastdiff */ "./node_modules/@ckeditor/ckeditor5-utils/src/fastdiff.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module utils/diff
*/
// The following code is based on the "O(NP) Sequence Comparison Algorithm"
// by Sun Wu, Udi Manber, Gene Myers, Webb Miller.
/**
* Calculates the difference between two arrays or strings producing an array containing a list of changes
* necessary to transform input into output.
*
* diff( 'aba', 'acca' ); // [ 'equal', 'insert', 'insert', 'delete', 'equal' ]
*
* This function is based on the "O(NP) Sequence Comparison Algorithm" by Sun Wu, Udi Manber, Gene Myers, Webb Miller.
* Unfortunately, while it gives the most precise results, its to complex for longer strings/arrow (above 200 items).
* Therefore, `diff()` automatically switches to {@link module:utils/fastdiff~fastDiff `fastDiff()`} when detecting
* such a scenario. The return formats of both functions are identical.
*
* @param {Array|String} a Input array or string.
* @param {Array|String} b Output array or string.
* @param {Function} [cmp] Optional function used to compare array values, by default === is used.
* @returns {Array} Array of changes.
*/
function diff( a, b, cmp ) {
// Set the comparator function.
cmp = cmp || function( a, b ) {
return a === b;
};
const aLength = a.length;
const bLength = b.length;
// Perform `fastDiff` for longer strings/arrays (see #269).
if ( aLength > 200 || bLength > 200 || aLength + bLength > 300 ) {
return diff.fastDiff( a, b, cmp, true );
}
// Temporary action type statics.
let _insert, _delete;
// Swapped the arrays to use the shorter one as the first one.
if ( bLength < aLength ) {
const tmp = a;
a = b;
b = tmp;
// We swap the action types as well.
_insert = 'delete';
_delete = 'insert';
} else {
_insert = 'insert';
_delete = 'delete';
}
const m = a.length;
const n = b.length;
const delta = n - m;
// Edit scripts, for each diagonal.
const es = {};
// Furthest points, the furthest y we can get on each diagonal.
const fp = {};
function snake( k ) {
// We use -1 as an alternative below to handle initial values ( instead of filling the fp with -1 first ).
// Furthest points (y) on the diagonal below k.
const y1 = ( fp[ k - 1 ] !== undefined ? fp[ k - 1 ] : -1 ) + 1;
// Furthest points (y) on the diagonal above k.
const y2 = fp[ k + 1 ] !== undefined ? fp[ k + 1 ] : -1;
// The way we should go to get further.
const dir = y1 > y2 ? -1 : 1;
// Clone previous changes array (if any).
if ( es[ k + dir ] ) {
es[ k ] = es[ k + dir ].slice( 0 );
}
// Create changes array.
if ( !es[ k ] ) {
es[ k ] = [];
}
// Push the action.
es[ k ].push( y1 > y2 ? _insert : _delete );
// Set the beginning coordinates.
let y = Math.max( y1, y2 );
let x = y - k;
// Traverse the diagonal as long as the values match.
while ( x < m && y < n && cmp( a[ x ], b[ y ] ) ) {
x++;
y++;
// Push no change action.
es[ k ].push( 'equal' );
}
return y;
}
let p = 0;
let k;
// Traverse the graph until we reach the end of the longer string.
do {
// Updates furthest points and edit scripts for diagonals below delta.
for ( k = -p; k < delta; k++ ) {
fp[ k ] = snake( k );
}
// Updates furthest points and edit scripts for diagonals above delta.
for ( k = delta + p; k > delta; k-- ) {
fp[ k ] = snake( k );
}
// Updates furthest point and edit script for the delta diagonal.
// note that the delta diagonal is the one which goes through the sink (m, n).
fp[ delta ] = snake( delta );
p++;
} while ( fp[ delta ] !== n );
// Return the final list of edit changes.
// We remove the first item that represents the action for the injected nulls.
return es[ delta ].slice( 1 );
}
// Store the API in static property to easily overwrite it in tests.
// Too bad dependency injection does not work in Webpack + ES 6 (const) + Babel.
diff.fastDiff = _src_fastdiff__WEBPACK_IMPORTED_MODULE_0__["default"];
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-utils/src/difftochanges.js":
/*!*********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-utils/src/difftochanges.js ***!
\*********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ diffToChanges)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module utils/difftochanges
*/
/**
* Creates a set of changes which need to be applied to the input in order to transform
* it into the output. This function can be used with strings or arrays.
*
* const input = Array.from( 'abc' );
* const output = Array.from( 'xaby' );
* const changes = diffToChanges( diff( input, output ), output );
*
* changes.forEach( change => {
* if ( change.type == 'insert' ) {
* input.splice( change.index, 0, ...change.values );
* } else if ( change.type == 'delete' ) {
* input.splice( change.index, change.howMany );
* }
* } );
*
* input.join( '' ) == output.join( '' ); // -> true
*
* @param {Array.<'equal'|'insert'|'delete'>} diff Result of {@link module:utils/diff~diff}.
* @param {String|Array} output The string or array which was passed as diff's output.
* @returns {Array.<Object>} Set of changes (insert or delete) which need to be applied to the input
* in order to transform it into the output.
*/
function diffToChanges( diff, output ) {
const changes = [];
let index = 0;
let lastOperation;
diff.forEach( change => {
if ( change == 'equal' ) {
pushLast();
index++;
} else if ( change == 'insert' ) {
if ( isContinuationOf( 'insert' ) ) {
lastOperation.values.push( output[ index ] );
} else {
pushLast();
lastOperation = {
type: 'insert',
index,
values: [ output[ index ] ]
};
}
index++;
} else /* if ( change == 'delete' ) */ {
if ( isContinuationOf( 'delete' ) ) {
lastOperation.howMany++;
} else {
pushLast();
lastOperation = {
type: 'delete',
index,
howMany: 1
};
}
}
} );
pushLast();
return changes;
function pushLast() {
if ( lastOperation ) {
changes.push( lastOperation );
lastOperation = null;
}
}
function isContinuationOf( expected ) {
return lastOperation && lastOperation.type == expected;
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/createelement.js":
/*!*************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-utils/src/dom/createelement.js ***!
\*************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ createElement)
/* harmony export */ });
/* harmony import */ var _isiterable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../isiterable */ "./node_modules/@ckeditor/ckeditor5-utils/src/isiterable.js");
/* harmony import */ var lodash_es__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! lodash-es */ "./node_modules/lodash-es/isString.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module utils/dom/createelement
*/
/**
* Creates element with attributes and children.
*
* createElement( document, 'p' ); // <p>
* createElement( document, 'p', { class: 'foo' } ); // <p class="foo">
* createElement( document, 'p', null, 'foo' ); // <p>foo</p>
* createElement( document, 'p', null, [ 'foo', createElement( document, 'img' ) ] ); // <p>foo<img></p>
*
* @param {Document} doc Document used to create element.
* @param {String} name Name of the element.
* @param {Object} [attributes] Object keys will become attributes keys and object values will became attributes values.
* @param {Node|String|Array.<Node|String>} [children] Child or array of children. Strings will be automatically turned
* into Text nodes.
* @returns {Element} Created element.
*/
function createElement( doc, name, attributes = {}, children = [] ) {
const namespace = attributes && attributes.xmlns;
const element = namespace ? doc.createElementNS( namespace, name ) : doc.createElement( name );
for ( const key in attributes ) {
element.setAttribute( key, attributes[ key ] );
}
if ( (0,lodash_es__WEBPACK_IMPORTED_MODULE_1__["default"])( children ) || !(0,_isiterable__WEBPACK_IMPORTED_MODULE_0__["default"])( children ) ) {
children = [ children ];
}
for ( let child of children ) {
if ( (0,lodash_es__WEBPACK_IMPORTED_MODULE_1__["default"])( child ) ) {
child = doc.createTextNode( child );
}
element.appendChild( child );
}
return element;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/emittermixin.js":
/*!************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-utils/src/dom/emittermixin.js ***!
\************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _emittermixin__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../emittermixin */ "./node_modules/@ckeditor/ckeditor5-utils/src/emittermixin.js");
/* harmony import */ var _uid__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../uid */ "./node_modules/@ckeditor/ckeditor5-utils/src/uid.js");
/* harmony import */ var _isnode__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./isnode */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/isnode.js");
/* harmony import */ var _iswindow__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./iswindow */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/iswindow.js");
/* harmony import */ var lodash_es__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! lodash-es */ "./node_modules/lodash-es/assignIn.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module utils/dom/emittermixin
*/
/**
* Mixin that injects the DOM events API into its host. It provides the API
* compatible with {@link module:utils/emittermixin~EmitterMixin}.
*
* DOM emitter mixin is by default available in the {@link module:ui/view~View} class,
* but it can also be mixed into any other class:
*
* import mix from '../utils/mix.js';
* import DomEmitterMixin from '../utils/dom/emittermixin.js';
*
* class SomeView {}
* mix( SomeView, DomEmitterMixin );
*
* const view = new SomeView();
* view.listenTo( domElement, ( evt, domEvt ) => {
* console.log( evt, domEvt );
* } );
*
* @mixin EmitterMixin
* @mixes module:utils/emittermixin~EmitterMixin
* @implements module:utils/dom/emittermixin~Emitter
*/
const DomEmitterMixin = (0,lodash_es__WEBPACK_IMPORTED_MODULE_4__["default"])( {}, _emittermixin__WEBPACK_IMPORTED_MODULE_0__["default"], {
/**
* Registers a callback function to be executed when an event is fired in a specific Emitter or DOM Node.
* It is backwards compatible with {@link module:utils/emittermixin~EmitterMixin#listenTo}.
*
* @param {module:utils/emittermixin~Emitter|Node} emitter The object that fires the event.
* @param {String} event The name of the event.
* @param {Function} callback The function to be called on event.
* @param {Object} [options={}] Additional options.
* @param {module:utils/priorities~PriorityString|Number} [options.priority='normal'] The priority of this event callback. The higher
* the priority value the sooner the callback will be fired. Events having the same priority are called in the
* order they were added.
* @param {Boolean} [options.useCapture=false] Indicates that events of this type will be dispatched to the registered
* listener before being dispatched to any EventTarget beneath it in the DOM tree.
* @param {Boolean} [options.usePassive=false] Indicates that the function specified by listener will never call preventDefault()
* and prevents blocking browser's main thread by this event handler.
*/
listenTo( emitter, event, callback, options = {} ) {
// Check if emitter is an instance of DOM Node. If so, use corresponding ProxyEmitter (or create one if not existing).
if ( (0,_isnode__WEBPACK_IMPORTED_MODULE_2__["default"])( emitter ) || (0,_iswindow__WEBPACK_IMPORTED_MODULE_3__["default"])( emitter ) ) {
const proxyOptions = {
capture: !!options.useCapture,
passive: !!options.usePassive
};
const proxyEmitter = this._getProxyEmitter( emitter, proxyOptions ) || new ProxyEmitter( emitter, proxyOptions );
this.listenTo( proxyEmitter, event, callback, options );
} else {
// Execute parent class method with Emitter (or ProxyEmitter) instance.
_emittermixin__WEBPACK_IMPORTED_MODULE_0__["default"].listenTo.call( this, emitter, event, callback, options );
}
},
/**
* Stops listening for events. It can be used at different levels:
* It is backwards compatible with {@link module:utils/emittermixin~EmitterMixin#listenTo}.
*
* * To stop listening to a specific callback.
* * To stop listening to a specific event.
* * To stop listening to all events fired by a specific object.
* * To stop listening to all events fired by all object.
*
* @param {module:utils/emittermixin~Emitter|Node} [emitter] The object to stop listening to. If omitted, stops it for all objects.
* @param {String} [event] (Requires the `emitter`) The name of the event to stop listening to. If omitted, stops it
* for all events from `emitter`.
* @param {Function} [callback] (Requires the `event`) The function to be removed from the call list for the given
* `event`.
*/
stopListening( emitter, event, callback ) {
// Check if the emitter is an instance of DOM Node. If so, forward the call to the corresponding ProxyEmitters.
if ( (0,_isnode__WEBPACK_IMPORTED_MODULE_2__["default"])( emitter ) || (0,_iswindow__WEBPACK_IMPORTED_MODULE_3__["default"])( emitter ) ) {
const proxyEmitters = this._getAllProxyEmitters( emitter );
for ( const proxy of proxyEmitters ) {
this.stopListening( proxy, event, callback );
}
} else {
// Execute parent class method with Emitter (or ProxyEmitter) instance.
_emittermixin__WEBPACK_IMPORTED_MODULE_0__["default"].stopListening.call( this, emitter, event, callback );
}
},
/**
* Retrieves ProxyEmitter instance for given DOM Node residing in this Host and given options.
*
* @private
* @param {Node} node DOM Node of the ProxyEmitter.
* @param {Object} [options] Additional options.
* @param {Boolean} [options.useCapture=false] Indicates that events of this type will be dispatched to the registered
* listener before being dispatched to any EventTarget beneath it in the DOM tree.
* @param {Boolean} [options.usePassive=false] Indicates that the function specified by listener will never call preventDefault()
* and prevents blocking browser's main thread by this event handler.
* @returns {module:utils/dom/emittermixin~ProxyEmitter|null} ProxyEmitter instance bound to the DOM Node.
*/
_getProxyEmitter( node, options ) {
return (0,_emittermixin__WEBPACK_IMPORTED_MODULE_0__._getEmitterListenedTo)( this, getProxyEmitterId( node, options ) );
},
/**
* Retrieves all the ProxyEmitter instances for given DOM Node residing in this Host.
*
* @private
* @param {Node} node DOM Node of the ProxyEmitter.
* @returns {Array.<module:utils/dom/emittermixin~ProxyEmitter>}
*/
_getAllProxyEmitters( node ) {
return [
{ capture: false, passive: false },
{ capture: false, passive: true },
{ capture: true, passive: false },
{ capture: true, passive: true }
].map( options => this._getProxyEmitter( node, options ) ).filter( proxy => !!proxy );
}
} );
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (DomEmitterMixin);
/**
* Creates a ProxyEmitter instance. Such an instance is a bridge between a DOM Node firing events
* and any Host listening to them. It is backwards compatible with {@link module:utils/emittermixin~EmitterMixin#on}.
* There is a separate instance for each combination of modes (useCapture & usePassive). The mode is concatenated with
* UID stored in HTMLElement to give each instance unique identifier.
*
* listenTo( click, ... )
* +-----------------------------------------+
* | stopListening( ... ) |
* +----------------------------+ | addEventListener( click, ... )
* | Host | | +---------------------------------------------+
* +----------------------------+ | | removeEventListener( click, ... ) |
* | _listeningTo: { | +----------v-------------+ |
* | UID+mode: { | | ProxyEmitter | |
* | emitter: ProxyEmitter, | +------------------------+ +------------v----------+
* | callbacks: { | | events: { | | Node (HTMLElement) |
* | click: [ callbacks ] | | click: [ callbacks ] | +-----------------------+
* | } | | }, | | data-ck-expando: UID |
* | } | | _domNode: Node, | +-----------------------+
* | } | | _domListeners: {}, | |
* | +------------------------+ | | _emitterId: UID+mode | |
* | | DomEmitterMixin | | +--------------^---------+ |
* | +------------------------+ | | | |
* +--------------^-------------+ | +---------------------------------------------+
* | | click (DOM Event)
* +-----------------------------------------+
* fire( click, DOM Event )
*
* @mixes module:utils/emittermixin~EmitterMixin
* @implements module:utils/dom/emittermixin~Emitter
* @private
*/
class ProxyEmitter {
/**
* @param {Node} node DOM Node that fires events.
* @param {Object} [options] Additional options.
* @param {Boolean} [options.useCapture=false] Indicates that events of this type will be dispatched to the registered
* listener before being dispatched to any EventTarget beneath it in the DOM tree.
* @param {Boolean} [options.usePassive=false] Indicates that the function specified by listener will never call preventDefault()
* and prevents blocking browser's main thread by this event handler.
*/
constructor( node, options ) {
// Set emitter ID to match DOM Node "expando" property.
(0,_emittermixin__WEBPACK_IMPORTED_MODULE_0__._setEmitterId)( this, getProxyEmitterId( node, options ) );
// Remember the DOM Node this ProxyEmitter is bound to.
this._domNode = node;
// And given options.
this._options = options;
}
}
(0,lodash_es__WEBPACK_IMPORTED_MODULE_4__["default"])( ProxyEmitter.prototype, _emittermixin__WEBPACK_IMPORTED_MODULE_0__["default"], {
/**
* Collection of native DOM listeners.
*
* @private
* @member {Object} module:utils/dom/emittermixin~ProxyEmitter#_domListeners
*/
/**
* Registers a callback function to be executed when an event is fired.
*
* It attaches a native DOM listener to the DOM Node. When fired,
* a corresponding Emitter event will also fire with DOM Event object as an argument.
*
* **Note**: This is automatically called by the
* {@link module:utils/emittermixin~EmitterMixin#listenTo `EmitterMixin#listenTo()`}.
*
* @method module:utils/dom/emittermixin~ProxyEmitter#attach
* @param {String} event The name of the event.
*/
attach( event ) {
// If the DOM Listener for given event already exist it is pointless
// to attach another one.
if ( this._domListeners && this._domListeners[ event ] ) {
return;
}
const domListener = this._createDomListener( event );
// Attach the native DOM listener to DOM Node.
this._domNode.addEventListener( event, domListener, this._options );
if ( !this._domListeners ) {
this._domListeners = {};
}
// Store the native DOM listener in this ProxyEmitter. It will be helpful
// when stopping listening to the event.
this._domListeners[ event ] = domListener;
},
/**
* Stops executing the callback on the given event.
*
* **Note**: This is automatically called by the
* {@link module:utils/emittermixin~EmitterMixin#stopListening `EmitterMixin#stopListening()`}.
*
* @method module:utils/dom/emittermixin~ProxyEmitter#detach
* @param {String} event The name of the event.
*/
detach( event ) {
let events;
// Remove native DOM listeners which are orphans. If no callbacks
// are awaiting given event, detach native DOM listener from DOM Node.
// See: {@link attach}.
if ( this._domListeners[ event ] && ( !( events = this._events[ event ] ) || !events.callbacks.length ) ) {
this._domListeners[ event ].removeListener();
}
},
/**
* Adds callback to emitter for given event.
*
* @protected
* @method module:utils/dom/emittermixin~ProxyEmitter#_addEventListener
* @param {String} event The name of the event.
* @param {Function} callback The function to be called on event.
* @param {Object} [options={}] Additional options.
* @param {module:utils/priorities~PriorityString|Number} [options.priority='normal'] The priority of this event callback. The higher
* the priority value the sooner the callback will be fired. Events having the same priority are called in the
* order they were added.
*/
_addEventListener( event, callback, options ) {
this.attach( event );
_emittermixin__WEBPACK_IMPORTED_MODULE_0__["default"]._addEventListener.call( this, event, callback, options );
},
/**
* Removes callback from emitter for given event.
*
* @protected
* @method module:utils/dom/emittermixin~ProxyEmitter#_removeEventListener
* @param {String} event The name of the event.
* @param {Function} callback The function to stop being called.
*/
_removeEventListener( event, callback ) {
_emittermixin__WEBPACK_IMPORTED_MODULE_0__["default"]._removeEventListener.call( this, event, callback );
this.detach( event );
},
/**
* Creates a native DOM listener callback. When the native DOM event
* is fired it will fire corresponding event on this ProxyEmitter.
* Note: A native DOM Event is passed as an argument.
*
* @private
* @method module:utils/dom/emittermixin~ProxyEmitter#_createDomListener
* @param {String} event The name of the event.
* @returns {Function} The DOM listener callback.
*/
_createDomListener( event ) {
const domListener = domEvt => {
this.fire( event, domEvt );
};
// Supply the DOM listener callback with a function that will help
// detach it from the DOM Node, when it is no longer necessary.
// See: {@link detach}.
domListener.removeListener = () => {
this._domNode.removeEventListener( event, domListener, this._options );
delete this._domListeners[ event ];
};
return domListener;
}
} );
// Gets an unique DOM Node identifier. The identifier will be set if not defined.
//
// @private
// @param {Node} node
// @returns {String} UID for given DOM Node.
function getNodeUID( node ) {
return node[ 'data-ck-expando' ] || ( node[ 'data-ck-expando' ] = (0,_uid__WEBPACK_IMPORTED_MODULE_1__["default"])() );
}
// Gets id of the ProxyEmitter for the given node.
//
// Combines DOM Node identifier and additional options.
//
// @private
// @param {Node} node
// @param {Object} options Additional options.
// @returns {String} ProxyEmitter id.
function getProxyEmitterId( node, options ) {
let id = getNodeUID( node );
for ( const option of Object.keys( options ).sort() ) {
if ( options[ option ] ) {
id += '-' + option;
}
}
return id;
}
/**
* Interface representing classes which mix in {@link module:utils/dom/emittermixin~EmitterMixin}.
*
* @interface Emitter
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/getancestors.js":
/*!************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-utils/src/dom/getancestors.js ***!
\************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ getAncestors)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/* globals Node */
/**
* @module utils/dom/getancestors
*/
/**
* Returns all ancestors of given DOM node, starting from the top-most (root). Includes the given node itself. If the
* node is a part of `DocumentFragment` that `DocumentFragment` will be returned. In contrary, if the node is
* appended to a `Document`, that `Document` will not be returned (algorithms operating on DOM tree care for `Document#documentElement`
* at most, which will be returned).
*
* @param {Node} node DOM node.
* @returns {Array.<Node|DocumentFragment>} Array of given `node` parents.
*/
function getAncestors( node ) {
const nodes = [];
// We are interested in `Node`s `DocumentFragment`s only.
while ( node && node.nodeType != Node.DOCUMENT_NODE ) {
nodes.unshift( node );
node = node.parentNode;
}
return nodes;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/getborderwidths.js":
/*!***************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-utils/src/dom/getborderwidths.js ***!
\***************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ getBorderWidths)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module utils/dom/getborderwidths
*/
/**
* Returns an object containing CSS border widths of a specified HTML element.
*
* @param {HTMLElement} element An element which has CSS borders.
* @returns {Object} An object containing `top`, `left`, `right` and `bottom` properties
* with numerical values of the `border-[top,left,right,bottom]-width` CSS styles.
*/
function getBorderWidths( element ) {
// Call getComputedStyle on the window the element document belongs to.
const style = element.ownerDocument.defaultView.getComputedStyle( element );
return {
top: parseInt( style.borderTopWidth, 10 ),
right: parseInt( style.borderRightWidth, 10 ),
bottom: parseInt( style.borderBottomWidth, 10 ),
left: parseInt( style.borderLeftWidth, 10 )
};
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/getdatafromelement.js":
/*!******************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-utils/src/dom/getdatafromelement.js ***!
\******************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ getDataFromElement)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/* globals HTMLTextAreaElement */
/**
* @module utils/dom/getdatafromelement
*/
/**
* Gets data from a given source element.
*
* @param {HTMLElement} el The element from which the data will be retrieved.
* @returns {String} The data string.
*/
function getDataFromElement( el ) {
if ( el instanceof HTMLTextAreaElement ) {
return el.value;
}
return el.innerHTML;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/getpositionedancestor.js":
/*!*********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-utils/src/dom/getpositionedancestor.js ***!
\*********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ getPositionedAncestor)
/* harmony export */ });
/* harmony import */ var _global__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./global */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/global.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module utils/dom/getpositionedancestor
*/
/**
* For a given element, returns the nearest ancestor element which CSS position is not "static".
*
* @param {HTMLElement} element The native DOM element to be checked.
* @returns {HTMLElement|null}
*/
function getPositionedAncestor( element ) {
if ( !element || !element.parentNode ) {
return null;
}
if ( element.offsetParent === _global__WEBPACK_IMPORTED_MODULE_0__["default"].document.body ) {
return null;
}
return element.offsetParent;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/global.js":
/*!******************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-utils/src/dom/global.js ***!
\******************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/* globals window, document */
/**
* @module utils/dom/global
*/
/**
* A helper (module) giving an access to the global DOM objects such as `window` and
* `document`. Accessing these objects using this helper allows easy and bulletproof
* testing, i.e. stubbing native properties:
*
* import global from 'ckeditor5/utils/dom/global.js';
*
* // This stub will work for any code using global module.
* testUtils.sinon.stub( global, 'window', {
* innerWidth: 10000
* } );
*
* console.log( global.window.innerWidth );
*/
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ({ window, document });
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/indexof.js":
/*!*******************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-utils/src/dom/indexof.js ***!
\*******************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ indexOf)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module utils/dom/indexof
*/
/**
* Returns index of the node in the parent element.
*
* @param {Node} node Node which index is tested.
* @returns {Number} Index of the node in the parent element. Returns 0 if node has no parent.
*/
function indexOf( node ) {
let index = 0;
while ( node.previousSibling ) {
node = node.previousSibling;
index++;
}
return index;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/insertat.js":
/*!********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-utils/src/dom/insertat.js ***!
\********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ insertAt)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module utils/dom/insertat
*/
/**
* Inserts node to the parent at given index.
*
* @param {Element} parentElement Parent element.
* @param {Number} index Insertions index.
* @param {Node} nodeToInsert Node to insert.
*/
function insertAt( parentElement, index, nodeToInsert ) {
parentElement.insertBefore( nodeToInsert, parentElement.childNodes[ index ] || null );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/iscomment.js":
/*!*********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-utils/src/dom/iscomment.js ***!
\*********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ isComment)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/* globals Node */
/**
* @module utils/dom/iscomment
*/
/**
* Checks whether the object is a native DOM Comment node.
*
* @param {*} obj
* @returns {Boolean}
*/
function isComment( obj ) {
return obj && obj.nodeType === Node.COMMENT_NODE;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/isnode.js":
/*!******************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-utils/src/dom/isnode.js ***!
\******************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ isNode)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module utils/dom/isnode
*/
/**
* Checks if the object is a native DOM Node.
*
* @param {*} obj
* @returns {Boolean}
*/
function isNode( obj ) {
if ( obj ) {
if ( obj.defaultView ) {
return obj instanceof obj.defaultView.Document;
} else if ( obj.ownerDocument && obj.ownerDocument.defaultView ) {
return obj instanceof obj.ownerDocument.defaultView.Node;
}
}
return false;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/isrange.js":
/*!*******************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-utils/src/dom/isrange.js ***!
\*******************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ isRange)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module utils/dom/isrange
*/
/**
* Checks if the object is a native DOM Range.
*
* @param {*} obj
* @returns {Boolean}
*/
function isRange( obj ) {
return Object.prototype.toString.apply( obj ) == '[object Range]';
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/istext.js":
/*!******************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-utils/src/dom/istext.js ***!
\******************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ isText)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module utils/dom/istext
*/
/**
* Checks if the object is a native DOM Text node.
*
* @param {*} obj
* @returns {Boolean}
*/
function isText( obj ) {
return Object.prototype.toString.call( obj ) == '[object Text]';
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/isvisible.js":
/*!*********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-utils/src/dom/isvisible.js ***!
\*********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ isVisible)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module utils/dom/isvisible
*/
/**
* Checks whether the element is visible to the user in DOM:
*
* * connected to the root of the document,
* * has no `display: none`,
* * has no ancestors with `display: none`.
*
* **Note**: This helper does not check whether the element is hidden by cropping, overflow, etc..
* To check that, use {@link module:utils/dom/rect~Rect} instead.
*
* @param {HTMLElement|null|undefined} element
* @returns {Boolean}
*/
function isVisible( element ) {
return !!( element && element.getClientRects && element.getClientRects().length );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/iswindow.js":
/*!********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-utils/src/dom/iswindow.js ***!
\********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ isWindow)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module utils/dom/iswindow
*/
/**
* Checks if the object is a native DOM Window.
*
* @param {*} obj
* @returns {Boolean}
*/
function isWindow( obj ) {
const stringifiedObject = Object.prototype.toString.apply( obj );
// Returns `true` for the `window` object in browser environments.
if ( stringifiedObject == '[object Window]' ) {
return true;
}
// Returns `true` for the `window` object in the Electron environment.
if ( stringifiedObject == '[object global]' ) {
return true;
}
return false;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/position.js":
/*!********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-utils/src/dom/position.js ***!
\********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "Position": () => (/* binding */ Position),
/* harmony export */ "getOptimalPosition": () => (/* binding */ getOptimalPosition)
/* harmony export */ });
/* harmony import */ var _global__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./global */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/global.js");
/* harmony import */ var _rect__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./rect */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/rect.js");
/* harmony import */ var _getpositionedancestor__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./getpositionedancestor */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/getpositionedancestor.js");
/* harmony import */ var _getborderwidths__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./getborderwidths */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/getborderwidths.js");
/* harmony import */ var lodash_es__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! lodash-es */ "./node_modules/lodash-es/isFunction.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module utils/dom/position
*/
// @if CK_DEBUG_POSITION // import { RectDrawer } from '@ckeditor/ckeditor5-minimap/src/utils';
/**
* Calculates the `position: absolute` coordinates of a given element so it can be positioned with respect to the
* target in the visually most efficient way, taking various restrictions like viewport or limiter geometry
* into consideration.
*
* // The element which is to be positioned.
* const element = document.body.querySelector( '#toolbar' );
*
* // A target to which the element is positioned relatively.
* const target = document.body.querySelector( '#container' );
*
* // Finding the optimal coordinates for the positioning.
* const { left, top, name } = getOptimalPosition( {
* element: element,
* target: target,
*
* // The algorithm will chose among these positions to meet the requirements such
* // as "limiter" element or "fitInViewport", set below. The positions are considered
* // in the order of the array.
* positions: [
* //
* // [ Target ]
* // +-----------------+
* // | Element |
* // +-----------------+
* //
* targetRect => ( {
* top: targetRect.bottom,
* left: targetRect.left,
* name: 'mySouthEastPosition'
* } ),
*
* //
* // +-----------------+
* // | Element |
* // +-----------------+
* // [ Target ]
* //
* ( targetRect, elementRect ) => ( {
* top: targetRect.top - elementRect.height,
* left: targetRect.left,
* name: 'myNorthEastPosition'
* } )
* ],
*
* // Find a position such guarantees the element remains within visible boundaries of <body>.
* limiter: document.body,
*
* // Find a position such guarantees the element remains within visible boundaries of the browser viewport.
* fitInViewport: true
* } );
*
* // The best position which fits into document.body and the viewport. May be useful
* // to set proper class on the `element`.
* console.log( name ); // -> "myNorthEastPosition"
*
* // Using the absolute coordinates which has been found to position the element
* // as in the diagram depicting the "myNorthEastPosition" position.
* element.style.top = top;
* element.style.left = left;
*
* @param {module:utils/dom/position~Options} options The input data and configuration of the helper.
* @returns {module:utils/dom/position~Position}
*/
function getOptimalPosition( { element, target, positions, limiter, fitInViewport, viewportOffsetConfig } ) {
// If the {@link module:utils/dom/position~Options#target} is a function, use what it returns.
// https://github.com/ckeditor/ckeditor5-utils/issues/157
if ( (0,lodash_es__WEBPACK_IMPORTED_MODULE_4__["default"])( target ) ) {
target = target();
}
// If the {@link module:utils/dom/position~Options#limiter} is a function, use what it returns.
// https://github.com/ckeditor/ckeditor5-ui/issues/260
if ( (0,lodash_es__WEBPACK_IMPORTED_MODULE_4__["default"])( limiter ) ) {
limiter = limiter();
}
const positionedElementAncestor = (0,_getpositionedancestor__WEBPACK_IMPORTED_MODULE_2__["default"])( element );
const elementRect = new _rect__WEBPACK_IMPORTED_MODULE_1__["default"]( element );
const targetRect = new _rect__WEBPACK_IMPORTED_MODULE_1__["default"]( target );
let bestPosition;
// @if CK_DEBUG_POSITION // RectDrawer.clear();
// @if CK_DEBUG_POSITION // RectDrawer.draw( targetRect, { outlineWidth: '5px' }, 'Target' );
const positionOptions = { targetRect, elementRect, positionedElementAncestor };
// If there are no limits, just grab the very first position and be done with that drama.
if ( !limiter && !fitInViewport ) {
bestPosition = new Position( positions[ 0 ], positionOptions );
} else {
const limiterRect = limiter && new _rect__WEBPACK_IMPORTED_MODULE_1__["default"]( limiter ).getVisible();
const viewportRect = fitInViewport && getConstrainedViewportRect( viewportOffsetConfig );
// @if CK_DEBUG_POSITION // if ( viewportRect ) {
// @if CK_DEBUG_POSITION // RectDrawer.draw( viewportRect, { outlineWidth: '5px' }, 'Viewport' );
// @if CK_DEBUG_POSITION // }
// @if CK_DEBUG_POSITION // if ( limiter ) {
// @if CK_DEBUG_POSITION // RectDrawer.draw( limiterRect, { outlineWidth: '5px', outlineColor: 'green' }, 'Visible limiter' );
// @if CK_DEBUG_POSITION // }
Object.assign( positionOptions, { limiterRect, viewportRect } );
// If there's no best position found, i.e. when all intersections have no area because
// rects have no width or height, then just use the first available position.
bestPosition = getBestPosition( positions, positionOptions ) || new Position( positions[ 0 ], positionOptions );
}
return bestPosition;
}
// Returns a viewport `Rect` shrunk by the viewport offset config from all sides.
//
// @private
// @param {Object} An object containing viewportOffset config.
// @returns {utils/dom/rect~Rect} A shrunken rect of the viewport.
function getConstrainedViewportRect( viewportOffsetConfig ) {
viewportOffsetConfig = Object.assign( { top: 0, bottom: 0, left: 0, right: 0 }, viewportOffsetConfig );
const viewportRect = new _rect__WEBPACK_IMPORTED_MODULE_1__["default"]( _global__WEBPACK_IMPORTED_MODULE_0__["default"].window );
viewportRect.top += viewportOffsetConfig.top;
viewportRect.height -= viewportOffsetConfig.top;
viewportRect.bottom -= viewportOffsetConfig.bottom;
viewportRect.height -= viewportOffsetConfig.bottom;
return viewportRect;
}
// For a given array of positioning functions, returns such that provides the best
// fit of the `elementRect` into the `limiterRect` and `viewportRect`.
//
// @private
//
// @param {Object} options
// @param {module:utils/dom/position~Options#positions} positions Functions returning
// {@link module:utils/dom/position~Position}to be checked, in the order of preference.
// @param {Object} options
// @param {utils/dom/rect~Rect} options.targetRect A rect of the {@link module:utils/dom/position~Options#target}.
// @param {utils/dom/rect~Rect} options.elementRect A rect of positioned
// {@link module:utils/dom/position~Options#element}.
// @param {utils/dom/rect~Rect} options.limiterRect A rect of the {@link module:utils/dom/position~Options#limiter}.
// @param {utils/dom/rect~Rect} options.viewportRect A rect of the {@link module:utils/dom/position~Options#viewport}.
//
// @returns {Array} An array containing the name of the position and it's rect.
function getBestPosition( positions, options ) {
const { elementRect } = options;
// This is when element is fully visible.
const elementRectArea = elementRect.getArea();
const positionInstances = positions
.map( positioningFunction => new Position( positioningFunction, options ) )
// Some positioning functions may return `null` if they don't want to participate.
.filter( position => !!position.name );
let maxFitFactor = 0;
let bestPosition = null;
for ( const position of positionInstances ) {
const { _limiterIntersectionArea, _viewportIntersectionArea } = position;
// If a such position is found that element is fully contained by the limiter then, obviously,
// there will be no better one, so finishing.
if ( _limiterIntersectionArea === elementRectArea ) {
return position;
}
// To maximize both viewport and limiter intersection areas we use distance on _viewportIntersectionArea
// and _limiterIntersectionArea plane (without sqrt because we are looking for max value).
const fitFactor = _viewportIntersectionArea ** 2 + _limiterIntersectionArea ** 2;
if ( fitFactor > maxFitFactor ) {
maxFitFactor = fitFactor;
bestPosition = position;
}
}
return bestPosition;
}
// For a given absolute Rect coordinates object and a positioned element ancestor, it returns an object with
// new Rect coordinates that make up for the position and the scroll of the ancestor.
//
// This is necessary because while Rects (and DOMRects) are relative to the browser's viewport, their coordinates
// are used in real–life to position elements with `position: absolute`, which are scoped by any positioned
// (and scrollable) ancestors.
//
// @private
//
// @param {utils/dom/rect~Rect} rect A rect with absolute rect coordinates.
// @param {Number} rect.top
// @param {Number} rect.left
// @param {HTMLElement} positionedElementAncestor An ancestor element that should be considered.
//
// @returns {utils/dom/rect~Rect} A rect corresponding to `absoluteRect` input but with values shifted
// to make up for the positioned element ancestor.
function shiftRectToCompensatePositionedAncestor( rect, positionedElementAncestor ) {
const ancestorPosition = getRectForAbsolutePositioning( new _rect__WEBPACK_IMPORTED_MODULE_1__["default"]( positionedElementAncestor ) );
const ancestorBorderWidths = (0,_getborderwidths__WEBPACK_IMPORTED_MODULE_3__["default"])( positionedElementAncestor );
let moveX = 0;
let moveY = 0;
// (https://github.com/ckeditor/ckeditor5-ui-default/issues/126)
// If there's some positioned ancestor of the panel, then its `Rect` must be taken into
// consideration. `Rect` is always relative to the viewport while `position: absolute` works
// with respect to that positioned ancestor.
moveX -= ancestorPosition.left;
moveY -= ancestorPosition.top;
// (https://github.com/ckeditor/ckeditor5-utils/issues/139)
// If there's some positioned ancestor of the panel, not only its position must be taken into
// consideration (see above) but also its internal scrolls. Scroll have an impact here because `Rect`
// is relative to the viewport (it doesn't care about scrolling), while `position: absolute`
// must compensate that scrolling.
moveX += positionedElementAncestor.scrollLeft;
moveY += positionedElementAncestor.scrollTop;
// (https://github.com/ckeditor/ckeditor5-utils/issues/139)
// If there's some positioned ancestor of the panel, then its `Rect` includes its CSS `borderWidth`
// while `position: absolute` positioning does not consider it.
// E.g. `{ position: absolute, top: 0, left: 0 }` means upper left corner of the element,
// not upper-left corner of its border.
moveX -= ancestorBorderWidths.left;
moveY -= ancestorBorderWidths.top;
rect.moveBy( moveX, moveY );
}
// DOMRect (also Rect) works in a scroll–independent geometry but `position: absolute` doesn't.
// This function converts Rect to `position: absolute` coordinates.
//
// @private
// @param {utils/dom/rect~Rect} rect A rect to be converted.
// @returns {Object} Object containing `left` and `top` properties, in absolute coordinates.
function getRectForAbsolutePositioning( rect ) {
const { scrollX, scrollY } = _global__WEBPACK_IMPORTED_MODULE_0__["default"].window;
return rect.clone().moveBy( scrollX, scrollY );
}
/**
* A position class which instances are created and used by the {@link module:utils/dom/position~getOptimalPosition} helper.
*
* {@link module:utils/dom/position~Position#top} and {@link module:utils/dom/position~Position#left} properties of the position instance
* translate directly to the `top` and `left` properties in CSS "`position: absolute` coordinate system". If set on the positioned element
* in DOM, they will make it display it in the right place in the viewport.
*/
class Position {
/**
* Creates an instance of the {@link module:utils/dom/position~Position} class.
*
* @param {module:utils/dom/position~positioningFunction} [positioningFunction] function The function that defines the expected
* coordinates the positioned element should move to.
* @param {Object} [options] options object.
* @param {module:utils/dom/rect~Rect} options.elementRect The positioned element rect.
* @param {module:utils/dom/rect~Rect} options.targetRect The target element rect.
* @param {module:utils/dom/rect~Rect} options.viewportRect The viewport rect.
* @param {HTMLElement|null} [options.positionedElementAncestor] Nearest element ancestor element which CSS position is not "static".
*/
constructor( positioningFunction, options ) {
const positioningFunctionOutput = positioningFunction( options.targetRect, options.elementRect, options.viewportRect );
// Nameless position for a function that didn't participate.
if ( !positioningFunctionOutput ) {
return;
}
const { left, top, name, config } = positioningFunctionOutput;
Object.assign( this, { name, config } );
this._positioningFunctionCorrdinates = { left, top };
this._options = options;
/**
* Position name.
*
* @readonly
* @member {String} #name
*/
/**
* Additional position configuration, as passed from the {@link module:utils/dom/position~positioningFunction positioning function}.
*
* This object can be use, for instance, to pass through presentation options used by the consumer of the
* {@link module:utils/dom/position~getOptimalPosition} helper.
*
* @readonly
* @member {Object} #config
*/
}
/**
* The left value in pixels in the CSS `position: absolute` coordinate system.
* Set it on the positioned element in DOM to move it to the position.
*
* @readonly
* @type {Number}
*/
get left() {
return this._absoluteRect.left;
}
/**
* The top value in pixels in the CSS `position: absolute` coordinate system.
* Set it on the positioned element in DOM to move it to the position.
*
* @readonly
* @type {Number}
*/
get top() {
return this._absoluteRect.top;
}
/**
* An intersection area between positioned element and limiter within viewport constraints.
*
* @readonly
* @private
* @type {Number}
*/
get _limiterIntersectionArea() {
const limiterRect = this._options.limiterRect;
if ( limiterRect ) {
const viewportRect = this._options.viewportRect;
if ( viewportRect ) {
// Consider only the part of the limiter which is visible in the viewport. So the limiter is getting limited.
const limiterViewportIntersectRect = limiterRect.getIntersection( viewportRect );
if ( limiterViewportIntersectRect ) {
// If the limiter is within the viewport, then check the intersection between that part of the
// limiter and actual position.
return limiterViewportIntersectRect.getIntersectionArea( this._rect );
}
} else {
return limiterRect.getIntersectionArea( this._rect );
}
}
return 0;
}
/**
* An intersection area between positioned element and viewport.
*
* @readonly
* @private
* @type {Number}
*/
get _viewportIntersectionArea() {
const viewportRect = this._options.viewportRect;
if ( viewportRect ) {
return viewportRect.getIntersectionArea( this._rect );
}
return 0;
}
/**
* An already positioned element rect. A clone of the element rect passed to the constructor
* but placed in the viewport according to the positioning function.
*
* @private
* @type {module:utils/dom/rect~Rect}
*/
get _rect() {
if ( this._cachedRect ) {
return this._cachedRect;
}
this._cachedRect = this._options.elementRect.clone().moveTo(
this._positioningFunctionCorrdinates.left,
this._positioningFunctionCorrdinates.top
);
return this._cachedRect;
}
/**
* An already absolutely positioned element rect. See ({@link #_rect}).
*
* @private
* @type {module:utils/dom/rect~Rect}
*/
get _absoluteRect() {
if ( this._cachedAbsoluteRect ) {
return this._cachedAbsoluteRect;
}
this._cachedAbsoluteRect = getRectForAbsolutePositioning( this._rect );
if ( this._options.positionedElementAncestor ) {
shiftRectToCompensatePositionedAncestor( this._cachedAbsoluteRect, this._options.positionedElementAncestor );
}
return this._cachedAbsoluteRect;
}
}
/**
* The `getOptimalPosition()` helper options.
*
* @interface module:utils/dom/position~Options
*/
/**
* Element that is to be positioned.
*
* @member {HTMLElement} #element
*/
/**
* Target with respect to which the `element` is to be positioned.
*
* @member {HTMLElement|Range|Window|ClientRect|DOMRect|module:utils/dom/rect~Rect|Object|Function} #target
*/
/**
* An array of positioning functions.
*
* **Note**: Positioning functions are processed in the order of preference. The first function that works
* in the current environment (e.g. offers the complete fit in the viewport geometry) will be picked by
* `getOptimalPosition()`.
*
* **Note**: Any positioning function returning `null` is ignored.
*
* @member {Array.<module:utils/dom/position~positioningFunction>} #positions
*/
/**
* When set, the algorithm will chose position which fits the most in the
* limiter's bounding rect.
*
* @member {HTMLElement|Range|Window|ClientRect|DOMRect|module:utils/dom/rect~Rect|Object|Function} #limiter
*/
/**
* When set, the algorithm will chose such a position which fits `element`
* the most inside visible viewport.
*
* @member {Boolean} #fitInViewport
*/
/**
* Viewport offset config object. It restricts the visible viewport available to the `getOptimalPosition()` from each side.
*
* {
* top: 50,
* right: 50,
* bottom: 50,
* left: 50
* }
*
* @member {Object} #viewportOffsetConfig
*/
/**
* A positioning function which, based on positioned element and target {@link module:utils/dom/rect~Rect Rects}, returns rect coordinates
* representing the geometrical relation between them. Used by the {@link module:utils/dom/position~getOptimalPosition} helper.
*
* // This simple position will place the element directly under the target, in the middle:
* //
* // [ Target ]
* // +-----------------+
* // | Element |
* // +-----------------+
* //
* const position = ( targetRect, elementRect, [ viewportRect ] ) => ( {
* top: targetRect.bottom,
* left: targetRect.left + targetRect.width / 2 - elementRect.width / 2,
* name: 'bottomMiddle',
*
* // Note: The config is optional.
* config: {
* zIndex: '999'
* }
* } );
*
* @callback module:utils/dom/position~positioningFunction
* @param {module:utils/dom/rect~Rect} elementRect The rect of the element to be positioned.
* @param {module:utils/dom/rect~Rect} targetRect The rect of the target the element (its rect) is relatively positioned to.
* @param {module:utils/dom/rect~Rect} viewportRect The rect of the visual browser viewport.
* @returns {Object|null} return When the function returns `null`, it will not be considered by
* {@link module:utils/dom/position~getOptimalPosition}.
* @returns {Number} return.top The `top` value of the element rect that would represent the position.
* @returns {Number} return.left The `left` value of the element rect that would represent the position.
* @returns {Number} return.name The name of the position. It helps the user of the {@link module:utils/dom/position~getOptimalPosition}
* helper to recognize different positioning function results. It will pass through to the {@link module:utils/dom/position~Position}
* returned by the helper.
* @returns {Number} [return.config] An optional configuration that will pass-through the
* {@link module:utils/dom/position~getOptimalPosition} helper to the {@link module:utils/dom/position~Position} returned by this helper.
* This configuration may, for instance, let the user of {@link module:utils/dom/position~getOptimalPosition} know that this particular
* position comes with a certain presentation.
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/rect.js":
/*!****************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-utils/src/dom/rect.js ***!
\****************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ Rect)
/* harmony export */ });
/* harmony import */ var _isrange__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./isrange */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/isrange.js");
/* harmony import */ var _iswindow__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./iswindow */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/iswindow.js");
/* harmony import */ var _getborderwidths__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./getborderwidths */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/getborderwidths.js");
/* harmony import */ var _istext__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./istext */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/istext.js");
/* harmony import */ var lodash_es__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! lodash-es */ "./node_modules/lodash-es/isElement.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module utils/dom/rect
*/
const rectProperties = [ 'top', 'right', 'bottom', 'left', 'width', 'height' ];
/**
* A helper class representing a `ClientRect` object, e.g. value returned by
* the native `object.getBoundingClientRect()` method. Provides a set of methods
* to manipulate the rect and compare it against other rect instances.
*/
class Rect {
/**
* Creates an instance of rect.
*
* // Rect of an HTMLElement.
* const rectA = new Rect( document.body );
*
* // Rect of a DOM Range.
* const rectB = new Rect( document.getSelection().getRangeAt( 0 ) );
*
* // Rect of a window (web browser viewport).
* const rectC = new Rect( window );
*
* // Rect out of an object.
* const rectD = new Rect( { top: 0, right: 10, bottom: 10, left: 0, width: 10, height: 10 } );
*
* // Rect out of another Rect instance.
* const rectE = new Rect( rectD );
*
* // Rect out of a ClientRect.
* const rectF = new Rect( document.body.getClientRects().item( 0 ) );
*
* **Note**: By default a rect of an HTML element includes its CSS borders and scrollbars (if any)
* ant the rect of a `window` includes scrollbars too. Use {@link #excludeScrollbarsAndBorders}
* to get the inner part of the rect.
*
* @param {HTMLElement|Range|Window|ClientRect|DOMRect|module:utils/dom/rect~Rect|Object} source A source object to create the rect.
*/
constructor( source ) {
const isSourceRange = (0,_isrange__WEBPACK_IMPORTED_MODULE_0__["default"])( source );
/**
* The object this rect is for.
*
* @protected
* @readonly
* @member {HTMLElement|Range|Window|ClientRect|DOMRect|module:utils/dom/rect~Rect|Object} #_source
*/
Object.defineProperty( this, '_source', {
// If the source is a Rect instance, copy it's #_source.
value: source._source || source,
writable: true,
enumerable: false
} );
if ( (0,lodash_es__WEBPACK_IMPORTED_MODULE_4__["default"])( source ) || isSourceRange ) {
// The `Rect` class depends on `getBoundingClientRect` and `getClientRects` DOM methods. If the source
// of a rect in an HTML element or a DOM range but it does not belong to any rendered DOM tree, these methods
// will fail to obtain the geometry and the rect instance makes little sense to the features using it.
// To get rid of this warning make sure the source passed to the constructor is a descendant of `window.document.body`.
// @if CK_DEBUG // const sourceNode = isSourceRange ? source.startContainer : source;
// @if CK_DEBUG // if ( !sourceNode.ownerDocument || !sourceNode.ownerDocument.body.contains( sourceNode ) ) {
// @if CK_DEBUG // console.warn(
// @if CK_DEBUG // 'rect-source-not-in-dom: The source of this rect does not belong to any rendered DOM tree.',
// @if CK_DEBUG // { source } );
// @if CK_DEBUG // }
if ( isSourceRange ) {
const rangeRects = Rect.getDomRangeRects( source );
copyRectProperties( this, Rect.getBoundingRect( rangeRects ) );
} else {
copyRectProperties( this, source.getBoundingClientRect() );
}
} else if ( (0,_iswindow__WEBPACK_IMPORTED_MODULE_1__["default"])( source ) ) {
const { innerWidth, innerHeight } = source;
copyRectProperties( this, {
top: 0,
right: innerWidth,
bottom: innerHeight,
left: 0,
width: innerWidth,
height: innerHeight
} );
} else {
copyRectProperties( this, source );
}
/**
* The "top" value of the rect.
*
* @readonly
* @member {Number} #top
*/
/**
* The "right" value of the rect.
*
* @readonly
* @member {Number} #right
*/
/**
* The "bottom" value of the rect.
*
* @readonly
* @member {Number} #bottom
*/
/**
* The "left" value of the rect.
*
* @readonly
* @member {Number} #left
*/
/**
* The "width" value of the rect.
*
* @readonly
* @member {Number} #width
*/
/**
* The "height" value of the rect.
*
* @readonly
* @member {Number} #height
*/
}
/**
* Returns a clone of the rect.
*
* @returns {module:utils/dom/rect~Rect} A cloned rect.
*/
clone() {
return new Rect( this );
}
/**
* Moves the rect so that its upper–left corner lands in desired `[ x, y ]` location.
*
* @param {Number} x Desired horizontal location.
* @param {Number} y Desired vertical location.
* @returns {module:utils/dom/rect~Rect} A rect which has been moved.
*/
moveTo( x, y ) {
this.top = y;
this.right = x + this.width;
this.bottom = y + this.height;
this.left = x;
return this;
}
/**
* Moves the rect in–place by a dedicated offset.
*
* @param {Number} x A horizontal offset.
* @param {Number} y A vertical offset
* @returns {module:utils/dom/rect~Rect} A rect which has been moved.
*/
moveBy( x, y ) {
this.top += y;
this.right += x;
this.left += x;
this.bottom += y;
return this;
}
/**
* Returns a new rect a a result of intersection with another rect.
*
* @param {module:utils/dom/rect~Rect} anotherRect
* @returns {module:utils/dom/rect~Rect}
*/
getIntersection( anotherRect ) {
const rect = {
top: Math.max( this.top, anotherRect.top ),
right: Math.min( this.right, anotherRect.right ),
bottom: Math.min( this.bottom, anotherRect.bottom ),
left: Math.max( this.left, anotherRect.left )
};
rect.width = rect.right - rect.left;
rect.height = rect.bottom - rect.top;
if ( rect.width < 0 || rect.height < 0 ) {
return null;
} else {
return new Rect( rect );
}
}
/**
* Returns the area of intersection with another rect.
*
* @param {module:utils/dom/rect~Rect} anotherRect [description]
* @returns {Number} Area of intersection.
*/
getIntersectionArea( anotherRect ) {
const rect = this.getIntersection( anotherRect );
if ( rect ) {
return rect.getArea();
} else {
return 0;
}
}
/**
* Returns the area of the rect.
*
* @returns {Number}
*/
getArea() {
return this.width * this.height;
}
/**
* Returns a new rect, a part of the original rect, which is actually visible to the user,
* e.g. an original rect cropped by parent element rects which have `overflow` set in CSS
* other than `"visible"`.
*
* If there's no such visible rect, which is when the rect is limited by one or many of
* the ancestors, `null` is returned.
*
* @returns {module:utils/dom/rect~Rect|null} A visible rect instance or `null`, if there's none.
*/
getVisible() {
const source = this._source;
let visibleRect = this.clone();
// There's no ancestor to crop <body> with the overflow.
if ( !isBody( source ) ) {
let parent = source.parentNode || source.commonAncestorContainer;
// Check the ancestors all the way up to the <body>.
while ( parent && !isBody( parent ) ) {
const parentRect = new Rect( parent );
const intersectionRect = visibleRect.getIntersection( parentRect );
if ( intersectionRect ) {
if ( intersectionRect.getArea() < visibleRect.getArea() ) {
// Reduce the visible rect to the intersection.
visibleRect = intersectionRect;
}
} else {
// There's no intersection, the rect is completely invisible.
return null;
}
parent = parent.parentNode;
}
}
return visibleRect;
}
/**
* Checks if all property values ({@link #top}, {@link #left}, {@link #right},
* {@link #bottom}, {@link #width} and {@link #height}) are the equal in both rect
* instances.
*
* @param {module:utils/dom/rect~Rect} rect A rect instance to compare with.
* @returns {Boolean} `true` when Rects are equal. `false` otherwise.
*/
isEqual( anotherRect ) {
for ( const prop of rectProperties ) {
if ( this[ prop ] !== anotherRect[ prop ] ) {
return false;
}
}
return true;
}
/**
* Checks whether a rect fully contains another rect instance.
*
* @param {module:utils/dom/rect~Rect} anotherRect
* @returns {Boolean} `true` if contains, `false` otherwise.
*/
contains( anotherRect ) {
const intersectRect = this.getIntersection( anotherRect );
return !!( intersectRect && intersectRect.isEqual( anotherRect ) );
}
/**
* Excludes scrollbars and CSS borders from the rect.
*
* * Borders are removed when {@link #_source} is an HTML element.
* * Scrollbars are excluded from HTML elements and the `window`.
*
* @returns {module:utils/dom/rect~Rect} A rect which has been updated.
*/
excludeScrollbarsAndBorders() {
const source = this._source;
let scrollBarWidth, scrollBarHeight, direction;
if ( (0,_iswindow__WEBPACK_IMPORTED_MODULE_1__["default"])( source ) ) {
scrollBarWidth = source.innerWidth - source.document.documentElement.clientWidth;
scrollBarHeight = source.innerHeight - source.document.documentElement.clientHeight;
direction = source.getComputedStyle( source.document.documentElement ).direction;
} else {
const borderWidths = (0,_getborderwidths__WEBPACK_IMPORTED_MODULE_2__["default"])( this._source );
scrollBarWidth = source.offsetWidth - source.clientWidth - borderWidths.left - borderWidths.right;
scrollBarHeight = source.offsetHeight - source.clientHeight - borderWidths.top - borderWidths.bottom;
direction = source.ownerDocument.defaultView.getComputedStyle( source ).direction;
this.left += borderWidths.left;
this.top += borderWidths.top;
this.right -= borderWidths.right;
this.bottom -= borderWidths.bottom;
this.width = this.right - this.left;
this.height = this.bottom - this.top;
}
this.width -= scrollBarWidth;
if ( direction === 'ltr' ) {
this.right -= scrollBarWidth;
} else {
this.left += scrollBarWidth;
}
this.height -= scrollBarHeight;
this.bottom -= scrollBarHeight;
return this;
}
/**
* Returns an array of rects of the given native DOM Range.
*
* @param {Range} range A native DOM range.
* @returns {Array.<module:utils/dom/rect~Rect>} DOM Range rects.
*/
static getDomRangeRects( range ) {
const rects = [];
// Safari does not iterate over ClientRectList using for...of loop.
const clientRects = Array.from( range.getClientRects() );
if ( clientRects.length ) {
for ( const rect of clientRects ) {
rects.push( new Rect( rect ) );
}
}
// If there's no client rects for the Range, use parent container's bounding rect
// instead and adjust rect's width to simulate the actual geometry of such range.
// https://github.com/ckeditor/ckeditor5-utils/issues/153
// https://github.com/ckeditor/ckeditor5-ui/issues/317
else {
let startContainer = range.startContainer;
if ( (0,_istext__WEBPACK_IMPORTED_MODULE_3__["default"])( startContainer ) ) {
startContainer = startContainer.parentNode;
}
const rect = new Rect( startContainer.getBoundingClientRect() );
rect.right = rect.left;
rect.width = 0;
rects.push( rect );
}
return rects;
}
/**
* Returns a bounding rectangle that contains all the given `rects`.
*
* @param {Iterable.<module:utils/dom/rect~Rect>} rects A list of rectangles that should be contained in the result rectangle.
* @returns {module:utils/dom/rect~Rect|null} Bounding rectangle or `null` if no `rects` were given.
*/
static getBoundingRect( rects ) {
const boundingRectData = {
left: Number.POSITIVE_INFINITY,
top: Number.POSITIVE_INFINITY,
right: Number.NEGATIVE_INFINITY,
bottom: Number.NEGATIVE_INFINITY
};
let rectangleCount = 0;
for ( const rect of rects ) {
rectangleCount++;
boundingRectData.left = Math.min( boundingRectData.left, rect.left );
boundingRectData.top = Math.min( boundingRectData.top, rect.top );
boundingRectData.right = Math.max( boundingRectData.right, rect.right );
boundingRectData.bottom = Math.max( boundingRectData.bottom, rect.bottom );
}
if ( rectangleCount == 0 ) {
return null;
}
boundingRectData.width = boundingRectData.right - boundingRectData.left;
boundingRectData.height = boundingRectData.bottom - boundingRectData.top;
return new Rect( boundingRectData );
}
}
// Acquires all the rect properties from the passed source.
//
// @private
// @param {module:utils/dom/rect~Rect} rect
// @param {ClientRect|module:utils/dom/rect~Rect|Object} source
function copyRectProperties( rect, source ) {
for ( const p of rectProperties ) {
rect[ p ] = source[ p ];
}
}
// Checks if provided object is a <body> HTML element.
//
// @private
// @param {HTMLElement|Range} elementOrRange
// @returns {Boolean}
function isBody( elementOrRange ) {
if ( !(0,lodash_es__WEBPACK_IMPORTED_MODULE_4__["default"])( elementOrRange ) ) {
return false;
}
return elementOrRange === elementOrRange.ownerDocument.body;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/remove.js":
/*!******************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-utils/src/dom/remove.js ***!
\******************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ remove)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module utils/dom/remove
*/
/**
* Removes given node from parent.
*
* @param {Node} node Node to remove.
*/
function remove( node ) {
const parent = node.parentNode;
if ( parent ) {
parent.removeChild( node );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/resizeobserver.js":
/*!**************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-utils/src/dom/resizeobserver.js ***!
\**************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ResizeObserver)
/* harmony export */ });
/* harmony import */ var _mix__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../mix */ "./node_modules/@ckeditor/ckeditor5-utils/src/mix.js");
/* harmony import */ var _global__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./global */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/global.js");
/* harmony import */ var _rect__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./rect */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/rect.js");
/* harmony import */ var _emittermixin__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./emittermixin */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/emittermixin.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module utils/dom/resizeobserver
*/
/* globals setTimeout, clearTimeout */
const RESIZE_CHECK_INTERVAL = 100;
/**
* A helper class which instances allow performing custom actions when native DOM elements are resized.
*
* const editableElement = editor.editing.view.getDomRoot();
*
* const observer = new ResizeObserver( editableElement, entry => {
* console.log( 'The editable element has been resized in DOM.' );
* console.log( entry.target ); // -> editableElement
* console.log( entry.contentRect.width ); // -> e.g. '423px'
* } );
*
* By default, it uses the [native DOM resize observer](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver)
* under the hood and in browsers that do not support the native API yet, a polyfilled observer is
* used instead.
*/
class ResizeObserver {
/**
* Creates an instance of the `ResizeObserver` class.
*
* @param {HTMLElement} element A DOM element that is to be observed for resizing. Note that
* the element must be visible (i.e. not detached from DOM) for the observer to work.
* @param {Function} callback A function called when the observed element was resized. It passes
* the [`ResizeObserverEntry`](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserverEntry)
* object with information about the resize event.
*/
constructor( element, callback ) {
// **Note**: For the maximum performance, this class ensures only a single instance of the native
// (or polyfilled) observer is used no matter how many instances of this class were created.
if ( !ResizeObserver._observerInstance ) {
ResizeObserver._createObserver();
}
/**
* The element observer by this observer.
*
* @readonly
* @private
* @member {HTMLElement}
*/
this._element = element;
/**
* The callback executed each time {@link #_element} is resized.
*
* @readonly
* @private
* @member {Function}
*/
this._callback = callback;
ResizeObserver._addElementCallback( element, callback );
ResizeObserver._observerInstance.observe( element );
}
/**
* Destroys the observer which disables the `callback` passed to the {@link #constructor}.
*/
destroy() {
ResizeObserver._deleteElementCallback( this._element, this._callback );
}
/**
* Registers a new resize callback for the DOM element.
*
* @private
* @static
* @param {HTMLElement} element
* @param {Function} callback
*/
static _addElementCallback( element, callback ) {
if ( !ResizeObserver._elementCallbacks ) {
ResizeObserver._elementCallbacks = new Map();
}
let callbacks = ResizeObserver._elementCallbacks.get( element );
if ( !callbacks ) {
callbacks = new Set();
ResizeObserver._elementCallbacks.set( element, callbacks );
}
callbacks.add( callback );
}
/**
* Removes a resize callback from the DOM element. If no callbacks are left
* for the element, it removes the element from the native observer.
*
* @private
* @static
* @param {HTMLElement} element
* @param {Function} callback
*/
static _deleteElementCallback( element, callback ) {
const callbacks = ResizeObserver._getElementCallbacks( element );
// Remove the element callback. Check if exist first in case someone
// called destroy() twice.
if ( callbacks ) {
callbacks.delete( callback );
// If no callbacks left for the element, also remove the element.
if ( !callbacks.size ) {
ResizeObserver._elementCallbacks.delete( element );
ResizeObserver._observerInstance.unobserve( element );
}
}
if ( ResizeObserver._elementCallbacks && !ResizeObserver._elementCallbacks.size ) {
ResizeObserver._observerInstance = null;
ResizeObserver._elementCallbacks = null;
}
}
/**
* Returns are registered resize callbacks for the DOM element.
*
* @private
* @static
* @param {HTMLElement} element
* @returns {Set.<HTMLElement>|null}
*/
static _getElementCallbacks( element ) {
if ( !ResizeObserver._elementCallbacks ) {
return null;
}
return ResizeObserver._elementCallbacks.get( element );
}
/**
* Creates the single native observer shared across all `ResizeObserver` instances.
* If the browser does not support the native API, it creates a polyfill.
*
* @private
* @static
*/
static _createObserver() {
let ObserverConstructor;
// TODO: One day, the `ResizeObserver` API will be supported in all modern web browsers.
// When it happens, this module will no longer make sense and should be removed and
// the native implementation should be used across the project to save bytes.
// Check out https://caniuse.com/#feat=resizeobserver.
if ( typeof _global__WEBPACK_IMPORTED_MODULE_1__["default"].window.ResizeObserver === 'function' ) {
ObserverConstructor = _global__WEBPACK_IMPORTED_MODULE_1__["default"].window.ResizeObserver;
} else {
ObserverConstructor = ResizeObserverPolyfill;
}
ResizeObserver._observerInstance = new ObserverConstructor( entries => {
for ( const entry of entries ) {
const callbacks = ResizeObserver._getElementCallbacks( entry.target );
if ( callbacks ) {
for ( const callback of callbacks ) {
callback( entry );
}
}
}
} );
}
}
/**
* The single native observer instance (or polyfill in browsers that do not support the API)
* shared across all {@link module:utils/dom/resizeobserver~ResizeObserver} instances.
*
* @static
* @protected
* @readonly
* @property {Object|null} module:utils/dom/resizeobserver~ResizeObserver#_observerInstance
*/
ResizeObserver._observerInstance = null;
/**
* A mapping of native DOM elements and their callbacks shared across all
* {@link module:utils/dom/resizeobserver~ResizeObserver} instances.
*
* @static
* @private
* @readonly
* @property {Map.<HTMLElement,Set>|null} module:utils/dom/resizeobserver~ResizeObserver#_elementCallbacks
*/
ResizeObserver._elementCallbacks = null;
/**
* A polyfill class for the native [`ResizeObserver`](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver).
*
* @private
* @mixes module:utils/domemittermixin~DomEmitterMixin
*/
class ResizeObserverPolyfill {
/**
* Creates an instance of the {@link module:utils/dom/resizeobserver~ResizeObserverPolyfill} class.
*
* It synchronously reacts to resize of the window to check if observed elements' geometry changed.
*
* Additionally, the polyfilled observer uses a timeout to check if observed elements' geometry has changed
* in some other way (dynamic layouts, scrollbars showing up, etc.), so its response can also be asynchronous.
*
* @param {Function} callback A function called when any observed element was resized. Refer to the
* native [`ResizeObserver`](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver) API to
* learn more.
*/
constructor( callback ) {
/**
* A function called when any observed {@link #_elements element} was resized.
*
* @readonly
* @protected
* @member {Function}
*/
this._callback = callback;
/**
* DOM elements currently observed by the observer instance.
*
* @readonly
* @protected
* @member {Set}
*/
this._elements = new Set();
/**
* Cached DOM {@link #_elements elements} bounding rects to compare to upon the next check.
*
* @readonly
* @protected
* @member {Map.<HTMLElement,module:utils/dom/rect~Rect>}
*/
this._previousRects = new Map();
/**
* An UID of the current timeout upon which the observed elements rects
* will be compared to the {@link #_previousRects previous rects} from the past.
*
* @readonly
* @protected
* @member {Map.<HTMLElement,module:utils/dom/rect~Rect>}
*/
this._periodicCheckTimeout = null;
}
/**
* Starts observing a DOM element.
*
* Learn more in the
* [native method documentation](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver/observe).
*
* @param {HTMLElement} element
*/
observe( element ) {
this._elements.add( element );
this._checkElementRectsAndExecuteCallback();
if ( this._elements.size === 1 ) {
this._startPeriodicCheck();
}
}
/**
* Stops observing a DOM element.
*
* Learn more in the
* [native method documentation](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver/unobserve).
*
* @param {HTMLElement} element
*/
unobserve( element ) {
this._elements.delete( element );
this._previousRects.delete( element );
if ( !this._elements.size ) {
this._stopPeriodicCheck();
}
}
/**
* When called, the observer calls the {@link #_callback resize callback} for all observed
* {@link #_elements elements} but also starts checking periodically for changes in the elements' geometry.
* If some are detected, {@link #_callback resize callback} is called for relevant elements that were resized.
*
* @protected
*/
_startPeriodicCheck() {
const periodicCheck = () => {
this._checkElementRectsAndExecuteCallback();
this._periodicCheckTimeout = setTimeout( periodicCheck, RESIZE_CHECK_INTERVAL );
};
this.listenTo( _global__WEBPACK_IMPORTED_MODULE_1__["default"].window, 'resize', () => {
this._checkElementRectsAndExecuteCallback();
} );
this._periodicCheckTimeout = setTimeout( periodicCheck, RESIZE_CHECK_INTERVAL );
}
/**
* Stops checking for changes in all observed {@link #_elements elements} geometry.
*
* @protected
*/
_stopPeriodicCheck() {
clearTimeout( this._periodicCheckTimeout );
this.stopListening();
this._previousRects.clear();
}
/**
* Checks if the geometry of any of the {@link #_elements element} has changed. If so, executes
* the {@link #_callback resize callback} with element geometry data.
*
* @protected
*/
_checkElementRectsAndExecuteCallback() {
const entries = [];
for ( const element of this._elements ) {
if ( this._hasRectChanged( element ) ) {
entries.push( {
target: element,
contentRect: this._previousRects.get( element )
} );
}
}
if ( entries.length ) {
this._callback( entries );
}
}
/**
* Compares the DOM element geometry to the {@link #_previousRects cached geometry} from the past.
* Returns `true` if geometry has changed or the element is checked for the first time.
*
* @protected
* @param {HTMLElement} element
* @returns {Boolean}
*/
_hasRectChanged( element ) {
if ( !element.ownerDocument.body.contains( element ) ) {
return false;
}
const currentRect = new _rect__WEBPACK_IMPORTED_MODULE_2__["default"]( element );
const previousRect = this._previousRects.get( element );
// The first check should always yield true despite no Previous rect to compare to.
// The native ResizeObserver does that and... that makes sense. Sort of.
const hasChanged = !previousRect || !previousRect.isEqual( currentRect );
this._previousRects.set( element, currentRect );
return hasChanged;
}
}
(0,_mix__WEBPACK_IMPORTED_MODULE_0__["default"])( ResizeObserverPolyfill, _emittermixin__WEBPACK_IMPORTED_MODULE_3__["default"] );
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/scroll.js":
/*!******************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-utils/src/dom/scroll.js ***!
\******************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "scrollAncestorsToShowTarget": () => (/* binding */ scrollAncestorsToShowTarget),
/* harmony export */ "scrollViewportToShowTarget": () => (/* binding */ scrollViewportToShowTarget)
/* harmony export */ });
/* harmony import */ var _isrange__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./isrange */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/isrange.js");
/* harmony import */ var _rect__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./rect */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/rect.js");
/* harmony import */ var _istext__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./istext */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/istext.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module utils/dom/scroll
*/
const utils = {};
/**
* Makes any page `HTMLElement` or `Range` (`target`) visible inside the browser viewport.
* This helper will scroll all `target` ancestors and the web browser viewport to reveal the target to
* the user. If the `target` is already visible, nothing will happen.
*
* @param {HTMLElement|Range} options.target A target, which supposed to become visible to the user.
* @param {Number} [options.viewportOffset] An offset from the edge of the viewport (in pixels)
* the `target` will be moved by when the viewport is scrolled. It enhances the user experience
* by keeping the `target` some distance from the edge of the viewport and thus making it easier to
* read or edit by the user.
*/
function scrollViewportToShowTarget( { target, viewportOffset = 0 } ) {
const targetWindow = getWindow( target );
let currentWindow = targetWindow;
let currentFrame = null;
// Iterate over all windows, starting from target's parent window up to window#top.
while ( currentWindow ) {
let firstAncestorToScroll;
// Let's scroll target's ancestors first to reveal it. Then, once the ancestor scrolls
// settled down, the algorithm can eventually scroll the viewport of the current window.
//
// Note: If the current window is target's **original** window (e.g. the first one),
// start scrolling the closest parent of the target. If not, scroll the closest parent
// of an iframe that resides in the current window.
if ( currentWindow == targetWindow ) {
firstAncestorToScroll = getParentElement( target );
} else {
firstAncestorToScroll = getParentElement( currentFrame );
}
// Scroll the target's ancestors first. Once done, scrolling the viewport is easy.
scrollAncestorsToShowRect( firstAncestorToScroll, () => {
// Note: If the target does not belong to the current window **directly**,
// i.e. it resides in an iframe belonging to the window, obtain the target's rect
// in the coordinates of the current window. By default, a Rect returns geometry
// relative to the current window's viewport. To make it work in a parent window,
// it must be shifted.
return getRectRelativeToWindow( target, currentWindow );
} );
// Obtain the rect of the target after it has been scrolled within its ancestors.
// It's time to scroll the viewport.
const targetRect = getRectRelativeToWindow( target, currentWindow );
scrollWindowToShowRect( currentWindow, targetRect, viewportOffset );
if ( currentWindow.parent != currentWindow ) {
// Keep the reference to the <iframe> element the "previous current window" was
// rendered within. It will be useful to re–calculate the rect of the target
// in the parent window's relative geometry. The target's rect must be shifted
// by it's iframe's position.
currentFrame = currentWindow.frameElement;
currentWindow = currentWindow.parent;
// If the current window has some parent but frameElement is inaccessible, then they have
// different domains/ports and, due to security reasons, accessing and scrolling
// the parent window won't be possible.
// See https://github.com/ckeditor/ckeditor5/issues/930.
if ( !currentFrame ) {
return;
}
} else {
currentWindow = null;
}
}
}
/**
* Makes any page `HTMLElement` or `Range` (target) visible within its scrollable ancestors,
* e.g. if they have `overflow: scroll` CSS style.
*
* @param {HTMLElement|Range} target A target, which supposed to become visible to the user.
*/
function scrollAncestorsToShowTarget( target ) {
const targetParent = getParentElement( target );
scrollAncestorsToShowRect( targetParent, () => {
return new _rect__WEBPACK_IMPORTED_MODULE_1__["default"]( target );
} );
}
// TODO: Using a property value shorthand in the top of the file
// causes JSDoc to throw errors. See https://github.com/cksource/docs-builder/issues/75.
Object.assign( utils, {
scrollViewportToShowTarget,
scrollAncestorsToShowTarget
} );
// Makes a given rect visible within its parent window.
//
// Note: Avoid the situation where the caret is still in the viewport, but totally
// at the edge of it. In such situation, if it moved beyond the viewport in the next
// action e.g. after paste, the scrolling would move it to the viewportOffset level
// and it all would look like the caret visually moved up/down:
//
// 1.
// | foo[]
// | <--- N px of space below the caret
// +---------------------------------...
//
// 2. *paste*
// 3.
// |
// |
// +-foo-----------------------------...
// bar[] <--- caret below viewport, scrolling...
//
// 4. *scrolling*
// 5.
// |
// | foo
// | bar[] <--- caret precisely at the edge
// +---------------------------------...
//
// To prevent this, this method checks the rects moved by the viewportOffset to cover
// the upper/lower edge of the viewport. It makes sure if the action repeats, there's
// no twitching – it's a purely visual improvement:
//
// 5. (after fix)
// |
// | foo
// | bar[]
// | <--- N px of space below the caret
// +---------------------------------...
//
// @private
// @param {Window} window A window which is scrolled to reveal the rect.
// @param {module:utils/dom/rect~Rect} rect A rect which is to be revealed.
// @param {Number} viewportOffset See scrollViewportToShowTarget.
function scrollWindowToShowRect( window, rect, viewportOffset ) {
const targetShiftedDownRect = rect.clone().moveBy( 0, viewportOffset );
const targetShiftedUpRect = rect.clone().moveBy( 0, -viewportOffset );
const viewportRect = new _rect__WEBPACK_IMPORTED_MODULE_1__["default"]( window ).excludeScrollbarsAndBorders();
const rects = [ targetShiftedUpRect, targetShiftedDownRect ];
if ( !rects.every( rect => viewportRect.contains( rect ) ) ) {
let { scrollX, scrollY } = window;
if ( isAbove( targetShiftedUpRect, viewportRect ) ) {
scrollY -= viewportRect.top - rect.top + viewportOffset;
} else if ( isBelow( targetShiftedDownRect, viewportRect ) ) {
scrollY += rect.bottom - viewportRect.bottom + viewportOffset;
}
// TODO: Web browsers scroll natively to place the target in the middle
// of the viewport. It's not a very popular case, though.
if ( isLeftOf( rect, viewportRect ) ) {
scrollX -= viewportRect.left - rect.left + viewportOffset;
} else if ( isRightOf( rect, viewportRect ) ) {
scrollX += rect.right - viewportRect.right + viewportOffset;
}
window.scrollTo( scrollX, scrollY );
}
}
// Recursively scrolls element ancestors to visually reveal a rect.
//
// @private
// @param {HTMLElement} A parent The first ancestors to start scrolling.
// @param {Function} getRect A function which returns the Rect, which is to be revealed.
function scrollAncestorsToShowRect( parent, getRect ) {
const parentWindow = getWindow( parent );
let parentRect, targetRect;
while ( parent != parentWindow.document.body ) {
targetRect = getRect();
parentRect = new _rect__WEBPACK_IMPORTED_MODULE_1__["default"]( parent ).excludeScrollbarsAndBorders();
if ( !parentRect.contains( targetRect ) ) {
if ( isAbove( targetRect, parentRect ) ) {
parent.scrollTop -= parentRect.top - targetRect.top;
} else if ( isBelow( targetRect, parentRect ) ) {
parent.scrollTop += targetRect.bottom - parentRect.bottom;
}
if ( isLeftOf( targetRect, parentRect ) ) {
parent.scrollLeft -= parentRect.left - targetRect.left;
} else if ( isRightOf( targetRect, parentRect ) ) {
parent.scrollLeft += targetRect.right - parentRect.right;
}
}
parent = parent.parentNode;
}
}
// Determines if a given `Rect` extends beyond the bottom edge of the second `Rect`.
//
// @private
// @param {module:utils/dom/rect~Rect} firstRect
// @param {module:utils/dom/rect~Rect} secondRect
function isBelow( firstRect, secondRect ) {
return firstRect.bottom > secondRect.bottom;
}
// Determines if a given `Rect` extends beyond the top edge of the second `Rect`.
//
// @private
// @param {module:utils/dom/rect~Rect} firstRect
// @param {module:utils/dom/rect~Rect} secondRect
function isAbove( firstRect, secondRect ) {
return firstRect.top < secondRect.top;
}
// Determines if a given `Rect` extends beyond the left edge of the second `Rect`.
//
// @private
// @param {module:utils/dom/rect~Rect} firstRect
// @param {module:utils/dom/rect~Rect} secondRect
function isLeftOf( firstRect, secondRect ) {
return firstRect.left < secondRect.left;
}
// Determines if a given `Rect` extends beyond the right edge of the second `Rect`.
//
// @private
// @param {module:utils/dom/rect~Rect} firstRect
// @param {module:utils/dom/rect~Rect} secondRect
function isRightOf( firstRect, secondRect ) {
return firstRect.right > secondRect.right;
}
// Returns the closest window of an element or range.
//
// @private
// @param {HTMLElement|Range} firstRect
// @returns {Window}
function getWindow( elementOrRange ) {
if ( (0,_isrange__WEBPACK_IMPORTED_MODULE_0__["default"])( elementOrRange ) ) {
return elementOrRange.startContainer.ownerDocument.defaultView;
} else {
return elementOrRange.ownerDocument.defaultView;
}
}
// Returns the closest parent of an element or DOM range.
//
// @private
// @param {HTMLElement|Range} firstRect
// @returns {HTMLelement}
function getParentElement( elementOrRange ) {
if ( (0,_isrange__WEBPACK_IMPORTED_MODULE_0__["default"])( elementOrRange ) ) {
let parent = elementOrRange.commonAncestorContainer;
// If a Range is attached to the Text, use the closest element ancestor.
if ( (0,_istext__WEBPACK_IMPORTED_MODULE_2__["default"])( parent ) ) {
parent = parent.parentNode;
}
return parent;
} else {
return elementOrRange.parentNode;
}
}
// Returns the rect of an element or range residing in an iframe.
// The result rect is relative to the geometry of the passed window instance.
//
// @private
// @param {HTMLElement|Range} target Element or range which rect should be returned.
// @param {Window} relativeWindow A window the rect should be relative to.
// @returns {module:utils/dom/rect~Rect}
function getRectRelativeToWindow( target, relativeWindow ) {
const targetWindow = getWindow( target );
const rect = new _rect__WEBPACK_IMPORTED_MODULE_1__["default"]( target );
if ( targetWindow === relativeWindow ) {
return rect;
} else {
let currentWindow = targetWindow;
while ( currentWindow != relativeWindow ) {
const frame = currentWindow.frameElement;
const frameRect = new _rect__WEBPACK_IMPORTED_MODULE_1__["default"]( frame ).excludeScrollbarsAndBorders();
rect.moveBy( frameRect.left, frameRect.top );
currentWindow = currentWindow.parent;
}
}
return rect;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/setdatainelement.js":
/*!****************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-utils/src/dom/setdatainelement.js ***!
\****************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ setDataInElement)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module utils/dom/setdatainelement
*/
/* globals HTMLTextAreaElement */
/**
* Sets data in a given element.
*
* @param {HTMLElement} el The element in which the data will be set.
* @param {String} data The data string.
*/
function setDataInElement( el, data ) {
if ( el instanceof HTMLTextAreaElement ) {
el.value = data;
}
el.innerHTML = data;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/tounit.js":
/*!******************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-utils/src/dom/tounit.js ***!
\******************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ toUnit)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module utils/dom/tounit
*/
/**
* Returns a helper function, which adds a desired trailing
* `unit` to the passed value.
*
* @param {String} unit An unit like "px" or "em".
* @returns {module:utils/dom/tounit~helper}
*/
function toUnit( unit ) {
/**
* A function, which adds a pre–defined trailing `unit`
* to the passed `value`.
*
* @function helper
* @param {*} value A value to be given the unit.
* @returns {String} A value with the trailing unit.
*/
return value => value + unit;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-utils/src/elementreplacer.js":
/*!***********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-utils/src/elementreplacer.js ***!
\***********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ElementReplacer)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module utils/elementreplacer
*/
/**
* Utility class allowing to hide existing HTML elements or replace them with given ones in a way that doesn't remove
* the original elements from the DOM.
*/
class ElementReplacer {
constructor() {
/**
* The elements replaced by {@link #replace} and their replacements.
*
* @private
* @member {Array.<Object>}
*/
this._replacedElements = [];
}
/**
* Hides the `element` and, if specified, inserts the the given element next to it.
*
* The effect of this method can be reverted by {@link #restore}.
*
* @param {HTMLElement} element The element to replace.
* @param {HTMLElement} [newElement] The replacement element. If not passed, then the `element` will just be hidden.
*/
replace( element, newElement ) {
this._replacedElements.push( { element, newElement } );
element.style.display = 'none';
if ( newElement ) {
element.parentNode.insertBefore( newElement, element.nextSibling );
}
}
/**
* Restores what {@link #replace} did.
*/
restore() {
this._replacedElements.forEach( ( { element, newElement } ) => {
element.style.display = '';
if ( newElement ) {
newElement.remove();
}
} );
this._replacedElements = [];
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-utils/src/emittermixin.js":
/*!********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-utils/src/emittermixin.js ***!
\********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "_getEmitterId": () => (/* binding */ _getEmitterId),
/* harmony export */ "_getEmitterListenedTo": () => (/* binding */ _getEmitterListenedTo),
/* harmony export */ "_setEmitterId": () => (/* binding */ _setEmitterId),
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _eventinfo__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./eventinfo */ "./node_modules/@ckeditor/ckeditor5-utils/src/eventinfo.js");
/* harmony import */ var _uid__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./uid */ "./node_modules/@ckeditor/ckeditor5-utils/src/uid.js");
/* harmony import */ var _priorities__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./priorities */ "./node_modules/@ckeditor/ckeditor5-utils/src/priorities.js");
/* harmony import */ var _version__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./version */ "./node_modules/@ckeditor/ckeditor5-utils/src/version.js");
/* harmony import */ var _ckeditorerror__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module utils/emittermixin
*/
// To check if component is loaded more than once.
const _listeningTo = Symbol( 'listeningTo' );
const _emitterId = Symbol( 'emitterId' );
/**
* Mixin that injects the {@link ~Emitter events API} into its host.
*
* Read more about the concept of emitters in the:
* * {@glink framework/guides/architecture/core-editor-architecture#event-system-and-observables Event system and observables}
* section of the {@glink framework/guides/architecture/core-editor-architecture Core editor architecture} guide.
* * {@glink framework/guides/deep-dive/event-system Event system} deep dive guide.
*
* @mixin EmitterMixin
* @implements module:utils/emittermixin~Emitter
*/
const EmitterMixin = {
/**
* @inheritDoc
*/
on( event, callback, options = {} ) {
this.listenTo( this, event, callback, options );
},
/**
* @inheritDoc
*/
once( event, callback, options ) {
let wasFired = false;
const onceCallback = function( event, ...args ) {
// Ensure the callback is called only once even if the callback itself leads to re-firing the event
// (which would call the callback again).
if ( !wasFired ) {
wasFired = true;
// Go off() at the first call.
event.off();
// Go with the original callback.
callback.call( this, event, ...args );
}
};
// Make a similar on() call, simply replacing the callback.
this.listenTo( this, event, onceCallback, options );
},
/**
* @inheritDoc
*/
off( event, callback ) {
this.stopListening( this, event, callback );
},
/**
* @inheritDoc
*/
listenTo( emitter, event, callback, options = {} ) {
let emitterInfo, eventCallbacks;
// _listeningTo contains a list of emitters that this object is listening to.
// This list has the following format:
//
// _listeningTo: {
// emitterId: {
// emitter: emitter,
// callbacks: {
// event1: [ callback1, callback2, ... ]
// ....
// }
// },
// ...
// }
if ( !this[ _listeningTo ] ) {
this[ _listeningTo ] = {};
}
const emitters = this[ _listeningTo ];
if ( !_getEmitterId( emitter ) ) {
_setEmitterId( emitter );
}
const emitterId = _getEmitterId( emitter );
if ( !( emitterInfo = emitters[ emitterId ] ) ) {
emitterInfo = emitters[ emitterId ] = {
emitter,
callbacks: {}
};
}
if ( !( eventCallbacks = emitterInfo.callbacks[ event ] ) ) {
eventCallbacks = emitterInfo.callbacks[ event ] = [];
}
eventCallbacks.push( callback );
// Finally register the callback to the event.
addEventListener( this, emitter, event, callback, options );
},
/**
* @inheritDoc
*/
stopListening( emitter, event, callback ) {
const emitters = this[ _listeningTo ];
let emitterId = emitter && _getEmitterId( emitter );
const emitterInfo = emitters && emitterId && emitters[ emitterId ];
const eventCallbacks = emitterInfo && event && emitterInfo.callbacks[ event ];
// Stop if nothing has been listened.
if ( !emitters || ( emitter && !emitterInfo ) || ( event && !eventCallbacks ) ) {
return;
}
// All params provided. off() that single callback.
if ( callback ) {
removeEventListener( this, emitter, event, callback );
// We must remove callbacks as well in order to prevent memory leaks.
// See https://github.com/ckeditor/ckeditor5/pull/8480
const index = eventCallbacks.indexOf( callback );
if ( index !== -1 ) {
if ( eventCallbacks.length === 1 ) {
delete emitterInfo.callbacks[ event ];
} else {
removeEventListener( this, emitter, event, callback );
}
}
}
// Only `emitter` and `event` provided. off() all callbacks for that event.
else if ( eventCallbacks ) {
while ( ( callback = eventCallbacks.pop() ) ) {
removeEventListener( this, emitter, event, callback );
}
delete emitterInfo.callbacks[ event ];
}
// Only `emitter` provided. off() all events for that emitter.
else if ( emitterInfo ) {
for ( event in emitterInfo.callbacks ) {
this.stopListening( emitter, event );
}
delete emitters[ emitterId ];
}
// No params provided. off() all emitters.
else {
for ( emitterId in emitters ) {
this.stopListening( emitters[ emitterId ].emitter );
}
delete this[ _listeningTo ];
}
},
/**
* @inheritDoc
*/
fire( eventOrInfo, ...args ) {
try {
const eventInfo = eventOrInfo instanceof _eventinfo__WEBPACK_IMPORTED_MODULE_0__["default"] ? eventOrInfo : new _eventinfo__WEBPACK_IMPORTED_MODULE_0__["default"]( this, eventOrInfo );
const event = eventInfo.name;
let callbacks = getCallbacksForEvent( this, event );
// Record that the event passed this emitter on its path.
eventInfo.path.push( this );
// Handle event listener callbacks first.
if ( callbacks ) {
// Arguments passed to each callback.
const callbackArgs = [ eventInfo, ...args ];
// Copying callbacks array is the easiest and most secure way of preventing infinite loops, when event callbacks
// are added while processing other callbacks. Previous solution involved adding counters (unique ids) but
// failed if callbacks were added to the queue before currently processed callback.
// If this proves to be too inefficient, another method is to change `.on()` so callbacks are stored if same
// event is currently processed. Then, `.fire()` at the end, would have to add all stored events.
callbacks = Array.from( callbacks );
for ( let i = 0; i < callbacks.length; i++ ) {
callbacks[ i ].callback.apply( this, callbackArgs );
// Remove the callback from future requests if off() has been called.
if ( eventInfo.off.called ) {
// Remove the called mark for the next calls.
delete eventInfo.off.called;
this._removeEventListener( event, callbacks[ i ].callback );
}
// Do not execute next callbacks if stop() was called.
if ( eventInfo.stop.called ) {
break;
}
}
}
// Delegate event to other emitters if needed.
if ( this._delegations ) {
const destinations = this._delegations.get( event );
const passAllDestinations = this._delegations.get( '*' );
if ( destinations ) {
fireDelegatedEvents( destinations, eventInfo, args );
}
if ( passAllDestinations ) {
fireDelegatedEvents( passAllDestinations, eventInfo, args );
}
}
return eventInfo.return;
} catch ( err ) {
// @if CK_DEBUG // throw err;
/* istanbul ignore next */
_ckeditorerror__WEBPACK_IMPORTED_MODULE_4__["default"].rethrowUnexpectedError( err, this );
}
},
/**
* @inheritDoc
*/
delegate( ...events ) {
return {
to: ( emitter, nameOrFunction ) => {
if ( !this._delegations ) {
this._delegations = new Map();
}
// Originally there was a for..of loop which unfortunately caused an error in Babel that didn't allow
// build an application. See: https://github.com/ckeditor/ckeditor5-react/issues/40.
events.forEach( eventName => {
const destinations = this._delegations.get( eventName );
if ( !destinations ) {
this._delegations.set( eventName, new Map( [ [ emitter, nameOrFunction ] ] ) );
} else {
destinations.set( emitter, nameOrFunction );
}
} );
}
};
},
/**
* @inheritDoc
*/
stopDelegating( event, emitter ) {
if ( !this._delegations ) {
return;
}
if ( !event ) {
this._delegations.clear();
} else if ( !emitter ) {
this._delegations.delete( event );
} else {
const destinations = this._delegations.get( event );
if ( destinations ) {
destinations.delete( emitter );
}
}
},
/**
* @inheritDoc
*/
_addEventListener( event, callback, options ) {
createEventNamespace( this, event );
const lists = getCallbacksListsForNamespace( this, event );
const priority = _priorities__WEBPACK_IMPORTED_MODULE_2__["default"].get( options.priority );
const callbackDefinition = {
callback,
priority
};
// Add the callback to all callbacks list.
for ( const callbacks of lists ) {
// Add the callback to the list in the right priority position.
let added = false;
for ( let i = 0; i < callbacks.length; i++ ) {
if ( callbacks[ i ].priority < priority ) {
callbacks.splice( i, 0, callbackDefinition );
added = true;
break;
}
}
// Add at the end, if right place was not found.
if ( !added ) {
callbacks.push( callbackDefinition );
}
}
},
/**
* @inheritDoc
*/
_removeEventListener( event, callback ) {
const lists = getCallbacksListsForNamespace( this, event );
for ( const callbacks of lists ) {
for ( let i = 0; i < callbacks.length; i++ ) {
if ( callbacks[ i ].callback == callback ) {
// Remove the callback from the list (fixing the next index).
callbacks.splice( i, 1 );
i--;
}
}
}
}
};
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (EmitterMixin);
/**
* Emitter/listener interface.
*
* Can be easily implemented by a class by mixing the {@link module:utils/emittermixin~EmitterMixin} mixin.
*
* Read more about the usage of this interface in the:
* * {@glink framework/guides/architecture/core-editor-architecture#event-system-and-observables Event system and observables}
* section of the {@glink framework/guides/architecture/core-editor-architecture Core editor architecture} guide.
* * {@glink framework/guides/deep-dive/event-system Event system} deep dive guide.
*
* @interface Emitter
*/
/**
* Registers a callback function to be executed when an event is fired.
*
* Shorthand for {@link #listenTo `this.listenTo( this, event, callback, options )`} (it makes the emitter
* listen on itself).
*
* @method #on
* @param {String} event The name of the event.
* @param {Function} callback The function to be called on event.
* @param {Object} [options={}] Additional options.
* @param {module:utils/priorities~PriorityString|Number} [options.priority='normal'] The priority of this event callback. The higher
* the priority value the sooner the callback will be fired. Events having the same priority are called in the
* order they were added.
*/
/**
* Registers a callback function to be executed on the next time the event is fired only. This is similar to
* calling {@link #on} followed by {@link #off} in the callback.
*
* @method #once
* @param {String} event The name of the event.
* @param {Function} callback The function to be called on event.
* @param {Object} [options={}] Additional options.
* @param {module:utils/priorities~PriorityString|Number} [options.priority='normal'] The priority of this event callback. The higher
* the priority value the sooner the callback will be fired. Events having the same priority are called in the
* order they were added.
*/
/**
* Stops executing the callback on the given event.
* Shorthand for {@link #stopListening `this.stopListening( this, event, callback )`}.
*
* @method #off
* @param {String} event The name of the event.
* @param {Function} callback The function to stop being called.
*/
/**
* Registers a callback function to be executed when an event is fired in a specific (emitter) object.
*
* Events can be grouped in namespaces using `:`.
* When namespaced event is fired, it additionally fires all callbacks for that namespace.
*
* // myEmitter.on( ... ) is a shorthand for myEmitter.listenTo( myEmitter, ... ).
* myEmitter.on( 'myGroup', genericCallback );
* myEmitter.on( 'myGroup:myEvent', specificCallback );
*
* // genericCallback is fired.
* myEmitter.fire( 'myGroup' );
* // both genericCallback and specificCallback are fired.
* myEmitter.fire( 'myGroup:myEvent' );
* // genericCallback is fired even though there are no callbacks for "foo".
* myEmitter.fire( 'myGroup:foo' );
*
* An event callback can {@link module:utils/eventinfo~EventInfo#stop stop the event} and
* set the {@link module:utils/eventinfo~EventInfo#return return value} of the {@link #fire} method.
*
* @method #listenTo
* @param {module:utils/emittermixin~Emitter} emitter The object that fires the event.
* @param {String} event The name of the event.
* @param {Function} callback The function to be called on event.
* @param {Object} [options={}] Additional options.
* @param {module:utils/priorities~PriorityString|Number} [options.priority='normal'] The priority of this event callback. The higher
* the priority value the sooner the callback will be fired. Events having the same priority are called in the
* order they were added.
*/
/**
* Stops listening for events. It can be used at different levels:
*
* * To stop listening to a specific callback.
* * To stop listening to a specific event.
* * To stop listening to all events fired by a specific object.
* * To stop listening to all events fired by all objects.
*
* @method #stopListening
* @param {module:utils/emittermixin~Emitter} [emitter] The object to stop listening to. If omitted, stops it for all objects.
* @param {String} [event] (Requires the `emitter`) The name of the event to stop listening to. If omitted, stops it
* for all events from `emitter`.
* @param {Function} [callback] (Requires the `event`) The function to be removed from the call list for the given
* `event`.
*/
/**
* Fires an event, executing all callbacks registered for it.
*
* The first parameter passed to callbacks is an {@link module:utils/eventinfo~EventInfo} object,
* followed by the optional `args` provided in the `fire()` method call.
*
* @method #fire
* @param {String|module:utils/eventinfo~EventInfo} eventOrInfo The name of the event or `EventInfo` object if event is delegated.
* @param {...*} [args] Additional arguments to be passed to the callbacks.
* @returns {*} By default the method returns `undefined`. However, the return value can be changed by listeners
* through modification of the {@link module:utils/eventinfo~EventInfo#return `evt.return`}'s property (the event info
* is the first param of every callback).
*/
/**
* Delegates selected events to another {@link module:utils/emittermixin~Emitter}. For instance:
*
* emitterA.delegate( 'eventX' ).to( emitterB );
* emitterA.delegate( 'eventX', 'eventY' ).to( emitterC );
*
* then `eventX` is delegated (fired by) `emitterB` and `emitterC` along with `data`:
*
* emitterA.fire( 'eventX', data );
*
* and `eventY` is delegated (fired by) `emitterC` along with `data`:
*
* emitterA.fire( 'eventY', data );
*
* @method #delegate
* @param {...String} events Event names that will be delegated to another emitter.
* @returns {module:utils/emittermixin~EmitterMixinDelegateChain}
*/
/**
* Stops delegating events. It can be used at different levels:
*
* * To stop delegating all events.
* * To stop delegating a specific event to all emitters.
* * To stop delegating a specific event to a specific emitter.
*
* @method #stopDelegating
* @param {String} [event] The name of the event to stop delegating. If omitted, stops it all delegations.
* @param {module:utils/emittermixin~Emitter} [emitter] (requires `event`) The object to stop delegating a particular event to.
* If omitted, stops delegation of `event` to all emitters.
*/
/**
* Adds callback to emitter for given event.
*
* @protected
* @method #_addEventListener
* @param {String} event The name of the event.
* @param {Function} callback The function to be called on event.
* @param {Object} [options={}] Additional options.
* @param {module:utils/priorities~PriorityString|Number} [options.priority='normal'] The priority of this event callback. The higher
* the priority value the sooner the callback will be fired. Events having the same priority are called in the
* order they were added.
*/
/**
* Removes callback from emitter for given event.
*
* @protected
* @method #_removeEventListener
* @param {String} event The name of the event.
* @param {Function} callback The function to stop being called.
*/
/**
* Checks if `listeningEmitter` listens to an emitter with given `listenedToEmitterId` and if so, returns that emitter.
* If not, returns `null`.
*
* @protected
* @param {module:utils/emittermixin~Emitter} listeningEmitter An emitter that listens.
* @param {String} listenedToEmitterId Unique emitter id of emitter listened to.
* @returns {module:utils/emittermixin~Emitter|null}
*/
function _getEmitterListenedTo( listeningEmitter, listenedToEmitterId ) {
if ( listeningEmitter[ _listeningTo ] && listeningEmitter[ _listeningTo ][ listenedToEmitterId ] ) {
return listeningEmitter[ _listeningTo ][ listenedToEmitterId ].emitter;
}
return null;
}
/**
* Sets emitter's unique id.
*
* **Note:** `_emitterId` can be set only once.
*
* @protected
* @param {module:utils/emittermixin~Emitter} emitter An emitter for which id will be set.
* @param {String} [id] Unique id to set. If not passed, random unique id will be set.
*/
function _setEmitterId( emitter, id ) {
if ( !emitter[ _emitterId ] ) {
emitter[ _emitterId ] = id || (0,_uid__WEBPACK_IMPORTED_MODULE_1__["default"])();
}
}
/**
* Returns emitter's unique id.
*
* @protected
* @param {module:utils/emittermixin~Emitter} emitter An emitter which id will be returned.
*/
function _getEmitterId( emitter ) {
return emitter[ _emitterId ];
}
// Gets the internal `_events` property of the given object.
// `_events` property store all lists with callbacks for registered event names.
// If there were no events registered on the object, empty `_events` object is created.
function getEvents( source ) {
if ( !source._events ) {
Object.defineProperty( source, '_events', {
value: {}
} );
}
return source._events;
}
// Creates event node for generic-specific events relation architecture.
function makeEventNode() {
return {
callbacks: [],
childEvents: []
};
}
// Creates an architecture for generic-specific events relation.
// If needed, creates all events for given eventName, i.e. if the first registered event
// is foo:bar:abc, it will create foo:bar:abc, foo:bar and foo event and tie them together.
// It also copies callbacks from more generic events to more specific events when
// specific events are created.
function createEventNamespace( source, eventName ) {
const events = getEvents( source );
// First, check if the event we want to add to the structure already exists.
if ( events[ eventName ] ) {
// If it exists, we don't have to do anything.
return;
}
// In other case, we have to create the structure for the event.
// Note, that we might need to create intermediate events too.
// I.e. if foo:bar:abc is being registered and we only have foo in the structure,
// we need to also register foo:bar.
// Currently processed event name.
let name = eventName;
// Name of the event that is a child event for currently processed event.
let childEventName = null;
// Array containing all newly created specific events.
const newEventNodes = [];
// While loop can't check for ':' index because we have to handle generic events too.
// In each loop, we truncate event name, going from the most specific name to the generic one.
// I.e. foo:bar:abc -> foo:bar -> foo.
while ( name !== '' ) {
if ( events[ name ] ) {
// If the currently processed event name is already registered, we can be sure
// that it already has all the structure created, so we can break the loop here
// as no more events need to be registered.
break;
}
// If this event is not yet registered, create a new object for it.
events[ name ] = makeEventNode();
// Add it to the array with newly created events.
newEventNodes.push( events[ name ] );
// Add previously processed event name as a child of this event.
if ( childEventName ) {
events[ name ].childEvents.push( childEventName );
}
childEventName = name;
// If `.lastIndexOf()` returns -1, `.substr()` will return '' which will break the loop.
name = name.substr( 0, name.lastIndexOf( ':' ) );
}
if ( name !== '' ) {
// If name is not empty, we found an already registered event that was a parent of the
// event we wanted to register.
// Copy that event's callbacks to newly registered events.
for ( const node of newEventNodes ) {
node.callbacks = events[ name ].callbacks.slice();
}
// Add last newly created event to the already registered event.
events[ name ].childEvents.push( childEventName );
}
}
// Gets an array containing callbacks list for a given event and it's more specific events.
// I.e. if given event is foo:bar and there is also foo:bar:abc event registered, this will
// return callback list of foo:bar and foo:bar:abc (but not foo).
function getCallbacksListsForNamespace( source, eventName ) {
const eventNode = getEvents( source )[ eventName ];
if ( !eventNode ) {
return [];
}
let callbacksLists = [ eventNode.callbacks ];
for ( let i = 0; i < eventNode.childEvents.length; i++ ) {
const childCallbacksLists = getCallbacksListsForNamespace( source, eventNode.childEvents[ i ] );
callbacksLists = callbacksLists.concat( childCallbacksLists );
}
return callbacksLists;
}
// Get the list of callbacks for a given event, but only if there any callbacks have been registered.
// If there are no callbacks registered for given event, it checks if this is a specific event and looks
// for callbacks for it's more generic version.
function getCallbacksForEvent( source, eventName ) {
let event;
if ( !source._events || !( event = source._events[ eventName ] ) || !event.callbacks.length ) {
// There are no callbacks registered for specified eventName.
// But this could be a specific-type event that is in a namespace.
if ( eventName.indexOf( ':' ) > -1 ) {
// If the eventName is specific, try to find callback lists for more generic event.
return getCallbacksForEvent( source, eventName.substr( 0, eventName.lastIndexOf( ':' ) ) );
} else {
// If this is a top-level generic event, return null;
return null;
}
}
return event.callbacks;
}
// Fires delegated events for given map of destinations.
//
// @private
// * @param {Map.<utils.Emitter>} destinations A map containing
// `[ {@link module:utils/emittermixin~Emitter}, "event name" ]` pair destinations.
// * @param {utils.EventInfo} eventInfo The original event info object.
// * @param {Array.<*>} fireArgs Arguments the original event was fired with.
function fireDelegatedEvents( destinations, eventInfo, fireArgs ) {
for ( let [ emitter, name ] of destinations ) {
if ( !name ) {
name = eventInfo.name;
} else if ( typeof name == 'function' ) {
name = name( eventInfo.name );
}
const delegatedInfo = new _eventinfo__WEBPACK_IMPORTED_MODULE_0__["default"]( eventInfo.source, name );
delegatedInfo.path = [ ...eventInfo.path ];
emitter.fire( delegatedInfo, ...fireArgs );
}
}
// Helper for registering event callback on the emitter.
function addEventListener( listener, emitter, event, callback, options ) {
if ( emitter._addEventListener ) {
emitter._addEventListener( event, callback, options );
} else {
// Allow listening on objects that do not implement Emitter interface.
// This is needed in some tests that are using mocks instead of the real objects with EmitterMixin mixed.
listener._addEventListener.call( emitter, event, callback, options );
}
}
// Helper for removing event callback from the emitter.
function removeEventListener( listener, emitter, event, callback ) {
if ( emitter._removeEventListener ) {
emitter._removeEventListener( event, callback );
} else {
// Allow listening on objects that do not implement Emitter interface.
// This is needed in some tests that are using mocks instead of the real objects with EmitterMixin mixed.
listener._removeEventListener.call( emitter, event, callback );
}
}
/**
* The return value of {@link ~EmitterMixin#delegate}.
*
* @interface module:utils/emittermixin~EmitterMixinDelegateChain
*/
/**
* Selects destination for {@link module:utils/emittermixin~EmitterMixin#delegate} events.
*
* @method #to
* @param {module:utils/emittermixin~Emitter} emitter An `EmitterMixin` instance which is the destination for delegated events.
* @param {String|Function} [nameOrFunction] A custom event name or function which converts the original name string.
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-utils/src/env.js":
/*!***********************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-utils/src/env.js ***!
\***********************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__),
/* harmony export */ "isAndroid": () => (/* binding */ isAndroid),
/* harmony export */ "isBlink": () => (/* binding */ isBlink),
/* harmony export */ "isGecko": () => (/* binding */ isGecko),
/* harmony export */ "isMac": () => (/* binding */ isMac),
/* harmony export */ "isRegExpUnicodePropertySupported": () => (/* binding */ isRegExpUnicodePropertySupported),
/* harmony export */ "isSafari": () => (/* binding */ isSafari),
/* harmony export */ "isWindows": () => (/* binding */ isWindows),
/* harmony export */ "isiOS": () => (/* binding */ isiOS)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/* globals navigator:false */
/**
* @module utils/env
*/
const userAgent = navigator.userAgent.toLowerCase();
/**
* A namespace containing environment and browser information.
*
* @namespace
*/
const env = {
/**
* Indicates that the application is running on Macintosh.
*
* @static
* @type {Boolean}
*/
isMac: isMac( userAgent ),
/**
* Indicates that the application is running on Windows.
*
* @static
* @type {Boolean}
*/
isWindows: isWindows( userAgent ),
/**
* Indicates that the application is running in Firefox (Gecko).
*
* @static
* @type {Boolean}
*/
isGecko: isGecko( userAgent ),
/**
* Indicates that the application is running in Safari.
*
* @static
* @type {Boolean}
*/
isSafari: isSafari( userAgent ),
/**
* Indicates the the application is running in iOS.
*
* @static
* @type {Boolean}
*/
isiOS: isiOS( userAgent ),
/**
* Indicates that the application is running on Android mobile device.
*
* @static
* @type {Boolean}
*/
isAndroid: isAndroid( userAgent ),
/**
* Indicates that the application is running in a browser using the Blink engine.
*
* @static
* @type {Boolean}
*/
isBlink: isBlink( userAgent ),
/**
* Environment features information.
*
* @memberOf module:utils/env~env
* @namespace
*/
features: {
/**
* Indicates that the environment supports ES2018 Unicode property escapes — like `\p{P}` or `\p{L}`.
* More information about unicode properties might be found
* [in Unicode Standard Annex #44](https://www.unicode.org/reports/tr44/#GC_Values_Table).
*
* @type {Boolean}
*/
isRegExpUnicodePropertySupported: isRegExpUnicodePropertySupported()
}
};
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (env);
/**
* Checks if User Agent represented by the string is running on Macintosh.
*
* @param {String} userAgent **Lowercase** `navigator.userAgent` string.
* @returns {Boolean} Whether User Agent is running on Macintosh or not.
*/
function isMac( userAgent ) {
return userAgent.indexOf( 'macintosh' ) > -1;
}
/**
* Checks if User Agent represented by the string is running on Windows.
*
* @param {String} userAgent **Lowercase** `navigator.userAgent` string.
* @returns {Boolean} Whether User Agent is running on Windows or not.
*/
function isWindows( userAgent ) {
return userAgent.indexOf( 'windows' ) > -1;
}
/**
* Checks if User Agent represented by the string is Firefox (Gecko).
*
* @param {String} userAgent **Lowercase** `navigator.userAgent` string.
* @returns {Boolean} Whether User Agent is Firefox or not.
*/
function isGecko( userAgent ) {
return !!userAgent.match( /gecko\/\d+/ );
}
/**
* Checks if User Agent represented by the string is Safari.
*
* @param {String} userAgent **Lowercase** `navigator.userAgent` string.
* @returns {Boolean} Whether User Agent is Safari or not.
*/
function isSafari( userAgent ) {
return userAgent.indexOf( ' applewebkit/' ) > -1 && userAgent.indexOf( 'chrome' ) === -1;
}
/**
* Checks if User Agent represented by the string is running in iOS.
*
* @param {String} userAgent **Lowercase** `navigator.userAgent` string.
* @returns {Boolean} Whether User Agent is running in iOS or not.
*/
function isiOS( userAgent ) {
// "Request mobile site" || "Request desktop site".
return !!userAgent.match( /iphone|ipad/i ) || ( isMac( userAgent ) && navigator.maxTouchPoints > 0 );
}
/**
* Checks if User Agent represented by the string is Android mobile device.
*
* @param {String} userAgent **Lowercase** `navigator.userAgent` string.
* @returns {Boolean} Whether User Agent is Safari or not.
*/
function isAndroid( userAgent ) {
return userAgent.indexOf( 'android' ) > -1;
}
/**
* Checks if User Agent represented by the string is Blink engine.
*
* @param {String} userAgent **Lowercase** `navigator.userAgent` string.
* @returns {Boolean} Whether User Agent is Blink engine or not.
*/
function isBlink( userAgent ) {
// The Edge browser before switching to the Blink engine used to report itself as Chrome (and "Edge/")
// but after switching to the Blink it replaced "Edge/" with "Edg/".
return userAgent.indexOf( 'chrome/' ) > -1 && userAgent.indexOf( 'edge/' ) < 0;
}
/**
* Checks if the current environment supports ES2018 Unicode properties like `\p{P}` or `\p{L}`.
* More information about unicode properties might be found
* [in Unicode Standard Annex #44](https://www.unicode.org/reports/tr44/#GC_Values_Table).
*
* @returns {Boolean}
*/
function isRegExpUnicodePropertySupported() {
let isSupported = false;
// Feature detection for Unicode properties. Added in ES2018. Currently Firefox does not support it.
// See https://github.com/ckeditor/ckeditor5-mention/issues/44#issuecomment-487002174.
try {
// Usage of regular expression literal cause error during build (ckeditor/ckeditor5-dev#534).
isSupported = 'ć'.search( new RegExp( '[\\p{L}]', 'u' ) ) === 0;
} catch ( error ) {
// Firefox throws a SyntaxError when the group is unsupported.
}
return isSupported;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-utils/src/eventinfo.js":
/*!*****************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-utils/src/eventinfo.js ***!
\*****************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ EventInfo)
/* harmony export */ });
/* harmony import */ var _spy__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./spy */ "./node_modules/@ckeditor/ckeditor5-utils/src/spy.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module utils/eventinfo
*/
/**
* The event object passed to event callbacks. It is used to provide information about the event as well as a tool to
* manipulate it.
*/
class EventInfo {
/**
* @param {Object} source The emitter.
* @param {String} name The event name.
*/
constructor( source, name ) {
/**
* The object that fired the event.
*
* @readonly
* @member {Object}
*/
this.source = source;
/**
* The event name.
*
* @readonly
* @member {String}
*/
this.name = name;
/**
* Path this event has followed. See {@link module:utils/emittermixin~EmitterMixin#delegate}.
*
* @readonly
* @member {Array.<Object>}
*/
this.path = [];
// The following methods are defined in the constructor because they must be re-created per instance.
/**
* Stops the event emitter to call further callbacks for this event interaction.
*
* @method #stop
*/
this.stop = (0,_spy__WEBPACK_IMPORTED_MODULE_0__["default"])();
/**
* Removes the current callback from future interactions of this event.
*
* @method #off
*/
this.off = (0,_spy__WEBPACK_IMPORTED_MODULE_0__["default"])();
/**
* The value which will be returned by {@link module:utils/emittermixin~EmitterMixin#fire}.
*
* It's `undefined` by default and can be changed by an event listener:
*
* dataController.fire( 'getSelectedContent', ( evt ) => {
* // This listener will make `dataController.fire( 'getSelectedContent' )`
* // always return an empty DocumentFragment.
* evt.return = new DocumentFragment();
*
* // Make sure no other listeners are executed.
* evt.stop();
* } );
*
* @member #return
*/
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-utils/src/fastdiff.js":
/*!****************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-utils/src/fastdiff.js ***!
\****************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ fastDiff)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module utils/fastdiff
*/
/**
* Finds positions of the first and last change in the given string/array and generates a set of changes:
*
* fastDiff( '12a', '12xyza' );
* // [ { index: 2, type: 'insert', values: [ 'x', 'y', 'z' ] } ]
*
* fastDiff( '12a', '12aa' );
* // [ { index: 3, type: 'insert', values: [ 'a' ] } ]
*
* fastDiff( '12xyza', '12a' );
* // [ { index: 2, type: 'delete', howMany: 3 } ]
*
* fastDiff( [ '1', '2', 'a', 'a' ], [ '1', '2', 'a' ] );
* // [ { index: 3, type: 'delete', howMany: 1 } ]
*
* fastDiff( [ '1', '2', 'a', 'b', 'c', '3' ], [ '2', 'a', 'b' ] );
* // [ { index: 0, type: 'insert', values: [ '2', 'a', 'b' ] }, { index: 3, type: 'delete', howMany: 6 } ]
*
* Passed arrays can contain any type of data, however to compare them correctly custom comparator function
* should be passed as a third parameter:
*
* fastDiff( [ { value: 1 }, { value: 2 } ], [ { value: 1 }, { value: 3 } ], ( a, b ) => {
* return a.value === b.value;
* } );
* // [ { index: 1, type: 'insert', values: [ { value: 3 } ] }, { index: 2, type: 'delete', howMany: 1 } ]
*
* The resulted set of changes can be applied to the input in order to transform it into the output, for example:
*
* let input = '12abc3';
* const output = '2ab';
* const changes = fastDiff( input, output );
*
* changes.forEach( change => {
* if ( change.type == 'insert' ) {
* input = input.substring( 0, change.index ) + change.values.join( '' ) + input.substring( change.index );
* } else if ( change.type == 'delete' ) {
* input = input.substring( 0, change.index ) + input.substring( change.index + change.howMany );
* }
* } );
*
* // input equals output now
*
* or in case of arrays:
*
* let input = [ '1', '2', 'a', 'b', 'c', '3' ];
* const output = [ '2', 'a', 'b' ];
* const changes = fastDiff( input, output );
*
* changes.forEach( change => {
* if ( change.type == 'insert' ) {
* input = input.slice( 0, change.index ).concat( change.values, input.slice( change.index ) );
* } else if ( change.type == 'delete' ) {
* input = input.slice( 0, change.index ).concat( input.slice( change.index + change.howMany ) );
* }
* } );
*
* // input equals output now
*
* By passing `true` as the fourth parameter (`atomicChanges`) the output of this function will become compatible with
* the {@link module:utils/diff~diff `diff()`} function:
*
* fastDiff( '12a', '12xyza' );
* // [ 'equal', 'equal', 'insert', 'insert', 'insert', 'equal' ]
*
* The default output format of this function is compatible with the output format of
* {@link module:utils/difftochanges~diffToChanges `diffToChanges()`}. The `diffToChanges()` input format is, in turn,
* compatible with the output of {@link module:utils/diff~diff `diff()`}:
*
* const a = '1234';
* const b = '12xyz34';
*
* // Both calls will return the same results (grouped changes format).
* fastDiff( a, b );
* diffToChanges( diff( a, b ) );
*
* // Again, both calls will return the same results (atomic changes format).
* fastDiff( a, b, null, true );
* diff( a, b );
*
*
* @param {Array|String} a Input array or string.
* @param {Array|String} b Input array or string.
* @param {Function} [cmp] Optional function used to compare array values, by default `===` (strict equal operator) is used.
* @param {Boolean} [atomicChanges=false] Whether an array of `inset|delete|equal` operations should
* be returned instead of changes set. This makes this function compatible with {@link module:utils/diff~diff `diff()`}.
* @returns {Array} Array of changes.
*/
function fastDiff( a, b, cmp, atomicChanges = false ) {
// Set the comparator function.
cmp = cmp || function( a, b ) {
return a === b;
};
// Convert the string (or any array-like object - eg. NodeList) to an array by using the slice() method because,
// unlike Array.from(), it returns array of UTF-16 code units instead of the code points of a string.
// One code point might be a surrogate pair of two code units. All text offsets are expected to be in code units.
// See ckeditor/ckeditor5#3147.
//
// We need to make sure here that fastDiff() works identical to diff().
if ( !Array.isArray( a ) ) {
a = Array.prototype.slice.call( a );
}
if ( !Array.isArray( b ) ) {
b = Array.prototype.slice.call( b );
}
// Find first and last change.
const changeIndexes = findChangeBoundaryIndexes( a, b, cmp );
// Transform into changes array.
return atomicChanges ? changeIndexesToAtomicChanges( changeIndexes, b.length ) : changeIndexesToChanges( b, changeIndexes );
}
// Finds position of the first and last change in the given arrays. For example:
//
// const indexes = findChangeBoundaryIndexes( [ '1', '2', '3', '4' ], [ '1', '3', '4', '2', '4' ] );
// console.log( indexes ); // { firstIndex: 1, lastIndexOld: 3, lastIndexNew: 4 }
//
// The above indexes means that in the first array the modified part is `1[23]4` and in the second array it is `1[342]4`.
// Based on such indexes, array with `insert`/`delete` operations which allows transforming first value into the second one
// can be generated.
//
// @param {Array} arr1
// @param {Array} arr2
// @param {Function} cmp Comparator function.
// @returns {Object}
// @returns {Number} return.firstIndex Index of the first change in both values (always the same for both).
// @returns {Number} result.lastIndexOld Index of the last common value in `arr1`.
// @returns {Number} result.lastIndexNew Index of the last common value in `arr2`.
function findChangeBoundaryIndexes( arr1, arr2, cmp ) {
// Find the first difference between passed values.
const firstIndex = findFirstDifferenceIndex( arr1, arr2, cmp );
// If arrays are equal return -1 indexes object.
if ( firstIndex === -1 ) {
return { firstIndex: -1, lastIndexOld: -1, lastIndexNew: -1 };
}
// Remove the common part of each value and reverse them to make it simpler to find the last difference between them.
const oldArrayReversed = cutAndReverse( arr1, firstIndex );
const newArrayReversed = cutAndReverse( arr2, firstIndex );
// Find the first difference between reversed values.
// It should be treated as "how many elements from the end the last difference occurred".
//
// For example:
//
// initial -> after cut -> reversed:
// oldValue: '321ba' -> '21ba' -> 'ab12'
// newValue: '31xba' -> '1xba' -> 'abx1'
// lastIndex: -> 2
//
// So the last change occurred two characters from the end of the arrays.
const lastIndex = findFirstDifferenceIndex( oldArrayReversed, newArrayReversed, cmp );
// Use `lastIndex` to calculate proper offset, starting from the beginning (`lastIndex` kind of starts from the end).
const lastIndexOld = arr1.length - lastIndex;
const lastIndexNew = arr2.length - lastIndex;
return { firstIndex, lastIndexOld, lastIndexNew };
}
// Returns a first index on which given arrays differ. If both arrays are the same, -1 is returned.
//
// @param {Array} arr1
// @param {Array} arr2
// @param {Function} cmp Comparator function.
// @returns {Number}
function findFirstDifferenceIndex( arr1, arr2, cmp ) {
for ( let i = 0; i < Math.max( arr1.length, arr2.length ); i++ ) {
if ( arr1[ i ] === undefined || arr2[ i ] === undefined || !cmp( arr1[ i ], arr2[ i ] ) ) {
return i;
}
}
return -1; // Return -1 if arrays are equal.
}
// Returns a copy of the given array with `howMany` elements removed starting from the beginning and in reversed order.
//
// @param {Array} arr Array to be processed.
// @param {Number} howMany How many elements from array beginning to remove.
// @returns {Array} Shortened and reversed array.
function cutAndReverse( arr, howMany ) {
return arr.slice( howMany ).reverse();
}
// Generates changes array based on change indexes from `findChangeBoundaryIndexes` function. This function will
// generate array with 0 (no changes), 1 (deletion or insertion) or 2 records (insertion and deletion).
//
// @param {Array} newArray New array for which change indexes were calculated.
// @param {Object} changeIndexes Change indexes object from `findChangeBoundaryIndexes` function.
// @returns {Array.<Object>} Array of changes compatible with {@link module:utils/difftochanges~diffToChanges} format.
function changeIndexesToChanges( newArray, changeIndexes ) {
const result = [];
const { firstIndex, lastIndexOld, lastIndexNew } = changeIndexes;
// Order operations as 'insert', 'delete' array to keep compatibility with {@link module:utils/difftochanges~diffToChanges}
// in most cases. However, 'diffToChanges' does not stick to any order so in some cases
// (for example replacing '12345' with 'abcd') it will generate 'delete', 'insert' order.
if ( lastIndexNew - firstIndex > 0 ) {
result.push( {
index: firstIndex,
type: 'insert',
values: newArray.slice( firstIndex, lastIndexNew )
} );
}
if ( lastIndexOld - firstIndex > 0 ) {
result.push( {
index: firstIndex + ( lastIndexNew - firstIndex ), // Increase index of what was inserted.
type: 'delete',
howMany: lastIndexOld - firstIndex
} );
}
return result;
}
// Generates array with set `equal|insert|delete` operations based on change indexes from `findChangeBoundaryIndexes` function.
//
// @param {Object} changeIndexes Change indexes object from `findChangeBoundaryIndexes` function.
// @param {Number} newLength Length of the new array on which `findChangeBoundaryIndexes` calculated change indexes.
// @returns {Array.<String>} Array of changes compatible with {@link module:utils/diff~diff} format.
function changeIndexesToAtomicChanges( changeIndexes, newLength ) {
const { firstIndex, lastIndexOld, lastIndexNew } = changeIndexes;
// No changes.
if ( firstIndex === -1 ) {
return Array( newLength ).fill( 'equal' );
}
let result = [];
if ( firstIndex > 0 ) {
result = result.concat( Array( firstIndex ).fill( 'equal' ) );
}
if ( lastIndexNew - firstIndex > 0 ) {
result = result.concat( Array( lastIndexNew - firstIndex ).fill( 'insert' ) );
}
if ( lastIndexOld - firstIndex > 0 ) {
result = result.concat( Array( lastIndexOld - firstIndex ).fill( 'delete' ) );
}
if ( lastIndexNew < newLength ) {
result = result.concat( Array( newLength - lastIndexNew ).fill( 'equal' ) );
}
return result;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-utils/src/first.js":
/*!*************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-utils/src/first.js ***!
\*************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ first)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module utils/first
*/
/**
* Returns first item of the given `iterable`.
*
* @param {Iterable.<*>} iterable
* @returns {*}
*/
function first( iterable ) {
const iteratorItem = iterable.next();
if ( iteratorItem.done ) {
return null;
}
return iteratorItem.value;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-utils/src/focustracker.js":
/*!********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-utils/src/focustracker.js ***!
\********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ FocusTracker)
/* harmony export */ });
/* harmony import */ var _dom_emittermixin__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./dom/emittermixin */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/emittermixin.js");
/* harmony import */ var _observablemixin__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./observablemixin */ "./node_modules/@ckeditor/ckeditor5-utils/src/observablemixin.js");
/* harmony import */ var _ckeditorerror__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/* harmony import */ var _mix__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./mix */ "./node_modules/@ckeditor/ckeditor5-utils/src/mix.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/* global setTimeout, clearTimeout */
/**
* @module utils/focustracker
*/
/**
* Allows observing a group of `HTMLElement`s whether at least one of them is focused.
*
* Used by the {@link module:core/editor/editor~Editor} in order to track whether the focus is still within the application,
* or were used outside of its UI.
*
* **Note** `focus` and `blur` listeners use event capturing, so it is only needed to register wrapper `HTMLElement`
* which contain other `focusable` elements. But note that this wrapper element has to be focusable too
* (have e.g. `tabindex="-1"`).
*
* Check out the {@glink framework/guides/deep-dive/ui/focus-tracking "Deep dive into focus tracking" guide} to learn more.
*
* @mixes module:utils/dom/emittermixin~EmitterMixin
* @mixes module:utils/observablemixin~ObservableMixin
*/
class FocusTracker {
constructor() {
/**
* True when one of the registered elements is focused.
*
* @readonly
* @observable
* @member {Boolean} #isFocused
*/
this.set( 'isFocused', false );
/**
* The currently focused element.
*
* While {@link #isFocused `isFocused`} remains `true`, the focus can
* move between different UI elements. This property tracks those
* elements and tells which one is currently focused.
*
* @readonly
* @observable
* @member {HTMLElement|null} #focusedElement
*/
this.set( 'focusedElement', null );
/**
* List of registered elements.
*
* @private
* @member {Set.<HTMLElement>}
*/
this._elements = new Set();
/**
* Event loop timeout.
*
* @private
* @member {Number}
*/
this._nextEventLoopTimeout = null;
}
/**
* Starts tracking the specified element.
*
* @param {HTMLElement} element
*/
add( element ) {
if ( this._elements.has( element ) ) {
/**
* This element is already tracked by {@link module:utils/focustracker~FocusTracker}.
*
* @error focustracker-add-element-already-exist
*/
throw new _ckeditorerror__WEBPACK_IMPORTED_MODULE_2__["default"]( 'focustracker-add-element-already-exist', this );
}
this.listenTo( element, 'focus', () => this._focus( element ), { useCapture: true } );
this.listenTo( element, 'blur', () => this._blur(), { useCapture: true } );
this._elements.add( element );
}
/**
* Stops tracking the specified element and stops listening on this element.
*
* @param {HTMLElement} element
*/
remove( element ) {
if ( element === this.focusedElement ) {
this._blur( element );
}
if ( this._elements.has( element ) ) {
this.stopListening( element );
this._elements.delete( element );
}
}
/**
* Destroys the focus tracker by:
* - Disabling all event listeners attached to tracked elements.
* - Removing all tracked elements that were previously added.
*/
destroy() {
this.stopListening();
}
/**
* Stores currently focused element and set {#isFocused} as `true`.
*
* @private
* @param {HTMLElement} element Element which has been focused.
*/
_focus( element ) {
clearTimeout( this._nextEventLoopTimeout );
this.focusedElement = element;
this.isFocused = true;
}
/**
* Clears currently focused element and set {@link #isFocused} as `false`.
* This method uses `setTimeout` to change order of fires `blur` and `focus` events.
*
* @private
* @fires blur
*/
_blur() {
clearTimeout( this._nextEventLoopTimeout );
this._nextEventLoopTimeout = setTimeout( () => {
this.focusedElement = null;
this.isFocused = false;
}, 0 );
}
/**
* @event focus
*/
/**
* @event blur
*/
}
(0,_mix__WEBPACK_IMPORTED_MODULE_3__["default"])( FocusTracker, _dom_emittermixin__WEBPACK_IMPORTED_MODULE_0__["default"] );
(0,_mix__WEBPACK_IMPORTED_MODULE_3__["default"])( FocusTracker, _observablemixin__WEBPACK_IMPORTED_MODULE_1__["default"] );
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-utils/src/index.js":
/*!*************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-utils/src/index.js ***!
\*************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "CKEditorError": () => (/* reexport safe */ _ckeditorerror__WEBPACK_IMPORTED_MODULE_5__["default"]),
/* harmony export */ "Collection": () => (/* reexport safe */ _collection__WEBPACK_IMPORTED_MODULE_19__["default"]),
/* harmony export */ "DomEmitterMixin": () => (/* reexport safe */ _dom_emittermixin__WEBPACK_IMPORTED_MODULE_8__["default"]),
/* harmony export */ "ElementReplacer": () => (/* reexport safe */ _elementreplacer__WEBPACK_IMPORTED_MODULE_6__["default"]),
/* harmony export */ "EmitterMixin": () => (/* reexport safe */ _emittermixin__WEBPACK_IMPORTED_MODULE_3__["default"]),
/* harmony export */ "FocusTracker": () => (/* reexport safe */ _focustracker__WEBPACK_IMPORTED_MODULE_21__["default"]),
/* harmony export */ "KeystrokeHandler": () => (/* reexport safe */ _keystrokehandler__WEBPACK_IMPORTED_MODULE_22__["default"]),
/* harmony export */ "Locale": () => (/* reexport safe */ _locale__WEBPACK_IMPORTED_MODULE_18__["default"]),
/* harmony export */ "ObservableMixin": () => (/* reexport safe */ _observablemixin__WEBPACK_IMPORTED_MODULE_4__["default"]),
/* harmony export */ "Rect": () => (/* reexport safe */ _dom_rect__WEBPACK_IMPORTED_MODULE_11__["default"]),
/* harmony export */ "ResizeObserver": () => (/* reexport safe */ _dom_resizeobserver__WEBPACK_IMPORTED_MODULE_12__["default"]),
/* harmony export */ "createElement": () => (/* reexport safe */ _dom_createelement__WEBPACK_IMPORTED_MODULE_7__["default"]),
/* harmony export */ "diff": () => (/* reexport safe */ _diff__WEBPACK_IMPORTED_MODULE_1__["default"]),
/* harmony export */ "env": () => (/* reexport safe */ _env__WEBPACK_IMPORTED_MODULE_0__["default"]),
/* harmony export */ "first": () => (/* reexport safe */ _first__WEBPACK_IMPORTED_MODULE_20__["default"]),
/* harmony export */ "getCode": () => (/* reexport safe */ _keyboard__WEBPACK_IMPORTED_MODULE_16__.getCode),
/* harmony export */ "getDataFromElement": () => (/* reexport safe */ _dom_getdatafromelement__WEBPACK_IMPORTED_MODULE_10__["default"]),
/* harmony export */ "getEnvKeystrokeText": () => (/* reexport safe */ _keyboard__WEBPACK_IMPORTED_MODULE_16__.getEnvKeystrokeText),
/* harmony export */ "getLanguageDirection": () => (/* reexport safe */ _language__WEBPACK_IMPORTED_MODULE_17__.getLanguageDirection),
/* harmony export */ "getLocalizedArrowKeyCodeDirection": () => (/* reexport safe */ _keyboard__WEBPACK_IMPORTED_MODULE_16__.getLocalizedArrowKeyCodeDirection),
/* harmony export */ "global": () => (/* reexport safe */ _dom_global__WEBPACK_IMPORTED_MODULE_9__["default"]),
/* harmony export */ "isArrowKeyCode": () => (/* reexport safe */ _keyboard__WEBPACK_IMPORTED_MODULE_16__.isArrowKeyCode),
/* harmony export */ "isForwardArrowKeyCode": () => (/* reexport safe */ _keyboard__WEBPACK_IMPORTED_MODULE_16__.isForwardArrowKeyCode),
/* harmony export */ "isVisible": () => (/* reexport safe */ _dom_isvisible__WEBPACK_IMPORTED_MODULE_15__["default"]),
/* harmony export */ "keyCodes": () => (/* reexport safe */ _keyboard__WEBPACK_IMPORTED_MODULE_16__.keyCodes),
/* harmony export */ "logError": () => (/* reexport safe */ _ckeditorerror__WEBPACK_IMPORTED_MODULE_5__.logError),
/* harmony export */ "logWarning": () => (/* reexport safe */ _ckeditorerror__WEBPACK_IMPORTED_MODULE_5__.logWarning),
/* harmony export */ "mix": () => (/* reexport safe */ _mix__WEBPACK_IMPORTED_MODULE_2__["default"]),
/* harmony export */ "parseKeystroke": () => (/* reexport safe */ _keyboard__WEBPACK_IMPORTED_MODULE_16__.parseKeystroke),
/* harmony export */ "priorities": () => (/* reexport safe */ _priorities__WEBPACK_IMPORTED_MODULE_25__["default"]),
/* harmony export */ "setDataInElement": () => (/* reexport safe */ _dom_setdatainelement__WEBPACK_IMPORTED_MODULE_13__["default"]),
/* harmony export */ "toArray": () => (/* reexport safe */ _toarray__WEBPACK_IMPORTED_MODULE_23__["default"]),
/* harmony export */ "toMap": () => (/* reexport safe */ _tomap__WEBPACK_IMPORTED_MODULE_24__["default"]),
/* harmony export */ "toUnit": () => (/* reexport safe */ _dom_tounit__WEBPACK_IMPORTED_MODULE_14__["default"]),
/* harmony export */ "uid": () => (/* reexport safe */ _uid__WEBPACK_IMPORTED_MODULE_26__["default"]),
/* harmony export */ "version": () => (/* reexport safe */ _version__WEBPACK_IMPORTED_MODULE_27__["default"])
/* harmony export */ });
/* harmony import */ var _env__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./env */ "./node_modules/@ckeditor/ckeditor5-utils/src/env.js");
/* harmony import */ var _diff__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./diff */ "./node_modules/@ckeditor/ckeditor5-utils/src/diff.js");
/* harmony import */ var _mix__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./mix */ "./node_modules/@ckeditor/ckeditor5-utils/src/mix.js");
/* harmony import */ var _emittermixin__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./emittermixin */ "./node_modules/@ckeditor/ckeditor5-utils/src/emittermixin.js");
/* harmony import */ var _observablemixin__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./observablemixin */ "./node_modules/@ckeditor/ckeditor5-utils/src/observablemixin.js");
/* harmony import */ var _ckeditorerror__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/* harmony import */ var _elementreplacer__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./elementreplacer */ "./node_modules/@ckeditor/ckeditor5-utils/src/elementreplacer.js");
/* harmony import */ var _dom_createelement__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./dom/createelement */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/createelement.js");
/* harmony import */ var _dom_emittermixin__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./dom/emittermixin */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/emittermixin.js");
/* harmony import */ var _dom_global__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./dom/global */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/global.js");
/* harmony import */ var _dom_getdatafromelement__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./dom/getdatafromelement */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/getdatafromelement.js");
/* harmony import */ var _dom_rect__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ./dom/rect */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/rect.js");
/* harmony import */ var _dom_resizeobserver__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! ./dom/resizeobserver */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/resizeobserver.js");
/* harmony import */ var _dom_setdatainelement__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(/*! ./dom/setdatainelement */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/setdatainelement.js");
/* harmony import */ var _dom_tounit__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(/*! ./dom/tounit */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/tounit.js");
/* harmony import */ var _dom_isvisible__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(/*! ./dom/isvisible */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/isvisible.js");
/* harmony import */ var _keyboard__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(/*! ./keyboard */ "./node_modules/@ckeditor/ckeditor5-utils/src/keyboard.js");
/* harmony import */ var _language__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(/*! ./language */ "./node_modules/@ckeditor/ckeditor5-utils/src/language.js");
/* harmony import */ var _locale__WEBPACK_IMPORTED_MODULE_18__ = __webpack_require__(/*! ./locale */ "./node_modules/@ckeditor/ckeditor5-utils/src/locale.js");
/* harmony import */ var _collection__WEBPACK_IMPORTED_MODULE_19__ = __webpack_require__(/*! ./collection */ "./node_modules/@ckeditor/ckeditor5-utils/src/collection.js");
/* harmony import */ var _first__WEBPACK_IMPORTED_MODULE_20__ = __webpack_require__(/*! ./first */ "./node_modules/@ckeditor/ckeditor5-utils/src/first.js");
/* harmony import */ var _focustracker__WEBPACK_IMPORTED_MODULE_21__ = __webpack_require__(/*! ./focustracker */ "./node_modules/@ckeditor/ckeditor5-utils/src/focustracker.js");
/* harmony import */ var _keystrokehandler__WEBPACK_IMPORTED_MODULE_22__ = __webpack_require__(/*! ./keystrokehandler */ "./node_modules/@ckeditor/ckeditor5-utils/src/keystrokehandler.js");
/* harmony import */ var _toarray__WEBPACK_IMPORTED_MODULE_23__ = __webpack_require__(/*! ./toarray */ "./node_modules/@ckeditor/ckeditor5-utils/src/toarray.js");
/* harmony import */ var _tomap__WEBPACK_IMPORTED_MODULE_24__ = __webpack_require__(/*! ./tomap */ "./node_modules/@ckeditor/ckeditor5-utils/src/tomap.js");
/* harmony import */ var _priorities__WEBPACK_IMPORTED_MODULE_25__ = __webpack_require__(/*! ./priorities */ "./node_modules/@ckeditor/ckeditor5-utils/src/priorities.js");
/* harmony import */ var _uid__WEBPACK_IMPORTED_MODULE_26__ = __webpack_require__(/*! ./uid */ "./node_modules/@ckeditor/ckeditor5-utils/src/uid.js");
/* harmony import */ var _version__WEBPACK_IMPORTED_MODULE_27__ = __webpack_require__(/*! ./version */ "./node_modules/@ckeditor/ckeditor5-utils/src/version.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module utils
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-utils/src/isiterable.js":
/*!******************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-utils/src/isiterable.js ***!
\******************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ isIterable)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module utils/isiterable
*/
/**
* Checks if value implements iterator interface.
*
* @param {*} value The value to check.
* @returns {Boolean} True if value implements iterator interface.
*/
function isIterable( value ) {
return !!( value && value[ Symbol.iterator ] );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-utils/src/keyboard.js":
/*!****************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-utils/src/keyboard.js ***!
\****************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "getCode": () => (/* binding */ getCode),
/* harmony export */ "getEnvKeystrokeText": () => (/* binding */ getEnvKeystrokeText),
/* harmony export */ "getLocalizedArrowKeyCodeDirection": () => (/* binding */ getLocalizedArrowKeyCodeDirection),
/* harmony export */ "isArrowKeyCode": () => (/* binding */ isArrowKeyCode),
/* harmony export */ "isForwardArrowKeyCode": () => (/* binding */ isForwardArrowKeyCode),
/* harmony export */ "keyCodes": () => (/* binding */ keyCodes),
/* harmony export */ "parseKeystroke": () => (/* binding */ parseKeystroke)
/* harmony export */ });
/* harmony import */ var _ckeditorerror__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/* harmony import */ var _env__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./env */ "./node_modules/@ckeditor/ckeditor5-utils/src/env.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* A set of utilities related to keyboard support.
*
* @module utils/keyboard
*/
const modifiersToGlyphsMac = {
ctrl: '⌃',
cmd: '⌘',
alt: '⌥',
shift: '⇧'
};
const modifiersToGlyphsNonMac = {
ctrl: 'Ctrl+',
alt: 'Alt+',
shift: 'Shift+'
};
/**
* An object with `keyName => keyCode` pairs for a set of known keys.
*
* Contains:
*
* * `a-z`,
* * `0-9`,
* * `f1-f12`,
* * `` ` ``, `-`, `=`, `[`, `]`, `;`, `'`, `,`, `.`, `/`, `\`,
* * `arrow(left|up|right|bottom)`,
* * `backspace`, `delete`, `enter`, `esc`, `tab`,
* * `ctrl`, `cmd`, `shift`, `alt`.
*/
const keyCodes = generateKnownKeyCodes();
const keyCodeNames = Object.fromEntries(
Object.entries( keyCodes ).map( ( [ name, code ] ) => [ code, name.charAt( 0 ).toUpperCase() + name.slice( 1 ) ] )
);
/**
* Converts a key name or {@link module:utils/keyboard~KeystrokeInfo keystroke info} into a key code.
*
* Note: Key names are matched with {@link module:utils/keyboard~keyCodes} in a case-insensitive way.
*
* @param {String|module:utils/keyboard~KeystrokeInfo} A key name (see {@link module:utils/keyboard~keyCodes})
* or a keystroke data object.
* @returns {Number} Key or keystroke code.
*/
function getCode( key ) {
let keyCode;
if ( typeof key == 'string' ) {
keyCode = keyCodes[ key.toLowerCase() ];
if ( !keyCode ) {
/**
* Unknown key name. Only key names included in the {@link module:utils/keyboard~keyCodes} can be used.
*
* @error keyboard-unknown-key
* @param {String} key
*/
throw new _ckeditorerror__WEBPACK_IMPORTED_MODULE_0__["default"]( 'keyboard-unknown-key', null, { key } );
}
} else {
keyCode = key.keyCode +
( key.altKey ? keyCodes.alt : 0 ) +
( key.ctrlKey ? keyCodes.ctrl : 0 ) +
( key.shiftKey ? keyCodes.shift : 0 ) +
( key.metaKey ? keyCodes.cmd : 0 );
}
return keyCode;
}
/**
* Parses the keystroke and returns a keystroke code that will match the code returned by
* {@link module:utils/keyboard~getCode} for the corresponding {@link module:utils/keyboard~KeystrokeInfo keystroke info}.
*
* The keystroke can be passed in two formats:
*
* * as a single string – e.g. `ctrl + A`,
* * as an array of {@link module:utils/keyboard~keyCodes known key names} and key codes – e.g.:
* * `[ 'ctrl', 32 ]` (ctrl + space),
* * `[ 'ctrl', 'a' ]` (ctrl + A).
*
* Note: Key names are matched with {@link module:utils/keyboard~keyCodes} in a case-insensitive way.
*
* Note: Only keystrokes with a single non-modifier key are supported (e.g. `ctrl+A` is OK, but `ctrl+A+B` is not).
*
* Note: On macOS, keystroke handling is translating the `Ctrl` key to the `Cmd` key and handling only that keystroke.
* For example, a registered keystroke `Ctrl+A` will be translated to `Cmd+A` on macOS. To disable the translation of some keystroke,
* use the forced modifier: `Ctrl!+A` (note the exclamation mark).
*
* @param {String|Array.<Number|String>} keystroke The keystroke definition.
* @returns {Number} Keystroke code.
*/
function parseKeystroke( keystroke ) {
if ( typeof keystroke == 'string' ) {
keystroke = splitKeystrokeText( keystroke );
}
return keystroke
.map( key => ( typeof key == 'string' ) ? getEnvKeyCode( key ) : key )
.reduce( ( key, sum ) => sum + key, 0 );
}
/**
* Translates any keystroke string text like `"Ctrl+A"` to an
* environment–specific keystroke, i.e. `"⌘A"` on macOS.
*
* @param {String} keystroke The keystroke text.
* @returns {String} The keystroke text specific for the environment.
*/
function getEnvKeystrokeText( keystroke ) {
let keystrokeCode = parseKeystroke( keystroke );
const modifiersToGlyphs = Object.entries( _env__WEBPACK_IMPORTED_MODULE_1__["default"].isMac ? modifiersToGlyphsMac : modifiersToGlyphsNonMac );
const modifiers = modifiersToGlyphs.reduce( ( modifiers, [ name, glyph ] ) => {
// Modifier keys are stored as a bit mask so extract those from the keystroke code.
if ( ( keystrokeCode & keyCodes[ name ] ) != 0 ) {
keystrokeCode &= ~keyCodes[ name ];
modifiers += glyph;
}
return modifiers;
}, '' );
return modifiers + ( keystrokeCode ? keyCodeNames[ keystrokeCode ] : '' );
}
/**
* Returns `true` if the provided key code represents one of the arrow keys.
*
* @param {Number} keyCode A key code as in {@link module:utils/keyboard~KeystrokeInfo#keyCode}.
* @returns {Boolean}
*/
function isArrowKeyCode( keyCode ) {
return keyCode == keyCodes.arrowright ||
keyCode == keyCodes.arrowleft ||
keyCode == keyCodes.arrowup ||
keyCode == keyCodes.arrowdown;
}
/**
* Returns the direction in which the {@link module:engine/model/documentselection~DocumentSelection selection}
* will move when the provided arrow key code is pressed considering the language direction of the editor content.
*
* For instance, in right–to–left (RTL) content languages, pressing the left arrow means moving the selection right (forward)
* in the model structure. Similarly, pressing the right arrow moves the selection left (backward).
*
* @param {Number} keyCode A key code as in {@link module:utils/keyboard~KeystrokeInfo#keyCode}.
* @param {'ltr'|'rtl'} contentLanguageDirection The content language direction, corresponding to
* {@link module:utils/locale~Locale#contentLanguageDirection}.
* @returns {'left'|'up'|'right'|'down'} Localized arrow direction.
*/
function getLocalizedArrowKeyCodeDirection( keyCode, contentLanguageDirection ) {
const isLtrContent = contentLanguageDirection === 'ltr';
switch ( keyCode ) {
case keyCodes.arrowleft:
return isLtrContent ? 'left' : 'right';
case keyCodes.arrowright:
return isLtrContent ? 'right' : 'left';
case keyCodes.arrowup:
return 'up';
case keyCodes.arrowdown:
return 'down';
}
}
// Converts a key name to the key code with mapping based on the env.
//
// See: {@link module:utils/keyboard~getCode}.
//
// @param {String} key The key name (see {@link module:utils/keyboard~keyCodes}).
// @returns {Number} Key code.
function getEnvKeyCode( key ) {
// Don't remap modifier key for forced modifiers.
if ( key.endsWith( '!' ) ) {
return getCode( key.slice( 0, -1 ) );
}
const code = getCode( key );
return _env__WEBPACK_IMPORTED_MODULE_1__["default"].isMac && code == keyCodes.ctrl ? keyCodes.cmd : code;
}
/**
* Determines if the provided key code moves the {@link module:engine/model/documentselection~DocumentSelection selection}
* forward or backward considering the language direction of the editor content.
*
* For instance, in right–to–left (RTL) languages, pressing the left arrow means moving forward
* in the model structure. Similarly, pressing the right arrow moves the selection backward.
*
* @param {Number} keyCode A key code as in {@link module:utils/keyboard~KeystrokeInfo#keyCode}.
* @param {'ltr'|'rtl'} contentLanguageDirection The content language direction, corresponding to
* {@link module:utils/locale~Locale#contentLanguageDirection}.
* @returns {Boolean}
*/
function isForwardArrowKeyCode( keyCode, contentLanguageDirection ) {
const localizedKeyCodeDirection = getLocalizedArrowKeyCodeDirection( keyCode, contentLanguageDirection );
return localizedKeyCodeDirection === 'down' || localizedKeyCodeDirection === 'right';
}
function generateKnownKeyCodes() {
const keyCodes = {
arrowleft: 37,
arrowup: 38,
arrowright: 39,
arrowdown: 40,
backspace: 8,
delete: 46,
enter: 13,
space: 32,
esc: 27,
tab: 9,
// The idea about these numbers is that they do not collide with any real key codes, so we can use them
// like bit masks.
ctrl: 0x110000,
shift: 0x220000,
alt: 0x440000,
cmd: 0x880000
};
// a-z
for ( let code = 65; code <= 90; code++ ) {
const letter = String.fromCharCode( code );
keyCodes[ letter.toLowerCase() ] = code;
}
// 0-9
for ( let code = 48; code <= 57; code++ ) {
keyCodes[ code - 48 ] = code;
}
// F1-F12
for ( let code = 112; code <= 123; code++ ) {
keyCodes[ 'f' + ( code - 111 ) ] = code;
}
// other characters
for ( const char of '`-=[];\',./\\' ) {
keyCodes[ char ] = char.charCodeAt( 0 );
}
return keyCodes;
}
function splitKeystrokeText( keystroke ) {
return keystroke.split( '+' ).map( key => key.trim() );
}
/**
* Information about the keystroke.
*
* @interface module:utils/keyboard~KeystrokeInfo
*/
/**
* The [key code](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode).
*
* @member {Number} module:utils/keyboard~KeystrokeInfo#keyCode
*/
/**
* Whether the <kbd>Alt</kbd> modifier was pressed.
*
* @member {Boolean} module:utils/keyboard~KeystrokeInfo#altKey
*/
/**
* Whether the <kbd>Ctrl</kbd> modifier was pressed.
*
* @member {Boolean} module:utils/keyboard~KeystrokeInfo#ctrlKey
*/
/**
* Whether the <kbd>Shift</kbd> modifier was pressed.
*
* @member {Boolean} module:utils/keyboard~KeystrokeInfo#shiftKey
*/
/**
* Whether the <kbd>Cmd</kbd> modifier was pressed.
*
* @member {Boolean} module:utils/keyboard~KeystrokeInfo#metaKey
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-utils/src/keystrokehandler.js":
/*!************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-utils/src/keystrokehandler.js ***!
\************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ KeystrokeHandler)
/* harmony export */ });
/* harmony import */ var _dom_emittermixin__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./dom/emittermixin */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/emittermixin.js");
/* harmony import */ var _keyboard__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./keyboard */ "./node_modules/@ckeditor/ckeditor5-utils/src/keyboard.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module utils/keystrokehandler
*/
/**
* Keystroke handler allows registering callbacks for given keystrokes.
*
* The most frequent use of this class is through the {@link module:core/editor/editor~Editor#keystrokes `editor.keystrokes`}
* property. It allows listening to keystrokes executed in the editing view:
*
* editor.keystrokes.set( 'Ctrl+A', ( keyEvtData, cancel ) => {
* console.log( 'Ctrl+A has been pressed' );
* cancel();
* } );
*
* However, this utility class can be used in various part of the UI. For instance, a certain {@link module:ui/view~View}
* can use it like this:
*
* class MyView extends View {
* constructor() {
* this.keystrokes = new KeystrokeHandler();
*
* this.keystrokes.set( 'tab', handleTabKey );
* }
*
* render() {
* super.render();
*
* this.keystrokes.listenTo( this.element );
* }
* }
*
* That keystroke handler will listen to `keydown` events fired in this view's main element.
*
*/
class KeystrokeHandler {
/**
* Creates an instance of the keystroke handler.
*/
constructor() {
/**
* Listener used to listen to events for easier keystroke handler destruction.
*
* @protected
* @member {module:utils/dom/emittermixin~Emitter}
*/
this._listener = Object.create( _dom_emittermixin__WEBPACK_IMPORTED_MODULE_0__["default"] );
}
/**
* Starts listening for `keydown` events from a given emitter.
*
* @param {module:utils/emittermixin~Emitter} emitter
*/
listenTo( emitter ) {
// The #_listener works here as a kind of dispatcher. It groups the events coming from the same
// keystroke so the listeners can be attached to them with different priorities.
//
// E.g. all the keystrokes with the `keyCode` of 42 coming from the `emitter` are propagated
// as a `_keydown:42` event by the `_listener`. If there's a callback created by the `set`
// method for this 42 keystroke, it listens to the `_listener#_keydown:42` event only and interacts
// only with other listeners of this particular event, thus making it possible to prioritize
// the listeners and safely cancel execution, when needed. Instead of duplicating the Emitter logic,
// the KeystrokeHandler re–uses it to do its job.
this._listener.listenTo( emitter, 'keydown', ( evt, keyEvtData ) => {
this._listener.fire( '_keydown:' + (0,_keyboard__WEBPACK_IMPORTED_MODULE_1__.getCode)( keyEvtData ), keyEvtData );
} );
}
/**
* Registers a handler for the specified keystroke.
*
* @param {String|Array.<String|Number>} keystroke Keystroke defined in a format accepted by
* the {@link module:utils/keyboard~parseKeystroke} function.
* @param {Function} callback A function called with the
* {@link module:engine/view/observer/keyobserver~KeyEventData key event data} object and
* a helper funcion to call both `preventDefault()` and `stopPropagation()` on the underlying event.
* @param {Object} [options={}] Additional options.
* @param {module:utils/priorities~PriorityString|Number} [options.priority='normal'] The priority of the keystroke
* callback. The higher the priority value the sooner the callback will be executed. Keystrokes having the same priority
* are called in the order they were added.
*/
set( keystroke, callback, options = {} ) {
const keyCode = (0,_keyboard__WEBPACK_IMPORTED_MODULE_1__.parseKeystroke)( keystroke );
const priority = options.priority;
// Execute the passed callback on KeystrokeHandler#_keydown.
// TODO: https://github.com/ckeditor/ckeditor5-utils/issues/144
this._listener.listenTo( this._listener, '_keydown:' + keyCode, ( evt, keyEvtData ) => {
callback( keyEvtData, () => {
// Stop the event in the DOM: no listener in the web page
// will be triggered by this event.
keyEvtData.preventDefault();
keyEvtData.stopPropagation();
// Stop the event in the KeystrokeHandler: no more callbacks
// will be executed for this keystroke.
evt.stop();
} );
// Mark this keystroke as handled by the callback. See: #press.
evt.return = true;
}, { priority } );
}
/**
* Triggers a keystroke handler for a specified key combination, if such a keystroke was {@link #set defined}.
*
* @param {module:engine/view/observer/keyobserver~KeyEventData} keyEvtData Key event data.
* @returns {Boolean} Whether the keystroke was handled.
*/
press( keyEvtData ) {
return !!this._listener.fire( '_keydown:' + (0,_keyboard__WEBPACK_IMPORTED_MODULE_1__.getCode)( keyEvtData ), keyEvtData );
}
/**
* Destroys the keystroke handler.
*/
destroy() {
this._listener.stopListening();
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-utils/src/language.js":
/*!****************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-utils/src/language.js ***!
\****************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "getLanguageDirection": () => (/* binding */ getLanguageDirection)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module utils/language
*/
const RTL_LANGUAGE_CODES = [
'ar', 'ara', // Arabic
'fa', 'per', 'fas', // Persian
'he', 'heb', // Hebrew
'ku', 'kur', // Kurdish
'ug', 'uig' // Uighur, Uyghur
];
/**
* Helps determine whether a language text direction is LTR or RTL.
*
* @param {String} language The ISO 639-1 or ISO 639-2 language code.
* @returns {'ltr'|'rtl'}
*/
function getLanguageDirection( languageCode ) {
return RTL_LANGUAGE_CODES.includes( languageCode ) ? 'rtl' : 'ltr';
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-utils/src/locale.js":
/*!**************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-utils/src/locale.js ***!
\**************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ Locale)
/* harmony export */ });
/* harmony import */ var _toarray__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./toarray */ "./node_modules/@ckeditor/ckeditor5-utils/src/toarray.js");
/* harmony import */ var _translation_service__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./translation-service */ "./node_modules/@ckeditor/ckeditor5-utils/src/translation-service.js");
/* harmony import */ var _language__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./language */ "./node_modules/@ckeditor/ckeditor5-utils/src/language.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module utils/locale
*/
/* globals console */
/**
* Represents the localization services.
*/
class Locale {
/**
* Creates a new instance of the locale class. Learn more about
* {@glink features/ui-language configuring the language of the editor}.
*
* @param {Object} [options] Locale configuration.
* @param {String} [options.uiLanguage='en'] The editor UI language code in the
* [ISO 639-1](https://en.wikipedia.org/wiki/ISO_639-1) format. See {@link #uiLanguage}.
* @param {String} [options.contentLanguage] The editor content language code in the
* [ISO 639-1](https://en.wikipedia.org/wiki/ISO_639-1) format. If not specified, the same as `options.language`.
* See {@link #contentLanguage}.
*/
constructor( options = {} ) {
/**
* The editor UI language code in the [ISO 639-1](https://en.wikipedia.org/wiki/ISO_639-1) format.
*
* If the {@link #contentLanguage content language} was not specified in the `Locale` constructor,
* it also defines the language of the content.
*
* @readonly
* @member {String}
*/
this.uiLanguage = options.uiLanguage || 'en';
/**
* The editor content language code in the [ISO 639-1](https://en.wikipedia.org/wiki/ISO_639-1) format.
*
* Usually the same as the {@link #uiLanguage editor language}, it can be customized by passing an optional
* argument to the `Locale` constructor.
*
* @readonly
* @member {String}
*/
this.contentLanguage = options.contentLanguage || this.uiLanguage;
/**
* Text direction of the {@link #uiLanguage editor UI language}. Either `'ltr'` or `'rtl'`.
*
* @readonly
* @member {String}
*/
this.uiLanguageDirection = (0,_language__WEBPACK_IMPORTED_MODULE_2__.getLanguageDirection)( this.uiLanguage );
/**
* Text direction of the {@link #contentLanguage editor content language}.
*
* If the content language was passed directly to the `Locale` constructor, this property represents the
* direction of that language.
*
* If the {@link #contentLanguage editor content language} was derived from the {@link #uiLanguage editor language},
* the content language direction is the same as the {@link #uiLanguageDirection UI language direction}.
*
* The value is either `'ltr'` or `'rtl'`.
*
* @readonly
* @member {String}
*/
this.contentLanguageDirection = (0,_language__WEBPACK_IMPORTED_MODULE_2__.getLanguageDirection)( this.contentLanguage );
/**
* Translates the given message to the {@link #uiLanguage}. This method is also available in
* {@link module:core/editor/editor~Editor#t `Editor`} and {@link module:ui/view~View#t `View`}.
*
* This method's context is statically bound to the `Locale` instance and **should always be called as a function**:
*
* const t = locale.t;
* t( 'Label' );
*
* The message can be either a string or an object implementing the {@link module:utils/translation-service~Message} interface.
*
* The message may contain placeholders (`%<index>`) for value(s) that are passed as a `values` parameter.
* For an array of values, the `%<index>` will be changed to an element of that array at the given index.
* For a single value passed as the second argument, only the `%0` placeholders will be changed to the provided value.
*
* t( 'Created file "%0" in %1ms.', [ fileName, timeTaken ] );
* t( 'Created file "%0", fileName );
*
* The message supports plural forms. To specify the plural form, use the `plural` property. Singular or plural form
* will be chosen depending on the first value from the passed `values`. The value of the `plural` property is used
* as a default plural translation when the translation for the target language is missing.
*
* t( { string: 'Add a space', plural: 'Add %0 spaces' }, 1 ); // 'Add a space' for the English language.
* t( { string: 'Add a space', plural: 'Add %0 spaces' }, 5 ); // 'Add 5 spaces' for the English language.
* t( { string: '%1 a space', plural: '%1 %0 spaces' }, [ 2, 'Add' ] ); // 'Add 2 spaces' for the English language.
*
* t( { string: 'Add a space', plural: 'Add %0 spaces' }, 1 ); // 'Dodaj spację' for the Polish language.
* t( { string: 'Add a space', plural: 'Add %0 spaces' }, 5 ); // 'Dodaj 5 spacji' for the Polish language.
* t( { string: '%1 a space', plural: '%1 %0 spaces' }, [ 2, 'Add' ] ); // 'Dodaj 2 spacje' for the Polish language.
*
* * The message should provide an ID using the `id` property when the message strings are not unique and their
* translations should be different.
*
* translate( 'en', { string: 'image', id: 'ADD_IMAGE' } );
* translate( 'en', { string: 'image', id: 'AN_IMAGE' } );
*
* @method #t
* @param {String|module:utils/translation-service~Message} message A message that will be localized (translated).
* @param {String|Number|Array.<String|Number>} [values] A value or an array of values that will fill message placeholders.
* For messages supporting plural forms the first value will determine the plural form.
* @returns {String}
*/
this.t = ( message, values ) => this._t( message, values );
}
/**
* The editor UI language code in the [ISO 639-1](https://en.wikipedia.org/wiki/ISO_639-1) format.
*
* **Note**: This property was deprecated. Please use {@link #uiLanguage} and {@link #contentLanguage}
* properties instead.
*
* @deprecated
* @member {String}
*/
get language() {
/**
* The {@link module:utils/locale~Locale#language `Locale#language`} property was deprecated and will
* be removed in the near future. Please use the {@link #uiLanguage} and {@link #contentLanguage} properties instead.
*
* @error locale-deprecated-language-property
*/
console.warn(
'locale-deprecated-language-property: ' +
'The Locale#language property has been deprecated and will be removed in the near future. ' +
'Please use #uiLanguage and #contentLanguage properties instead.' );
return this.uiLanguage;
}
/**
* An unbound version of the {@link #t} method.
*
* @private
* @param {String|module:utils/translation-service~Message} message
* @param {Number|String|Array.<Number|String>} [values]
* @returns {String}
*/
_t( message, values = [] ) {
values = (0,_toarray__WEBPACK_IMPORTED_MODULE_0__["default"])( values );
if ( typeof message === 'string' ) {
message = { string: message };
}
const hasPluralForm = !!message.plural;
const quantity = hasPluralForm ? values[ 0 ] : 1;
const translatedString = (0,_translation_service__WEBPACK_IMPORTED_MODULE_1__._translate)( this.uiLanguage, message, quantity );
return interpolateString( translatedString, values );
}
}
// Fills the `%0, %1, ...` string placeholders with values.
function interpolateString( string, values ) {
return string.replace( /%(\d+)/g, ( match, index ) => {
return ( index < values.length ) ? values[ index ] : match;
} );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-utils/src/mix.js":
/*!***********************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-utils/src/mix.js ***!
\***********************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ mix)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module utils/mix
*/
/**
* Copies enumerable properties and symbols from the objects given as 2nd+ parameters to the
* prototype of first object (a constructor).
*
* class Editor {
* ...
* }
*
* const SomeMixin = {
* a() {
* return 'a';
* }
* };
*
* mix( Editor, SomeMixin, ... );
*
* new Editor().a(); // -> 'a'
*
* Note: Properties which already exist in the base class will not be overriden.
*
* @param {Function} [baseClass] Class which prototype will be extended.
* @param {Object} [...mixins] Objects from which to get properties.
*/
function mix( baseClass, ...mixins ) {
mixins.forEach( mixin => {
Object.getOwnPropertyNames( mixin ).concat( Object.getOwnPropertySymbols( mixin ) )
.forEach( key => {
if ( key in baseClass.prototype ) {
return;
}
const sourceDescriptor = Object.getOwnPropertyDescriptor( mixin, key );
sourceDescriptor.enumerable = false;
Object.defineProperty( baseClass.prototype, key, sourceDescriptor );
} );
} );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-utils/src/objecttomap.js":
/*!*******************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-utils/src/objecttomap.js ***!
\*******************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ objectToMap)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module utils/objecttomap
*/
/**
* Transforms object to map.
*
* const map = objectToMap( { 'foo': 1, 'bar': 2 } );
* map.get( 'foo' ); // 1
*
* **Note**: For mixed data (`Object` or `Iterable`) there's a dedicated {@link module:utils/tomap~toMap} function.
*
* @param {Object} obj Object to transform.
* @returns {Map} Map created from object.
*/
function objectToMap( obj ) {
const map = new Map();
for ( const key in obj ) {
map.set( key, obj[ key ] );
}
return map;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-utils/src/observablemixin.js":
/*!***********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-utils/src/observablemixin.js ***!
\***********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _emittermixin__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./emittermixin */ "./node_modules/@ckeditor/ckeditor5-utils/src/emittermixin.js");
/* harmony import */ var _ckeditorerror__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/* harmony import */ var lodash_es__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! lodash-es */ "./node_modules/lodash-es/isObject.js");
/* harmony import */ var lodash_es__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! lodash-es */ "./node_modules/lodash-es/assignIn.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module utils/observablemixin
*/
const observablePropertiesSymbol = Symbol( 'observableProperties' );
const boundObservablesSymbol = Symbol( 'boundObservables' );
const boundPropertiesSymbol = Symbol( 'boundProperties' );
const _decoratedMethods = Symbol( 'decoratedMethods' );
const _decoratedOriginal = Symbol( 'decoratedOriginal' );
/**
* A mixin that injects the "observable properties" and data binding functionality described in the
* {@link ~Observable} interface.
*
* Read more about the concept of observables in the:
* * {@glink framework/guides/architecture/core-editor-architecture#event-system-and-observables Event system and observables}
* section of the {@glink framework/guides/architecture/core-editor-architecture Core editor architecture} guide,
* * {@glink framework/guides/deep-dive/observables Observables deep dive} guide.
*
* @mixin ObservableMixin
* @mixes module:utils/emittermixin~EmitterMixin
* @implements module:utils/observablemixin~Observable
*/
const ObservableMixin = {
/**
* @inheritDoc
*/
set( name, value ) {
// If the first parameter is an Object, iterate over its properties.
if ( (0,lodash_es__WEBPACK_IMPORTED_MODULE_2__["default"])( name ) ) {
Object.keys( name ).forEach( property => {
this.set( property, name[ property ] );
}, this );
return;
}
initObservable( this );
const properties = this[ observablePropertiesSymbol ];
if ( ( name in this ) && !properties.has( name ) ) {
/**
* Cannot override an existing property.
*
* This error is thrown when trying to {@link ~Observable#set set} a property with
* a name of an already existing property. For example:
*
* let observable = new Model();
* observable.property = 1;
* observable.set( 'property', 2 ); // throws
*
* observable.set( 'property', 1 );
* observable.set( 'property', 2 ); // ok, because this is an existing property.
*
* @error observable-set-cannot-override
*/
throw new _ckeditorerror__WEBPACK_IMPORTED_MODULE_1__["default"]( 'observable-set-cannot-override', this );
}
Object.defineProperty( this, name, {
enumerable: true,
configurable: true,
get() {
return properties.get( name );
},
set( value ) {
const oldValue = properties.get( name );
// Fire `set` event before the new value will be set to make it possible
// to override observable property without affecting `change` event.
// See https://github.com/ckeditor/ckeditor5-utils/issues/171.
let newValue = this.fire( 'set:' + name, name, value, oldValue );
if ( newValue === undefined ) {
newValue = value;
}
// Allow undefined as an initial value like A.define( 'x', undefined ) (#132).
// Note: When properties map has no such own property, then its value is undefined.
if ( oldValue !== newValue || !properties.has( name ) ) {
properties.set( name, newValue );
this.fire( 'change:' + name, name, newValue, oldValue );
}
}
} );
this[ name ] = value;
},
/**
* @inheritDoc
*/
bind( ...bindProperties ) {
if ( !bindProperties.length || !isStringArray( bindProperties ) ) {
/**
* All properties must be strings.
*
* @error observable-bind-wrong-properties
*/
throw new _ckeditorerror__WEBPACK_IMPORTED_MODULE_1__["default"]( 'observable-bind-wrong-properties', this );
}
if ( ( new Set( bindProperties ) ).size !== bindProperties.length ) {
/**
* Properties must be unique.
*
* @error observable-bind-duplicate-properties
*/
throw new _ckeditorerror__WEBPACK_IMPORTED_MODULE_1__["default"]( 'observable-bind-duplicate-properties', this );
}
initObservable( this );
const boundProperties = this[ boundPropertiesSymbol ];
bindProperties.forEach( propertyName => {
if ( boundProperties.has( propertyName ) ) {
/**
* Cannot bind the same property more than once.
*
* @error observable-bind-rebind
*/
throw new _ckeditorerror__WEBPACK_IMPORTED_MODULE_1__["default"]( 'observable-bind-rebind', this );
}
} );
const bindings = new Map();
// @typedef {Object} Binding
// @property {Array} property Property which is bound.
// @property {Array} to Array of observable–property components of the binding (`{ observable: ..., property: .. }`).
// @property {Array} callback A function which processes `to` components.
bindProperties.forEach( a => {
const binding = { property: a, to: [] };
boundProperties.set( a, binding );
bindings.set( a, binding );
} );
// @typedef {Object} BindChain
// @property {Function} to See {@link ~ObservableMixin#_bindTo}.
// @property {Function} toMany See {@link ~ObservableMixin#_bindToMany}.
// @property {module:utils/observablemixin~Observable} _observable The observable which initializes the binding.
// @property {Array} _bindProperties Array of `_observable` properties to be bound.
// @property {Array} _to Array of `to()` observable–properties (`{ observable: toObservable, properties: ...toProperties }`).
// @property {Map} _bindings Stores bindings to be kept in
// {@link ~ObservableMixin#_boundProperties}/{@link ~ObservableMixin#_boundObservables}
// initiated in this binding chain.
return {
to: bindTo,
toMany: bindToMany,
_observable: this,
_bindProperties: bindProperties,
_to: [],
_bindings: bindings
};
},
/**
* @inheritDoc
*/
unbind( ...unbindProperties ) {
// Nothing to do here if not inited yet.
if ( !( this[ observablePropertiesSymbol ] ) ) {
return;
}
const boundProperties = this[ boundPropertiesSymbol ];
const boundObservables = this[ boundObservablesSymbol ];
if ( unbindProperties.length ) {
if ( !isStringArray( unbindProperties ) ) {
/**
* Properties must be strings.
*
* @error observable-unbind-wrong-properties
*/
throw new _ckeditorerror__WEBPACK_IMPORTED_MODULE_1__["default"]( 'observable-unbind-wrong-properties', this );
}
unbindProperties.forEach( propertyName => {
const binding = boundProperties.get( propertyName );
// Nothing to do if the binding is not defined
if ( !binding ) {
return;
}
let toObservable, toProperty, toProperties, toPropertyBindings;
binding.to.forEach( to => {
// TODO: ES6 destructuring.
toObservable = to[ 0 ];
toProperty = to[ 1 ];
toProperties = boundObservables.get( toObservable );
toPropertyBindings = toProperties[ toProperty ];
toPropertyBindings.delete( binding );
if ( !toPropertyBindings.size ) {
delete toProperties[ toProperty ];
}
if ( !Object.keys( toProperties ).length ) {
boundObservables.delete( toObservable );
this.stopListening( toObservable, 'change' );
}
} );
boundProperties.delete( propertyName );
} );
} else {
boundObservables.forEach( ( bindings, boundObservable ) => {
this.stopListening( boundObservable, 'change' );
} );
boundObservables.clear();
boundProperties.clear();
}
},
/**
* @inheritDoc
*/
decorate( methodName ) {
const originalMethod = this[ methodName ];
if ( !originalMethod ) {
/**
* Cannot decorate an undefined method.
*
* @error observablemixin-cannot-decorate-undefined
* @param {Object} object The object which method should be decorated.
* @param {String} methodName Name of the method which does not exist.
*/
throw new _ckeditorerror__WEBPACK_IMPORTED_MODULE_1__["default"](
'observablemixin-cannot-decorate-undefined',
this,
{ object: this, methodName }
);
}
this.on( methodName, ( evt, args ) => {
evt.return = originalMethod.apply( this, args );
} );
this[ methodName ] = function( ...args ) {
return this.fire( methodName, args );
};
this[ methodName ][ _decoratedOriginal ] = originalMethod;
if ( !this[ _decoratedMethods ] ) {
this[ _decoratedMethods ] = [];
}
this[ _decoratedMethods ].push( methodName );
}
};
(0,lodash_es__WEBPACK_IMPORTED_MODULE_3__["default"])( ObservableMixin, _emittermixin__WEBPACK_IMPORTED_MODULE_0__["default"] );
// Override the EmitterMixin stopListening method to be able to clean (and restore) decorated methods.
// This is needed in case of:
// 1. Have x.foo() decorated.
// 2. Call x.stopListening()
// 3. Call x.foo(). Problem: nothing happens (the original foo() method is not executed)
ObservableMixin.stopListening = function( emitter, event, callback ) {
// Removing all listeners so let's clean the decorated methods to the original state.
if ( !emitter && this[ _decoratedMethods ] ) {
for ( const methodName of this[ _decoratedMethods ] ) {
this[ methodName ] = this[ methodName ][ _decoratedOriginal ];
}
delete this[ _decoratedMethods ];
}
_emittermixin__WEBPACK_IMPORTED_MODULE_0__["default"].stopListening.call( this, emitter, event, callback );
};
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (ObservableMixin);
// Init symbol properties needed for the observable mechanism to work.
//
// @private
// @param {module:utils/observablemixin~ObservableMixin} observable
function initObservable( observable ) {
// Do nothing if already inited.
if ( observable[ observablePropertiesSymbol ] ) {
return;
}
// The internal hash containing the observable's state.
//
// @private
// @type {Map}
Object.defineProperty( observable, observablePropertiesSymbol, {
value: new Map()
} );
// Map containing bindings to external observables. It shares the binding objects
// (`{ observable: A, property: 'a', to: ... }`) with {@link module:utils/observablemixin~ObservableMixin#_boundProperties} and
// it is used to observe external observables to update own properties accordingly.
// See {@link module:utils/observablemixin~ObservableMixin#bind}.
//
// A.bind( 'a', 'b', 'c' ).to( B, 'x', 'y', 'x' );
// console.log( A._boundObservables );
//
// Map( {
// B: {
// x: Set( [
// { observable: A, property: 'a', to: [ [ B, 'x' ] ] },
// { observable: A, property: 'c', to: [ [ B, 'x' ] ] }
// ] ),
// y: Set( [
// { observable: A, property: 'b', to: [ [ B, 'y' ] ] },
// ] )
// }
// } )
//
// A.bind( 'd' ).to( B, 'z' ).to( C, 'w' ).as( callback );
// console.log( A._boundObservables );
//
// Map( {
// B: {
// x: Set( [
// { observable: A, property: 'a', to: [ [ B, 'x' ] ] },
// { observable: A, property: 'c', to: [ [ B, 'x' ] ] }
// ] ),
// y: Set( [
// { observable: A, property: 'b', to: [ [ B, 'y' ] ] },
// ] ),
// z: Set( [
// { observable: A, property: 'd', to: [ [ B, 'z' ], [ C, 'w' ] ], callback: callback }
// ] )
// },
// C: {
// w: Set( [
// { observable: A, property: 'd', to: [ [ B, 'z' ], [ C, 'w' ] ], callback: callback }
// ] )
// }
// } )
//
// @private
// @type {Map}
Object.defineProperty( observable, boundObservablesSymbol, {
value: new Map()
} );
// Object that stores which properties of this observable are bound and how. It shares
// the binding objects (`{ observable: A, property: 'a', to: ... }`) with
// {@link module:utils/observablemixin~ObservableMixin#_boundObservables}. This data structure is
// a reverse of {@link module:utils/observablemixin~ObservableMixin#_boundObservables} and it is helpful for
// {@link module:utils/observablemixin~ObservableMixin#unbind}.
//
// See {@link module:utils/observablemixin~ObservableMixin#bind}.
//
// A.bind( 'a', 'b', 'c' ).to( B, 'x', 'y', 'x' );
// console.log( A._boundProperties );
//
// Map( {
// a: { observable: A, property: 'a', to: [ [ B, 'x' ] ] },
// b: { observable: A, property: 'b', to: [ [ B, 'y' ] ] },
// c: { observable: A, property: 'c', to: [ [ B, 'x' ] ] }
// } )
//
// A.bind( 'd' ).to( B, 'z' ).to( C, 'w' ).as( callback );
// console.log( A._boundProperties );
//
// Map( {
// a: { observable: A, property: 'a', to: [ [ B, 'x' ] ] },
// b: { observable: A, property: 'b', to: [ [ B, 'y' ] ] },
// c: { observable: A, property: 'c', to: [ [ B, 'x' ] ] },
// d: { observable: A, property: 'd', to: [ [ B, 'z' ], [ C, 'w' ] ], callback: callback }
// } )
//
// @private
// @type {Map}
Object.defineProperty( observable, boundPropertiesSymbol, {
value: new Map()
} );
}
// A chaining for {@link module:utils/observablemixin~ObservableMixin#bind} providing `.to()` interface.
//
// @private
// @param {...[Observable|String|Function]} args Arguments of the `.to( args )` binding.
function bindTo( ...args ) {
const parsedArgs = parseBindToArgs( ...args );
const bindingsKeys = Array.from( this._bindings.keys() );
const numberOfBindings = bindingsKeys.length;
// Eliminate A.bind( 'x' ).to( B, C )
if ( !parsedArgs.callback && parsedArgs.to.length > 1 ) {
/**
* Binding multiple observables only possible with callback.
*
* @error observable-bind-to-no-callback
*/
throw new _ckeditorerror__WEBPACK_IMPORTED_MODULE_1__["default"]( 'observable-bind-to-no-callback', this );
}
// Eliminate A.bind( 'x', 'y' ).to( B, callback )
if ( numberOfBindings > 1 && parsedArgs.callback ) {
/**
* Cannot bind multiple properties and use a callback in one binding.
*
* @error observable-bind-to-extra-callback
*/
throw new _ckeditorerror__WEBPACK_IMPORTED_MODULE_1__["default"](
'observable-bind-to-extra-callback',
this
);
}
parsedArgs.to.forEach( to => {
// Eliminate A.bind( 'x', 'y' ).to( B, 'a' )
if ( to.properties.length && to.properties.length !== numberOfBindings ) {
/**
* The number of properties must match.
*
* @error observable-bind-to-properties-length
*/
throw new _ckeditorerror__WEBPACK_IMPORTED_MODULE_1__["default"]( 'observable-bind-to-properties-length', this );
}
// When no to.properties specified, observing source properties instead i.e.
// A.bind( 'x', 'y' ).to( B ) -> Observe B.x and B.y
if ( !to.properties.length ) {
to.properties = this._bindProperties;
}
} );
this._to = parsedArgs.to;
// Fill {@link BindChain#_bindings} with callback. When the callback is set there's only one binding.
if ( parsedArgs.callback ) {
this._bindings.get( bindingsKeys[ 0 ] ).callback = parsedArgs.callback;
}
attachBindToListeners( this._observable, this._to );
// Update observable._boundProperties and observable._boundObservables.
updateBindToBound( this );
// Set initial values of bound properties.
this._bindProperties.forEach( propertyName => {
updateBoundObservableProperty( this._observable, propertyName );
} );
}
// Binds to an attribute in a set of iterable observables.
//
// @private
// @param {Array.<Observable>} observables
// @param {String} attribute
// @param {Function} callback
function bindToMany( observables, attribute, callback ) {
if ( this._bindings.size > 1 ) {
/**
* Binding one attribute to many observables only possible with one attribute.
*
* @error observable-bind-to-many-not-one-binding
*/
throw new _ckeditorerror__WEBPACK_IMPORTED_MODULE_1__["default"]( 'observable-bind-to-many-not-one-binding', this );
}
this.to(
// Bind to #attribute of each observable...
...getBindingTargets( observables, attribute ),
// ...using given callback to parse attribute values.
callback
);
}
// Returns an array of binding components for
// {@link Observable#bind} from a set of iterable observables.
//
// @param {Array.<Observable>} observables
// @param {String} attribute
// @returns {Array.<String|Observable>}
function getBindingTargets( observables, attribute ) {
const observableAndAttributePairs = observables.map( observable => [ observable, attribute ] );
// Merge pairs to one-dimension array of observables and attributes.
return Array.prototype.concat.apply( [], observableAndAttributePairs );
}
// Check if all entries of the array are of `String` type.
//
// @private
// @param {Array} arr An array to be checked.
// @returns {Boolean}
function isStringArray( arr ) {
return arr.every( a => typeof a == 'string' );
}
// Parses and validates {@link Observable#bind}`.to( args )` arguments and returns
// an object with a parsed structure. For example
//
// A.bind( 'x' ).to( B, 'a', C, 'b', call );
//
// becomes
//
// {
// to: [
// { observable: B, properties: [ 'a' ] },
// { observable: C, properties: [ 'b' ] },
// ],
// callback: call
// }
//
// @private
// @param {...*} args Arguments of {@link Observable#bind}`.to( args )`.
// @returns {Object}
function parseBindToArgs( ...args ) {
// Eliminate A.bind( 'x' ).to()
if ( !args.length ) {
/**
* Invalid argument syntax in `to()`.
*
* @error observable-bind-to-parse-error
*/
throw new _ckeditorerror__WEBPACK_IMPORTED_MODULE_1__["default"]( 'observable-bind-to-parse-error', null );
}
const parsed = { to: [] };
let lastObservable;
if ( typeof args[ args.length - 1 ] == 'function' ) {
parsed.callback = args.pop();
}
args.forEach( a => {
if ( typeof a == 'string' ) {
lastObservable.properties.push( a );
} else if ( typeof a == 'object' ) {
lastObservable = { observable: a, properties: [] };
parsed.to.push( lastObservable );
} else {
throw new _ckeditorerror__WEBPACK_IMPORTED_MODULE_1__["default"]( 'observable-bind-to-parse-error', null );
}
} );
return parsed;
}
// Synchronizes {@link module:utils/observablemixin#_boundObservables} with {@link Binding}.
//
// @private
// @param {Binding} binding A binding to store in {@link Observable#_boundObservables}.
// @param {Observable} toObservable A observable, which is a new component of `binding`.
// @param {String} toPropertyName A name of `toObservable`'s property, a new component of the `binding`.
function updateBoundObservables( observable, binding, toObservable, toPropertyName ) {
const boundObservables = observable[ boundObservablesSymbol ];
const bindingsToObservable = boundObservables.get( toObservable );
const bindings = bindingsToObservable || {};
if ( !bindings[ toPropertyName ] ) {
bindings[ toPropertyName ] = new Set();
}
// Pass the binding to a corresponding Set in `observable._boundObservables`.
bindings[ toPropertyName ].add( binding );
if ( !bindingsToObservable ) {
boundObservables.set( toObservable, bindings );
}
}
// Synchronizes {@link Observable#_boundProperties} and {@link Observable#_boundObservables}
// with {@link BindChain}.
//
// Assuming the following binding being created
//
// A.bind( 'a', 'b' ).to( B, 'x', 'y' );
//
// the following bindings were initialized by {@link Observable#bind} in {@link BindChain#_bindings}:
//
// {
// a: { observable: A, property: 'a', to: [] },
// b: { observable: A, property: 'b', to: [] },
// }
//
// Iterate over all bindings in this chain and fill their `to` properties with
// corresponding to( ... ) arguments (components of the binding), so
//
// {
// a: { observable: A, property: 'a', to: [ B, 'x' ] },
// b: { observable: A, property: 'b', to: [ B, 'y' ] },
// }
//
// Then update the structure of {@link Observable#_boundObservables} with updated
// binding, so it becomes:
//
// Map( {
// B: {
// x: Set( [
// { observable: A, property: 'a', to: [ [ B, 'x' ] ] }
// ] ),
// y: Set( [
// { observable: A, property: 'b', to: [ [ B, 'y' ] ] },
// ] )
// }
// } )
//
// @private
// @param {BindChain} chain The binding initialized by {@link Observable#bind}.
function updateBindToBound( chain ) {
let toProperty;
chain._bindings.forEach( ( binding, propertyName ) => {
// Note: For a binding without a callback, this will run only once
// like in A.bind( 'x', 'y' ).to( B, 'a', 'b' )
// TODO: ES6 destructuring.
chain._to.forEach( to => {
toProperty = to.properties[ binding.callback ? 0 : chain._bindProperties.indexOf( propertyName ) ];
binding.to.push( [ to.observable, toProperty ] );
updateBoundObservables( chain._observable, binding, to.observable, toProperty );
} );
} );
}
// Updates an property of a {@link Observable} with a value
// determined by an entry in {@link Observable#_boundProperties}.
//
// @private
// @param {Observable} observable A observable which property is to be updated.
// @param {String} propertyName An property to be updated.
function updateBoundObservableProperty( observable, propertyName ) {
const boundProperties = observable[ boundPropertiesSymbol ];
const binding = boundProperties.get( propertyName );
let propertyValue;
// When a binding with callback is created like
//
// A.bind( 'a' ).to( B, 'b', C, 'c', callback );
//
// collect B.b and C.c, then pass them to callback to set A.a.
if ( binding.callback ) {
propertyValue = binding.callback.apply( observable, binding.to.map( to => to[ 0 ][ to[ 1 ] ] ) );
} else {
propertyValue = binding.to[ 0 ];
propertyValue = propertyValue[ 0 ][ propertyValue[ 1 ] ];
}
if ( Object.prototype.hasOwnProperty.call( observable, propertyName ) ) {
observable[ propertyName ] = propertyValue;
} else {
observable.set( propertyName, propertyValue );
}
}
// Starts listening to changes in {@link BindChain._to} observables to update
// {@link BindChain._observable} {@link BindChain._bindProperties}. Also sets the
// initial state of {@link BindChain._observable}.
//
// @private
// @param {BindChain} chain The chain initialized by {@link Observable#bind}.
function attachBindToListeners( observable, toBindings ) {
toBindings.forEach( to => {
const boundObservables = observable[ boundObservablesSymbol ];
let bindings;
// If there's already a chain between the observables (`observable` listens to
// `to.observable`), there's no need to create another `change` event listener.
if ( !boundObservables.get( to.observable ) ) {
observable.listenTo( to.observable, 'change', ( evt, propertyName ) => {
bindings = boundObservables.get( to.observable )[ propertyName ];
// Note: to.observable will fire for any property change, react
// to changes of properties which are bound only.
if ( bindings ) {
bindings.forEach( binding => {
updateBoundObservableProperty( observable, binding.property );
} );
}
} );
}
} );
}
/**
* An interface which adds "observable properties" and data binding functionality.
*
* Can be easily implemented by a class by mixing the {@link module:utils/observablemixin~ObservableMixin} mixin.
*
* Read more about the usage of this interface in the:
* * {@glink framework/guides/architecture/core-editor-architecture#event-system-and-observables Event system and observables}
* section of the {@glink framework/guides/architecture/core-editor-architecture Core editor architecture} guide,
* * {@glink framework/guides/deep-dive/observables Observables deep dive} guide.
*
* @interface Observable
* @extends module:utils/emittermixin~Emitter
*/
/**
* Fired when a property changed value.
*
* observable.set( 'prop', 1 );
*
* observable.on( 'change:prop', ( evt, propertyName, newValue, oldValue ) => {
* console.log( `${ propertyName } has changed from ${ oldValue } to ${ newValue }` );
* } );
*
* observable.prop = 2; // -> 'prop has changed from 1 to 2'
*
* @event change:{property}
* @param {String} name The property name.
* @param {*} value The new property value.
* @param {*} oldValue The previous property value.
*/
/**
* Fired when a property value is going to be set but is not set yet (before the `change` event is fired).
*
* You can control the final value of the property by using
* the {@link module:utils/eventinfo~EventInfo#return event's `return` property}.
*
* observable.set( 'prop', 1 );
*
* observable.on( 'set:prop', ( evt, propertyName, newValue, oldValue ) => {
* console.log( `Value is going to be changed from ${ oldValue } to ${ newValue }` );
* console.log( `Current property value is ${ observable[ propertyName ] }` );
*
* // Let's override the value.
* evt.return = 3;
* } );
*
* observable.on( 'change:prop', ( evt, propertyName, newValue, oldValue ) => {
* console.log( `Value has changed from ${ oldValue } to ${ newValue }` );
* } );
*
* observable.prop = 2; // -> 'Value is going to be changed from 1 to 2'
* // -> 'Current property value is 1'
* // -> 'Value has changed from 1 to 3'
*
* **Note:** The event is fired even when the new value is the same as the old value.
*
* @event set:{property}
* @param {String} name The property name.
* @param {*} value The new property value.
* @param {*} oldValue The previous property value.
*/
/**
* Creates and sets the value of an observable property of this object. Such a property becomes a part
* of the state and is observable.
*
* It accepts also a single object literal containing key/value pairs with properties to be set.
*
* This method throws the `observable-set-cannot-override` error if the observable instance already
* has a property with the given property name. This prevents from mistakenly overriding existing
* properties and methods, but means that `foo.set( 'bar', 1 )` may be slightly slower than `foo.bar = 1`.
*
* @method #set
* @param {String|Object} name The property's name or object with `name=>value` pairs.
* @param {*} [value] The property's value (if `name` was passed in the first parameter).
*/
/**
* Binds {@link #set observable properties} to other objects implementing the
* {@link module:utils/observablemixin~Observable} interface.
*
* Read more in the {@glink framework/guides/deep-dive/observables#property-bindings dedicated guide}
* covering the topic of property bindings with some additional examples.
*
* Consider two objects: a `button` and an associated `command` (both `Observable`).
*
* A simple property binding could be as follows:
*
* button.bind( 'isEnabled' ).to( command, 'isEnabled' );
*
* or even shorter:
*
* button.bind( 'isEnabled' ).to( command );
*
* which works in the following way:
*
* * `button.isEnabled` **instantly equals** `command.isEnabled`,
* * whenever `command.isEnabled` changes, `button.isEnabled` will immediately reflect its value.
*
* **Note**: To release the binding, use {@link module:utils/observablemixin~Observable#unbind}.
*
* You can also "rename" the property in the binding by specifying the new name in the `to()` chain:
*
* button.bind( 'isEnabled' ).to( command, 'isWorking' );
*
* It is possible to bind more than one property at a time to shorten the code:
*
* button.bind( 'isEnabled', 'value' ).to( command );
*
* which corresponds to:
*
* button.bind( 'isEnabled' ).to( command );
* button.bind( 'value' ).to( command );
*
* The binding can include more than one observable, combining multiple data sources in a custom callback:
*
* button.bind( 'isEnabled' ).to( command, 'isEnabled', ui, 'isVisible',
* ( isCommandEnabled, isUIVisible ) => isCommandEnabled && isUIVisible );
*
* Using a custom callback allows processing the value before passing it to the target property:
*
* button.bind( 'isEnabled' ).to( command, 'value', value => value === 'heading1' );
*
* It is also possible to bind to the same property in an array of observables.
* To bind a `button` to multiple commands (also `Observables`) so that each and every one of them
* must be enabled for the button to become enabled, use the following code:
*
* button.bind( 'isEnabled' ).toMany( [ commandA, commandB, commandC ], 'isEnabled',
* ( isAEnabled, isBEnabled, isCEnabled ) => isAEnabled && isBEnabled && isCEnabled );
*
* @method #bind
* @param {...String} bindProperties Observable properties that will be bound to other observable(s).
* @returns {Object} The bind chain with the `to()` and `toMany()` methods.
*/
/**
* Removes the binding created with {@link #bind}.
*
* // Removes the binding for the 'a' property.
* A.unbind( 'a' );
*
* // Removes bindings for all properties.
* A.unbind();
*
* @method #unbind
* @param {...String} [unbindProperties] Observable properties to be unbound. All the bindings will
* be released if no properties are provided.
*/
/**
* Turns the given methods of this object into event-based ones. This means that the new method will fire an event
* (named after the method) and the original action will be plugged as a listener to that event.
*
* Read more in the {@glink framework/guides/deep-dive/observables#decorating-object-methods dedicated guide}
* covering the topic of decorating methods with some additional examples.
*
* Decorating the method does not change its behavior (it only adds an event),
* but it allows to modify it later on by listening to the method's event.
*
* For example, to cancel the method execution the event can be {@link module:utils/eventinfo~EventInfo#stop stopped}:
*
* class Foo {
* constructor() {
* this.decorate( 'method' );
* }
*
* method() {
* console.log( 'called!' );
* }
* }
*
* const foo = new Foo();
* foo.on( 'method', ( evt ) => {
* evt.stop();
* }, { priority: 'high' } );
*
* foo.method(); // Nothing is logged.
*
*
* **Note**: The high {@link module:utils/priorities~PriorityString priority} listener
* has been used to execute this particular callback before the one which calls the original method
* (which uses the "normal" priority).
*
* It is also possible to change the returned value:
*
* foo.on( 'method', ( evt ) => {
* evt.return = 'Foo!';
* } );
*
* foo.method(); // -> 'Foo'
*
* Finally, it is possible to access and modify the arguments the method is called with:
*
* method( a, b ) {
* console.log( `${ a }, ${ b }` );
* }
*
* // ...
*
* foo.on( 'method', ( evt, args ) => {
* args[ 0 ] = 3;
*
* console.log( args[ 1 ] ); // -> 2
* }, { priority: 'high' } );
*
* foo.method( 1, 2 ); // -> '3, 2'
*
* @method #decorate
* @param {String} methodName Name of the method to decorate.
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-utils/src/priorities.js":
/*!******************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-utils/src/priorities.js ***!
\******************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module utils/priorities
*/
/**
* String representing a priority value.
*
* @typedef {'highest'|'high'|'normal'|'low'|'lowest'} module:utils/priorities~PriorityString
*/
/**
* Provides group of constants to use instead of hardcoding numeric priority values.
*
* @namespace
*/
const priorities = {
/**
* Converts a string with priority name to it's numeric value. If `Number` is given, it just returns it.
*
* @static
* @param {module:utils/priorities~PriorityString|Number} priority Priority to convert.
* @returns {Number} Converted priority.
*/
get( priority ) {
if ( typeof priority != 'number' ) {
return this[ priority ] || this.normal;
} else {
return priority;
}
},
highest: 100000,
high: 1000,
normal: 0,
low: -1000,
lowest: -100000
};
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (priorities);
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-utils/src/spy.js":
/*!***********************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-utils/src/spy.js ***!
\***********************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module utils/spy
*/
/**
* Creates a spy function (ala Sinon.js) that can be used to inspect call to it.
*
* The following are the present features:
*
* * spy.called: property set to `true` if the function has been called at least once.
*
* @returns {Function} The spy function.
*/
function spy() {
return function spy() {
spy.called = true;
};
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (spy);
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-utils/src/toarray.js":
/*!***************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-utils/src/toarray.js ***!
\***************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ toArray)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module utils/toarray
*/
/**
* Transforms any value to an array. If the provided value is already an array, it is returned unchanged.
*
* @param {*} data The value to transform to an array.
* @returns {Array} An array created from data.
*/
function toArray( data ) {
return Array.isArray( data ) ? data : [ data ];
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-utils/src/tomap.js":
/*!*************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-utils/src/tomap.js ***!
\*************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ toMap)
/* harmony export */ });
/* harmony import */ var _objecttomap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./objecttomap */ "./node_modules/@ckeditor/ckeditor5-utils/src/objecttomap.js");
/* harmony import */ var _isiterable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./isiterable */ "./node_modules/@ckeditor/ckeditor5-utils/src/isiterable.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module utils/tomap
*/
/**
* Transforms object or iterable to map. Iterable needs to be in the format acceptable by the `Map` constructor.
*
* map = toMap( { 'foo': 1, 'bar': 2 } );
* map = toMap( [ [ 'foo', 1 ], [ 'bar', 2 ] ] );
* map = toMap( anotherMap );
*
* @param {Object|Iterable} data Object or iterable to transform.
* @returns {Map} Map created from data.
*/
function toMap( data ) {
if ( (0,_isiterable__WEBPACK_IMPORTED_MODULE_1__["default"])( data ) ) {
return new Map( data );
} else {
return (0,_objecttomap__WEBPACK_IMPORTED_MODULE_0__["default"])( data );
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-utils/src/translation-service.js":
/*!***************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-utils/src/translation-service.js ***!
\***************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "_clear": () => (/* binding */ _clear),
/* harmony export */ "_translate": () => (/* binding */ _translate),
/* harmony export */ "add": () => (/* binding */ add)
/* harmony export */ });
/* harmony import */ var _ckeditorerror__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/* globals window */
/**
* @module utils/translation-service
*/
/* istanbul ignore else */
if ( !window.CKEDITOR_TRANSLATIONS ) {
window.CKEDITOR_TRANSLATIONS = {};
}
/**
* Adds translations to existing ones or overrides the existing translations. These translations will later
* be available for the {@link module:utils/locale~Locale#t `t()`} function.
*
* The `translations` is an object which consists of `messageId: translation` pairs. Note that the message ID can be
* either constructed from the message string or from the message ID if it was passed
* (this happens rarely and mostly for short messages or messages with placeholders).
* Since the editor displays only the message string, the message ID can be found either in the source code or in the
* built translations for another language.
*
* add( 'pl', {
* 'Cancel': 'Anuluj',
* 'IMAGE': 'obraz', // Note that the `IMAGE` comes from the message ID, while the string can be `image`.
* } );
*
* If the message is supposed to support various plural forms, make sure to provide an array with the singular form and all plural forms:
*
* add( 'pl', {
* 'Add space': [ 'Dodaj spację', 'Dodaj %0 spacje', 'Dodaj %0 spacji' ]
* } );
*
* You should also specify the third argument (the `getPluralForm()` function) that will be used to determine the plural form if no
* language file was loaded for that language. All language files coming from CKEditor 5 sources will have this option set, so
* these plural form rules will be reused by other translations added to the registered languages. The `getPluralForm()` function
* can return either a Boolean or a number.
*
* add( 'en', {
* // ... Translations.
* }, n => n !== 1 );
* add( 'pl', {
* // ... Translations.
* }, n => n == 1 ? 0 : n % 10 >= 2 && n % 10 <= 4 && ( n % 100 < 10 || n % 100 >= 20 ) ? 1 : 2 );
*
* All translations extend the global `window.CKEDITOR_TRANSLATIONS` object. An example of this object can be found below:
*
* {
* pl: {
* dictionary: {
* 'Cancel': 'Anuluj',
* 'Add space': [ 'Dodaj spację', 'Dodaj %0 spacje', 'Dodaj %0 spacji' ]
* },
* // A function that returns the plural form index.
* getPluralForm: n => n !==1
* }
* // Other languages.
* }
*
* If you cannot import this function from this module (e.g. because you use a CKEditor 5 build), you can
* still add translations by extending the global `window.CKEDITOR_TRANSLATIONS` object by using a function like
* the one below:
*
* function addTranslations( language, translations, getPluralForm ) {
* if ( !window.CKEDITOR_TRANSLATIONS ) {
* window.CKEDITOR_TRANSLATIONS = {};
* }
* if ( !window.CKEDITOR_TRANSLATIONS[ language ] ) {
* window.CKEDITOR_TRANSLATIONS[ language ] = {};
* }
*
* const languageTranslations = window.CKEDITOR_TRANSLATIONS[ language ];
*
* languageTranslations.dictionary = languageTranslations.dictionary || {};
* languageTranslations.getPluralForm = getPluralForm || languageTranslations.getPluralForm;
*
* // Extend the dictionary for the given language.
* Object.assign( languageTranslations.dictionary, translations );
* }
*
* @param {String} language Target language.
* @param {Object.<String,*>} translations An object with translations which will be added to the dictionary.
* For each message ID the value should be either a translation or an array of translations if the message
* should support plural forms.
* @param {Function} getPluralForm A function that returns the plural form index (a number).
*/
function add( language, translations, getPluralForm ) {
if ( !window.CKEDITOR_TRANSLATIONS[ language ] ) {
window.CKEDITOR_TRANSLATIONS[ language ] = {};
}
const languageTranslations = window.CKEDITOR_TRANSLATIONS[ language ];
languageTranslations.dictionary = languageTranslations.dictionary || {};
languageTranslations.getPluralForm = getPluralForm || languageTranslations.getPluralForm;
Object.assign( languageTranslations.dictionary, translations );
}
/**
* **Note:** This method is internal, use {@link module:utils/locale~Locale#t the `t()` function} instead to translate
* the editor UI parts.
*
* This function is responsible for translating messages to the specified language. It uses translations added perviously
* by {@link module:utils/translation-service~add} (a translations dictionary and the `getPluralForm()` function
* to provide accurate translations of plural forms).
*
* When no translation is defined in the dictionary or the dictionary does not exist, this function returns
* the original message string or the message plural depending on the number of elements.
*
* translate( 'pl', { string: 'Cancel' } ); // 'Cancel'
*
* The third optional argument is the number of elements, based on which the single form or one of the plural forms
* should be picked when the message is supposed to support various plural forms.
*
* translate( 'en', { string: 'Add a space', plural: 'Add %0 spaces' }, 1 ); // 'Add a space'
* translate( 'en', { string: 'Add a space', plural: 'Add %0 spaces' }, 3 ); // 'Add %0 spaces'
*
* The message should provide an ID using the `id` property when the message strings are not unique and their
* translations should be different.
*
* translate( 'en', { string: 'image', id: 'ADD_IMAGE' } );
* translate( 'en', { string: 'image', id: 'AN_IMAGE' } );
*
* @protected
* @param {String} language Target language.
* @param {module:utils/translation-service~Message} message A message that will be translated.
* @param {Number} [quantity] The number of elements for which a plural form should be picked from the target language dictionary.
* @returns {String} Translated sentence.
*/
function _translate( language, message, quantity = 1 ) {
if ( typeof quantity !== 'number' ) {
/**
* An incorrect value was passed to the translation function. This was probably caused
* by an incorrect message interpolation of a plural form. Note that for messages supporting plural forms
* the second argument of the `t()` function should always be a number or an array with a number as the first element.
*
* @error translation-service-quantity-not-a-number
*/
throw new _ckeditorerror__WEBPACK_IMPORTED_MODULE_0__["default"]( 'translation-service-quantity-not-a-number', null, { quantity } );
}
const numberOfLanguages = getNumberOfLanguages();
if ( numberOfLanguages === 1 ) {
// Override the language to the only supported one.
// This can't be done in the `Locale` class, because the translations comes after the `Locale` class initialization.
language = Object.keys( window.CKEDITOR_TRANSLATIONS )[ 0 ];
}
const messageId = message.id || message.string;
if ( numberOfLanguages === 0 || !hasTranslation( language, messageId ) ) {
if ( quantity !== 1 ) {
// Return the default plural form that was passed in the `message.plural` parameter.
return message.plural;
}
return message.string;
}
const dictionary = window.CKEDITOR_TRANSLATIONS[ language ].dictionary;
const getPluralForm = window.CKEDITOR_TRANSLATIONS[ language ].getPluralForm || ( n => n === 1 ? 0 : 1 );
if ( typeof dictionary[ messageId ] === 'string' ) {
return dictionary[ messageId ];
}
const pluralFormIndex = Number( getPluralForm( quantity ) );
// Note: The `translate` function is not responsible for replacing `%0, %1, ...` with values.
return dictionary[ messageId ][ pluralFormIndex ];
}
/**
* Clears dictionaries for test purposes.
*
* @protected
*/
function _clear() {
window.CKEDITOR_TRANSLATIONS = {};
}
// Checks whether the dictionary exists and translation in that dictionary exists.
function hasTranslation( language, messageId ) {
return (
!!window.CKEDITOR_TRANSLATIONS[ language ] &&
!!window.CKEDITOR_TRANSLATIONS[ language ].dictionary[ messageId ]
);
}
function getNumberOfLanguages() {
return Object.keys( window.CKEDITOR_TRANSLATIONS ).length;
}
/**
* The internationalization message interface. A message that implements this interface can be passed to the `t()` function
* to be translated to the target UI language.
*
* @typedef {Object} module:utils/translation-service~Message
*
* @property {String} string The message string to translate. Acts as a default translation if the translation for a given language
* is not defined. When the message is supposed to support plural forms, the string should be the English singular form of the message.
* @property {String} [id] The message ID. If passed, the message ID is taken from this property instead of the `message.string`.
* This property is useful when various messages share the same message string, for example, the `editor` string in `in the editor`
* and `my editor` sentences.
* @property {String} [plural] The plural form of the message. This property should be skipped when a message is not supposed
* to support plural forms. Otherwise it should always be set to a string with the English plural form of the message.
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-utils/src/uid.js":
/*!***********************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-utils/src/uid.js ***!
\***********************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ uid)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module utils/uid
*/
// A hash table of hex numbers to avoid using toString() in uid() which is costly.
// [ '00', '01', '02', ..., 'fe', 'ff' ]
const HEX_NUMBERS = new Array( 256 ).fill()
.map( ( val, index ) => ( '0' + ( index ).toString( 16 ) ).slice( -2 ) );
/**
* Returns a unique id. The id starts with an "e" character and a randomly generated string of
* 32 alphanumeric characters.
*
* **Note**: The characters the unique id is built from correspond to the hex number notation
* (from "0" to "9", from "a" to "f"). In other words, each id corresponds to an "e" followed
* by 16 8-bit numbers next to each other.
*
* @returns {String} An unique id string.
*/
function uid() {
// Let's create some positive random 32bit integers first.
//
// 1. Math.random() is a float between 0 and 1.
// 2. 0x100000000 is 2^32 = 4294967296.
// 3. >>> 0 enforces integer (in JS all numbers are floating point).
//
// For instance:
// Math.random() * 0x100000000 = 3366450031.853859
// but
// Math.random() * 0x100000000 >>> 0 = 3366450031.
const r1 = Math.random() * 0x100000000 >>> 0;
const r2 = Math.random() * 0x100000000 >>> 0;
const r3 = Math.random() * 0x100000000 >>> 0;
const r4 = Math.random() * 0x100000000 >>> 0;
// Make sure that id does not start with number.
return 'e' +
HEX_NUMBERS[ r1 >> 0 & 0xFF ] +
HEX_NUMBERS[ r1 >> 8 & 0xFF ] +
HEX_NUMBERS[ r1 >> 16 & 0xFF ] +
HEX_NUMBERS[ r1 >> 24 & 0xFF ] +
HEX_NUMBERS[ r2 >> 0 & 0xFF ] +
HEX_NUMBERS[ r2 >> 8 & 0xFF ] +
HEX_NUMBERS[ r2 >> 16 & 0xFF ] +
HEX_NUMBERS[ r2 >> 24 & 0xFF ] +
HEX_NUMBERS[ r3 >> 0 & 0xFF ] +
HEX_NUMBERS[ r3 >> 8 & 0xFF ] +
HEX_NUMBERS[ r3 >> 16 & 0xFF ] +
HEX_NUMBERS[ r3 >> 24 & 0xFF ] +
HEX_NUMBERS[ r4 >> 0 & 0xFF ] +
HEX_NUMBERS[ r4 >> 8 & 0xFF ] +
HEX_NUMBERS[ r4 >> 16 & 0xFF ] +
HEX_NUMBERS[ r4 >> 24 & 0xFF ];
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-utils/src/unicode.js":
/*!***************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-utils/src/unicode.js ***!
\***************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "isCombiningMark": () => (/* binding */ isCombiningMark),
/* harmony export */ "isHighSurrogateHalf": () => (/* binding */ isHighSurrogateHalf),
/* harmony export */ "isInsideCombinedSymbol": () => (/* binding */ isInsideCombinedSymbol),
/* harmony export */ "isInsideEmojiSequence": () => (/* binding */ isInsideEmojiSequence),
/* harmony export */ "isInsideSurrogatePair": () => (/* binding */ isInsideSurrogatePair),
/* harmony export */ "isLowSurrogateHalf": () => (/* binding */ isLowSurrogateHalf)
/* harmony export */ });
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* Set of utils to handle unicode characters.
*
* @module utils/unicode
*/
/**
* Checks whether given `character` is a combining mark.
*
* @param {String} character Character to check.
* @returns {Boolean}
*/
function isCombiningMark( character ) {
// eslint-disable-next-line no-misleading-character-class
return !!character && character.length == 1 && /[\u0300-\u036f\u1ab0-\u1aff\u1dc0-\u1dff\u20d0-\u20ff\ufe20-\ufe2f]/.test( character );
}
/**
* Checks whether given `character` is a high half of surrogate pair.
*
* Using UTF-16 terminology, a surrogate pair denotes UTF-16 character using two UTF-8 characters. The surrogate pair
* consist of high surrogate pair character followed by low surrogate pair character.
*
* @param {String} character Character to check.
* @returns {Boolean}
*/
function isHighSurrogateHalf( character ) {
return !!character && character.length == 1 && /[\ud800-\udbff]/.test( character );
}
/**
* Checks whether given `character` is a low half of surrogate pair.
*
* Using UTF-16 terminology, a surrogate pair denotes UTF-16 character using two UTF-8 characters. The surrogate pair
* consist of high surrogate pair character followed by low surrogate pair character.
*
* @param {String} character Character to check.
* @returns {Boolean}
*/
function isLowSurrogateHalf( character ) {
return !!character && character.length == 1 && /[\udc00-\udfff]/.test( character );
}
/**
* Checks whether given offset in a string is inside a surrogate pair (between two surrogate halves).
*
* @param {String} string String to check.
* @param {Number} offset Offset to check.
* @returns {Boolean}
*/
function isInsideSurrogatePair( string, offset ) {
return isHighSurrogateHalf( string.charAt( offset - 1 ) ) && isLowSurrogateHalf( string.charAt( offset ) );
}
/**
* Checks whether given offset in a string is between base character and combining mark or between two combining marks.
*
* @param {String} string String to check.
* @param {Number} offset Offset to check.
* @returns {Boolean}
*/
function isInsideCombinedSymbol( string, offset ) {
return isCombiningMark( string.charAt( offset ) );
}
const EMOJI_PATTERN = buildEmojiRegexp();
/**
* Checks whether given offset in a string is inside multi-character emoji sequence.
*
* @param {String} string String to check.
* @param {Number} offset Offset to check.
* @returns {Boolean}
*/
function isInsideEmojiSequence( string, offset ) {
const matches = String( string ).matchAll( EMOJI_PATTERN );
return Array.from( matches ).some( match => match.index < offset && offset < match.index + match[ 0 ].length );
}
function buildEmojiRegexp() {
const parts = [
// Emoji Tag Sequence (ETS)
/\p{Emoji}[\u{E0020}-\u{E007E}]+\u{E007F}/u,
// Emoji Keycap Sequence
/\p{Emoji}\u{FE0F}?\u{20E3}/u,
// Emoji Presentation Sequence
/\p{Emoji}\u{FE0F}/u,
// Single-Character Emoji / Emoji Modifier Sequence
/(?=\p{General_Category=Other_Symbol})\p{Emoji}\p{Emoji_Modifier}*/u
];
const flagSequence = /\p{Regional_Indicator}{2}/u.source;
const emoji = '(?:' + parts.map( part => part.source ).join( '|' ) + ')';
const sequence = `${ flagSequence }|${ emoji }(?:\u{200D}${ emoji })*`;
return new RegExp( sequence, 'ug' );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-utils/src/version.js":
/*!***************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-utils/src/version.js ***!
\***************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _ckeditorerror__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module utils/version
*/
/* globals window, global */
const version = '33.0.0';
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (version);
/* istanbul ignore next */
const windowOrGlobal = typeof window === 'object' ? window : __webpack_require__.g;
/* istanbul ignore next */
if ( windowOrGlobal.CKEDITOR_VERSION ) {
/**
* This error is thrown when due to a mistake in how CKEditor 5 was installed or initialized, some
* of its modules were duplicated (evaluated and executed twice). Module duplication leads to inevitable runtime
* errors.
*
* There are many situations in which some modules can be loaded twice. In the worst case scenario,
* you may need to check your project for each of these issues and fix them all.
*
* # Trying to add a plugin to an existing build
*
* If you import an existing CKEditor 5 build and a plugin like this:
*
* import ClassicEditor from '@ckeditor/ckeditor5-build-classic';
* import Highlight from '@ckeditor/ckeditor5-highlight/src/highlight';
*
* Then your project loads some CKEditor 5 packages twice. How does it happen?
*
* The build package contains a file which is already compiled with webpack. This means
* that it contains all the necessary code from e.g. `@ckeditor/ckeditor5-engine` and `@ckeditor/ckeditor5-utils`.
*
* However, the `Highlight` plugin imports some of the modules from these packages, too. If you ask webpack to
* build such a project, you will end up with the modules being included (and run) twice — first, because they are
* included inside the build package, and second, because they are required by the `Highlight` plugin.
*
* Therefore, **you must never add plugins to an existing build** unless your plugin has no dependencies.
*
* Adding plugins to a build is done by taking the source version of this build (so, before it was built with webpack)
* and adding plugins there. In this situation, webpack will know that it only needs to load each plugin once.
*
* Read more in the {@glink builds/guides/integration/installing-plugins "Installing plugins"} guide.
*
* # Confused an editor build with an editor implementation
*
* This scenario is very similar to the previous one, but has a different origin.
*
* Let's assume that you wanted to use CKEditor 5 from source, as explained in the
* {@glink builds/guides/integration/advanced-setup#scenario-2-building-from-source "Building from source"} section
* or in the {@glink framework/guides/quick-start "Quick start"} guide of CKEditor 5 Framework.
*
* The correct way to do so is to import an editor and plugins and run them together like this:
*
* import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
* import Essentials from '@ckeditor/ckeditor5-essentials/src/essentials';
* import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
* import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold';
* import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic';
*
* ClassicEditor
* .create( document.querySelector( '#editor' ), {
* plugins: [ Essentials, Paragraph, Bold, Italic ],
* toolbar: [ 'bold', 'italic' ]
* } )
* .then( editor => {
* console.log( 'Editor was initialized', editor );
* } )
* .catch( error => {
* console.error( error.stack );
* } );
*
* However, you might have mistakenly imported a build instead of the source `ClassicEditor`. In this case
* your imports will look like this:
*
* import ClassicEditor from '@ckeditor/ckeditor5-build-classic';
* import Essentials from '@ckeditor/ckeditor5-essentials/src/essentials';
* import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
* import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold';
* import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic';
*
* This creates the same situation as in the previous section because you use a build together with source plugins.
*
* Remember: `@ckeditor/ckeditor5-build-*` packages contain editor builds and `@ckeditor/ckeditor5-editor-*` contain source editors.
*
* # Loading two or more builds on one page
*
* If you use CKEditor 5 builds, you might have loaded two (or more) `ckeditor.js` files on one web page.
* Check your web page for duplicated `<script>` elements or make sure your page builder/bundler includes CKEditor only once.
*
* If you want to use two different types of editors at once, see the
* {@glink builds/guides/integration/advanced-setup#scenario-3-using-two-different-editors "Using two different editors"}
* section.
*
* # Using outdated packages
*
* Building CKEditor 5 from source requires using multiple npm packages. These packages have their dependencies
* to other packages. If you use the latest version of, for example, `@ckeditor/ckeditor5-editor-classic` with
* an outdated version of `@ckeditor/ckeditor5-image`, npm or yarn will need to install two different versions of
* `@ckeditor/ckeditor5-core` because `@ckeditor/ckeditor5-editor-classic` and `@ckeditor/ckeditor5-image` may require
* different versions of the core package.
*
* The solution to this issue is to update all packages to their latest version. We recommend
* using tools like [`npm-check-updates`](https://www.npmjs.com/package/npm-check-updates) which simplify this process.
*
* # Conflicting version of dependencies
*
* This is a special case of the previous scenario. If you use CKEditor 5 with some third-party plugins,
* it may happen that even if you use the latest versions of the official packages and the latest version of
* these third-party packages, there will be a conflict between some of their dependencies.
*
* Such a problem can be resolved by either downgrading CKEditor 5 packages (which we do not recommend) or
* asking the author of the third-party package to upgrade its depdendencies (or forking their project and doing this yourself).
*
* **Note:** All official CKEditor 5 packages (excluding integrations and `ckeditor5-dev-*` packages) are released in the
* same major version. This is — in the `x.y.z`, the `x` is the same for all packages. This is the simplest way to check
* whether you use packages coming from the same CKEditor 5 version. You can read more about versioning in the
* {@glink framework/guides/support/versioning-policy Versioning policy} guide.
*
* # Packages were duplicated in `node_modules`
*
* In some situations, especially when calling `npm install` multiple times, it may happen
* that npm will not correctly "deduplicate" packages.
*
* Normally, npm deduplicates all packages so, for example, `@ckeditor/ckeditor5-core` is installed only once in `node_modules/`.
* However, it is known to fail to do so from time to time.
*
* We recommend checking if any of the steps listed below help:
*
* * `rm -rf node_modules && npm install` to make sure you have a clean `node_modules/` directory. This step
* is known to help in most cases.
* * If you use `yarn.lock` or `package-lock.json`, remove it before `npm install`.
* * Check whether all CKEditor 5 packages are up to date and reinstall them
* if you changed anything (`rm -rf node_modules && npm install`).
*
* If all packages are correct and compatible with each other, the steps above are known to help. If not, you may
* try to check with `npm ls` how many times packages like `@ckeditor/ckeditor5-core`, `@ckeditor/ckeditor5-engine` and
*`@ckeditor/ckeditor5-utils` are installed. If more than once, verify which package causes that.
*
* @error ckeditor-duplicated-modules
*/
throw new _ckeditorerror__WEBPACK_IMPORTED_MODULE_0__["default"](
'ckeditor-duplicated-modules',
null
);
} else {
windowOrGlobal.CKEDITOR_VERSION = version;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-widget/src/highlightstack.js":
/*!***********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-widget/src/highlightstack.js ***!
\***********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ HighlightStack)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_utils_src_emittermixin__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/emittermixin */ "./node_modules/@ckeditor/ckeditor5-utils/src/emittermixin.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/mix */ "./node_modules/@ckeditor/ckeditor5-utils/src/mix.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module widget/highlightstack
*/
/**
* Class used to handle correct order of highlights on elements.
*
* When different highlights are applied to same element correct order should be preserved:
*
* * highlight with highest priority should be applied,
* * if two highlights have same priority - sort by CSS class provided in
* {@link module:engine/conversion/downcasthelpers~HighlightDescriptor}.
*
* This way, highlight will be applied with the same rules it is applied on texts.
*/
class HighlightStack {
/**
* Creates class instance.
*/
constructor() {
this._stack = [];
}
/**
* Adds highlight descriptor to the stack.
*
* @fires change:top
* @param {module:engine/conversion/downcasthelpers~HighlightDescriptor} descriptor
* @param {module:engine/view/downcastwriter~DowncastWriter} writer
*/
add( descriptor, writer ) {
const stack = this._stack;
// Save top descriptor and insert new one. If top is changed - fire event.
const oldTop = stack[ 0 ];
this._insertDescriptor( descriptor );
const newTop = stack[ 0 ];
// When new object is at the top and stores different information.
if ( oldTop !== newTop && !compareDescriptors( oldTop, newTop ) ) {
this.fire( 'change:top', {
oldDescriptor: oldTop,
newDescriptor: newTop,
writer
} );
}
}
/**
* Removes highlight descriptor from the stack.
*
* @fires change:top
* @param {String} id Id of the descriptor to remove.
* @param {module:engine/view/downcastwriter~DowncastWriter} writer
*/
remove( id, writer ) {
const stack = this._stack;
const oldTop = stack[ 0 ];
this._removeDescriptor( id );
const newTop = stack[ 0 ];
// When new object is at the top and stores different information.
if ( oldTop !== newTop && !compareDescriptors( oldTop, newTop ) ) {
this.fire( 'change:top', {
oldDescriptor: oldTop,
newDescriptor: newTop,
writer
} );
}
}
/**
* Inserts given descriptor in correct place in the stack. It also takes care about updating information when
* descriptor with same id is already present.
*
* @private
* @param {module:engine/conversion/downcasthelpers~HighlightDescriptor} descriptor
*/
_insertDescriptor( descriptor ) {
const stack = this._stack;
const index = stack.findIndex( item => item.id === descriptor.id );
// Inserting exact same descriptor - do nothing.
if ( compareDescriptors( descriptor, stack[ index ] ) ) {
return;
}
// If descriptor with same id but with different information is on the stack - remove it.
if ( index > -1 ) {
stack.splice( index, 1 );
}
// Find correct place to insert descriptor in the stack.
// It have different information (for example priority) so it must be re-inserted in correct place.
let i = 0;
while ( stack[ i ] && shouldABeBeforeB( stack[ i ], descriptor ) ) {
i++;
}
stack.splice( i, 0, descriptor );
}
/**
* Removes descriptor with given id from the stack.
*
* @private
* @param {String} id Descriptor's id.
*/
_removeDescriptor( id ) {
const stack = this._stack;
const index = stack.findIndex( item => item.id === id );
// If descriptor with same id is on the list - remove it.
if ( index > -1 ) {
stack.splice( index, 1 );
}
}
}
(0,_ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_1__["default"])( HighlightStack, _ckeditor_ckeditor5_utils_src_emittermixin__WEBPACK_IMPORTED_MODULE_0__["default"] );
// Compares two descriptors by checking their priority and class list.
//
// @param {module:engine/conversion/downcasthelpers~HighlightDescriptor} a
// @param {module:engine/conversion/downcasthelpers~HighlightDescriptor} b
// @returns {Boolean} Returns true if both descriptors are defined and have same priority and classes.
function compareDescriptors( a, b ) {
return a && b && a.priority == b.priority && classesToString( a.classes ) == classesToString( b.classes );
}
// Checks whenever first descriptor should be placed in the stack before second one.
//
// @param {module:engine/conversion/downcasthelpers~HighlightDescriptor} a
// @param {module:engine/conversion/downcasthelpers~HighlightDescriptor} b
// @returns {Boolean}
function shouldABeBeforeB( a, b ) {
if ( a.priority > b.priority ) {
return true;
} else if ( a.priority < b.priority ) {
return false;
}
// When priorities are equal and names are different - use classes to compare.
return classesToString( a.classes ) > classesToString( b.classes );
}
// Converts CSS classes passed with {@link module:engine/conversion/downcasthelpers~HighlightDescriptor} to
// sorted string.
//
// @param {String|Array<String>} descriptor
// @returns {String}
function classesToString( classes ) {
return Array.isArray( classes ) ? classes.sort().join( ',' ) : classes;
}
/**
* Fired when top element on {@link module:widget/highlightstack~HighlightStack} has been changed
*
* @event change:top
* @param {Object} data Additional information about the change.
* @param {module:engine/conversion/downcasthelpers~HighlightDescriptor} [data.newDescriptor] New highlight
* descriptor. It will be `undefined` when last descriptor is removed from the stack.
* @param {module:engine/conversion/downcasthelpers~HighlightDescriptor} [data.oldDescriptor] Old highlight
* descriptor. It will be `undefined` when first descriptor is added to the stack.
* @param {module:engine/view/downcastwriter~DowncastWriter} writer View writer that can be used to modify element.
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-widget/src/index.js":
/*!**************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-widget/src/index.js ***!
\**************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "WIDGET_CLASS_NAME": () => (/* reexport safe */ _utils__WEBPACK_IMPORTED_MODULE_4__.WIDGET_CLASS_NAME),
/* harmony export */ "WIDGET_SELECTED_CLASS_NAME": () => (/* reexport safe */ _utils__WEBPACK_IMPORTED_MODULE_4__.WIDGET_SELECTED_CLASS_NAME),
/* harmony export */ "Widget": () => (/* reexport safe */ _widget__WEBPACK_IMPORTED_MODULE_0__["default"]),
/* harmony export */ "WidgetResize": () => (/* reexport safe */ _widgetresize__WEBPACK_IMPORTED_MODULE_2__["default"]),
/* harmony export */ "WidgetToolbarRepository": () => (/* reexport safe */ _widgettoolbarrepository__WEBPACK_IMPORTED_MODULE_1__["default"]),
/* harmony export */ "WidgetTypeAround": () => (/* reexport safe */ _widgettypearound_widgettypearound__WEBPACK_IMPORTED_MODULE_3__["default"]),
/* harmony export */ "findOptimalInsertionRange": () => (/* reexport safe */ _utils__WEBPACK_IMPORTED_MODULE_4__.findOptimalInsertionRange),
/* harmony export */ "getLabel": () => (/* reexport safe */ _utils__WEBPACK_IMPORTED_MODULE_4__.getLabel),
/* harmony export */ "isWidget": () => (/* reexport safe */ _utils__WEBPACK_IMPORTED_MODULE_4__.isWidget),
/* harmony export */ "setHighlightHandling": () => (/* reexport safe */ _utils__WEBPACK_IMPORTED_MODULE_4__.setHighlightHandling),
/* harmony export */ "setLabel": () => (/* reexport safe */ _utils__WEBPACK_IMPORTED_MODULE_4__.setLabel),
/* harmony export */ "toWidget": () => (/* reexport safe */ _utils__WEBPACK_IMPORTED_MODULE_4__.toWidget),
/* harmony export */ "toWidgetEditable": () => (/* reexport safe */ _utils__WEBPACK_IMPORTED_MODULE_4__.toWidgetEditable),
/* harmony export */ "viewToModelPositionOutsideModelElement": () => (/* reexport safe */ _utils__WEBPACK_IMPORTED_MODULE_4__.viewToModelPositionOutsideModelElement)
/* harmony export */ });
/* harmony import */ var _widget__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./widget */ "./node_modules/@ckeditor/ckeditor5-widget/src/widget.js");
/* harmony import */ var _widgettoolbarrepository__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./widgettoolbarrepository */ "./node_modules/@ckeditor/ckeditor5-widget/src/widgettoolbarrepository.js");
/* harmony import */ var _widgetresize__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./widgetresize */ "./node_modules/@ckeditor/ckeditor5-widget/src/widgetresize.js");
/* harmony import */ var _widgettypearound_widgettypearound__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./widgettypearound/widgettypearound */ "./node_modules/@ckeditor/ckeditor5-widget/src/widgettypearound/widgettypearound.js");
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./utils */ "./node_modules/@ckeditor/ckeditor5-widget/src/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-widget/src/utils.js":
/*!**************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-widget/src/utils.js ***!
\**************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "WIDGET_CLASS_NAME": () => (/* binding */ WIDGET_CLASS_NAME),
/* harmony export */ "WIDGET_SELECTED_CLASS_NAME": () => (/* binding */ WIDGET_SELECTED_CLASS_NAME),
/* harmony export */ "findOptimalInsertionRange": () => (/* binding */ findOptimalInsertionRange),
/* harmony export */ "getLabel": () => (/* binding */ getLabel),
/* harmony export */ "isWidget": () => (/* binding */ isWidget),
/* harmony export */ "setHighlightHandling": () => (/* binding */ setHighlightHandling),
/* harmony export */ "setLabel": () => (/* binding */ setLabel),
/* harmony export */ "toWidget": () => (/* binding */ toWidget),
/* harmony export */ "toWidgetEditable": () => (/* binding */ toWidgetEditable),
/* harmony export */ "viewToModelPositionOutsideModelElement": () => (/* binding */ viewToModelPositionOutsideModelElement)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_toarray__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/toarray */ "./node_modules/@ckeditor/ckeditor5-utils/src/toarray.js");
/* harmony import */ var _highlightstack__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./highlightstack */ "./node_modules/@ckeditor/ckeditor5-widget/src/highlightstack.js");
/* harmony import */ var _widgettypearound_utils__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./widgettypearound/utils */ "./node_modules/@ckeditor/ckeditor5-widget/src/widgettypearound/utils.js");
/* harmony import */ var _ckeditor_ckeditor5_ui_src_icon_iconview__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! @ckeditor/ckeditor5-ui/src/icon/iconview */ "./node_modules/@ckeditor/ckeditor5-ui/src/icon/iconview.js");
/* harmony import */ var _theme_icons_drag_handle_svg__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../theme/icons/drag-handle.svg */ "./node_modules/@ckeditor/ckeditor5-widget/theme/icons/drag-handle.svg");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module widget/utils
*/
/**
* CSS class added to each widget element.
*
* @const {String}
*/
const WIDGET_CLASS_NAME = 'ck-widget';
/**
* CSS class added to currently selected widget element.
*
* @const {String}
*/
const WIDGET_SELECTED_CLASS_NAME = 'ck-widget_selected';
/**
* Returns `true` if given {@link module:engine/view/node~Node} is an {@link module:engine/view/element~Element} and a widget.
*
* @param {module:engine/view/node~Node} node
* @returns {Boolean}
*/
function isWidget( node ) {
if ( !node.is( 'element' ) ) {
return false;
}
return !!node.getCustomProperty( 'widget' );
}
/**
* Converts the given {@link module:engine/view/element~Element} to a widget in the following way:
*
* * sets the `contenteditable` attribute to `"false"`,
* * adds the `ck-widget` CSS class,
* * adds a custom {@link module:engine/view/element~Element#getFillerOffset `getFillerOffset()`} method returning `null`,
* * adds a custom property allowing to recognize widget elements by using {@link ~isWidget `isWidget()`},
* * implements the {@link ~setHighlightHandling view highlight on widgets}.
*
* This function needs to be used in conjunction with
* {@link module:engine/conversion/downcasthelpers~DowncastHelpers downcast conversion helpers}
* like {@link module:engine/conversion/downcasthelpers~DowncastHelpers#elementToElement `elementToElement()`}.
* Moreover, typically you will want to use `toWidget()` only for `editingDowncast`, while keeping the `dataDowncast` clean.
*
* For example, in order to convert a `<widget>` model element to `<div class="widget">` in the view, you can define
* such converters:
*
* editor.conversion.for( 'editingDowncast' )
* .elementToElement( {
* model: 'widget',
* view: ( modelItem, { writer } ) => {
* const div = writer.createContainerElement( 'div', { class: 'widget' } );
*
* return toWidget( div, writer, { label: 'some widget' } );
* }
* } );
*
* editor.conversion.for( 'dataDowncast' )
* .elementToElement( {
* model: 'widget',
* view: ( modelItem, { writer } ) => {
* return writer.createContainerElement( 'div', { class: 'widget' } );
* }
* } );
*
* See the full source code of the widget (with a nested editable) schema definition and converters in
* [this sample](https://github.com/ckeditor/ckeditor5-widget/blob/master/tests/manual/widget-with-nestededitable.js).
*
* @param {module:engine/view/element~Element} element
* @param {module:engine/view/downcastwriter~DowncastWriter} writer
* @param {Object} [options={}]
* @param {String|Function} [options.label] Element's label provided to the {@link ~setLabel} function. It can be passed as
* a plain string or a function returning a string. It represents the widget for assistive technologies (like screen readers).
* @param {Boolean} [options.hasSelectionHandle=false] If `true`, the widget will have a selection handle added.
* @returns {module:engine/view/element~Element} Returns the same element.
*/
function toWidget( element, writer, options = {} ) {
if ( !element.is( 'containerElement' ) ) {
/**
* The element passed to `toWidget()` must be a {@link module:engine/view/containerelement~ContainerElement}
* instance.
*
* @error widget-to-widget-wrong-element-type
* @param {String} element The view element passed to `toWidget()`.
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_0__["default"](
'widget-to-widget-wrong-element-type',
null,
{ element }
);
}
writer.setAttribute( 'contenteditable', 'false', element );
writer.addClass( WIDGET_CLASS_NAME, element );
writer.setCustomProperty( 'widget', true, element );
element.getFillerOffset = getFillerOffset;
if ( options.label ) {
setLabel( element, options.label, writer );
}
if ( options.hasSelectionHandle ) {
addSelectionHandle( element, writer );
}
setHighlightHandling( element, writer );
return element;
}
// Default handler for adding a highlight on a widget.
// It adds CSS class and attributes basing on the given highlight descriptor.
//
// @param {module:engine/view/element~Element} element
// @param {module:engine/conversion/downcasthelpers~HighlightDescriptor} descriptor
// @param {module:engine/view/downcastwriter~DowncastWriter} writer
function addHighlight( element, descriptor, writer ) {
if ( descriptor.classes ) {
writer.addClass( (0,_ckeditor_ckeditor5_utils_src_toarray__WEBPACK_IMPORTED_MODULE_1__["default"])( descriptor.classes ), element );
}
if ( descriptor.attributes ) {
for ( const key in descriptor.attributes ) {
writer.setAttribute( key, descriptor.attributes[ key ], element );
}
}
}
// Default handler for removing a highlight from a widget.
// It removes CSS class and attributes basing on the given highlight descriptor.
//
// @param {module:engine/view/element~Element} element
// @param {module:engine/conversion/downcasthelpers~HighlightDescriptor} descriptor
// @param {module:engine/view/downcastwriter~DowncastWriter} writer
function removeHighlight( element, descriptor, writer ) {
if ( descriptor.classes ) {
writer.removeClass( (0,_ckeditor_ckeditor5_utils_src_toarray__WEBPACK_IMPORTED_MODULE_1__["default"])( descriptor.classes ), element );
}
if ( descriptor.attributes ) {
for ( const key in descriptor.attributes ) {
writer.removeAttribute( key, element );
}
}
}
/**
* Sets highlight handling methods. Uses {@link module:widget/highlightstack~HighlightStack} to
* properly determine which highlight descriptor should be used at given time.
*
* @param {module:engine/view/element~Element} element
* @param {module:engine/view/downcastwriter~DowncastWriter} writer
* @param {Function} [add]
* @param {Function} [remove]
*/
function setHighlightHandling( element, writer, add = addHighlight, remove = removeHighlight ) {
const stack = new _highlightstack__WEBPACK_IMPORTED_MODULE_2__["default"]();
stack.on( 'change:top', ( evt, data ) => {
if ( data.oldDescriptor ) {
remove( element, data.oldDescriptor, data.writer );
}
if ( data.newDescriptor ) {
add( element, data.newDescriptor, data.writer );
}
} );
writer.setCustomProperty( 'addHighlight', ( element, descriptor, writer ) => stack.add( descriptor, writer ), element );
writer.setCustomProperty( 'removeHighlight', ( element, id, writer ) => stack.remove( id, writer ), element );
}
/**
* Sets label for given element.
* It can be passed as a plain string or a function returning a string. Function will be called each time label is retrieved by
* {@link ~getLabel `getLabel()`}.
*
* @param {module:engine/view/element~Element} element
* @param {String|Function} labelOrCreator
* @param {module:engine/view/downcastwriter~DowncastWriter} writer
*/
function setLabel( element, labelOrCreator, writer ) {
writer.setCustomProperty( 'widgetLabel', labelOrCreator, element );
}
/**
* Returns the label of the provided element.
*
* @param {module:engine/view/element~Element} element
* @returns {String}
*/
function getLabel( element ) {
const labelCreator = element.getCustomProperty( 'widgetLabel' );
if ( !labelCreator ) {
return '';
}
return typeof labelCreator == 'function' ? labelCreator() : labelCreator;
}
/**
* Adds functionality to the provided {@link module:engine/view/editableelement~EditableElement} to act as a widget's editable:
*
* * sets the `contenteditable` attribute to `true` when {@link module:engine/view/editableelement~EditableElement#isReadOnly} is `false`,
* otherwise sets it to `false`,
* * adds the `ck-editor__editable` and `ck-editor__nested-editable` CSS classes,
* * adds the `ck-editor__nested-editable_focused` CSS class when the editable is focused and removes it when it is blurred.
* * implements the {@link ~setHighlightHandling view highlight on widget's editable}.
*
* Similarly to {@link ~toWidget `toWidget()`} this function should be used in `editingDowncast` only and it is usually
* used together with {@link module:engine/conversion/downcasthelpers~DowncastHelpers#elementToElement `elementToElement()`}.
*
* For example, in order to convert a `<nested>` model element to `<div class="nested">` in the view, you can define
* such converters:
*
* editor.conversion.for( 'editingDowncast' )
* .elementToElement( {
* model: 'nested',
* view: ( modelItem, { writer } ) => {
* const div = writer.createEditableElement( 'div', { class: 'nested' } );
*
* return toWidgetEditable( nested, writer );
* }
* } );
*
* editor.conversion.for( 'dataDowncast' )
* .elementToElement( {
* model: 'nested',
* view: ( modelItem, { writer } ) => {
* return writer.createContainerElement( 'div', { class: 'nested' } );
* }
* } );
*
* See the full source code of the widget (with nested editable) schema definition and converters in
* [this sample](https://github.com/ckeditor/ckeditor5-widget/blob/master/tests/manual/widget-with-nestededitable.js).
*
* @param {module:engine/view/editableelement~EditableElement} editable
* @param {module:engine/view/downcastwriter~DowncastWriter} writer
* @returns {module:engine/view/editableelement~EditableElement} Returns the same element that was provided in the `editable` parameter
*/
function toWidgetEditable( editable, writer ) {
writer.addClass( [ 'ck-editor__editable', 'ck-editor__nested-editable' ], editable );
// Set initial contenteditable value.
writer.setAttribute( 'contenteditable', editable.isReadOnly ? 'false' : 'true', editable );
// Bind the contenteditable property to element#isReadOnly.
editable.on( 'change:isReadOnly', ( evt, property, is ) => {
writer.setAttribute( 'contenteditable', is ? 'false' : 'true', editable );
} );
editable.on( 'change:isFocused', ( evt, property, is ) => {
if ( is ) {
writer.addClass( 'ck-editor__nested-editable_focused', editable );
} else {
writer.removeClass( 'ck-editor__nested-editable_focused', editable );
}
} );
setHighlightHandling( editable, writer );
return editable;
}
/**
* Returns a model range which is optimal (in terms of UX) for inserting a widget block.
*
* For instance, if a selection is in the middle of a paragraph, the collapsed range before this paragraph
* will be returned so that it is not split. If the selection is at the end of a paragraph,
* the collapsed range after this paragraph will be returned.
*
* Note: If the selection is placed in an empty block, the range in that block will be returned. If that range
* is then passed to {@link module:engine/model/model~Model#insertContent}, the block will be fully replaced
* by the inserted widget block.
*
* @param {module:engine/model/selection~Selection|module:engine/model/documentselection~DocumentSelection} selection
* The selection based on which the insertion position should be calculated.
* @param {module:engine/model/model~Model} model Model instance.
* @returns {module:engine/model/range~Range} The optimal range.
*/
function findOptimalInsertionRange( selection, model ) {
const selectedElement = selection.getSelectedElement();
if ( selectedElement ) {
const typeAroundFakeCaretPosition = (0,_widgettypearound_utils__WEBPACK_IMPORTED_MODULE_3__.getTypeAroundFakeCaretPosition)( selection );
// If the WidgetTypeAround "fake caret" is displayed, use its position for the insertion
// to provide the most predictable UX (https://github.com/ckeditor/ckeditor5/issues/7438).
if ( typeAroundFakeCaretPosition ) {
return model.createRange( model.createPositionAt( selectedElement, typeAroundFakeCaretPosition ) );
}
if ( model.schema.isObject( selectedElement ) && !model.schema.isInline( selectedElement ) ) {
return model.createRangeOn( selectedElement );
}
}
const firstBlock = selection.getSelectedBlocks().next().value;
if ( firstBlock ) {
// If inserting into an empty block – return position in that block. It will get
// replaced with the image by insertContent(). #42.
if ( firstBlock.isEmpty ) {
return model.createRange( model.createPositionAt( firstBlock, 0 ) );
}
const positionAfter = model.createPositionAfter( firstBlock );
// If selection is at the end of the block - return position after the block.
if ( selection.focus.isTouching( positionAfter ) ) {
return model.createRange( positionAfter );
}
// Otherwise return position before the block.
return model.createRange( model.createPositionBefore( firstBlock ) );
}
return model.createRange( selection.focus );
}
/**
* A util to be used in order to map view positions to correct model positions when implementing a widget
* which renders non-empty view element for an empty model element.
*
* For example:
*
* // Model:
* <placeholder type="name"></placeholder>
*
* // View:
* <span class="placeholder">name</span>
*
* In such case, view positions inside `<span>` cannot be correct mapped to the model (because the model element is empty).
* To handle mapping positions inside `<span class="placeholder">` to the model use this util as follows:
*
* editor.editing.mapper.on(
* 'viewToModelPosition',
* viewToModelPositionOutsideModelElement( model, viewElement => viewElement.hasClass( 'placeholder' ) )
* );
*
* The callback will try to map the view offset of selection to an expected model position.
*
* 1. When the position is at the end (or in the middle) of the inline widget:
*
* // View:
* <p>foo <span class="placeholder">name|</span> bar</p>
*
* // Model:
* <paragraph>foo <placeholder type="name"></placeholder>| bar</paragraph>
*
* 2. When the position is at the beginning of the inline widget:
*
* // View:
* <p>foo <span class="placeholder">|name</span> bar</p>
*
* // Model:
* <paragraph>foo |<placeholder type="name"></placeholder> bar</paragraph>
*
* @param {module:engine/model/model~Model} model Model instance on which the callback operates.
* @param {Function} viewElementMatcher Function that is passed a view element and should return `true` if the custom mapping
* should be applied to the given view element.
* @return {Function}
*/
function viewToModelPositionOutsideModelElement( model, viewElementMatcher ) {
return ( evt, data ) => {
const { mapper, viewPosition } = data;
const viewParent = mapper.findMappedViewAncestor( viewPosition );
if ( !viewElementMatcher( viewParent ) ) {
return;
}
const modelParent = mapper.toModelElement( viewParent );
data.modelPosition = model.createPositionAt( modelParent, viewPosition.isAtStart ? 'before' : 'after' );
};
}
// Default filler offset function applied to all widget elements.
//
// @returns {null}
function getFillerOffset() {
return null;
}
// Adds a drag handle to the widget.
//
// @param {module:engine/view/containerelement~ContainerElement}
// @param {module:engine/view/downcastwriter~DowncastWriter} writer
function addSelectionHandle( widgetElement, writer ) {
const selectionHandle = writer.createUIElement( 'div', { class: 'ck ck-widget__selection-handle' }, function( domDocument ) {
const domElement = this.toDomElement( domDocument );
// Use the IconView from the ui library.
const icon = new _ckeditor_ckeditor5_ui_src_icon_iconview__WEBPACK_IMPORTED_MODULE_4__["default"]();
icon.set( 'content', _theme_icons_drag_handle_svg__WEBPACK_IMPORTED_MODULE_5__["default"] );
// Render the icon view right away to append its #element to the selectionHandle DOM element.
icon.render();
domElement.appendChild( icon.element );
return domElement;
} );
// Append the selection handle into the widget wrapper.
writer.insert( writer.createPositionAt( widgetElement, 0 ), selectionHandle );
writer.addClass( [ 'ck-widget_with-selection-handle' ], widgetElement );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-widget/src/verticalnavigation.js":
/*!***************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-widget/src/verticalnavigation.js ***!
\***************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ verticalNavigationHandler)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_utils_src_keyboard__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/keyboard */ "./node_modules/@ckeditor/ckeditor5-utils/src/keyboard.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_dom_rect__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/dom/rect */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/rect.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module widget/verticalnavigationhandler
*/
/**
* Returns 'keydown' handler for up/down arrow keys that modifies the caret movement if it's in a text line next to an object.
*
* @param {module:engine/controller/editingcontroller~EditingController} editing The editing controller.
* @returns {Function}
*/
function verticalNavigationHandler( editing ) {
const model = editing.model;
return ( evt, data ) => {
const arrowUpPressed = data.keyCode == _ckeditor_ckeditor5_utils_src_keyboard__WEBPACK_IMPORTED_MODULE_0__.keyCodes.arrowup;
const arrowDownPressed = data.keyCode == _ckeditor_ckeditor5_utils_src_keyboard__WEBPACK_IMPORTED_MODULE_0__.keyCodes.arrowdown;
const expandSelection = data.shiftKey;
const selection = model.document.selection;
if ( !arrowUpPressed && !arrowDownPressed ) {
return;
}
const isForward = arrowDownPressed;
// Navigation is in the opposite direction than the selection direction so this is shrinking of the selection.
// Selection for sure will not approach any object.
if ( expandSelection && selectionWillShrink( selection, isForward ) ) {
return;
}
// Find a range between selection and closest limit element.
const range = findTextRangeFromSelection( editing, selection, isForward );
// There is no selection position inside the limit element.
if ( !range ) {
return;
}
// If already at the edge of a limit element.
if ( range.isCollapsed ) {
// A collapsed selection at limit edge - nothing more to do.
if ( selection.isCollapsed ) {
return;
}
// A non collapsed selection is at the limit edge while expanding the selection - let others do their stuff.
else if ( expandSelection ) {
return;
}
}
// If the range is a single line (there is no word wrapping) then move the selection to the position closest to the limit element.
//
// We can't move the selection directly to the isObject element (eg. table cell) because of dual position at the end/beginning
// of wrapped line (it's at the same time at the end of one line and at the start of the next line).
if ( range.isCollapsed || isSingleLineRange( editing, range, isForward ) ) {
model.change( writer => {
const newPosition = isForward ? range.end : range.start;
if ( expandSelection ) {
const newSelection = model.createSelection( selection.anchor );
newSelection.setFocus( newPosition );
writer.setSelection( newSelection );
} else {
writer.setSelection( newPosition );
}
} );
evt.stop();
data.preventDefault();
data.stopPropagation();
}
};
}
// Finds the range between selection and closest limit element (in the direction of navigation).
// The position next to limit element is adjusted to the closest allowed `$text` position.
//
// Returns `null` if, according to the schema, the resulting range cannot contain a `$text` element.
//
// @param {module:engine/controller/editingcontroller~EditingController} editing The editing controller.
// @param {module:engine/model/selection~Selection} selection The current selection.
// @param {Boolean} isForward The expected navigation direction.
// @returns {module:engine/model/range~Range|null}
//
function findTextRangeFromSelection( editing, selection, isForward ) {
const model = editing.model;
if ( isForward ) {
const startPosition = selection.isCollapsed ? selection.focus : selection.getLastPosition();
const endPosition = getNearestNonInlineLimit( model, startPosition, 'forward' );
// There is no limit element, browser should handle this.
if ( !endPosition ) {
return null;
}
const range = model.createRange( startPosition, endPosition );
const lastRangePosition = getNearestTextPosition( model.schema, range, 'backward' );
if ( lastRangePosition ) {
return model.createRange( startPosition, lastRangePosition );
}
return null;
} else {
const endPosition = selection.isCollapsed ? selection.focus : selection.getFirstPosition();
const startPosition = getNearestNonInlineLimit( model, endPosition, 'backward' );
// There is no limit element, browser should handle this.
if ( !startPosition ) {
return null;
}
const range = model.createRange( startPosition, endPosition );
const firstRangePosition = getNearestTextPosition( model.schema, range, 'forward' );
if ( firstRangePosition ) {
return model.createRange( firstRangePosition, endPosition );
}
return null;
}
}
// Finds the limit element position that is closest to startPosition.
//
// @param {module:engine/model/model~Model} model
// @param {<module:engine/model/position~Position>} startPosition
// @param {'forward'|'backward'} direction Search direction.
// @returns {<module:engine/model/position~Position>|null}
//
function getNearestNonInlineLimit( model, startPosition, direction ) {
const schema = model.schema;
const range = model.createRangeIn( startPosition.root );
const walkerValueType = direction == 'forward' ? 'elementStart' : 'elementEnd';
for ( const { previousPosition, item, type } of range.getWalker( { startPosition, direction } ) ) {
if ( schema.isLimit( item ) && !schema.isInline( item ) ) {
return previousPosition;
}
// Stop looking for isLimit element if the next element is a block element (it is for sure not single line).
if ( type == walkerValueType && schema.isBlock( item ) ) {
return null;
}
}
return null;
}
// Basing on the provided range, finds the first or last (depending on `direction`) position inside the range
// that can contain `$text` (according to schema).
//
// @param {module:engine/model/schema~Schema} schema The schema.
// @param {module:engine/model/range~Range} range The range to find the position in.
// @param {'forward'|'backward'} direction Search direction.
// @returns {module:engine/model/position~Position|null} The nearest selection position.
//
function getNearestTextPosition( schema, range, direction ) {
const position = direction == 'backward' ? range.end : range.start;
if ( schema.checkChild( position, '$text' ) ) {
return position;
}
for ( const { nextPosition } of range.getWalker( { direction } ) ) {
if ( schema.checkChild( nextPosition, '$text' ) ) {
return nextPosition;
}
}
return null;
}
// Checks if the DOM range corresponding to the provided model range renders as a single line by analyzing DOMRects
// (verifying if they visually wrap content to the next line).
//
// @param {module:engine/controller/editingcontroller~EditingController} editing The editing controller.
// @param {module:engine/model/range~Range} modelRange The current table cell content range.
// @param {Boolean} isForward The expected navigation direction.
// @returns {Boolean}
//
function isSingleLineRange( editing, modelRange, isForward ) {
const model = editing.model;
const domConverter = editing.view.domConverter;
// Wrapped lines contain exactly the same position at the end of current line
// and at the beginning of next line. That position's client rect is at the end
// of current line. In case of caret at first position of the last line that 'dual'
// position would be detected as it's not the last line.
if ( isForward ) {
const probe = model.createSelection( modelRange.start );
model.modifySelection( probe );
// If the new position is at the end of the container then we can't use this position
// because it would provide incorrect result for eg caption of image and selection
// just before end of it. Also in this case there is no "dual" position.
if ( !probe.focus.isAtEnd && !modelRange.start.isEqual( probe.focus ) ) {
modelRange = model.createRange( probe.focus, modelRange.end );
}
}
const viewRange = editing.mapper.toViewRange( modelRange );
const domRange = domConverter.viewRangeToDom( viewRange );
const rects = _ckeditor_ckeditor5_utils_src_dom_rect__WEBPACK_IMPORTED_MODULE_1__["default"].getDomRangeRects( domRange );
let boundaryVerticalPosition;
for ( const rect of rects ) {
if ( boundaryVerticalPosition === undefined ) {
boundaryVerticalPosition = Math.round( rect.bottom );
continue;
}
// Let's check if this rect is in new line.
if ( Math.round( rect.top ) >= boundaryVerticalPosition ) {
return false;
}
boundaryVerticalPosition = Math.max( boundaryVerticalPosition, Math.round( rect.bottom ) );
}
return true;
}
function selectionWillShrink( selection, isForward ) {
return !selection.isCollapsed && selection.isBackward == isForward;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-widget/src/widget.js":
/*!***************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-widget/src/widget.js ***!
\***************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ Widget)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_core_src_plugin__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-core/src/plugin */ "./node_modules/@ckeditor/ckeditor5-core/src/plugin.js");
/* harmony import */ var _ckeditor_ckeditor5_engine_src_view_observer_mouseobserver__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-engine/src/view/observer/mouseobserver */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/mouseobserver.js");
/* harmony import */ var _widgettypearound_widgettypearound__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./widgettypearound/widgettypearound */ "./node_modules/@ckeditor/ckeditor5-widget/src/widgettypearound/widgettypearound.js");
/* harmony import */ var _ckeditor_ckeditor5_typing_src_delete__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @ckeditor/ckeditor5-typing/src/delete */ "./node_modules/@ckeditor/ckeditor5-typing/src/delete.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_env__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/env */ "./node_modules/@ckeditor/ckeditor5-utils/src/env.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_keyboard__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/keyboard */ "./node_modules/@ckeditor/ckeditor5-utils/src/keyboard.js");
/* harmony import */ var _verticalnavigation__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./verticalnavigation */ "./node_modules/@ckeditor/ckeditor5-widget/src/verticalnavigation.js");
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./utils */ "./node_modules/@ckeditor/ckeditor5-widget/src/utils.js");
/* harmony import */ var _theme_widget_css__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ../theme/widget.css */ "./node_modules/@ckeditor/ckeditor5-widget/theme/widget.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module widget/widget
*/
/**
* The widget plugin. It enables base support for widgets.
*
* See {@glink api/widget package page} for more details and documentation.
*
* This plugin enables multiple behaviors required by widgets:
*
* * The model to view selection converter for the editing pipeline (it handles widget custom selection rendering).
* If a converted selection wraps around a widget element, that selection is marked as
* {@link module:engine/view/selection~Selection#isFake fake}. Additionally, the `ck-widget_selected` CSS class
* is added to indicate that widget has been selected.
* * The mouse and keyboard events handling on and around widget elements.
*
* @extends module:core/plugin~Plugin
*/
class Widget extends _ckeditor_ckeditor5_core_src_plugin__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* @inheritDoc
*/
static get pluginName() {
return 'Widget';
}
/**
* @inheritDoc
*/
static get requires() {
return [ _widgettypearound_widgettypearound__WEBPACK_IMPORTED_MODULE_2__["default"], _ckeditor_ckeditor5_typing_src_delete__WEBPACK_IMPORTED_MODULE_3__["default"] ];
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const view = editor.editing.view;
const viewDocument = view.document;
/**
* Holds previously selected widgets.
*
* @private
* @type {Set.<module:engine/view/element~Element>}
*/
this._previouslySelected = new Set();
// Model to view selection converter.
// Converts selection placed over widget element to fake selection.
//
// By default, the selection is downcasted by the engine to surround the attribute element, even though its only
// child is an inline widget. A similar thing also happens when a collapsed marker is rendered as a UI element
// next to an inline widget: the view selection contains both the widget and the marker.
//
// This prevents creating a correct fake selection when this inline widget is selected. Normalize the selection
// in these cases based on the model:
//
// [<attributeElement><inlineWidget /></attributeElement>] -> <attributeElement>[<inlineWidget />]</attributeElement>
// [<uiElement></uiElement><inlineWidget />] -> <uiElement></uiElement>[<inlineWidget />]
//
// Thanks to this:
//
// * fake selection can be set correctly,
// * any logic depending on (View)Selection#getSelectedElement() also works OK.
//
// See https://github.com/ckeditor/ckeditor5/issues/9524.
this.editor.editing.downcastDispatcher.on( 'selection', ( evt, data, conversionApi ) => {
const viewWriter = conversionApi.writer;
const modelSelection = data.selection;
// The collapsed selection can't contain any widget.
if ( modelSelection.isCollapsed ) {
return;
}
const selectedModelElement = modelSelection.getSelectedElement();
if ( !selectedModelElement ) {
return;
}
const selectedViewElement = editor.editing.mapper.toViewElement( selectedModelElement );
if ( !(0,_utils__WEBPACK_IMPORTED_MODULE_7__.isWidget)( selectedViewElement ) ) {
return;
}
if ( !conversionApi.consumable.consume( modelSelection, 'selection' ) ) {
return;
}
viewWriter.setSelection( viewWriter.createRangeOn( selectedViewElement ), {
fake: true,
label: (0,_utils__WEBPACK_IMPORTED_MODULE_7__.getLabel)( selectedViewElement )
} );
} );
// Mark all widgets inside the selection with the css class.
// This handler is registered at the 'low' priority so it's triggered after the real selection conversion.
this.editor.editing.downcastDispatcher.on( 'selection', ( evt, data, conversionApi ) => {
// Remove selected class from previously selected widgets.
this._clearPreviouslySelectedWidgets( conversionApi.writer );
const viewWriter = conversionApi.writer;
const viewSelection = viewWriter.document.selection;
let lastMarked = null;
for ( const range of viewSelection.getRanges() ) {
// Note: There could be multiple selected widgets in a range but no fake selection.
// All of them must be marked as selected, for instance [<widget></widget><widget></widget>]
for ( const value of range ) {
const node = value.item;
// Do not mark nested widgets in selected one. See: #4594
if ( (0,_utils__WEBPACK_IMPORTED_MODULE_7__.isWidget)( node ) && !isChild( node, lastMarked ) ) {
viewWriter.addClass( _utils__WEBPACK_IMPORTED_MODULE_7__.WIDGET_SELECTED_CLASS_NAME, node );
this._previouslySelected.add( node );
lastMarked = node;
}
}
}
}, { priority: 'low' } );
// If mouse down is pressed on widget - create selection over whole widget.
view.addObserver( _ckeditor_ckeditor5_engine_src_view_observer_mouseobserver__WEBPACK_IMPORTED_MODULE_1__["default"] );
this.listenTo( viewDocument, 'mousedown', ( ...args ) => this._onMousedown( ...args ) );
// There are two keydown listeners working on different priorities. This allows other
// features such as WidgetTypeAround or TableKeyboard to attach their listeners in between
// and customize the behavior even further in different content/selection scenarios.
//
// * The first listener handles changing the selection on arrow key press
// if the widget is selected or if the selection is next to a widget and the widget
// should become selected upon the arrow key press.
//
// * The second (late) listener makes sure the default browser action on arrow key press is
// prevented when a widget is selected. This prevents the selection from being moved
// from a fake selection container.
this.listenTo( viewDocument, 'arrowKey', ( ...args ) => {
this._handleSelectionChangeOnArrowKeyPress( ...args );
}, { context: [ _utils__WEBPACK_IMPORTED_MODULE_7__.isWidget, '$text' ] } );
this.listenTo( viewDocument, 'arrowKey', ( ...args ) => {
this._preventDefaultOnArrowKeyPress( ...args );
}, { context: '$root' } );
this.listenTo( viewDocument, 'arrowKey', (0,_verticalnavigation__WEBPACK_IMPORTED_MODULE_6__["default"])( this.editor.editing ), { context: '$text' } );
// Handle custom delete behaviour.
this.listenTo( viewDocument, 'delete', ( evt, data ) => {
if ( this._handleDelete( data.direction == 'forward' ) ) {
data.preventDefault();
evt.stop();
}
}, { context: '$root' } );
}
/**
* Handles {@link module:engine/view/document~Document#event:mousedown mousedown} events on widget elements.
*
* @private
* @param {module:utils/eventinfo~EventInfo} eventInfo
* @param {module:engine/view/observer/domeventdata~DomEventData} domEventData
*/
_onMousedown( eventInfo, domEventData ) {
const editor = this.editor;
const view = editor.editing.view;
const viewDocument = view.document;
let element = domEventData.target;
// Do nothing for single or double click inside nested editable.
if ( isInsideNestedEditable( element ) ) {
// But at least triple click inside nested editable causes broken selection in Safari.
// For such event, we select the entire nested editable element.
// See: https://github.com/ckeditor/ckeditor5/issues/1463.
if ( ( _ckeditor_ckeditor5_utils_src_env__WEBPACK_IMPORTED_MODULE_4__["default"].isSafari || _ckeditor_ckeditor5_utils_src_env__WEBPACK_IMPORTED_MODULE_4__["default"].isGecko ) && domEventData.domEvent.detail >= 3 ) {
const mapper = editor.editing.mapper;
const viewElement = element.is( 'attributeElement' ) ?
element.findAncestor( element => !element.is( 'attributeElement' ) ) : element;
const modelElement = mapper.toModelElement( viewElement );
domEventData.preventDefault();
this.editor.model.change( writer => {
writer.setSelection( modelElement, 'in' );
} );
}
return;
}
// If target is not a widget element - check if one of the ancestors is.
if ( !(0,_utils__WEBPACK_IMPORTED_MODULE_7__.isWidget)( element ) ) {
element = element.findAncestor( _utils__WEBPACK_IMPORTED_MODULE_7__.isWidget );
if ( !element ) {
return;
}
}
// On Android selection would jump to the first table cell, on other devices
// we can't block it (and don't need to) because of drag and drop support.
if ( _ckeditor_ckeditor5_utils_src_env__WEBPACK_IMPORTED_MODULE_4__["default"].isAndroid ) {
domEventData.preventDefault();
}
// Focus editor if is not focused already.
if ( !viewDocument.isFocused ) {
view.focus();
}
// Create model selection over widget.
const modelElement = editor.editing.mapper.toModelElement( element );
this._setSelectionOverElement( modelElement );
}
/**
* Handles {@link module:engine/view/document~Document#event:keydown keydown} events and changes
* the model selection when:
*
* * arrow key is pressed when the widget is selected,
* * the selection is next to a widget and the widget should become selected upon the arrow key press.
*
* See {@link #_preventDefaultOnArrowKeyPress}.
*
* @private
* @param {module:utils/eventinfo~EventInfo} eventInfo
* @param {module:engine/view/observer/domeventdata~DomEventData} domEventData
*/
_handleSelectionChangeOnArrowKeyPress( eventInfo, domEventData ) {
const keyCode = domEventData.keyCode;
const model = this.editor.model;
const schema = model.schema;
const modelSelection = model.document.selection;
const objectElement = modelSelection.getSelectedElement();
const direction = (0,_ckeditor_ckeditor5_utils_src_keyboard__WEBPACK_IMPORTED_MODULE_5__.getLocalizedArrowKeyCodeDirection)( keyCode, this.editor.locale.contentLanguageDirection );
const isForward = direction == 'down' || direction == 'right';
const isVerticalNavigation = direction == 'up' || direction == 'down';
// If object element is selected.
if ( objectElement && schema.isObject( objectElement ) ) {
const position = isForward ? modelSelection.getLastPosition() : modelSelection.getFirstPosition();
const newRange = schema.getNearestSelectionRange( position, isForward ? 'forward' : 'backward' );
if ( newRange ) {
model.change( writer => {
writer.setSelection( newRange );
} );
domEventData.preventDefault();
eventInfo.stop();
}
return;
}
// Handle collapsing of the selection when there is any widget on the edge of selection.
// This is needed because browsers have problems with collapsing such selection.
if ( !modelSelection.isCollapsed && !domEventData.shiftKey ) {
const firstPosition = modelSelection.getFirstPosition();
const lastPosition = modelSelection.getLastPosition();
const firstSelectedNode = firstPosition.nodeAfter;
const lastSelectedNode = lastPosition.nodeBefore;
if ( firstSelectedNode && schema.isObject( firstSelectedNode ) || lastSelectedNode && schema.isObject( lastSelectedNode ) ) {
model.change( writer => {
writer.setSelection( isForward ? lastPosition : firstPosition );
} );
domEventData.preventDefault();
eventInfo.stop();
}
return;
}
// Return if not collapsed.
if ( !modelSelection.isCollapsed ) {
return;
}
// If selection is next to object element.
const objectElementNextToSelection = this._getObjectElementNextToSelection( isForward );
if ( objectElementNextToSelection && schema.isObject( objectElementNextToSelection ) ) {
// Do not select an inline widget while handling up/down arrow.
if ( schema.isInline( objectElementNextToSelection ) && isVerticalNavigation ) {
return;
}
this._setSelectionOverElement( objectElementNextToSelection );
domEventData.preventDefault();
eventInfo.stop();
}
}
/**
* Handles {@link module:engine/view/document~Document#event:keydown keydown} events and prevents
* the default browser behavior to make sure the fake selection is not being moved from a fake selection
* container.
*
* See {@link #_handleSelectionChangeOnArrowKeyPress}.
*
* @private
* @param {module:utils/eventinfo~EventInfo} eventInfo
* @param {module:engine/view/observer/domeventdata~DomEventData} domEventData
*/
_preventDefaultOnArrowKeyPress( eventInfo, domEventData ) {
const model = this.editor.model;
const schema = model.schema;
const objectElement = model.document.selection.getSelectedElement();
// If object element is selected.
if ( objectElement && schema.isObject( objectElement ) ) {
domEventData.preventDefault();
eventInfo.stop();
}
}
/**
* Handles delete keys: backspace and delete.
*
* @private
* @param {Boolean} isForward Set to true if delete was performed in forward direction.
* @returns {Boolean|undefined} Returns `true` if keys were handled correctly.
*/
_handleDelete( isForward ) {
// Do nothing when the read only mode is enabled.
if ( this.editor.isReadOnly ) {
return;
}
const modelDocument = this.editor.model.document;
const modelSelection = modelDocument.selection;
// Do nothing on non-collapsed selection.
if ( !modelSelection.isCollapsed ) {
return;
}
const objectElement = this._getObjectElementNextToSelection( isForward );
if ( objectElement ) {
this.editor.model.change( writer => {
let previousNode = modelSelection.anchor.parent;
// Remove previous element if empty.
while ( previousNode.isEmpty ) {
const nodeToRemove = previousNode;
previousNode = nodeToRemove.parent;
writer.remove( nodeToRemove );
}
this._setSelectionOverElement( objectElement );
} );
return true;
}
}
/**
* Sets {@link module:engine/model/selection~Selection document's selection} over given element.
*
* @protected
* @param {module:engine/model/element~Element} element
*/
_setSelectionOverElement( element ) {
this.editor.model.change( writer => {
writer.setSelection( writer.createRangeOn( element ) );
} );
}
/**
* Checks if {@link module:engine/model/element~Element element} placed next to the current
* {@link module:engine/model/selection~Selection model selection} exists and is marked in
* {@link module:engine/model/schema~Schema schema} as `object`.
*
* @protected
* @param {Boolean} forward Direction of checking.
* @returns {module:engine/model/element~Element|null}
*/
_getObjectElementNextToSelection( forward ) {
const model = this.editor.model;
const schema = model.schema;
const modelSelection = model.document.selection;
// Clone current selection to use it as a probe. We must leave default selection as it is so it can return
// to its current state after undo.
const probe = model.createSelection( modelSelection );
model.modifySelection( probe, { direction: forward ? 'forward' : 'backward' } );
// The selection didn't change so there is nothing there.
if ( probe.isEqual( modelSelection ) ) {
return null;
}
const objectElement = forward ? probe.focus.nodeBefore : probe.focus.nodeAfter;
if ( !!objectElement && schema.isObject( objectElement ) ) {
return objectElement;
}
return null;
}
/**
* Removes CSS class from previously selected widgets.
*
* @private
* @param {module:engine/view/downcastwriter~DowncastWriter} writer
*/
_clearPreviouslySelectedWidgets( writer ) {
for ( const widget of this._previouslySelected ) {
writer.removeClass( _utils__WEBPACK_IMPORTED_MODULE_7__.WIDGET_SELECTED_CLASS_NAME, widget );
}
this._previouslySelected.clear();
}
}
// Returns `true` when element is a nested editable or is placed inside one.
//
// @param {module:engine/view/element~Element}
// @returns {Boolean}
function isInsideNestedEditable( element ) {
while ( element ) {
if ( element.is( 'editableElement' ) && !element.is( 'rootElement' ) ) {
return true;
}
// Click on nested widget should select it.
if ( (0,_utils__WEBPACK_IMPORTED_MODULE_7__.isWidget)( element ) ) {
return false;
}
element = element.parent;
}
return false;
}
// Checks whether the specified `element` is a child of the `parent` element.
//
// @param {module:engine/view/element~Element} element An element to check.
// @param {module:engine/view/element~Element|null} parent A parent for the element.
// @returns {Boolean}
function isChild( element, parent ) {
if ( !parent ) {
return false;
}
return Array.from( element.getAncestors() ).includes( parent );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-widget/src/widgetresize.js":
/*!*********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-widget/src/widgetresize.js ***!
\*********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ WidgetResize)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_core_src_plugin__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-core/src/plugin */ "./node_modules/@ckeditor/ckeditor5-core/src/plugin.js");
/* harmony import */ var _widgetresize_resizer__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./widgetresize/resizer */ "./node_modules/@ckeditor/ckeditor5-widget/src/widgetresize/resizer.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_dom_emittermixin__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/dom/emittermixin */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/emittermixin.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_dom_global__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/dom/global */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/global.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_observablemixin__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/observablemixin */ "./node_modules/@ckeditor/ckeditor5-utils/src/observablemixin.js");
/* harmony import */ var _ckeditor_ckeditor5_engine_src_view_observer_mouseobserver__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! @ckeditor/ckeditor5-engine/src/view/observer/mouseobserver */ "./node_modules/@ckeditor/ckeditor5-engine/src/view/observer/mouseobserver.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/mix */ "./node_modules/@ckeditor/ckeditor5-utils/src/mix.js");
/* harmony import */ var lodash_es__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! lodash-es */ "./node_modules/lodash-es/throttle.js");
/* harmony import */ var _theme_widgetresize_css__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ../theme/widgetresize.css */ "./node_modules/@ckeditor/ckeditor5-widget/theme/widgetresize.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module widget/widgetresize
*/
/**
* The widget resize feature plugin.
*
* Use the {@link module:widget/widgetresize~WidgetResize#attachTo} method to create a resizer for the specified widget.
*
* @extends module:core/plugin~Plugin
* @mixes module:utils/observablemixin~ObservableMixin
*/
class WidgetResize extends _ckeditor_ckeditor5_core_src_plugin__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* @inheritDoc
*/
static get pluginName() {
return 'WidgetResize';
}
/**
* @inheritDoc
*/
init() {
const editing = this.editor.editing;
const domDocument = _ckeditor_ckeditor5_utils_src_dom_global__WEBPACK_IMPORTED_MODULE_3__["default"].window.document;
/**
* The currently visible resizer.
*
* @observable
* @member {module:widget/widgetresize/resizer~Resizer|null} #visibleResizer
*/
this.set( 'visibleResizer', null );
/**
* References an active resizer.
*
* Active resizer means a resizer which handle is actively used by the end user.
*
* @protected
* @observable
* @member {module:widget/widgetresize/resizer~Resizer|null} #_activeResizer
*/
this.set( '_activeResizer', null );
/**
* A map of resizers created using this plugin instance.
*
* @protected
* @type {Map.<module:engine/view/containerelement~ContainerElement, module:widget/widgetresize/resizer~Resizer>}
*/
this._resizers = new Map();
editing.view.addObserver( _ckeditor_ckeditor5_engine_src_view_observer_mouseobserver__WEBPACK_IMPORTED_MODULE_5__["default"] );
this._observer = Object.create( _ckeditor_ckeditor5_utils_src_dom_emittermixin__WEBPACK_IMPORTED_MODULE_2__["default"] );
this.listenTo( editing.view.document, 'mousedown', this._mouseDownListener.bind( this ), { priority: 'high' } );
this._observer.listenTo( domDocument, 'mousemove', this._mouseMoveListener.bind( this ) );
this._observer.listenTo( domDocument, 'mouseup', this._mouseUpListener.bind( this ) );
const redrawFocusedResizer = () => {
if ( this.visibleResizer ) {
this.visibleResizer.redraw();
}
};
this._redrawFocusedResizerThrottled = (0,lodash_es__WEBPACK_IMPORTED_MODULE_8__["default"])( redrawFocusedResizer, 200 );
// Redraws occurring upon a change of visible resizer must not be throttled, as it is crucial for the initial
// render. Without it the resizer frame would be misaligned with resizing host for a fraction of second.
this.on( 'change:visibleResizer', redrawFocusedResizer );
// Redrawing on any change of the UI of the editor (including content changes).
this.editor.ui.on( 'update', this._redrawFocusedResizerThrottled );
// Remove view widget-resizer mappings for widgets that have been removed from the document.
// https://github.com/ckeditor/ckeditor5/issues/10156
// https://github.com/ckeditor/ckeditor5/issues/10266
this.editor.model.document.on( 'change', () => {
for ( const [ viewElement, resizer ] of this._resizers ) {
if ( !viewElement.isAttached() ) {
this._resizers.delete( viewElement );
resizer.destroy();
}
}
}, { priority: 'lowest' } );
// Resizers need to be redrawn upon window resize, because new window might shrink resize host.
this._observer.listenTo( _ckeditor_ckeditor5_utils_src_dom_global__WEBPACK_IMPORTED_MODULE_3__["default"].window, 'resize', this._redrawFocusedResizerThrottled );
const viewSelection = this.editor.editing.view.document.selection;
viewSelection.on( 'change', () => {
const selectedElement = viewSelection.getSelectedElement();
this.visibleResizer = this.getResizerByViewElement( selectedElement ) || null;
} );
}
/**
* @inheritDoc
*/
destroy() {
this._observer.stopListening();
for ( const resizer of this._resizers.values() ) {
resizer.destroy();
}
this._redrawFocusedResizerThrottled.cancel();
}
/**
* @param {module:widget/widgetresize~ResizerOptions} [options] Resizer options.
* @returns {module:widget/widgetresize/resizer~Resizer}
*/
attachTo( options ) {
const resizer = new _widgetresize_resizer__WEBPACK_IMPORTED_MODULE_1__["default"]( options );
const plugins = this.editor.plugins;
resizer.attach();
if ( plugins.has( 'WidgetToolbarRepository' ) ) {
// Hiding widget toolbar to improve the performance
// (https://github.com/ckeditor/ckeditor5-widget/pull/112#issuecomment-564528765).
const widgetToolbarRepository = plugins.get( 'WidgetToolbarRepository' );
resizer.on( 'begin', () => {
widgetToolbarRepository.forceDisabled( 'resize' );
}, { priority: 'lowest' } );
resizer.on( 'cancel', () => {
widgetToolbarRepository.clearForceDisabled( 'resize' );
}, { priority: 'highest' } );
resizer.on( 'commit', () => {
widgetToolbarRepository.clearForceDisabled( 'resize' );
}, { priority: 'highest' } );
}
this._resizers.set( options.viewElement, resizer );
const viewSelection = this.editor.editing.view.document.selection;
const selectedElement = viewSelection.getSelectedElement();
// If the element the resizer is created for is currently focused, it should become visible.
if ( this.getResizerByViewElement( selectedElement ) == resizer ) {
this.visibleResizer = resizer;
}
return resizer;
}
/**
* Returns a resizer created for a given view element (widget element).
*
* @param {module:engine/view/containerelement~ContainerElement} viewElement View element associated with the resizer.
* @returns {module:widget/widgetresize/resizer~Resizer|undefined}
*/
getResizerByViewElement( viewElement ) {
return this._resizers.get( viewElement );
}
/**
* Returns a resizer that contains a given resize handle.
*
* @protected
* @param {HTMLElement} domResizeHandle
* @returns {module:widget/widgetresize/resizer~Resizer}
*/
_getResizerByHandle( domResizeHandle ) {
for ( const resizer of this._resizers.values() ) {
if ( resizer.containsHandle( domResizeHandle ) ) {
return resizer;
}
}
}
/**
* @protected
* @param {module:utils/eventinfo~EventInfo} event
* @param {Event} domEventData Native DOM event.
*/
_mouseDownListener( event, domEventData ) {
const resizeHandle = domEventData.domTarget;
if ( !_widgetresize_resizer__WEBPACK_IMPORTED_MODULE_1__["default"].isResizeHandle( resizeHandle ) ) {
return;
}
this._activeResizer = this._getResizerByHandle( resizeHandle );
if ( this._activeResizer ) {
this._activeResizer.begin( resizeHandle );
// Do not call other events when resizing. See: #6755.
event.stop();
domEventData.preventDefault();
}
}
/**
* @protected
* @param {module:utils/eventinfo~EventInfo} event
* @param {Event} domEventData Native DOM event.
*/
_mouseMoveListener( event, domEventData ) {
if ( this._activeResizer ) {
this._activeResizer.updateSize( domEventData );
}
}
/**
* @protected
*/
_mouseUpListener() {
if ( this._activeResizer ) {
this._activeResizer.commit();
this._activeResizer = null;
}
}
}
(0,_ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_6__["default"])( WidgetResize, _ckeditor_ckeditor5_utils_src_observablemixin__WEBPACK_IMPORTED_MODULE_4__["default"] );
/**
* Interface describing a resizer. It allows to specify the resizing host, custom logic for calculating aspect ratio, etc.
*
* @interface ResizerOptions
*/
/**
* Editor instance associated with the resizer.
*
* @member {module:core/editor/editor~Editor} module:widget/widgetresize~ResizerOptions#editor
*/
/**
* @member {module:engine/model/element~Element} module:widget/widgetresize~ResizerOptions#modelElement
*/
/**
* A view of an element to be resized. Typically it's the main widget's view instance.
*
* @member {module:engine/view/containerelement~ContainerElement} module:widget/widgetresize~ResizerOptions#viewElement
*/
/**
* A callback to be executed once the resizing process is done.
*
* It receives a `Number` (`newValue`) as a parameter.
*
* For example, {@link module:image/imageresize~ImageResize} uses it to execute the resize image command
* which puts the new value into the model.
*
* ```js
* {
* editor,
* modelElement: data.item,
* viewElement: widget,
*
* onCommit( newValue ) {
* editor.execute( 'resizeImage', { width: newValue } );
* }
* };
* ```
*
*
* @member {Function} module:widget/widgetresize~ResizerOptions#onCommit
*/
/**
* @member {Function} module:widget/widgetresize~ResizerOptions#getResizeHost
*/
/**
* @member {Function} module:widget/widgetresize~ResizerOptions#isCentered
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-widget/src/widgetresize/resizer.js":
/*!*****************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-widget/src/widgetresize/resizer.js ***!
\*****************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ Resizer)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_ui_src_template__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-ui/src/template */ "./node_modules/@ckeditor/ckeditor5-ui/src/template.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_dom_rect__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/dom/rect */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/rect.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_comparearrays__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/comparearrays */ "./node_modules/@ckeditor/ckeditor5-utils/src/comparearrays.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_observablemixin__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/observablemixin */ "./node_modules/@ckeditor/ckeditor5-utils/src/observablemixin.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/mix */ "./node_modules/@ckeditor/ckeditor5-utils/src/mix.js");
/* harmony import */ var _resizerstate__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./resizerstate */ "./node_modules/@ckeditor/ckeditor5-widget/src/widgetresize/resizerstate.js");
/* harmony import */ var _sizeview__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./sizeview */ "./node_modules/@ckeditor/ckeditor5-widget/src/widgetresize/sizeview.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module widget/widgetresize/resizer
*/
/**
* Represents a resizer for a single resizable object.
*
* @mixes module:utils/observablemixin~ObservableMixin
*/
class Resizer {
/**
* @param {module:widget/widgetresize~ResizerOptions} options Resizer options.
*/
constructor( options ) {
/**
* Stores the state of the resizable host geometry, such as the original width, the currently proposed height, etc.
*
* Note that a new state is created for each resize transaction.
*
* @readonly
* @member {module:widget/widgetresize/resizerstate~ResizerState} #state
*/
/**
* A view displaying the proposed new element size during the resizing.
*
* @protected
* @readonly
* @member {module:widget/widgetresize/sizeview~SizeView} #_sizeView
*/
/**
* Options passed to the {@link #constructor}.
*
* @private
* @type {module:widget/widgetresize~ResizerOptions}
*/
this._options = options;
/**
* A wrapper that is controlled by the resizer. This is usually a widget element.
*
* @private
* @type {module:engine/view/element~Element|null}
*/
this._viewResizerWrapper = null;
/**
* The width of the resized {@link module:widget/widgetresize~ResizerOptions#viewElement viewElement} before the resizing started.
*
* @private
* @member {Number|String|undefined} #_initialViewWidth
*/
/**
* @observable
*/
this.set( 'isEnabled', true );
this.decorate( 'begin' );
this.decorate( 'cancel' );
this.decorate( 'commit' );
this.decorate( 'updateSize' );
this.on( 'commit', event => {
// State might not be initialized yet. In this case, prevent further handling and make sure that the resizer is
// cleaned up (#5195).
if ( !this.state.proposedWidth && !this.state.proposedWidthPercents ) {
this._cleanup();
event.stop();
}
}, { priority: 'high' } );
this.on( 'change:isEnabled', () => {
// We should redraw the resize handles when the plugin is enabled again.
// Otherwise they won't show up.
if ( this.isEnabled ) {
this.redraw();
}
} );
}
/**
* Attaches the resizer to the DOM.
*/
attach() {
const that = this;
const widgetElement = this._options.viewElement;
const editingView = this._options.editor.editing.view;
editingView.change( writer => {
const viewResizerWrapper = writer.createUIElement( 'div', {
class: 'ck ck-reset_all ck-widget__resizer'
}, function( domDocument ) {
const domElement = this.toDomElement( domDocument );
that._appendHandles( domElement );
that._appendSizeUI( domElement );
that.on( 'change:isEnabled', ( evt, propName, newValue ) => {
domElement.style.display = newValue ? '' : 'none';
} );
domElement.style.display = that.isEnabled ? '' : 'none';
return domElement;
} );
// Append the resizer wrapper to the widget's wrapper.
writer.insert( writer.createPositionAt( widgetElement, 'end' ), viewResizerWrapper );
writer.addClass( 'ck-widget_with-resizer', widgetElement );
this._viewResizerWrapper = viewResizerWrapper;
} );
}
/**
* Starts the resizing process.
*
* Creates a new {@link #state} for the current process.
*
* @fires begin
* @param {HTMLElement} domResizeHandle Clicked handle.
*/
begin( domResizeHandle ) {
this.state = new _resizerstate__WEBPACK_IMPORTED_MODULE_5__["default"]( this._options );
this._sizeView._bindToState( this._options, this.state );
this._initialViewWidth = this._options.viewElement.getStyle( 'width' );
this.state.begin( domResizeHandle, this._getHandleHost(), this._getResizeHost() );
}
/**
* Updates the proposed size based on `domEventData`.
*
* @fires updateSize
* @param {Event} domEventData
*/
updateSize( domEventData ) {
const newSize = this._proposeNewSize( domEventData );
const editingView = this._options.editor.editing.view;
editingView.change( writer => {
const unit = this._options.unit || '%';
const newWidth = ( unit === '%' ? newSize.widthPercents : newSize.width ) + unit;
writer.setStyle( 'width', newWidth, this._options.viewElement );
} );
// Get an actual image width, and:
// * reflect this size to the resize wrapper
// * apply this **real** size to the state
const domHandleHost = this._getHandleHost();
const domHandleHostRect = new _ckeditor_ckeditor5_utils_src_dom_rect__WEBPACK_IMPORTED_MODULE_1__["default"]( domHandleHost );
newSize.handleHostWidth = Math.round( domHandleHostRect.width );
newSize.handleHostHeight = Math.round( domHandleHostRect.height );
// Handle max-width limitation.
const domResizeHostRect = new _ckeditor_ckeditor5_utils_src_dom_rect__WEBPACK_IMPORTED_MODULE_1__["default"]( domHandleHost );
newSize.width = Math.round( domResizeHostRect.width );
newSize.height = Math.round( domResizeHostRect.height );
this.redraw( domHandleHostRect );
this.state.update( newSize );
}
/**
* Applies the geometry proposed with the resizer.
*
* @fires commit
*/
commit() {
const unit = this._options.unit || '%';
const newValue = ( unit === '%' ? this.state.proposedWidthPercents : this.state.proposedWidth ) + unit;
// Both cleanup and onCommit callback are very likely to make view changes. Ensure that it is made in a single step.
this._options.editor.editing.view.change( () => {
this._cleanup();
this._options.onCommit( newValue );
} );
}
/**
* Cancels and rejects the proposed resize dimensions, hiding the UI.
*
* @fires cancel
*/
cancel() {
this._cleanup();
}
/**
* Destroys the resizer.
*/
destroy() {
this.cancel();
}
/**
* Redraws the resizer.
*
* @param {module:utils/dom/rect~Rect} [handleHostRect] Handle host rectangle might be given to improve performance.
*/
redraw( handleHostRect ) {
const domWrapper = this._domResizerWrapper;
// Refresh only if resizer exists in the DOM.
if ( !existsInDom( domWrapper ) ) {
return;
}
const widgetWrapper = domWrapper.parentElement;
const handleHost = this._getHandleHost();
const resizerWrapper = this._viewResizerWrapper;
const currentDimensions = [
resizerWrapper.getStyle( 'width' ),
resizerWrapper.getStyle( 'height' ),
resizerWrapper.getStyle( 'left' ),
resizerWrapper.getStyle( 'top' )
];
let newDimensions;
if ( widgetWrapper.isSameNode( handleHost ) ) {
const clientRect = handleHostRect || new _ckeditor_ckeditor5_utils_src_dom_rect__WEBPACK_IMPORTED_MODULE_1__["default"]( handleHost );
newDimensions = [
clientRect.width + 'px',
clientRect.height + 'px',
undefined,
undefined
];
}
// In case a resizing host is not a widget wrapper, we need to compensate
// for any additional offsets the resize host might have. E.g. wrapper padding
// or simply another editable. By doing that the border and resizers are shown
// only around the resize host.
else {
newDimensions = [
handleHost.offsetWidth + 'px',
handleHost.offsetHeight + 'px',
handleHost.offsetLeft + 'px',
handleHost.offsetTop + 'px'
];
}
// Make changes to the view only if the resizer should actually get new dimensions.
// Otherwise, if View#change() was always called, this would cause EditorUI#update
// loops because the WidgetResize plugin listens to EditorUI#update and updates
// the resizer.
// https://github.com/ckeditor/ckeditor5/issues/7633
if ( (0,_ckeditor_ckeditor5_utils_src_comparearrays__WEBPACK_IMPORTED_MODULE_2__["default"])( currentDimensions, newDimensions ) !== 'same' ) {
this._options.editor.editing.view.change( writer => {
writer.setStyle( {
width: newDimensions[ 0 ],
height: newDimensions[ 1 ],
left: newDimensions[ 2 ],
top: newDimensions[ 3 ]
}, resizerWrapper );
} );
}
}
containsHandle( domElement ) {
return this._domResizerWrapper.contains( domElement );
}
static isResizeHandle( domElement ) {
return domElement.classList.contains( 'ck-widget__resizer__handle' );
}
/**
* Cleans up the context state.
*
* @protected
*/
_cleanup() {
this._sizeView._dismiss();
const editingView = this._options.editor.editing.view;
editingView.change( writer => {
writer.setStyle( 'width', this._initialViewWidth, this._options.viewElement );
} );
}
/**
* Calculates the proposed size as the resize handles are dragged.
*
* @private
* @param {Event} domEventData Event data that caused the size update request. It should be used to calculate the proposed size.
* @returns {Object} return
* @returns {Number} return.width Proposed width.
* @returns {Number} return.height Proposed height.
*/
_proposeNewSize( domEventData ) {
const state = this.state;
const currentCoordinates = extractCoordinates( domEventData );
const isCentered = this._options.isCentered ? this._options.isCentered( this ) : true;
// Enlargement defines how much the resize host has changed in a given axis. Naturally it could be a negative number
// meaning that it has been shrunk.
//
// +----------------+--+
// | | |
// | img | |
// | /handle host | |
// +----------------+ | ^
// | | | - enlarge y
// +-------------------+ v
// <-->
// enlarge x
const enlargement = {
x: state._referenceCoordinates.x - ( currentCoordinates.x + state.originalWidth ),
y: ( currentCoordinates.y - state.originalHeight ) - state._referenceCoordinates.y
};
if ( isCentered && state.activeHandlePosition.endsWith( '-right' ) ) {
enlargement.x = currentCoordinates.x - ( state._referenceCoordinates.x + state.originalWidth );
}
// Objects needs to be resized twice as much in horizontal axis if centered, since enlargement is counted from
// one resized corner to your cursor. It needs to be duplicated to compensate for the other side too.
if ( isCentered ) {
enlargement.x *= 2;
}
// const resizeHost = this._getResizeHost();
// The size proposed by the user. It does not consider the aspect ratio.
const proposedSize = {
width: Math.abs( state.originalWidth + enlargement.x ),
height: Math.abs( state.originalHeight + enlargement.y )
};
// Dominant determination must take the ratio into account.
proposedSize.dominant = proposedSize.width / state.aspectRatio > proposedSize.height ? 'width' : 'height';
proposedSize.max = proposedSize[ proposedSize.dominant ];
// Proposed size, respecting the aspect ratio.
const targetSize = {
width: proposedSize.width,
height: proposedSize.height
};
if ( proposedSize.dominant == 'width' ) {
targetSize.height = targetSize.width / state.aspectRatio;
} else {
targetSize.width = targetSize.height * state.aspectRatio;
}
return {
width: Math.round( targetSize.width ),
height: Math.round( targetSize.height ),
widthPercents: Math.min( Math.round( state.originalWidthPercents / state.originalWidth * targetSize.width * 100 ) / 100, 100 )
};
}
/**
* Obtains the resize host.
*
* Resize host is an object that receives dimensions which are the result of resizing.
*
* @protected
* @returns {HTMLElement}
*/
_getResizeHost() {
const widgetWrapper = this._domResizerWrapper.parentElement;
return this._options.getResizeHost( widgetWrapper );
}
/**
* Obtains the handle host.
*
* Handle host is an object that the handles are aligned to.
*
* Handle host will not always be an entire widget itself. Take an image as an example. The image widget
* contains an image and a caption. Only the image should be surrounded with handles.
*
* @protected
* @returns {HTMLElement}
*/
_getHandleHost() {
const widgetWrapper = this._domResizerWrapper.parentElement;
return this._options.getHandleHost( widgetWrapper );
}
/**
* DOM container of the entire resize UI.
*
* Note that this property will have a value only after the element bound with the resizer is rendered
* (otherwise `null`).
*
* @private
* @member {HTMLElement|null}
*/
get _domResizerWrapper() {
return this._options.editor.editing.view.domConverter.mapViewToDom( this._viewResizerWrapper );
}
/**
* Renders the resize handles in the DOM.
*
* @private
* @param {HTMLElement} domElement The resizer wrapper.
*/
_appendHandles( domElement ) {
const resizerPositions = [ 'top-left', 'top-right', 'bottom-right', 'bottom-left' ];
for ( const currentPosition of resizerPositions ) {
domElement.appendChild( ( new _ckeditor_ckeditor5_ui_src_template__WEBPACK_IMPORTED_MODULE_0__["default"]( {
tag: 'div',
attributes: {
class: `ck-widget__resizer__handle ${ getResizerClass( currentPosition ) }`
}
} ).render() ) );
}
}
/**
* Sets up the {@link #_sizeView} property and adds it to the passed `domElement`.
*
* @private
* @param {HTMLElement} domElement
*/
_appendSizeUI( domElement ) {
this._sizeView = new _sizeview__WEBPACK_IMPORTED_MODULE_6__["default"]();
// Make sure icon#element is rendered before passing to appendChild().
this._sizeView.render();
domElement.appendChild( this._sizeView.element );
}
/**
* @event begin
*/
/**
* @event updateSize
*/
/**
* @event commit
*/
/**
* @event cancel
*/
}
(0,_ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_4__["default"])( Resizer, _ckeditor_ckeditor5_utils_src_observablemixin__WEBPACK_IMPORTED_MODULE_3__["default"] );
// @private
// @param {String} resizerPosition Expected resizer position like `"top-left"`, `"bottom-right"`.
// @returns {String} A prefixed HTML class name for the resizer element
function getResizerClass( resizerPosition ) {
return `ck-widget__resizer__handle-${ resizerPosition }`;
}
function extractCoordinates( event ) {
return {
x: event.pageX,
y: event.pageY
};
}
function existsInDom( element ) {
return element && element.ownerDocument && element.ownerDocument.contains( element );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-widget/src/widgetresize/resizerstate.js":
/*!**********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-widget/src/widgetresize/resizerstate.js ***!
\**********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ ResizeState)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_utils_src_dom_rect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/dom/rect */ "./node_modules/@ckeditor/ckeditor5-utils/src/dom/rect.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_observablemixin__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/observablemixin */ "./node_modules/@ckeditor/ckeditor5-utils/src/observablemixin.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/mix */ "./node_modules/@ckeditor/ckeditor5-utils/src/mix.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module widget/widgetresize/resizerstate
*/
/**
* Stores the internal state of a single resizable object.
*
*/
class ResizeState {
/**
* @param {module:widget/widgetresize~ResizerOptions} options Resizer options.
*/
constructor( options ) {
/**
* The original width (pixels) of the resized object when the resize process was started.
*
* @readonly
* @member {Number} #originalWidth
*/
/**
* The original height (pixels) of the resized object when the resize process was started.
*
* @readonly
* @member {Number} #originalHeight
*/
/**
* The original width (percents) of the resized object when the resize process was started.
*
* @readonly
* @member {Number} #originalWidthPercents
*/
/**
* The position of the handle that initiated the resizing. E.g. `"top-left"`, `"bottom-right"` etc. or `null`
* if unknown.
*
* @readonly
* @observable
* @member {String|null} #activeHandlePosition
*/
this.set( 'activeHandlePosition', null );
/**
* The width (percents) proposed, but not committed yet, in the current resize process.
*
* @readonly
* @observable
* @member {Number|null} #proposedWidthPercents
*/
this.set( 'proposedWidthPercents', null );
/**
* The width (pixels) proposed, but not committed yet, in the current resize process.
*
* @readonly
* @observable
* @member {Number|null} #proposedWidthPixels
*/
this.set( 'proposedWidth', null );
/**
* The height (pixels) proposed, but not committed yet, in the current resize process.
*
* @readonly
* @observable
* @member {Number|null} #proposedHeightPixels
*/
this.set( 'proposedHeight', null );
this.set( 'proposedHandleHostWidth', null );
this.set( 'proposedHandleHostHeight', null );
/**
* A width to height ratio of the resized image.
*
* @readonly
* @member {Number} #aspectRatio
*/
/**
* @private
* @type {module:widget/widgetresize~ResizerOptions}
*/
this._options = options;
/**
* The reference point of the resizer where the dragging started. It is used to measure the distance the user cursor
* traveled, so how much the image should be enlarged.
* This information is only known after the DOM was rendered, so it will be updated later.
*
* @private
* @type {Object}
*/
this._referenceCoordinates = null;
}
/**
*
* @param {HTMLElement} domResizeHandle The handle used to calculate the reference point.
* @param {HTMLElement} domHandleHost
* @param {HTMLElement} domResizeHost
*/
begin( domResizeHandle, domHandleHost, domResizeHost ) {
const clientRect = new _ckeditor_ckeditor5_utils_src_dom_rect__WEBPACK_IMPORTED_MODULE_0__["default"]( domHandleHost );
this.activeHandlePosition = getHandlePosition( domResizeHandle );
this._referenceCoordinates = getAbsoluteBoundaryPoint( domHandleHost, getOppositePosition( this.activeHandlePosition ) );
this.originalWidth = clientRect.width;
this.originalHeight = clientRect.height;
this.aspectRatio = clientRect.width / clientRect.height;
const widthStyle = domResizeHost.style.width;
if ( widthStyle && widthStyle.match( /^\d+(\.\d*)?%$/ ) ) {
this.originalWidthPercents = parseFloat( widthStyle );
} else {
this.originalWidthPercents = calculateHostPercentageWidth( domResizeHost, clientRect );
}
}
update( newSize ) {
this.proposedWidth = newSize.width;
this.proposedHeight = newSize.height;
this.proposedWidthPercents = newSize.widthPercents;
this.proposedHandleHostWidth = newSize.handleHostWidth;
this.proposedHandleHostHeight = newSize.handleHostHeight;
}
}
(0,_ckeditor_ckeditor5_utils_src_mix__WEBPACK_IMPORTED_MODULE_2__["default"])( ResizeState, _ckeditor_ckeditor5_utils_src_observablemixin__WEBPACK_IMPORTED_MODULE_1__["default"] );
// Calculates a relative width of a `domResizeHost` compared to it's parent in percents.
//
// @private
// @param {HTMLElement} domResizeHost
// @param {module:utils/dom/rect~Rect} resizeHostRect
// @returns {Number}
function calculateHostPercentageWidth( domResizeHost, resizeHostRect ) {
const domResizeHostParent = domResizeHost.parentElement;
// Need to use computed style as it properly excludes parent's paddings from the returned value.
const parentWidth = parseFloat( domResizeHostParent.ownerDocument.defaultView.getComputedStyle( domResizeHostParent ).width );
return resizeHostRect.width / parentWidth * 100;
}
// Returns coordinates of the top-left corner of an element, relative to the document's top-left corner.
//
// @private
// @param {HTMLElement} element
// @param {String} resizerPosition The position of the resize handle, e.g. `"top-left"`, `"bottom-right"`.
// @returns {Object} return
// @returns {Number} return.x
// @returns {Number} return.y
function getAbsoluteBoundaryPoint( element, resizerPosition ) {
const elementRect = new _ckeditor_ckeditor5_utils_src_dom_rect__WEBPACK_IMPORTED_MODULE_0__["default"]( element );
const positionParts = resizerPosition.split( '-' );
const ret = {
x: positionParts[ 1 ] == 'right' ? elementRect.right : elementRect.left,
y: positionParts[ 0 ] == 'bottom' ? elementRect.bottom : elementRect.top
};
ret.x += element.ownerDocument.defaultView.scrollX;
ret.y += element.ownerDocument.defaultView.scrollY;
return ret;
}
// @private
// @param {String} resizerPosition The expected resizer position, like `"top-left"`, `"bottom-right"`.
// @returns {String} A prefixed HTML class name for the resizer element.
function getResizerHandleClass( resizerPosition ) {
return `ck-widget__resizer__handle-${ resizerPosition }`;
}
// Determines the position of a given resize handle.
//
// @private
// @param {HTMLElement} domHandle Handle used to calculate the reference point.
// @returns {String|undefined} Returns a string like `"top-left"` or `undefined` if not matched.
function getHandlePosition( domHandle ) {
const resizerPositions = [ 'top-left', 'top-right', 'bottom-right', 'bottom-left' ];
for ( const position of resizerPositions ) {
if ( domHandle.classList.contains( getResizerHandleClass( position ) ) ) {
return position;
}
}
}
// @private
// @param {String} position Like `"top-left"`.
// @returns {String} Inverted `position`, e.g. it returns `"bottom-right"` if `"top-left"` was given as `position`.
function getOppositePosition( position ) {
const parts = position.split( '-' );
const replacements = {
top: 'bottom',
bottom: 'top',
left: 'right',
right: 'left'
};
return `${ replacements[ parts[ 0 ] ] }-${ replacements[ parts[ 1 ] ] }`;
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-widget/src/widgetresize/sizeview.js":
/*!******************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-widget/src/widgetresize/sizeview.js ***!
\******************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ SizeView)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_ui_src_view__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-ui/src/view */ "./node_modules/@ckeditor/ckeditor5-ui/src/view.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module widget/widgetresize/sizeview
*/
/**
* A view displaying the proposed new element size during the resizing.
*
* @protected
* @extends {module:ui/view~View}
*/
class SizeView extends _ckeditor_ckeditor5_ui_src_view__WEBPACK_IMPORTED_MODULE_0__["default"] {
constructor() {
super();
/**
* The visibility of the view defined based on the existence of the host proposed dimensions.
*
* @private
* @observable
* @readonly
* @member {Boolean} #_isVisible
*/
/**
* The text that will be displayed in the `SizeView` child.
* It can be formatted as the pixel values (e.g. 10x20) or the percentage value (e.g. 10%).
*
* @private
* @observable
* @readonly
* @member {Boolean} #_label
*/
/**
* The position of the view defined based on the host size and active handle position.
*
* @private
* @observable
* @readonly
* @member {String} #_viewPosition
*/
const bind = this.bindTemplate;
this.setTemplate( {
tag: 'div',
attributes: {
class: [
'ck',
'ck-size-view',
bind.to( '_viewPosition', value => value ? `ck-orientation-${ value }` : '' )
],
style: {
display: bind.if( '_isVisible', 'none', visible => !visible )
}
},
children: [ {
text: bind.to( '_label' )
} ]
} );
}
/**
* A method used for binding the `SizeView` instance properties to the `ResizeState` instance observable properties.
*
* @protected
* @param {module:widget/widgetresize~ResizerOptions} options
* An object defining the resizer options, used for setting the proper size label.
* @param {module:widget/widgetresize/resizerstate~ResizeState} resizeState
* The `ResizeState` class instance, used for keeping the `SizeView` state up to date.
*/
_bindToState( options, resizeState ) {
this.bind( '_isVisible' ).to( resizeState, 'proposedWidth', resizeState, 'proposedHeight', ( width, height ) =>
width !== null && height !== null );
this.bind( '_label' ).to(
resizeState, 'proposedHandleHostWidth',
resizeState, 'proposedHandleHostHeight',
resizeState, 'proposedWidthPercents',
( width, height, widthPercents ) => {
if ( options.unit === 'px' ) {
return `${ width }×${ height }`;
} else {
return `${ widthPercents }%`;
}
}
);
this.bind( '_viewPosition' ).to(
resizeState, 'activeHandlePosition',
resizeState, 'proposedHandleHostWidth',
resizeState, 'proposedHandleHostHeight',
// If the widget is too small to contain the size label, display the label above.
( position, width, height ) => width < 50 || height < 50 ? 'above-center' : position
);
}
/**
* A method used for cleaning up. It removes the bindings and hides the view.
*
* @protected
*/
_dismiss() {
this.unbind();
this._isVisible = false;
}
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-widget/src/widgettoolbarrepository.js":
/*!********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-widget/src/widgettoolbarrepository.js ***!
\********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ WidgetToolbarRepository)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_core_src_plugin__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-core/src/plugin */ "./node_modules/@ckeditor/ckeditor5-core/src/plugin.js");
/* harmony import */ var _ckeditor_ckeditor5_ui_src_panel_balloon_contextualballoon__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-ui/src/panel/balloon/contextualballoon */ "./node_modules/@ckeditor/ckeditor5-ui/src/panel/balloon/contextualballoon.js");
/* harmony import */ var _ckeditor_ckeditor5_ui_src_toolbar_toolbarview__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @ckeditor/ckeditor5-ui/src/toolbar/toolbarview */ "./node_modules/@ckeditor/ckeditor5-ui/src/toolbar/toolbarview.js");
/* harmony import */ var _ckeditor_ckeditor5_ui_src_panel_balloon_balloonpanelview__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @ckeditor/ckeditor5-ui/src/panel/balloon/balloonpanelview */ "./node_modules/@ckeditor/ckeditor5-ui/src/panel/balloon/balloonpanelview.js");
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./utils */ "./node_modules/@ckeditor/ckeditor5-widget/src/utils.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/ckeditorerror */ "./node_modules/@ckeditor/ckeditor5-utils/src/ckeditorerror.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module widget/widgettoolbarrepository
*/
/**
* Widget toolbar repository plugin. A central point for registering widget toolbars. This plugin handles the whole
* toolbar rendering process and exposes a concise API.
*
* To add a toolbar for your widget use the {@link ~WidgetToolbarRepository#register `WidgetToolbarRepository#register()`} method.
*
* The following example comes from the {@link module:image/imagetoolbar~ImageToolbar} plugin:
*
* class ImageToolbar extends Plugin {
* static get requires() {
* return [ WidgetToolbarRepository ];
* }
*
* afterInit() {
* const editor = this.editor;
* const widgetToolbarRepository = editor.plugins.get( WidgetToolbarRepository );
*
* widgetToolbarRepository.register( 'image', {
* items: editor.config.get( 'image.toolbar' ),
* getRelatedElement: getClosestSelectedImageWidget
* } );
* }
* }
*/
class WidgetToolbarRepository extends _ckeditor_ckeditor5_core_src_plugin__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* @inheritDoc
*/
static get requires() {
return [ _ckeditor_ckeditor5_ui_src_panel_balloon_contextualballoon__WEBPACK_IMPORTED_MODULE_1__["default"] ];
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'WidgetToolbarRepository';
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
// Disables the default balloon toolbar for all widgets.
if ( editor.plugins.has( 'BalloonToolbar' ) ) {
const balloonToolbar = editor.plugins.get( 'BalloonToolbar' );
this.listenTo( balloonToolbar, 'show', evt => {
if ( isWidgetSelected( editor.editing.view.document.selection ) ) {
evt.stop();
}
}, { priority: 'high' } );
}
/**
* A map of toolbar definitions.
*
* @protected
* @member {Map.<String,module:widget/widgettoolbarrepository~WidgetRepositoryToolbarDefinition>} #_toolbarDefinitions
*/
this._toolbarDefinitions = new Map();
/**
* @private
*/
this._balloon = this.editor.plugins.get( 'ContextualBalloon' );
this.on( 'change:isEnabled', () => {
this._updateToolbarsVisibility();
} );
this.listenTo( editor.ui, 'update', () => {
this._updateToolbarsVisibility();
} );
// UI#update is not fired after focus is back in editor, we need to check if balloon panel should be visible.
this.listenTo( editor.ui.focusTracker, 'change:isFocused', () => {
this._updateToolbarsVisibility();
}, { priority: 'low' } );
}
destroy() {
super.destroy();
for ( const toolbarConfig of this._toolbarDefinitions.values() ) {
toolbarConfig.view.destroy();
}
}
/**
* Registers toolbar in the WidgetToolbarRepository. It renders it in the `ContextualBalloon` based on the value of the invoked
* `getRelatedElement` function. Toolbar items are gathered from `items` array.
* The balloon's CSS class is by default `ck-toolbar-container` and may be override with the `balloonClassName` option.
*
* Note: This method should be called in the {@link module:core/plugin~PluginInterface#afterInit `Plugin#afterInit()`}
* callback (or later) to make sure that the given toolbar items were already registered by other plugins.
*
* @param {String} toolbarId An id for the toolbar. Used to
* @param {Object} options
* @param {String} [options.ariaLabel] Label used by assistive technologies to describe this toolbar element.
* @param {Array.<String>} options.items Array of toolbar items.
* @param {Function} options.getRelatedElement Callback which returns an element the toolbar should be attached to.
* @param {String} [options.balloonClassName='ck-toolbar-container'] CSS class for the widget balloon.
*/
register( toolbarId, { ariaLabel, items, getRelatedElement, balloonClassName = 'ck-toolbar-container' } ) {
// Trying to register a toolbar without any item.
if ( !items.length ) {
/**
* When {@link #register registering} a new widget toolbar, you need to provide a non-empty array with
* the items that will be inserted into the toolbar.
*
* If you see this error when integrating the editor, you likely forgot to configure one of the widget toolbars.
*
* See for instance:
*
* * {@link module:table/table~TableConfig#contentToolbar `config.table.contentToolbar`}
* * {@link module:image/image~ImageConfig#toolbar `config.image.toolbar`}
*
* @error widget-toolbar-no-items
* @param {String} toolbarId The id of the toolbar that has not been configured correctly.
*/
(0,_ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_5__.logWarning)( 'widget-toolbar-no-items', { toolbarId } );
return;
}
const editor = this.editor;
const t = editor.t;
const toolbarView = new _ckeditor_ckeditor5_ui_src_toolbar_toolbarview__WEBPACK_IMPORTED_MODULE_2__["default"]( editor.locale );
toolbarView.ariaLabel = ariaLabel || t( 'Widget toolbar' );
if ( this._toolbarDefinitions.has( toolbarId ) ) {
/**
* Toolbar with the given id was already added.
*
* @error widget-toolbar-duplicated
* @param toolbarId Toolbar id.
*/
throw new _ckeditor_ckeditor5_utils_src_ckeditorerror__WEBPACK_IMPORTED_MODULE_5__["default"]( 'widget-toolbar-duplicated', this, { toolbarId } );
}
toolbarView.fillFromConfig( items, editor.ui.componentFactory );
this._toolbarDefinitions.set( toolbarId, {
view: toolbarView,
getRelatedElement,
balloonClassName
} );
}
/**
* Iterates over stored toolbars and makes them visible or hidden.
*
* @private
*/
_updateToolbarsVisibility() {
let maxRelatedElementDepth = 0;
let deepestRelatedElement = null;
let deepestToolbarDefinition = null;
for ( const definition of this._toolbarDefinitions.values() ) {
const relatedElement = definition.getRelatedElement( this.editor.editing.view.document.selection );
if ( !this.isEnabled || !relatedElement ) {
if ( this._isToolbarInBalloon( definition ) ) {
this._hideToolbar( definition );
}
} else if ( !this.editor.ui.focusTracker.isFocused ) {
if ( this._isToolbarVisible( definition ) ) {
this._hideToolbar( definition );
}
} else {
const relatedElementDepth = relatedElement.getAncestors().length;
// Many toolbars can express willingness to be displayed but they do not know about
// each other. Figure out which toolbar is deepest in the view tree to decide which
// should be displayed. For instance, if a selected image is inside a table cell, display
// the ImageToolbar rather than the TableToolbar (#60).
if ( relatedElementDepth > maxRelatedElementDepth ) {
maxRelatedElementDepth = relatedElementDepth;
deepestRelatedElement = relatedElement;
deepestToolbarDefinition = definition;
}
}
}
if ( deepestToolbarDefinition ) {
this._showToolbar( deepestToolbarDefinition, deepestRelatedElement );
}
}
/**
* Hides the given toolbar.
*
* @private
* @param {module:widget/widgettoolbarrepository~WidgetRepositoryToolbarDefinition} toolbarDefinition
*/
_hideToolbar( toolbarDefinition ) {
this._balloon.remove( toolbarDefinition.view );
this.stopListening( this._balloon, 'change:visibleView' );
}
/**
* Shows up the toolbar if the toolbar is not visible.
* Otherwise, repositions the toolbar's balloon when toolbar's view is the most top view in balloon stack.
*
* It might happen here that the toolbar's view is under another view. Then do nothing as the other toolbar view
* should be still visible after the {@link module:core/editor/editorui~EditorUI#event:update}.
*
* @private
* @param {module:widget/widgettoolbarrepository~WidgetRepositoryToolbarDefinition} toolbarDefinition
* @param {module:engine/view/element~Element} relatedElement
*/
_showToolbar( toolbarDefinition, relatedElement ) {
if ( this._isToolbarVisible( toolbarDefinition ) ) {
repositionContextualBalloon( this.editor, relatedElement );
} else if ( !this._isToolbarInBalloon( toolbarDefinition ) ) {
this._balloon.add( {
view: toolbarDefinition.view,
position: getBalloonPositionData( this.editor, relatedElement ),
balloonClassName: toolbarDefinition.balloonClassName
} );
// Update toolbar position each time stack with toolbar view is switched to visible.
// This is in a case target element has changed when toolbar was in invisible stack
// e.g. target image was wrapped by a block quote.
// See https://github.com/ckeditor/ckeditor5-widget/issues/92.
this.listenTo( this._balloon, 'change:visibleView', () => {
for ( const definition of this._toolbarDefinitions.values() ) {
if ( this._isToolbarVisible( definition ) ) {
const relatedElement = definition.getRelatedElement( this.editor.editing.view.document.selection );
repositionContextualBalloon( this.editor, relatedElement );
}
}
} );
}
}
/**
* @private
* @param {Object} toolbar
* @returns {Boolean}
*/
_isToolbarVisible( toolbar ) {
return this._balloon.visibleView === toolbar.view;
}
/**
* @private
* @param {Object} toolbar
* @returns {Boolean}
*/
_isToolbarInBalloon( toolbar ) {
return this._balloon.hasView( toolbar.view );
}
}
function repositionContextualBalloon( editor, relatedElement ) {
const balloon = editor.plugins.get( 'ContextualBalloon' );
const position = getBalloonPositionData( editor, relatedElement );
balloon.updatePosition( position );
}
function getBalloonPositionData( editor, relatedElement ) {
const editingView = editor.editing.view;
const defaultPositions = _ckeditor_ckeditor5_ui_src_panel_balloon_balloonpanelview__WEBPACK_IMPORTED_MODULE_3__["default"].defaultPositions;
return {
target: editingView.domConverter.mapViewToDom( relatedElement ),
positions: [
defaultPositions.northArrowSouth,
defaultPositions.northArrowSouthWest,
defaultPositions.northArrowSouthEast,
defaultPositions.southArrowNorth,
defaultPositions.southArrowNorthWest,
defaultPositions.southArrowNorthEast,
defaultPositions.viewportStickyNorth
]
};
}
function isWidgetSelected( selection ) {
const viewElement = selection.getSelectedElement();
return !!( viewElement && (0,_utils__WEBPACK_IMPORTED_MODULE_4__.isWidget)( viewElement ) );
}
/**
* The toolbar definition object used by the toolbar repository to manage toolbars.
* It contains information necessary to display the toolbar in the
* {@link module:ui/panel/balloon/contextualballoon~ContextualBalloon contextual balloon} and
* update it during its life (display) cycle.
*
* @typedef {Object} module:widget/widgettoolbarrepository~WidgetRepositoryToolbarDefinition
*
* @property {module:ui/view~View} view The UI view of the toolbar.
* @property {Function} getRelatedElement A function that returns an engine {@link module:engine/view/view~View}
* element the toolbar is to be attached to. For instance, an image widget or a table widget (or `null` when
* there is no such element). The function accepts an instance of {@link module:engine/view/selection~Selection}.
* @property {String} balloonClassName CSS class for the widget balloon when a toolbar is displayed.
*/
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-widget/src/widgettypearound/utils.js":
/*!*******************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-widget/src/widgettypearound/utils.js ***!
\*******************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "TYPE_AROUND_SELECTION_ATTRIBUTE": () => (/* binding */ TYPE_AROUND_SELECTION_ATTRIBUTE),
/* harmony export */ "getClosestTypeAroundDomButton": () => (/* binding */ getClosestTypeAroundDomButton),
/* harmony export */ "getClosestWidgetViewElement": () => (/* binding */ getClosestWidgetViewElement),
/* harmony export */ "getTypeAroundButtonPosition": () => (/* binding */ getTypeAroundButtonPosition),
/* harmony export */ "getTypeAroundFakeCaretPosition": () => (/* binding */ getTypeAroundFakeCaretPosition),
/* harmony export */ "isTypeAroundWidget": () => (/* binding */ isTypeAroundWidget)
/* harmony export */ });
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../utils */ "./node_modules/@ckeditor/ckeditor5-widget/src/utils.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module widget/widgettypearound/utils
*/
/**
* The name of the type around model selection attribute responsible for
* displaying a fake caret next to a selected widget.
*/
const TYPE_AROUND_SELECTION_ATTRIBUTE = 'widget-type-around';
/**
* Checks if an element is a widget that qualifies to get the widget type around UI.
*
* @param {module:engine/view/element~Element} viewElement
* @param {module:engine/model/element~Element} modelElement
* @param {module:engine/model/schema~Schema} schema
* @returns {Boolean}
*/
function isTypeAroundWidget( viewElement, modelElement, schema ) {
return viewElement && (0,_utils__WEBPACK_IMPORTED_MODULE_0__.isWidget)( viewElement ) && !schema.isInline( modelElement );
}
/**
* For the passed HTML element, this helper finds the closest widget type around button ancestor.
*
* @param {HTMLElement} domElement
* @returns {HTMLElement|null}
*/
function getClosestTypeAroundDomButton( domElement ) {
return domElement.closest( '.ck-widget__type-around__button' );
}
/**
* For the passed widget type around button element, this helper determines at which position
* the paragraph would be inserted into the content if, for instance, the button was
* clicked by the user.
*
* @param {HTMLElement} domElement
* @returns {'before'|'after'} The position of the button.
*/
function getTypeAroundButtonPosition( domElement ) {
return domElement.classList.contains( 'ck-widget__type-around__button_before' ) ? 'before' : 'after';
}
/**
* For the passed HTML element, this helper returns the closest view widget ancestor.
*
* @param {HTMLElement} domElement
* @param {module:engine/view/domconverter~DomConverter} domConverter
* @returns {module:engine/view/element~Element}
*/
function getClosestWidgetViewElement( domElement, domConverter ) {
const widgetDomElement = domElement.closest( '.ck-widget' );
return domConverter.mapDomToView( widgetDomElement );
}
/**
* For the passed selection instance, it returns the position of the fake caret displayed next to a widget.
*
* **Note**: If the fake caret is not currently displayed, `null` is returned.
*
* @param {module:engine/model/selection~Selection|module:engine/model/documentselection~DocumentSelection} selection
* @returns {'before'|'after'|null} The position of the fake caret or `null` when none is present.
*/
function getTypeAroundFakeCaretPosition( selection ) {
return selection.getAttribute( TYPE_AROUND_SELECTION_ATTRIBUTE );
}
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-widget/src/widgettypearound/widgettypearound.js":
/*!******************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-widget/src/widgettypearound/widgettypearound.js ***!
\******************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ WidgetTypeAround)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_core_src_plugin__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-core/src/plugin */ "./node_modules/@ckeditor/ckeditor5-core/src/plugin.js");
/* harmony import */ var _ckeditor_ckeditor5_ui_src_template__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-ui/src/template */ "./node_modules/@ckeditor/ckeditor5-ui/src/template.js");
/* harmony import */ var _ckeditor_ckeditor5_enter_src_enter__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @ckeditor/ckeditor5-enter/src/enter */ "./node_modules/@ckeditor/ckeditor5-enter/src/enter.js");
/* harmony import */ var _ckeditor_ckeditor5_typing_src_delete__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @ckeditor/ckeditor5-typing/src/delete */ "./node_modules/@ckeditor/ckeditor5-typing/src/delete.js");
/* harmony import */ var _ckeditor_ckeditor5_utils_src_keyboard__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils/src/keyboard */ "./node_modules/@ckeditor/ckeditor5-utils/src/keyboard.js");
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./utils */ "./node_modules/@ckeditor/ckeditor5-widget/src/widgettypearound/utils.js");
/* harmony import */ var _ckeditor_ckeditor5_typing_src_utils_injectunsafekeystrokeshandling__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! @ckeditor/ckeditor5-typing/src/utils/injectunsafekeystrokeshandling */ "./node_modules/@ckeditor/ckeditor5-typing/src/utils/injectunsafekeystrokeshandling.js");
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ../utils */ "./node_modules/@ckeditor/ckeditor5-widget/src/utils.js");
/* harmony import */ var _theme_icons_return_arrow_svg__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ../../theme/icons/return-arrow.svg */ "./node_modules/@ckeditor/ckeditor5-widget/theme/icons/return-arrow.svg");
/* harmony import */ var _theme_widgettypearound_css__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ../../theme/widgettypearound.css */ "./node_modules/@ckeditor/ckeditor5-widget/theme/widgettypearound.css");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/* global DOMParser */
/**
* @module widget/widgettypearound
*/
const POSSIBLE_INSERTION_POSITIONS = [ 'before', 'after' ];
// Do the SVG parsing once and then clone the result <svg> DOM element for each new button.
const RETURN_ARROW_ICON_ELEMENT = new DOMParser().parseFromString( _theme_icons_return_arrow_svg__WEBPACK_IMPORTED_MODULE_8__["default"], 'image/svg+xml' ).firstChild;
const PLUGIN_DISABLED_EDITING_ROOT_CLASS = 'ck-widget__type-around_disabled';
/**
* A plugin that allows users to type around widgets where normally it is impossible to place the caret due
* to limitations of web browsers. These "tight spots" occur, for instance, before (or after) a widget being
* the first (or last) child of its parent or between two block widgets.
*
* This plugin extends the {@link module:widget/widget~Widget `Widget`} plugin and injects the user interface
* with two buttons into each widget instance in the editor. Each of the buttons can be clicked by the
* user if the widget is next to the "tight spot". Once clicked, a paragraph is created with the selection anchored
* in it so that users can type (or insert content, paste, etc.) straight away.
*
* @extends module:core/plugin~Plugin
*/
class WidgetTypeAround extends _ckeditor_ckeditor5_core_src_plugin__WEBPACK_IMPORTED_MODULE_0__["default"] {
/**
* @inheritDoc
*/
static get pluginName() {
return 'WidgetTypeAround';
}
/**
* @inheritDoc
*/
static get requires() {
return [ _ckeditor_ckeditor5_enter_src_enter__WEBPACK_IMPORTED_MODULE_2__["default"], _ckeditor_ckeditor5_typing_src_delete__WEBPACK_IMPORTED_MODULE_3__["default"] ];
}
/**
* @inheritDoc
*/
constructor( editor ) {
super( editor );
/**
* A reference to the model widget element that has the fake caret active
* on either side of it. It is later used to remove CSS classes associated with the fake caret
* when the widget no longer needs it.
*
* @private
* @member {module:engine/model/element~Element|null}
*/
this._currentFakeCaretModelElement = null;
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const editingView = editor.editing.view;
// Set a CSS class on the view editing root when the plugin is disabled so all the buttons
// and lines visually disappear. All the interactions are disabled in individual plugin methods.
this.on( 'change:isEnabled', ( evt, data, isEnabled ) => {
editingView.change( writer => {
for ( const root of editingView.document.roots ) {
if ( isEnabled ) {
writer.removeClass( PLUGIN_DISABLED_EDITING_ROOT_CLASS, root );
} else {
writer.addClass( PLUGIN_DISABLED_EDITING_ROOT_CLASS, root );
}
}
} );
if ( !isEnabled ) {
editor.model.change( writer => {
writer.removeSelectionAttribute( _utils__WEBPACK_IMPORTED_MODULE_5__.TYPE_AROUND_SELECTION_ATTRIBUTE );
} );
}
} );
this._enableTypeAroundUIInjection();
this._enableInsertingParagraphsOnButtonClick();
this._enableInsertingParagraphsOnEnterKeypress();
this._enableInsertingParagraphsOnTypingKeystroke();
this._enableTypeAroundFakeCaretActivationUsingKeyboardArrows();
this._enableDeleteIntegration();
this._enableInsertContentIntegration();
this._enableDeleteContentIntegration();
}
/**
* @inheritDoc
*/
destroy() {
this._currentFakeCaretModelElement = null;
}
/**
* Inserts a new paragraph next to a widget element with the selection anchored in it.
*
* **Note**: This method is heavily user-oriented and will both focus the editing view and scroll
* the viewport to the selection in the inserted paragraph.
*
* @protected
* @param {module:engine/model/element~Element} widgetModelElement The model widget element next to which a paragraph is inserted.
* @param {'before'|'after'} position The position where the paragraph is inserted. Either `'before'` or `'after'` the widget.
*/
_insertParagraph( widgetModelElement, position ) {
const editor = this.editor;
const editingView = editor.editing.view;
editor.execute( 'insertParagraph', {
position: editor.model.createPositionAt( widgetModelElement, position )
} );
editingView.focus();
editingView.scrollToTheSelection();
}
/**
* A wrapper for the {@link module:utils/emittermixin~EmitterMixin#listenTo} method that executes the callbacks only
* when the plugin {@link #isEnabled is enabled}.
*
* @private
* @param {module:utils/emittermixin~Emitter} emitter The object that fires the event.
* @param {String} event The name of the event.
* @param {Function} callback The function to be called on event.
* @param {Object} [options={}] Additional options.
* @param {module:utils/priorities~PriorityString|Number} [options.priority='normal'] The priority of this event callback. The higher
* the priority value the sooner the callback will be fired. Events having the same priority are called in the
* order they were added.
*/
_listenToIfEnabled( emitter, event, callback, options ) {
this.listenTo( emitter, event, ( ...args ) => {
// Do not respond if the plugin is disabled.
if ( this.isEnabled ) {
callback( ...args );
}
}, options );
}
/**
* Similar to {@link #_insertParagraph}, this method inserts a paragraph except that it
* does not expect a position. Instead, it performs the insertion next to a selected widget
* according to the `widget-type-around` model selection attribute value (fake caret position).
*
* Because this method requires the `widget-type-around` attribute to be set,
* the insertion can only happen when the widget's fake caret is active (e.g. activated
* using the keyboard).
*
* @private
* @returns {Boolean} Returns `true` when the paragraph was inserted (the attribute was present) and `false` otherwise.
*/
_insertParagraphAccordingToFakeCaretPosition() {
const editor = this.editor;
const model = editor.model;
const modelSelection = model.document.selection;
const typeAroundFakeCaretPosition = (0,_utils__WEBPACK_IMPORTED_MODULE_5__.getTypeAroundFakeCaretPosition)( modelSelection );
if ( !typeAroundFakeCaretPosition ) {
return false;
}
const selectedModelElement = modelSelection.getSelectedElement();
this._insertParagraph( selectedModelElement, typeAroundFakeCaretPosition );
return true;
}
/**
* Creates a listener in the editing conversion pipeline that injects the widget type around
* UI into every single widget instance created in the editor.
*
* The UI is delivered as a {@link module:engine/view/uielement~UIElement}
* wrapper which renders DOM buttons that users can use to insert paragraphs.
*
* @private
*/
_enableTypeAroundUIInjection() {
const editor = this.editor;
const schema = editor.model.schema;
const t = editor.locale.t;
const buttonTitles = {
before: t( 'Insert paragraph before block' ),
after: t( 'Insert paragraph after block' )
};
editor.editing.downcastDispatcher.on( 'insert', ( evt, data, conversionApi ) => {
const viewElement = conversionApi.mapper.toViewElement( data.item );
// Filter out non-widgets and inline widgets.
if ( (0,_utils__WEBPACK_IMPORTED_MODULE_5__.isTypeAroundWidget)( viewElement, data.item, schema ) ) {
injectUIIntoWidget( conversionApi.writer, buttonTitles, viewElement );
}
}, { priority: 'low' } );
}
/**
* Brings support for the fake caret that appears when either:
*
* * the selection moves to a widget from a position next to it using arrow keys,
* * the arrow key is pressed when the widget is already selected.
*
* The fake caret lets the user know that they can start typing or just press
* <kbd>Enter</kbd> to insert a paragraph at the position next to a widget as suggested by the fake caret.
*
* The fake caret disappears when the user changes the selection or the editor
* gets blurred.
*
* The whole idea is as follows:
*
* 1. A user does one of the 2 scenarios described at the beginning.
* 2. The "keydown" listener is executed and the decision is made whether to show or hide the fake caret.
* 3. If it should show up, the `widget-type-around` model selection attribute is set indicating
* on which side of the widget it should appear.
* 4. The selection dispatcher reacts to the selection attribute and sets CSS classes responsible for the
* fake caret on the view widget.
* 5. If the fake caret should disappear, the selection attribute is removed and the dispatcher
* does the CSS class clean-up in the view.
* 6. Additionally, `change:range` and `FocusTracker#isFocused` listeners also remove the selection
* attribute (the former also removes widget CSS classes).
*
* @private
*/
_enableTypeAroundFakeCaretActivationUsingKeyboardArrows() {
const editor = this.editor;
const model = editor.model;
const modelSelection = model.document.selection;
const schema = model.schema;
const editingView = editor.editing.view;
// This is the main listener responsible for the fake caret.
// Note: The priority must precede the default Widget class keydown handler ("high").
this._listenToIfEnabled( editingView.document, 'arrowKey', ( evt, domEventData ) => {
this._handleArrowKeyPress( evt, domEventData );
}, { context: [ _utils__WEBPACK_IMPORTED_MODULE_7__.isWidget, '$text' ], priority: 'high' } );
// This listener makes sure the widget type around selection attribute will be gone from the model
// selection as soon as the model range changes. This attribute only makes sense when a widget is selected
// (and the "fake horizontal caret" is visible) so whenever the range changes (e.g. selection moved somewhere else),
// let's get rid of the attribute so that the selection downcast dispatcher isn't even bothered.
this._listenToIfEnabled( modelSelection, 'change:range', ( evt, data ) => {
// Do not reset the selection attribute when the change was indirect.
if ( !data.directChange ) {
return;
}
// Get rid of the widget type around attribute of the selection on every change:range.
// If the range changes, it means for sure, the user is no longer in the active ("fake horizontal caret") mode.
editor.model.change( writer => {
writer.removeSelectionAttribute( _utils__WEBPACK_IMPORTED_MODULE_5__.TYPE_AROUND_SELECTION_ATTRIBUTE );
} );
} );
// Get rid of the widget type around attribute of the selection on every document change
// that makes widget not selected any more (i.e. widget was removed).
this._listenToIfEnabled( model.document, 'change:data', () => {
const selectedModelElement = modelSelection.getSelectedElement();
if ( selectedModelElement ) {
const selectedViewElement = editor.editing.mapper.toViewElement( selectedModelElement );
if ( (0,_utils__WEBPACK_IMPORTED_MODULE_5__.isTypeAroundWidget)( selectedViewElement, selectedModelElement, schema ) ) {
return;
}
}
editor.model.change( writer => {
writer.removeSelectionAttribute( _utils__WEBPACK_IMPORTED_MODULE_5__.TYPE_AROUND_SELECTION_ATTRIBUTE );
} );
} );
// React to changes of the model selection attribute made by the arrow keys listener.
// If the block widget is selected and the attribute changes, downcast the attribute to special
// CSS classes associated with the active ("fake horizontal caret") mode of the widget.
this._listenToIfEnabled( editor.editing.downcastDispatcher, 'selection', ( evt, data, conversionApi ) => {
const writer = conversionApi.writer;
if ( this._currentFakeCaretModelElement ) {
const selectedViewElement = conversionApi.mapper.toViewElement( this._currentFakeCaretModelElement );
if ( selectedViewElement ) {
// Get rid of CSS classes associated with the active ("fake horizontal caret") mode from the view widget.
writer.removeClass( POSSIBLE_INSERTION_POSITIONS.map( positionToWidgetCssClass ), selectedViewElement );
this._currentFakeCaretModelElement = null;
}
}
const selectedModelElement = data.selection.getSelectedElement();
if ( !selectedModelElement ) {
return;
}
const selectedViewElement = conversionApi.mapper.toViewElement( selectedModelElement );
if ( !(0,_utils__WEBPACK_IMPORTED_MODULE_5__.isTypeAroundWidget)( selectedViewElement, selectedModelElement, schema ) ) {
return;
}
const typeAroundFakeCaretPosition = (0,_utils__WEBPACK_IMPORTED_MODULE_5__.getTypeAroundFakeCaretPosition)( data.selection );
if ( !typeAroundFakeCaretPosition ) {
return;
}
writer.addClass( positionToWidgetCssClass( typeAroundFakeCaretPosition ), selectedViewElement );
// Remember the view widget that got the "fake-caret" CSS class. This class should be removed ASAP when the
// selection changes
this._currentFakeCaretModelElement = selectedModelElement;
} );
this._listenToIfEnabled( editor.ui.focusTracker, 'change:isFocused', ( evt, name, isFocused ) => {
if ( !isFocused ) {
editor.model.change( writer => {
writer.removeSelectionAttribute( _utils__WEBPACK_IMPORTED_MODULE_5__.TYPE_AROUND_SELECTION_ATTRIBUTE );
} );
}
} );
function positionToWidgetCssClass( position ) {
return `ck-widget_type-around_show-fake-caret_${ position }`;
}
}
/**
* A listener executed on each "keydown" in the view document, a part of
* {@link #_enableTypeAroundFakeCaretActivationUsingKeyboardArrows}.
*
* It decides whether the arrow keypress should activate the fake caret or not (also whether it should
* be deactivated).
*
* The fake caret activation is done by setting the `widget-type-around` model selection attribute
* in this listener, and stopping and preventing the event that would normally be handled by the widget
* plugin that is responsible for the regular keyboard navigation near/across all widgets (that
* includes inline widgets, which are ignored by the widget type around plugin).
*
* @private
*/
_handleArrowKeyPress( evt, domEventData ) {
const editor = this.editor;
const model = editor.model;
const modelSelection = model.document.selection;
const schema = model.schema;
const editingView = editor.editing.view;
const keyCode = domEventData.keyCode;
const isForward = (0,_ckeditor_ckeditor5_utils_src_keyboard__WEBPACK_IMPORTED_MODULE_4__.isForwardArrowKeyCode)( keyCode, editor.locale.contentLanguageDirection );
const selectedViewElement = editingView.document.selection.getSelectedElement();
const selectedModelElement = editor.editing.mapper.toModelElement( selectedViewElement );
let shouldStopAndPreventDefault;
// Handle keyboard navigation when a type-around-compatible widget is currently selected.
if ( (0,_utils__WEBPACK_IMPORTED_MODULE_5__.isTypeAroundWidget)( selectedViewElement, selectedModelElement, schema ) ) {
shouldStopAndPreventDefault = this._handleArrowKeyPressOnSelectedWidget( isForward );
}
// Handle keyboard arrow navigation when the selection is next to a type-around-compatible widget
// and the widget is about to be selected.
else if ( modelSelection.isCollapsed ) {
shouldStopAndPreventDefault = this._handleArrowKeyPressWhenSelectionNextToAWidget( isForward );
}
// Handle collapsing a non-collapsed selection that is wider than on a single widget.
else if ( !domEventData.shiftKey ) {
shouldStopAndPreventDefault = this._handleArrowKeyPressWhenNonCollapsedSelection( isForward );
}
if ( shouldStopAndPreventDefault ) {
domEventData.preventDefault();
evt.stop();
}
}
/**
* Handles the keyboard navigation on "keydown" when a widget is currently selected and activates or deactivates
* the fake caret for that widget, depending on the current value of the `widget-type-around` model
* selection attribute and the direction of the pressed arrow key.
*
* @private
* @param {Boolean} isForward `true` when the pressed arrow key was responsible for the forward model selection movement
* as in {@link module:utils/keyboard~isForwardArrowKeyCode}.
* @returns {Boolean} Returns `true` when the keypress was handled and no other keydown listener of the editor should
* process the event any further. Returns `false` otherwise.
*/
_handleArrowKeyPressOnSelectedWidget( isForward ) {
const editor = this.editor;
const model = editor.model;
const modelSelection = model.document.selection;
const typeAroundFakeCaretPosition = (0,_utils__WEBPACK_IMPORTED_MODULE_5__.getTypeAroundFakeCaretPosition)( modelSelection );
return model.change( writer => {
// If the fake caret is displayed...
if ( typeAroundFakeCaretPosition ) {
const isLeavingWidget = typeAroundFakeCaretPosition === ( isForward ? 'after' : 'before' );
// If the keyboard arrow works against the value of the selection attribute...
// then remove the selection attribute but prevent default DOM actions
// and do not let the Widget plugin listener move the selection. This brings
// the widget back to the state, for instance, like if was selected using the mouse.
//
// **Note**: If leaving the widget when the fake caret is active, then the default
// Widget handler will change the selection and, in turn, this will automatically discard
// the selection attribute.
if ( !isLeavingWidget ) {
writer.removeSelectionAttribute( _utils__WEBPACK_IMPORTED_MODULE_5__.TYPE_AROUND_SELECTION_ATTRIBUTE );
return true;
}
}
// If the fake caret wasn't displayed, let's set it now according to the direction of the arrow
// key press. This also means we cannot let the Widget plugin listener move the selection.
else {
writer.setSelectionAttribute( _utils__WEBPACK_IMPORTED_MODULE_5__.TYPE_AROUND_SELECTION_ATTRIBUTE, isForward ? 'after' : 'before' );
return true;
}
return false;
} );
}
/**
* Handles the keyboard navigation on "keydown" when **no** widget is selected but the selection is **directly** next
* to one and upon the fake caret should become active for this widget upon arrow keypress
* (AKA entering/selecting the widget).
*
* **Note**: This code mirrors the implementation from the widget plugin but also adds the selection attribute.
* Unfortunately, there is no safe way to let the widget plugin do the selection part first and then just set the
* selection attribute here in the widget type around plugin. This is why this code must duplicate some from the widget plugin.
*
* @private
* @param {Boolean} isForward `true` when the pressed arrow key was responsible for the forward model selection movement
* as in {@link module:utils/keyboard~isForwardArrowKeyCode}.
* @returns {Boolean} Returns `true` when the keypress was handled and no other keydown listener of the editor should
* process the event any further. Returns `false` otherwise.
*/
_handleArrowKeyPressWhenSelectionNextToAWidget( isForward ) {
const editor = this.editor;
const model = editor.model;
const schema = model.schema;
const widgetPlugin = editor.plugins.get( 'Widget' );
// This is the widget the selection is about to be set on.
const modelElementNextToSelection = widgetPlugin._getObjectElementNextToSelection( isForward );
const viewElementNextToSelection = editor.editing.mapper.toViewElement( modelElementNextToSelection );
if ( (0,_utils__WEBPACK_IMPORTED_MODULE_5__.isTypeAroundWidget)( viewElementNextToSelection, modelElementNextToSelection, schema ) ) {
model.change( writer => {
widgetPlugin._setSelectionOverElement( modelElementNextToSelection );
writer.setSelectionAttribute( _utils__WEBPACK_IMPORTED_MODULE_5__.TYPE_AROUND_SELECTION_ATTRIBUTE, isForward ? 'before' : 'after' );
} );
// The change() block above does the same job as the Widget plugin. The event can
// be safely canceled.
return true;
}
return false;
}
/**
* Handles the keyboard navigation on "keydown" when a widget is currently selected (together with some other content)
* and the widget is the first or last element in the selection. It activates or deactivates the fake caret for that widget.
*
* @private
* @param {Boolean} isForward `true` when the pressed arrow key was responsible for the forward model selection movement
* as in {@link module:utils/keyboard~isForwardArrowKeyCode}.
* @returns {Boolean} Returns `true` when the keypress was handled and no other keydown listener of the editor should
* process the event any further. Returns `false` otherwise.
*/
_handleArrowKeyPressWhenNonCollapsedSelection( isForward ) {
const editor = this.editor;
const model = editor.model;
const schema = model.schema;
const mapper = editor.editing.mapper;
const modelSelection = model.document.selection;
const selectedModelNode = isForward ?
modelSelection.getLastPosition().nodeBefore :
modelSelection.getFirstPosition().nodeAfter;
const selectedViewNode = mapper.toViewElement( selectedModelNode );
// There is a widget at the collapse position so collapse the selection to the fake caret on it.
if ( (0,_utils__WEBPACK_IMPORTED_MODULE_5__.isTypeAroundWidget)( selectedViewNode, selectedModelNode, schema ) ) {
model.change( writer => {
writer.setSelection( selectedModelNode, 'on' );
writer.setSelectionAttribute( _utils__WEBPACK_IMPORTED_MODULE_5__.TYPE_AROUND_SELECTION_ATTRIBUTE, isForward ? 'after' : 'before' );
} );
return true;
}
return false;
}
/**
* Registers a `mousedown` listener for the view document which intercepts events
* coming from the widget type around UI, which happens when a user clicks one of the buttons
* that insert a paragraph next to a widget.
*
* @private
*/
_enableInsertingParagraphsOnButtonClick() {
const editor = this.editor;
const editingView = editor.editing.view;
this._listenToIfEnabled( editingView.document, 'mousedown', ( evt, domEventData ) => {
const button = (0,_utils__WEBPACK_IMPORTED_MODULE_5__.getClosestTypeAroundDomButton)( domEventData.domTarget );
if ( !button ) {
return;
}
const buttonPosition = (0,_utils__WEBPACK_IMPORTED_MODULE_5__.getTypeAroundButtonPosition)( button );
const widgetViewElement = (0,_utils__WEBPACK_IMPORTED_MODULE_5__.getClosestWidgetViewElement)( button, editingView.domConverter );
const widgetModelElement = editor.editing.mapper.toModelElement( widgetViewElement );
this._insertParagraph( widgetModelElement, buttonPosition );
domEventData.preventDefault();
evt.stop();
} );
}
/**
* Creates the <kbd>Enter</kbd> key listener on the view document that allows the user to insert a paragraph
* near the widget when either:
*
* * The fake caret was first activated using the arrow keys,
* * The entire widget is selected in the model.
*
* In the first case, the new paragraph is inserted according to the `widget-type-around` selection
* attribute (see {@link #_handleArrowKeyPress}).
*
* In the second case, the new paragraph is inserted based on whether a soft (<kbd>Shift</kbd>+<kbd>Enter</kbd>) keystroke
* was pressed or not.
*
* @private
*/
_enableInsertingParagraphsOnEnterKeypress() {
const editor = this.editor;
const selection = editor.model.document.selection;
const editingView = editor.editing.view;
this._listenToIfEnabled( editingView.document, 'enter', ( evt, domEventData ) => {
// This event could be triggered from inside the widget but we are interested
// only when the widget is selected itself.
if ( evt.eventPhase != 'atTarget' ) {
return;
}
const selectedModelElement = selection.getSelectedElement();
const selectedViewElement = editor.editing.mapper.toViewElement( selectedModelElement );
const schema = editor.model.schema;
let wasHandled;
// First check if the widget is selected and there's a type around selection attribute associated
// with the fake caret that would tell where to insert a new paragraph.
if ( this._insertParagraphAccordingToFakeCaretPosition() ) {
wasHandled = true;
}
// Then, if there is no selection attribute associated with the fake caret, check if the widget
// simply is selected and create a new paragraph according to the keystroke (Shift+)Enter.
else if ( (0,_utils__WEBPACK_IMPORTED_MODULE_5__.isTypeAroundWidget)( selectedViewElement, selectedModelElement, schema ) ) {
this._insertParagraph( selectedModelElement, domEventData.isSoft ? 'before' : 'after' );
wasHandled = true;
}
if ( wasHandled ) {
domEventData.preventDefault();
evt.stop();
}
}, { context: _utils__WEBPACK_IMPORTED_MODULE_7__.isWidget } );
}
/**
* Similar to the {@link #_enableInsertingParagraphsOnEnterKeypress}, it allows the user
* to insert a paragraph next to a widget when the fake caret was activated using arrow
* keys but it responds to typing keystrokes instead of <kbd>Enter</kbd>.
*
* "Typing keystrokes" are keystrokes that insert new content into the document,
* for instance, letters ("a") or numbers ("4"). The "keydown" listener enabled by this method
* will insert a new paragraph according to the `widget-type-around` model selection attribute
* as the user simply starts typing, which creates the impression that the fake caret
* behaves like a real one rendered by the browser (AKA your text appears where the caret was).
*
* **Note**: At the moment this listener creates 2 undo steps: one for the `insertParagraph` command
* and another one for actual typing. It is not a disaster but this may need to be fixed
* sooner or later.
*
* Learn more in {@link module:typing/utils/injectunsafekeystrokeshandling}.
*
* @private
*/
_enableInsertingParagraphsOnTypingKeystroke() {
const editor = this.editor;
const editingView = editor.editing.view;
const keyCodesHandledSomewhereElse = [
_ckeditor_ckeditor5_utils_src_keyboard__WEBPACK_IMPORTED_MODULE_4__.keyCodes.enter,
_ckeditor_ckeditor5_utils_src_keyboard__WEBPACK_IMPORTED_MODULE_4__.keyCodes["delete"],
_ckeditor_ckeditor5_utils_src_keyboard__WEBPACK_IMPORTED_MODULE_4__.keyCodes.backspace
];
// Note: The priority must precede the default observers.
this._listenToIfEnabled( editingView.document, 'keydown', ( evt, domEventData ) => {
// Don't handle enter/backspace/delete here. They are handled in dedicated listeners.
if ( !keyCodesHandledSomewhereElse.includes( domEventData.keyCode ) && !(0,_ckeditor_ckeditor5_typing_src_utils_injectunsafekeystrokeshandling__WEBPACK_IMPORTED_MODULE_6__.isNonTypingKeystroke)( domEventData ) ) {
this._insertParagraphAccordingToFakeCaretPosition();
}
}, { priority: 'high' } );
}
/**
* It creates a "delete" event listener on the view document to handle cases when the <kbd>Delete</kbd> or <kbd>Backspace</kbd>
* is pressed and the fake caret is currently active.
*
* The fake caret should create an illusion of a real browser caret so that when it appears before or after
* a widget, pressing <kbd>Delete</kbd> or <kbd>Backspace</kbd> should remove a widget or delete the content
* before or after a widget (depending on the content surrounding the widget).
*
* @private
*/
_enableDeleteIntegration() {
const editor = this.editor;
const editingView = editor.editing.view;
const model = editor.model;
const schema = model.schema;
this._listenToIfEnabled( editingView.document, 'delete', ( evt, domEventData ) => {
// This event could be triggered from inside the widget but we are interested
// only when the widget is selected itself.
if ( evt.eventPhase != 'atTarget' ) {
return;
}
const typeAroundFakeCaretPosition = (0,_utils__WEBPACK_IMPORTED_MODULE_5__.getTypeAroundFakeCaretPosition)( model.document.selection );
// This listener handles only these cases when the fake caret is active.
if ( !typeAroundFakeCaretPosition ) {
return;
}
const direction = domEventData.direction;
const selectedModelWidget = model.document.selection.getSelectedElement();
const isFakeCaretBefore = typeAroundFakeCaretPosition === 'before';
const isDeleteForward = direction == 'forward';
const shouldDeleteEntireWidget = isFakeCaretBefore === isDeleteForward;
if ( shouldDeleteEntireWidget ) {
editor.execute( 'delete', {
selection: model.createSelection( selectedModelWidget, 'on' )
} );
} else {
const range = schema.getNearestSelectionRange(
model.createPositionAt( selectedModelWidget, typeAroundFakeCaretPosition ),
direction
);
// If there is somewhere to move selection to, then there will be something to delete.
if ( range ) {
// If the range is NOT collapsed, then we know that the range contains an object (see getNearestSelectionRange() docs).
if ( !range.isCollapsed ) {
model.change( writer => {
writer.setSelection( range );
editor.execute( isDeleteForward ? 'deleteForward' : 'delete' );
} );
} else {
const probe = model.createSelection( range.start );
model.modifySelection( probe, { direction } );
// If the range is collapsed, let's see if a non-collapsed range exists that can could be deleted.
// If such range exists, use the editor command because it it safe for collaboration (it merges where it can).
if ( !probe.focus.isEqual( range.start ) ) {
model.change( writer => {
writer.setSelection( range );
editor.execute( isDeleteForward ? 'deleteForward' : 'delete' );
} );
}
// If there is no non-collapsed range to be deleted then we are sure that there is an empty element
// next to a widget that should be removed. "delete" and "deleteForward" commands cannot get rid of it
// so calling Model#deleteContent here manually.
else {
const deepestEmptyRangeAncestor = getDeepestEmptyElementAncestor( schema, range.start.parent );
model.deleteContent( model.createSelection( deepestEmptyRangeAncestor, 'on' ), {
doNotAutoparagraph: true
} );
}
}
}
}
// If some content was deleted, don't let the handler from the Widget plugin kick in.
// If nothing was deleted, then the default handler will have nothing to do anyway.
domEventData.preventDefault();
evt.stop();
}, { context: _utils__WEBPACK_IMPORTED_MODULE_7__.isWidget } );
}
/**
* Attaches the {@link module:engine/model/model~Model#event:insertContent} event listener that, for instance, allows the user to paste
* content near a widget when the fake caret is first activated using the arrow keys.
*
* The content is inserted according to the `widget-type-around` selection attribute (see {@link #_handleArrowKeyPress}).
*
* @private
*/
_enableInsertContentIntegration() {
const editor = this.editor;
const model = this.editor.model;
const documentSelection = model.document.selection;
this._listenToIfEnabled( editor.model, 'insertContent', ( evt, [ content, selectable ] ) => {
if ( selectable && !selectable.is( 'documentSelection' ) ) {
return;
}
const typeAroundFakeCaretPosition = (0,_utils__WEBPACK_IMPORTED_MODULE_5__.getTypeAroundFakeCaretPosition)( documentSelection );
if ( !typeAroundFakeCaretPosition ) {
return;
}
evt.stop();
return model.change( writer => {
const selectedElement = documentSelection.getSelectedElement();
const position = model.createPositionAt( selectedElement, typeAroundFakeCaretPosition );
const selection = writer.createSelection( position );
const result = model.insertContent( content, selection );
writer.setSelection( selection );
return result;
} );
}, { priority: 'high' } );
}
/**
* Attaches the {@link module:engine/model/model~Model#event:deleteContent} event listener to block the event when the fake
* caret is active.
*
* This is required for cases that trigger {@link module:engine/model/model~Model#deleteContent `model.deleteContent()`}
* before calling {@link module:engine/model/model~Model#insertContent `model.insertContent()`} like, for instance,
* plain text pasting.
*
* @private
*/
_enableDeleteContentIntegration() {
const editor = this.editor;
const model = this.editor.model;
const documentSelection = model.document.selection;
this._listenToIfEnabled( editor.model, 'deleteContent', ( evt, [ selection ] ) => {
if ( selection && !selection.is( 'documentSelection' ) ) {
return;
}
const typeAroundFakeCaretPosition = (0,_utils__WEBPACK_IMPORTED_MODULE_5__.getTypeAroundFakeCaretPosition)( documentSelection );
// Disable removing the selection content while pasting plain text.
if ( typeAroundFakeCaretPosition ) {
evt.stop();
}
}, { priority: 'high' } );
}
}
// Injects the type around UI into a view widget instance.
//
// @param {module:engine/view/downcastwriter~DowncastWriter} viewWriter
// @param {Object.<String,String>} buttonTitles
// @param {module:engine/view/element~Element} widgetViewElement
function injectUIIntoWidget( viewWriter, buttonTitles, widgetViewElement ) {
const typeAroundWrapper = viewWriter.createUIElement( 'div', {
class: 'ck ck-reset_all ck-widget__type-around'
}, function( domDocument ) {
const wrapperDomElement = this.toDomElement( domDocument );
injectButtons( wrapperDomElement, buttonTitles );
injectFakeCaret( wrapperDomElement );
return wrapperDomElement;
} );
// Inject the type around wrapper into the widget's wrapper.
viewWriter.insert( viewWriter.createPositionAt( widgetViewElement, 'end' ), typeAroundWrapper );
}
// FYI: Not using the IconView class because each instance would need to be destroyed to avoid memory leaks
// and it's pretty hard to figure out when a view (widget) is gone for good so it's cheaper to use raw
// <svg> here.
//
// @param {HTMLElement} wrapperDomElement
// @param {Object.<String,String>} buttonTitles
function injectButtons( wrapperDomElement, buttonTitles ) {
for ( const position of POSSIBLE_INSERTION_POSITIONS ) {
const buttonTemplate = new _ckeditor_ckeditor5_ui_src_template__WEBPACK_IMPORTED_MODULE_1__["default"]( {
tag: 'div',
attributes: {
class: [
'ck',
'ck-widget__type-around__button',
`ck-widget__type-around__button_${ position }`
],
title: buttonTitles[ position ]
},
children: [
wrapperDomElement.ownerDocument.importNode( RETURN_ARROW_ICON_ELEMENT, true )
]
} );
wrapperDomElement.appendChild( buttonTemplate.render() );
}
}
// @param {HTMLElement} wrapperDomElement
function injectFakeCaret( wrapperDomElement ) {
const caretTemplate = new _ckeditor_ckeditor5_ui_src_template__WEBPACK_IMPORTED_MODULE_1__["default"]( {
tag: 'div',
attributes: {
class: [
'ck',
'ck-widget__type-around__fake-caret'
]
}
} );
wrapperDomElement.appendChild( caretTemplate.render() );
}
// Returns the ancestor of an element closest to the root which is empty. For instance,
// for `<baz>`:
//
// <foo>abc<bar><baz></baz></bar></foo>
//
// it returns `<bar>`.
//
// @param {module:engine/model/schema~Schema} schema
// @param {module:engine/model/element~Element} element
// @returns {module:engine/model/element~Element|null}
function getDeepestEmptyElementAncestor( schema, element ) {
let deepestEmptyAncestor = element;
for ( const ancestor of element.getAncestors( { parentFirst: true } ) ) {
if ( ancestor.childCount > 1 || schema.isLimit( ancestor ) ) {
break;
}
deepestEmptyAncestor = ancestor;
}
return deepestEmptyAncestor;
}
/***/ }),
/***/ "./node_modules/ckeditor5/src/clipboard.js":
/*!*************************************************!*\
!*** ./node_modules/ckeditor5/src/clipboard.js ***!
\*************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "Clipboard": () => (/* reexport safe */ _ckeditor_ckeditor5_clipboard__WEBPACK_IMPORTED_MODULE_0__.Clipboard),
/* harmony export */ "ClipboardPipeline": () => (/* reexport safe */ _ckeditor_ckeditor5_clipboard__WEBPACK_IMPORTED_MODULE_0__.ClipboardPipeline),
/* harmony export */ "DragDrop": () => (/* reexport safe */ _ckeditor_ckeditor5_clipboard__WEBPACK_IMPORTED_MODULE_0__.DragDrop),
/* harmony export */ "PastePlainText": () => (/* reexport safe */ _ckeditor_ckeditor5_clipboard__WEBPACK_IMPORTED_MODULE_0__.PastePlainText)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_clipboard__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-clipboard */ "./node_modules/@ckeditor/ckeditor5-clipboard/src/index.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module ckeditor5/clipboard
*/
/***/ }),
/***/ "./node_modules/ckeditor5/src/core.js":
/*!********************************************!*\
!*** ./node_modules/ckeditor5/src/core.js ***!
\********************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "Command": () => (/* reexport safe */ _ckeditor_ckeditor5_core__WEBPACK_IMPORTED_MODULE_0__.Command),
/* harmony export */ "Context": () => (/* reexport safe */ _ckeditor_ckeditor5_core__WEBPACK_IMPORTED_MODULE_0__.Context),
/* harmony export */ "ContextPlugin": () => (/* reexport safe */ _ckeditor_ckeditor5_core__WEBPACK_IMPORTED_MODULE_0__.ContextPlugin),
/* harmony export */ "DataApiMixin": () => (/* reexport safe */ _ckeditor_ckeditor5_core__WEBPACK_IMPORTED_MODULE_0__.DataApiMixin),
/* harmony export */ "Editor": () => (/* reexport safe */ _ckeditor_ckeditor5_core__WEBPACK_IMPORTED_MODULE_0__.Editor),
/* harmony export */ "EditorUI": () => (/* reexport safe */ _ckeditor_ckeditor5_core__WEBPACK_IMPORTED_MODULE_0__.EditorUI),
/* harmony export */ "ElementApiMixin": () => (/* reexport safe */ _ckeditor_ckeditor5_core__WEBPACK_IMPORTED_MODULE_0__.ElementApiMixin),
/* harmony export */ "MultiCommand": () => (/* reexport safe */ _ckeditor_ckeditor5_core__WEBPACK_IMPORTED_MODULE_0__.MultiCommand),
/* harmony export */ "PendingActions": () => (/* reexport safe */ _ckeditor_ckeditor5_core__WEBPACK_IMPORTED_MODULE_0__.PendingActions),
/* harmony export */ "Plugin": () => (/* reexport safe */ _ckeditor_ckeditor5_core__WEBPACK_IMPORTED_MODULE_0__.Plugin),
/* harmony export */ "attachToForm": () => (/* reexport safe */ _ckeditor_ckeditor5_core__WEBPACK_IMPORTED_MODULE_0__.attachToForm),
/* harmony export */ "icons": () => (/* reexport safe */ _ckeditor_ckeditor5_core__WEBPACK_IMPORTED_MODULE_0__.icons),
/* harmony export */ "secureSourceElement": () => (/* reexport safe */ _ckeditor_ckeditor5_core__WEBPACK_IMPORTED_MODULE_0__.secureSourceElement)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-core */ "./node_modules/@ckeditor/ckeditor5-core/src/index.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module ckeditor5/core
*/
/***/ }),
/***/ "./node_modules/ckeditor5/src/engine.js":
/*!**********************************************!*\
!*** ./node_modules/ckeditor5/src/engine.js ***!
\**********************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "ClickObserver": () => (/* reexport safe */ _ckeditor_ckeditor5_engine__WEBPACK_IMPORTED_MODULE_0__.ClickObserver),
/* harmony export */ "Conversion": () => (/* reexport safe */ _ckeditor_ckeditor5_engine__WEBPACK_IMPORTED_MODULE_0__.Conversion),
/* harmony export */ "DataController": () => (/* reexport safe */ _ckeditor_ckeditor5_engine__WEBPACK_IMPORTED_MODULE_0__.DataController),
/* harmony export */ "DocumentSelection": () => (/* reexport safe */ _ckeditor_ckeditor5_engine__WEBPACK_IMPORTED_MODULE_0__.DocumentSelection),
/* harmony export */ "DomConverter": () => (/* reexport safe */ _ckeditor_ckeditor5_engine__WEBPACK_IMPORTED_MODULE_0__.DomConverter),
/* harmony export */ "DomEventData": () => (/* reexport safe */ _ckeditor_ckeditor5_engine__WEBPACK_IMPORTED_MODULE_0__.DomEventData),
/* harmony export */ "DomEventObserver": () => (/* reexport safe */ _ckeditor_ckeditor5_engine__WEBPACK_IMPORTED_MODULE_0__.DomEventObserver),
/* harmony export */ "DowncastWriter": () => (/* reexport safe */ _ckeditor_ckeditor5_engine__WEBPACK_IMPORTED_MODULE_0__.DowncastWriter),
/* harmony export */ "EditingController": () => (/* reexport safe */ _ckeditor_ckeditor5_engine__WEBPACK_IMPORTED_MODULE_0__.EditingController),
/* harmony export */ "Element": () => (/* reexport safe */ _ckeditor_ckeditor5_engine__WEBPACK_IMPORTED_MODULE_0__.Element),
/* harmony export */ "History": () => (/* reexport safe */ _ckeditor_ckeditor5_engine__WEBPACK_IMPORTED_MODULE_0__.History),
/* harmony export */ "HtmlDataProcessor": () => (/* reexport safe */ _ckeditor_ckeditor5_engine__WEBPACK_IMPORTED_MODULE_0__.HtmlDataProcessor),
/* harmony export */ "InsertOperation": () => (/* reexport safe */ _ckeditor_ckeditor5_engine__WEBPACK_IMPORTED_MODULE_0__.InsertOperation),
/* harmony export */ "LivePosition": () => (/* reexport safe */ _ckeditor_ckeditor5_engine__WEBPACK_IMPORTED_MODULE_0__.LivePosition),
/* harmony export */ "LiveRange": () => (/* reexport safe */ _ckeditor_ckeditor5_engine__WEBPACK_IMPORTED_MODULE_0__.LiveRange),
/* harmony export */ "MarkerOperation": () => (/* reexport safe */ _ckeditor_ckeditor5_engine__WEBPACK_IMPORTED_MODULE_0__.MarkerOperation),
/* harmony export */ "Matcher": () => (/* reexport safe */ _ckeditor_ckeditor5_engine__WEBPACK_IMPORTED_MODULE_0__.Matcher),
/* harmony export */ "Model": () => (/* reexport safe */ _ckeditor_ckeditor5_engine__WEBPACK_IMPORTED_MODULE_0__.Model),
/* harmony export */ "MouseObserver": () => (/* reexport safe */ _ckeditor_ckeditor5_engine__WEBPACK_IMPORTED_MODULE_0__.MouseObserver),
/* harmony export */ "Observer": () => (/* reexport safe */ _ckeditor_ckeditor5_engine__WEBPACK_IMPORTED_MODULE_0__.Observer),
/* harmony export */ "OperationFactory": () => (/* reexport safe */ _ckeditor_ckeditor5_engine__WEBPACK_IMPORTED_MODULE_0__.OperationFactory),
/* harmony export */ "Range": () => (/* reexport safe */ _ckeditor_ckeditor5_engine__WEBPACK_IMPORTED_MODULE_0__.Range),
/* harmony export */ "Renderer": () => (/* reexport safe */ _ckeditor_ckeditor5_engine__WEBPACK_IMPORTED_MODULE_0__.Renderer),
/* harmony export */ "StylesProcessor": () => (/* reexport safe */ _ckeditor_ckeditor5_engine__WEBPACK_IMPORTED_MODULE_0__.StylesProcessor),
/* harmony export */ "TreeWalker": () => (/* reexport safe */ _ckeditor_ckeditor5_engine__WEBPACK_IMPORTED_MODULE_0__.TreeWalker),
/* harmony export */ "UpcastWriter": () => (/* reexport safe */ _ckeditor_ckeditor5_engine__WEBPACK_IMPORTED_MODULE_0__.UpcastWriter),
/* harmony export */ "ViewDocument": () => (/* reexport safe */ _ckeditor_ckeditor5_engine__WEBPACK_IMPORTED_MODULE_0__.ViewDocument),
/* harmony export */ "addBackgroundRules": () => (/* reexport safe */ _ckeditor_ckeditor5_engine__WEBPACK_IMPORTED_MODULE_0__.addBackgroundRules),
/* harmony export */ "addBorderRules": () => (/* reexport safe */ _ckeditor_ckeditor5_engine__WEBPACK_IMPORTED_MODULE_0__.addBorderRules),
/* harmony export */ "addMarginRules": () => (/* reexport safe */ _ckeditor_ckeditor5_engine__WEBPACK_IMPORTED_MODULE_0__.addMarginRules),
/* harmony export */ "addPaddingRules": () => (/* reexport safe */ _ckeditor_ckeditor5_engine__WEBPACK_IMPORTED_MODULE_0__.addPaddingRules),
/* harmony export */ "disablePlaceholder": () => (/* reexport safe */ _ckeditor_ckeditor5_engine__WEBPACK_IMPORTED_MODULE_0__.disablePlaceholder),
/* harmony export */ "enablePlaceholder": () => (/* reexport safe */ _ckeditor_ckeditor5_engine__WEBPACK_IMPORTED_MODULE_0__.enablePlaceholder),
/* harmony export */ "getBoxSidesShorthandValue": () => (/* reexport safe */ _ckeditor_ckeditor5_engine__WEBPACK_IMPORTED_MODULE_0__.getBoxSidesShorthandValue),
/* harmony export */ "getBoxSidesValueReducer": () => (/* reexport safe */ _ckeditor_ckeditor5_engine__WEBPACK_IMPORTED_MODULE_0__.getBoxSidesValueReducer),
/* harmony export */ "getBoxSidesValues": () => (/* reexport safe */ _ckeditor_ckeditor5_engine__WEBPACK_IMPORTED_MODULE_0__.getBoxSidesValues),
/* harmony export */ "getFillerOffset": () => (/* reexport safe */ _ckeditor_ckeditor5_engine__WEBPACK_IMPORTED_MODULE_0__.getFillerOffset),
/* harmony export */ "getPositionShorthandNormalizer": () => (/* reexport safe */ _ckeditor_ckeditor5_engine__WEBPACK_IMPORTED_MODULE_0__.getPositionShorthandNormalizer),
/* harmony export */ "getShorthandValues": () => (/* reexport safe */ _ckeditor_ckeditor5_engine__WEBPACK_IMPORTED_MODULE_0__.getShorthandValues),
/* harmony export */ "hidePlaceholder": () => (/* reexport safe */ _ckeditor_ckeditor5_engine__WEBPACK_IMPORTED_MODULE_0__.hidePlaceholder),
/* harmony export */ "isAttachment": () => (/* reexport safe */ _ckeditor_ckeditor5_engine__WEBPACK_IMPORTED_MODULE_0__.isAttachment),
/* harmony export */ "isColor": () => (/* reexport safe */ _ckeditor_ckeditor5_engine__WEBPACK_IMPORTED_MODULE_0__.isColor),
/* harmony export */ "isLength": () => (/* reexport safe */ _ckeditor_ckeditor5_engine__WEBPACK_IMPORTED_MODULE_0__.isLength),
/* harmony export */ "isLineStyle": () => (/* reexport safe */ _ckeditor_ckeditor5_engine__WEBPACK_IMPORTED_MODULE_0__.isLineStyle),
/* harmony export */ "isPercentage": () => (/* reexport safe */ _ckeditor_ckeditor5_engine__WEBPACK_IMPORTED_MODULE_0__.isPercentage),
/* harmony export */ "isPosition": () => (/* reexport safe */ _ckeditor_ckeditor5_engine__WEBPACK_IMPORTED_MODULE_0__.isPosition),
/* harmony export */ "isRepeat": () => (/* reexport safe */ _ckeditor_ckeditor5_engine__WEBPACK_IMPORTED_MODULE_0__.isRepeat),
/* harmony export */ "isURL": () => (/* reexport safe */ _ckeditor_ckeditor5_engine__WEBPACK_IMPORTED_MODULE_0__.isURL),
/* harmony export */ "needsPlaceholder": () => (/* reexport safe */ _ckeditor_ckeditor5_engine__WEBPACK_IMPORTED_MODULE_0__.needsPlaceholder),
/* harmony export */ "showPlaceholder": () => (/* reexport safe */ _ckeditor_ckeditor5_engine__WEBPACK_IMPORTED_MODULE_0__.showPlaceholder),
/* harmony export */ "transformSets": () => (/* reexport safe */ _ckeditor_ckeditor5_engine__WEBPACK_IMPORTED_MODULE_0__.transformSets)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_engine__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-engine */ "./node_modules/@ckeditor/ckeditor5-engine/src/index.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module ckeditor5/engine
*/
/***/ }),
/***/ "./node_modules/ckeditor5/src/enter.js":
/*!*********************************************!*\
!*** ./node_modules/ckeditor5/src/enter.js ***!
\*********************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "Enter": () => (/* reexport safe */ _ckeditor_ckeditor5_enter__WEBPACK_IMPORTED_MODULE_0__.Enter),
/* harmony export */ "ShiftEnter": () => (/* reexport safe */ _ckeditor_ckeditor5_enter__WEBPACK_IMPORTED_MODULE_0__.ShiftEnter)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_enter__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-enter */ "./node_modules/@ckeditor/ckeditor5-enter/src/index.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module ckeditor5/enter
*/
/***/ }),
/***/ "./node_modules/ckeditor5/src/paragraph.js":
/*!*************************************************!*\
!*** ./node_modules/ckeditor5/src/paragraph.js ***!
\*************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "Paragraph": () => (/* reexport safe */ _ckeditor_ckeditor5_paragraph__WEBPACK_IMPORTED_MODULE_0__.Paragraph),
/* harmony export */ "ParagraphButtonUI": () => (/* reexport safe */ _ckeditor_ckeditor5_paragraph__WEBPACK_IMPORTED_MODULE_0__.ParagraphButtonUI)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_paragraph__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-paragraph */ "./node_modules/@ckeditor/ckeditor5-paragraph/src/index.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module ckeditor5/paragraph
*/
/***/ }),
/***/ "./node_modules/ckeditor5/src/select-all.js":
/*!**************************************************!*\
!*** ./node_modules/ckeditor5/src/select-all.js ***!
\**************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "SelectAll": () => (/* reexport safe */ _ckeditor_ckeditor5_select_all__WEBPACK_IMPORTED_MODULE_0__.SelectAll),
/* harmony export */ "SelectAllEditing": () => (/* reexport safe */ _ckeditor_ckeditor5_select_all__WEBPACK_IMPORTED_MODULE_0__.SelectAllEditing),
/* harmony export */ "SelectAllUI": () => (/* reexport safe */ _ckeditor_ckeditor5_select_all__WEBPACK_IMPORTED_MODULE_0__.SelectAllUI)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_select_all__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-select-all */ "./node_modules/@ckeditor/ckeditor5-select-all/src/index.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module ckeditor5/select-all
*/
/***/ }),
/***/ "./node_modules/ckeditor5/src/typing.js":
/*!**********************************************!*\
!*** ./node_modules/ckeditor5/src/typing.js ***!
\**********************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "Delete": () => (/* reexport safe */ _ckeditor_ckeditor5_typing__WEBPACK_IMPORTED_MODULE_0__.Delete),
/* harmony export */ "Input": () => (/* reexport safe */ _ckeditor_ckeditor5_typing__WEBPACK_IMPORTED_MODULE_0__.Input),
/* harmony export */ "TextTransformation": () => (/* reexport safe */ _ckeditor_ckeditor5_typing__WEBPACK_IMPORTED_MODULE_0__.TextTransformation),
/* harmony export */ "TextWatcher": () => (/* reexport safe */ _ckeditor_ckeditor5_typing__WEBPACK_IMPORTED_MODULE_0__.TextWatcher),
/* harmony export */ "TwoStepCaretMovement": () => (/* reexport safe */ _ckeditor_ckeditor5_typing__WEBPACK_IMPORTED_MODULE_0__.TwoStepCaretMovement),
/* harmony export */ "Typing": () => (/* reexport safe */ _ckeditor_ckeditor5_typing__WEBPACK_IMPORTED_MODULE_0__.Typing),
/* harmony export */ "findAttributeRange": () => (/* reexport safe */ _ckeditor_ckeditor5_typing__WEBPACK_IMPORTED_MODULE_0__.findAttributeRange),
/* harmony export */ "getLastTextLine": () => (/* reexport safe */ _ckeditor_ckeditor5_typing__WEBPACK_IMPORTED_MODULE_0__.getLastTextLine),
/* harmony export */ "inlineHighlight": () => (/* reexport safe */ _ckeditor_ckeditor5_typing__WEBPACK_IMPORTED_MODULE_0__.inlineHighlight),
/* harmony export */ "isNonTypingKeystroke": () => (/* reexport safe */ _ckeditor_ckeditor5_typing__WEBPACK_IMPORTED_MODULE_0__.isNonTypingKeystroke)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_typing__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-typing */ "./node_modules/@ckeditor/ckeditor5-typing/src/index.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module ckeditor5/typing
*/
/***/ }),
/***/ "./node_modules/ckeditor5/src/ui.js":
/*!******************************************!*\
!*** ./node_modules/ckeditor5/src/ui.js ***!
\******************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "BalloonPanelView": () => (/* reexport safe */ _ckeditor_ckeditor5_ui__WEBPACK_IMPORTED_MODULE_0__.BalloonPanelView),
/* harmony export */ "BalloonToolbar": () => (/* reexport safe */ _ckeditor_ckeditor5_ui__WEBPACK_IMPORTED_MODULE_0__.BalloonToolbar),
/* harmony export */ "BlockToolbar": () => (/* reexport safe */ _ckeditor_ckeditor5_ui__WEBPACK_IMPORTED_MODULE_0__.BlockToolbar),
/* harmony export */ "BodyCollection": () => (/* reexport safe */ _ckeditor_ckeditor5_ui__WEBPACK_IMPORTED_MODULE_0__.BodyCollection),
/* harmony export */ "BoxedEditorUIView": () => (/* reexport safe */ _ckeditor_ckeditor5_ui__WEBPACK_IMPORTED_MODULE_0__.BoxedEditorUIView),
/* harmony export */ "ButtonView": () => (/* reexport safe */ _ckeditor_ckeditor5_ui__WEBPACK_IMPORTED_MODULE_0__.ButtonView),
/* harmony export */ "ColorGridView": () => (/* reexport safe */ _ckeditor_ckeditor5_ui__WEBPACK_IMPORTED_MODULE_0__.ColorGridView),
/* harmony export */ "ColorTileView": () => (/* reexport safe */ _ckeditor_ckeditor5_ui__WEBPACK_IMPORTED_MODULE_0__.ColorTileView),
/* harmony export */ "ContextualBalloon": () => (/* reexport safe */ _ckeditor_ckeditor5_ui__WEBPACK_IMPORTED_MODULE_0__.ContextualBalloon),
/* harmony export */ "DropdownButtonView": () => (/* reexport safe */ _ckeditor_ckeditor5_ui__WEBPACK_IMPORTED_MODULE_0__.DropdownButtonView),
/* harmony export */ "EditorUIView": () => (/* reexport safe */ _ckeditor_ckeditor5_ui__WEBPACK_IMPORTED_MODULE_0__.EditorUIView),
/* harmony export */ "FocusCycler": () => (/* reexport safe */ _ckeditor_ckeditor5_ui__WEBPACK_IMPORTED_MODULE_0__.FocusCycler),
/* harmony export */ "FormHeaderView": () => (/* reexport safe */ _ckeditor_ckeditor5_ui__WEBPACK_IMPORTED_MODULE_0__.FormHeaderView),
/* harmony export */ "IconView": () => (/* reexport safe */ _ckeditor_ckeditor5_ui__WEBPACK_IMPORTED_MODULE_0__.IconView),
/* harmony export */ "IframeView": () => (/* reexport safe */ _ckeditor_ckeditor5_ui__WEBPACK_IMPORTED_MODULE_0__.IframeView),
/* harmony export */ "InlineEditableUIView": () => (/* reexport safe */ _ckeditor_ckeditor5_ui__WEBPACK_IMPORTED_MODULE_0__.InlineEditableUIView),
/* harmony export */ "InputNumberView": () => (/* reexport safe */ _ckeditor_ckeditor5_ui__WEBPACK_IMPORTED_MODULE_0__.InputNumberView),
/* harmony export */ "InputTextView": () => (/* reexport safe */ _ckeditor_ckeditor5_ui__WEBPACK_IMPORTED_MODULE_0__.InputTextView),
/* harmony export */ "InputView": () => (/* reexport safe */ _ckeditor_ckeditor5_ui__WEBPACK_IMPORTED_MODULE_0__.InputView),
/* harmony export */ "LabelView": () => (/* reexport safe */ _ckeditor_ckeditor5_ui__WEBPACK_IMPORTED_MODULE_0__.LabelView),
/* harmony export */ "LabeledFieldView": () => (/* reexport safe */ _ckeditor_ckeditor5_ui__WEBPACK_IMPORTED_MODULE_0__.LabeledFieldView),
/* harmony export */ "ListItemView": () => (/* reexport safe */ _ckeditor_ckeditor5_ui__WEBPACK_IMPORTED_MODULE_0__.ListItemView),
/* harmony export */ "ListView": () => (/* reexport safe */ _ckeditor_ckeditor5_ui__WEBPACK_IMPORTED_MODULE_0__.ListView),
/* harmony export */ "Model": () => (/* reexport safe */ _ckeditor_ckeditor5_ui__WEBPACK_IMPORTED_MODULE_0__.Model),
/* harmony export */ "Notification": () => (/* reexport safe */ _ckeditor_ckeditor5_ui__WEBPACK_IMPORTED_MODULE_0__.Notification),
/* harmony export */ "SplitButtonView": () => (/* reexport safe */ _ckeditor_ckeditor5_ui__WEBPACK_IMPORTED_MODULE_0__.SplitButtonView),
/* harmony export */ "StickyPanelView": () => (/* reexport safe */ _ckeditor_ckeditor5_ui__WEBPACK_IMPORTED_MODULE_0__.StickyPanelView),
/* harmony export */ "SwitchButtonView": () => (/* reexport safe */ _ckeditor_ckeditor5_ui__WEBPACK_IMPORTED_MODULE_0__.SwitchButtonView),
/* harmony export */ "Template": () => (/* reexport safe */ _ckeditor_ckeditor5_ui__WEBPACK_IMPORTED_MODULE_0__.Template),
/* harmony export */ "ToolbarSeparatorView": () => (/* reexport safe */ _ckeditor_ckeditor5_ui__WEBPACK_IMPORTED_MODULE_0__.ToolbarSeparatorView),
/* harmony export */ "ToolbarView": () => (/* reexport safe */ _ckeditor_ckeditor5_ui__WEBPACK_IMPORTED_MODULE_0__.ToolbarView),
/* harmony export */ "TooltipView": () => (/* reexport safe */ _ckeditor_ckeditor5_ui__WEBPACK_IMPORTED_MODULE_0__.TooltipView),
/* harmony export */ "View": () => (/* reexport safe */ _ckeditor_ckeditor5_ui__WEBPACK_IMPORTED_MODULE_0__.View),
/* harmony export */ "ViewCollection": () => (/* reexport safe */ _ckeditor_ckeditor5_ui__WEBPACK_IMPORTED_MODULE_0__.ViewCollection),
/* harmony export */ "addListToDropdown": () => (/* reexport safe */ _ckeditor_ckeditor5_ui__WEBPACK_IMPORTED_MODULE_0__.addListToDropdown),
/* harmony export */ "addToolbarToDropdown": () => (/* reexport safe */ _ckeditor_ckeditor5_ui__WEBPACK_IMPORTED_MODULE_0__.addToolbarToDropdown),
/* harmony export */ "clickOutsideHandler": () => (/* reexport safe */ _ckeditor_ckeditor5_ui__WEBPACK_IMPORTED_MODULE_0__.clickOutsideHandler),
/* harmony export */ "createDropdown": () => (/* reexport safe */ _ckeditor_ckeditor5_ui__WEBPACK_IMPORTED_MODULE_0__.createDropdown),
/* harmony export */ "createLabeledDropdown": () => (/* reexport safe */ _ckeditor_ckeditor5_ui__WEBPACK_IMPORTED_MODULE_0__.createLabeledDropdown),
/* harmony export */ "createLabeledInputNumber": () => (/* reexport safe */ _ckeditor_ckeditor5_ui__WEBPACK_IMPORTED_MODULE_0__.createLabeledInputNumber),
/* harmony export */ "createLabeledInputText": () => (/* reexport safe */ _ckeditor_ckeditor5_ui__WEBPACK_IMPORTED_MODULE_0__.createLabeledInputText),
/* harmony export */ "enableToolbarKeyboardFocus": () => (/* reexport safe */ _ckeditor_ckeditor5_ui__WEBPACK_IMPORTED_MODULE_0__.enableToolbarKeyboardFocus),
/* harmony export */ "getLocalizedColorOptions": () => (/* reexport safe */ _ckeditor_ckeditor5_ui__WEBPACK_IMPORTED_MODULE_0__.getLocalizedColorOptions),
/* harmony export */ "injectCssTransitionDisabler": () => (/* reexport safe */ _ckeditor_ckeditor5_ui__WEBPACK_IMPORTED_MODULE_0__.injectCssTransitionDisabler),
/* harmony export */ "normalizeColorOptions": () => (/* reexport safe */ _ckeditor_ckeditor5_ui__WEBPACK_IMPORTED_MODULE_0__.normalizeColorOptions),
/* harmony export */ "normalizeSingleColorDefinition": () => (/* reexport safe */ _ckeditor_ckeditor5_ui__WEBPACK_IMPORTED_MODULE_0__.normalizeSingleColorDefinition),
/* harmony export */ "normalizeToolbarConfig": () => (/* reexport safe */ _ckeditor_ckeditor5_ui__WEBPACK_IMPORTED_MODULE_0__.normalizeToolbarConfig),
/* harmony export */ "submitHandler": () => (/* reexport safe */ _ckeditor_ckeditor5_ui__WEBPACK_IMPORTED_MODULE_0__.submitHandler)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_ui__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-ui */ "./node_modules/@ckeditor/ckeditor5-ui/src/index.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module ckeditor5/ui
*/
/***/ }),
/***/ "./node_modules/ckeditor5/src/undo.js":
/*!********************************************!*\
!*** ./node_modules/ckeditor5/src/undo.js ***!
\********************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "Undo": () => (/* reexport safe */ _ckeditor_ckeditor5_undo__WEBPACK_IMPORTED_MODULE_0__.Undo),
/* harmony export */ "UndoEditing": () => (/* reexport safe */ _ckeditor_ckeditor5_undo__WEBPACK_IMPORTED_MODULE_0__.UndoEditing),
/* harmony export */ "UndoUi": () => (/* reexport safe */ _ckeditor_ckeditor5_undo__WEBPACK_IMPORTED_MODULE_0__.UndoUi)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_undo__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-undo */ "./node_modules/@ckeditor/ckeditor5-undo/src/index.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module ckeditor5/undo
*/
/***/ }),
/***/ "./node_modules/ckeditor5/src/upload.js":
/*!**********************************************!*\
!*** ./node_modules/ckeditor5/src/upload.js ***!
\**********************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "Base64UploadAdapter": () => (/* reexport safe */ _ckeditor_ckeditor5_upload__WEBPACK_IMPORTED_MODULE_0__.Base64UploadAdapter),
/* harmony export */ "FileDialogButtonView": () => (/* reexport safe */ _ckeditor_ckeditor5_upload__WEBPACK_IMPORTED_MODULE_0__.FileDialogButtonView),
/* harmony export */ "FileRepository": () => (/* reexport safe */ _ckeditor_ckeditor5_upload__WEBPACK_IMPORTED_MODULE_0__.FileRepository),
/* harmony export */ "SimpleUploadAdapter": () => (/* reexport safe */ _ckeditor_ckeditor5_upload__WEBPACK_IMPORTED_MODULE_0__.SimpleUploadAdapter)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_upload__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-upload */ "./node_modules/@ckeditor/ckeditor5-upload/src/index.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module ckeditor5/upload
*/
/***/ }),
/***/ "./node_modules/ckeditor5/src/utils.js":
/*!*********************************************!*\
!*** ./node_modules/ckeditor5/src/utils.js ***!
\*********************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "CKEditorError": () => (/* reexport safe */ _ckeditor_ckeditor5_utils__WEBPACK_IMPORTED_MODULE_0__.CKEditorError),
/* harmony export */ "Collection": () => (/* reexport safe */ _ckeditor_ckeditor5_utils__WEBPACK_IMPORTED_MODULE_0__.Collection),
/* harmony export */ "DomEmitterMixin": () => (/* reexport safe */ _ckeditor_ckeditor5_utils__WEBPACK_IMPORTED_MODULE_0__.DomEmitterMixin),
/* harmony export */ "ElementReplacer": () => (/* reexport safe */ _ckeditor_ckeditor5_utils__WEBPACK_IMPORTED_MODULE_0__.ElementReplacer),
/* harmony export */ "EmitterMixin": () => (/* reexport safe */ _ckeditor_ckeditor5_utils__WEBPACK_IMPORTED_MODULE_0__.EmitterMixin),
/* harmony export */ "FocusTracker": () => (/* reexport safe */ _ckeditor_ckeditor5_utils__WEBPACK_IMPORTED_MODULE_0__.FocusTracker),
/* harmony export */ "KeystrokeHandler": () => (/* reexport safe */ _ckeditor_ckeditor5_utils__WEBPACK_IMPORTED_MODULE_0__.KeystrokeHandler),
/* harmony export */ "Locale": () => (/* reexport safe */ _ckeditor_ckeditor5_utils__WEBPACK_IMPORTED_MODULE_0__.Locale),
/* harmony export */ "ObservableMixin": () => (/* reexport safe */ _ckeditor_ckeditor5_utils__WEBPACK_IMPORTED_MODULE_0__.ObservableMixin),
/* harmony export */ "Rect": () => (/* reexport safe */ _ckeditor_ckeditor5_utils__WEBPACK_IMPORTED_MODULE_0__.Rect),
/* harmony export */ "ResizeObserver": () => (/* reexport safe */ _ckeditor_ckeditor5_utils__WEBPACK_IMPORTED_MODULE_0__.ResizeObserver),
/* harmony export */ "createElement": () => (/* reexport safe */ _ckeditor_ckeditor5_utils__WEBPACK_IMPORTED_MODULE_0__.createElement),
/* harmony export */ "diff": () => (/* reexport safe */ _ckeditor_ckeditor5_utils__WEBPACK_IMPORTED_MODULE_0__.diff),
/* harmony export */ "env": () => (/* reexport safe */ _ckeditor_ckeditor5_utils__WEBPACK_IMPORTED_MODULE_0__.env),
/* harmony export */ "first": () => (/* reexport safe */ _ckeditor_ckeditor5_utils__WEBPACK_IMPORTED_MODULE_0__.first),
/* harmony export */ "getCode": () => (/* reexport safe */ _ckeditor_ckeditor5_utils__WEBPACK_IMPORTED_MODULE_0__.getCode),
/* harmony export */ "getDataFromElement": () => (/* reexport safe */ _ckeditor_ckeditor5_utils__WEBPACK_IMPORTED_MODULE_0__.getDataFromElement),
/* harmony export */ "getEnvKeystrokeText": () => (/* reexport safe */ _ckeditor_ckeditor5_utils__WEBPACK_IMPORTED_MODULE_0__.getEnvKeystrokeText),
/* harmony export */ "getLanguageDirection": () => (/* reexport safe */ _ckeditor_ckeditor5_utils__WEBPACK_IMPORTED_MODULE_0__.getLanguageDirection),
/* harmony export */ "getLocalizedArrowKeyCodeDirection": () => (/* reexport safe */ _ckeditor_ckeditor5_utils__WEBPACK_IMPORTED_MODULE_0__.getLocalizedArrowKeyCodeDirection),
/* harmony export */ "global": () => (/* reexport safe */ _ckeditor_ckeditor5_utils__WEBPACK_IMPORTED_MODULE_0__.global),
/* harmony export */ "isArrowKeyCode": () => (/* reexport safe */ _ckeditor_ckeditor5_utils__WEBPACK_IMPORTED_MODULE_0__.isArrowKeyCode),
/* harmony export */ "isForwardArrowKeyCode": () => (/* reexport safe */ _ckeditor_ckeditor5_utils__WEBPACK_IMPORTED_MODULE_0__.isForwardArrowKeyCode),
/* harmony export */ "isVisible": () => (/* reexport safe */ _ckeditor_ckeditor5_utils__WEBPACK_IMPORTED_MODULE_0__.isVisible),
/* harmony export */ "keyCodes": () => (/* reexport safe */ _ckeditor_ckeditor5_utils__WEBPACK_IMPORTED_MODULE_0__.keyCodes),
/* harmony export */ "logError": () => (/* reexport safe */ _ckeditor_ckeditor5_utils__WEBPACK_IMPORTED_MODULE_0__.logError),
/* harmony export */ "logWarning": () => (/* reexport safe */ _ckeditor_ckeditor5_utils__WEBPACK_IMPORTED_MODULE_0__.logWarning),
/* harmony export */ "mix": () => (/* reexport safe */ _ckeditor_ckeditor5_utils__WEBPACK_IMPORTED_MODULE_0__.mix),
/* harmony export */ "parseKeystroke": () => (/* reexport safe */ _ckeditor_ckeditor5_utils__WEBPACK_IMPORTED_MODULE_0__.parseKeystroke),
/* harmony export */ "priorities": () => (/* reexport safe */ _ckeditor_ckeditor5_utils__WEBPACK_IMPORTED_MODULE_0__.priorities),
/* harmony export */ "setDataInElement": () => (/* reexport safe */ _ckeditor_ckeditor5_utils__WEBPACK_IMPORTED_MODULE_0__.setDataInElement),
/* harmony export */ "toArray": () => (/* reexport safe */ _ckeditor_ckeditor5_utils__WEBPACK_IMPORTED_MODULE_0__.toArray),
/* harmony export */ "toMap": () => (/* reexport safe */ _ckeditor_ckeditor5_utils__WEBPACK_IMPORTED_MODULE_0__.toMap),
/* harmony export */ "toUnit": () => (/* reexport safe */ _ckeditor_ckeditor5_utils__WEBPACK_IMPORTED_MODULE_0__.toUnit),
/* harmony export */ "uid": () => (/* reexport safe */ _ckeditor_ckeditor5_utils__WEBPACK_IMPORTED_MODULE_0__.uid),
/* harmony export */ "version": () => (/* reexport safe */ _ckeditor_ckeditor5_utils__WEBPACK_IMPORTED_MODULE_0__.version)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_utils__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-utils */ "./node_modules/@ckeditor/ckeditor5-utils/src/index.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module ckeditor5/utils
*/
/***/ }),
/***/ "./node_modules/ckeditor5/src/widget.js":
/*!**********************************************!*\
!*** ./node_modules/ckeditor5/src/widget.js ***!
\**********************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "WIDGET_CLASS_NAME": () => (/* reexport safe */ _ckeditor_ckeditor5_widget__WEBPACK_IMPORTED_MODULE_0__.WIDGET_CLASS_NAME),
/* harmony export */ "WIDGET_SELECTED_CLASS_NAME": () => (/* reexport safe */ _ckeditor_ckeditor5_widget__WEBPACK_IMPORTED_MODULE_0__.WIDGET_SELECTED_CLASS_NAME),
/* harmony export */ "Widget": () => (/* reexport safe */ _ckeditor_ckeditor5_widget__WEBPACK_IMPORTED_MODULE_0__.Widget),
/* harmony export */ "WidgetResize": () => (/* reexport safe */ _ckeditor_ckeditor5_widget__WEBPACK_IMPORTED_MODULE_0__.WidgetResize),
/* harmony export */ "WidgetToolbarRepository": () => (/* reexport safe */ _ckeditor_ckeditor5_widget__WEBPACK_IMPORTED_MODULE_0__.WidgetToolbarRepository),
/* harmony export */ "WidgetTypeAround": () => (/* reexport safe */ _ckeditor_ckeditor5_widget__WEBPACK_IMPORTED_MODULE_0__.WidgetTypeAround),
/* harmony export */ "findOptimalInsertionRange": () => (/* reexport safe */ _ckeditor_ckeditor5_widget__WEBPACK_IMPORTED_MODULE_0__.findOptimalInsertionRange),
/* harmony export */ "getLabel": () => (/* reexport safe */ _ckeditor_ckeditor5_widget__WEBPACK_IMPORTED_MODULE_0__.getLabel),
/* harmony export */ "isWidget": () => (/* reexport safe */ _ckeditor_ckeditor5_widget__WEBPACK_IMPORTED_MODULE_0__.isWidget),
/* harmony export */ "setHighlightHandling": () => (/* reexport safe */ _ckeditor_ckeditor5_widget__WEBPACK_IMPORTED_MODULE_0__.setHighlightHandling),
/* harmony export */ "setLabel": () => (/* reexport safe */ _ckeditor_ckeditor5_widget__WEBPACK_IMPORTED_MODULE_0__.setLabel),
/* harmony export */ "toWidget": () => (/* reexport safe */ _ckeditor_ckeditor5_widget__WEBPACK_IMPORTED_MODULE_0__.toWidget),
/* harmony export */ "toWidgetEditable": () => (/* reexport safe */ _ckeditor_ckeditor5_widget__WEBPACK_IMPORTED_MODULE_0__.toWidgetEditable),
/* harmony export */ "viewToModelPositionOutsideModelElement": () => (/* reexport safe */ _ckeditor_ckeditor5_widget__WEBPACK_IMPORTED_MODULE_0__.viewToModelPositionOutsideModelElement)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_widget__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-widget */ "./node_modules/@ckeditor/ckeditor5-widget/src/index.js");
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module ckeditor5/widget
*/
/***/ }),
/***/ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].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[1].use[2]!./node_modules/@ckeditor/ckeditor5-basic-styles/theme/code.css ***!
\**********************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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);padding:.15em;border-radius:2px}.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,CACvC,aAAc,CACd,iBACD,CAEA,0CACC,kCACD","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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[1].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[1].use[2]!./node_modules/@ckeditor/ckeditor5-block-quote/theme/blockquote.css ***!
\***************************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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{overflow:hidden;padding-right:1.5em;padding-left:1.5em;margin-left:0;margin-right:0;font-style:italic;border-left:5px solid #ccc}.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,uBAEC,eAAgB,CAGhB,mBAAoB,CACpB,kBAAmB,CAEnB,aAAc,CACd,cAAe,CACf,iBAAkB,CAClB,0BACD,CAEA,gCACC,aAAc,CACd,2BACD","sourcesContent":["/**\n * @license Copyright (c) 2003-2022, 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[1].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[1].use[2]!./node_modules/@ckeditor/ckeditor5-clipboard/theme/clipboard.css ***!
\************************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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;position:relative;pointer-events:none}.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}: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{bottom:calc(var(--ck-clipboard-drop-target-dot-height)*-0.5);top:calc(var(--ck-clipboard-drop-target-dot-height)*-0.5);border:1px solid var(--ck-clipboard-drop-target-color);background:var(--ck-clipboard-drop-target-color);margin-left:-1px}.ck.ck-editor__editable .ck.ck-clipboard-drop-target-position span:after{content:\"\";width:0;height:0;display:block;position:absolute;left:50%;top:calc(var(--ck-clipboard-drop-target-dot-height)*-0.5);transform:translateX(-50%);border-left:calc(var(--ck-clipboard-drop-target-dot-width)*0.5) solid transparent;border-bottom:0 solid transparent;border-right:calc(var(--ck-clipboard-drop-target-dot-width)*0.5) solid transparent;border-top:calc(var(--ck-clipboard-drop-target-dot-height)) solid var(--ck-clipboard-drop-target-color)}.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}", "",{"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,CACf,iBAAkB,CAClB,mBAMD,CAJC,mEACC,iBAAkB,CAClB,OACD,CAWA,qJACC,YACD,CCzBF,MACC,yCAA0C,CAC1C,yCAA0C,CAC1C,6DACD,CAOE,mEACC,4DAA8D,CAC9D,yDAA2D,CAC3D,sDAAuD,CACvD,gDAAiD,CACjD,gBAkBD,CAfC,yEACC,UAAW,CACX,OAAQ,CACR,QAAS,CAET,aAAc,CACd,iBAAkB,CAClB,QAAS,CACT,yDAA2D,CAE3D,0BAA2B,CAG3B,iFAAmB,CAAnB,iCAAmB,CAAnB,kFAAmB,CAAnB,uGACD,CA2DF,kEACC,gGACD,CAKA,gDACC,OAAS,CACT,sBACD","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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 * Copyright (c) 2003-2022, 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-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(var(--ck-clipboard-drop-target-dot-height) * -.5);\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// Horizontal drop target (between blocks).\n\t& .ck.ck-clipboard-drop-target-position {\n\t\tdisplay: block;\n\t\tposition: relative;\n\t\twidth: 100%;\n\t\theight: 0;\n\t\tmargin: 0;\n\t\ttext-align: initial;\n\n\t\t& .ck-clipboard-drop-target__line {\n\t\t\tposition: absolute;\n\t\t\twidth: 100%;\n\t\t\theight: 0;\n\t\t\tborder: 1px solid var(--ck-clipboard-drop-target-color);\n\t\t\tmargin-top: -1px;\n\n\t\t\t&::before {\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: calc(-1 * var(--ck-clipboard-drop-target-dot-size));\n\t\t\t\ttop: 0;\n\n\t\t\t\ttransform: translateY(-50%);\n\t\t\t\tborder-color: transparent transparent transparent var(--ck-clipboard-drop-target-color);\n\t\t\t\tborder-width: var(--ck-clipboard-drop-target-dot-size) 0 var(--ck-clipboard-drop-target-dot-size) calc(2 * var(--ck-clipboard-drop-target-dot-size));\n\t\t\t\tborder-style: solid;\n\t\t\t}\n\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\tright: calc(-1 * var(--ck-clipboard-drop-target-dot-size));\n\t\t\t\ttop: 0;\n\n\t\t\t\ttransform: translateY(-50%);\n\t\t\t\tborder-color: transparent var(--ck-clipboard-drop-target-color) transparent transparent;\n\t\t\t\tborder-width: var(--ck-clipboard-drop-target-dot-size) calc(2 * var(--ck-clipboard-drop-target-dot-size)) var(--ck-clipboard-drop-target-dot-size) 0;\n\t\t\t\tborder-style: solid;\n\t\t\t}\n\t\t}\n\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"],"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[1].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[1].use[2]!./node_modules/@ckeditor/ckeditor5-code-block/theme/codeblock.css ***!
\*************************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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{padding:1em;color:#353535;background:hsla(0,0%,78%,.3);border:1px solid #c4c4c4;border-radius:2px;text-align:left;direction:ltr;tab-size:4;white-space:pre-wrap;font-style:normal;min-width:200px}.ck-content pre code{background:unset;padding:0;border-radius: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{top:-1px;right:10px;background:var(--ck-color-code-block-label-background);font-size:10px;font-family:var(--ck-font-face);line-height:16px;padding:var(--ck-spacing-tiny) var(--ck-spacing-medium);color:#fff;white-space:nowrap}.ck.ck-code-block-dropdown .ck-dropdown__panel{max-height:250px;overflow-y:auto;overflow-x:hidden}", "",{"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,gBACC,WAAY,CACZ,aAAwB,CACxB,4BAAiC,CACjC,wBAAiC,CACjC,iBAAkB,CAGlB,eAAgB,CAChB,aAAc,CAEd,UAAW,CACX,oBAAqB,CAGrB,iBAAkB,CAGlB,eAOD,CALC,qBACC,gBAAiB,CACjB,SAAU,CACV,eACD,CAGD,4BACC,iBAMD,CAJC,iDACC,2BAA4B,CAC5B,iBACD,CCjCD,MACC,8CACD,CAEA,iDACC,QAAS,CACT,UAAW,CACX,sDAAuD,CAEvD,cAAe,CACf,+BAAgC,CAChC,gBAAiB,CACjB,uDAAwD,CACxD,UAAuB,CACvB,kBACD,CAEA,+CAEC,gBAAiB,CACjB,eAAgB,CAChB,iBACD","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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-2022, 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[1].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[1].use[2]!./node_modules/@ckeditor/ckeditor5-editor-classic/theme/classiceditor.css ***!
\*********************************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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-modal)}.ck.ck-editor__top .ck-sticky-panel .ck-toolbar{border-radius:0}.ck-rounded-corners .ck.ck-editor__top .ck-sticky-panel .ck-toolbar,.ck.ck-editor__top .ck-sticky-panel .ck-toolbar.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-toolbar{border-bottom-width:0}.ck.ck-editor__top .ck-sticky-panel .ck-sticky-panel__content_sticky .ck-toolbar{border-bottom-width:1px;border-radius:0}.ck-rounded-corners .ck.ck-editor__top .ck-sticky-panel .ck-sticky-panel__content_sticky .ck-toolbar,.ck.ck-editor__top .ck-sticky-panel .ck-sticky-panel__content_sticky .ck-toolbar.ck-rounded-corners{border-radius:var(--ck-border-radius);border-radius: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,gDCED,eDKC,CAPA,uICMA,qCAAsC,CDJpC,2BAA4B,CAC5B,4BAIF,CAPA,gDAMC,qBACD,CAEA,iFACC,uBAAwB,CCR1B,eDaC,CANA,yMCHA,qCAAsC,CDOpC,eAEF,CAKF,yCAEC,0CAA2C,CCpB3C,eD8BD,CAZA,yHCdE,qCAAsC,CDmBtC,wBAAyB,CACzB,yBAMF,CAHC,0DACC,wCACD","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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 <body>, etc.,\n\t it must land outside of the `.ck-editor` in DOM. */\n\tposition: relative;\n\n\t& .ck-editor__top .ck-sticky-panel .ck-toolbar {\n\t\t/* https://github.com/ckeditor/ckeditor5-editor-classic/issues/62 */\n\t\tz-index: var(--ck-z-modal);\n\t}\n}\n","/*\n * Copyright (c) 2003-2022, 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 \"../mixins/_rounded.css\";\n\n.ck.ck-editor__top {\n\t& .ck-sticky-panel {\n\t\t& .ck-toolbar {\n\t\t\t@mixin ck-rounded-corners {\n\t\t\t\tborder-bottom-left-radius: 0;\n\t\t\t\tborder-bottom-right-radius: 0;\n\t\t\t}\n\n\t\t\tborder-bottom-width: 0;\n\t\t}\n\n\t\t& .ck-sticky-panel__content_sticky .ck-toolbar {\n\t\t\tborder-bottom-width: 1px;\n\n\t\t\t@mixin ck-rounded-corners {\n\t\t\t\tborder-radius: 0;\n\t\t\t}\n\t\t}\n\t}\n}\n\n/* Note: Use ck-editor__main to make sure these styles don't apply to other editor types */\n.ck.ck-editor__main > .ck-editor__editable {\n\t/* https://github.com/ckeditor/ckeditor5-theme-lark/issues/113 */\n\tbackground: var(--ck-color-base-background);\n\n\t@mixin ck-rounded-corners {\n\t\tborder-top-left-radius: 0;\n\t\tborder-top-right-radius: 0;\n\t}\n\n\t&:not(.ck-focused) {\n\t\tborder-color: var(--ck-color-base-border);\n\t}\n}\n","/*\n * Copyright (c) 2003-2022, 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/**\n * Implements rounded corner interface for .ck-rounded-corners class.\n *\n * @see $ck-border-radius\n */\n@define-mixin ck-rounded-corners {\n\tborder-radius: 0;\n\n\t@nest .ck-rounded-corners &,\n\t&.ck-rounded-corners {\n\t\tborder-radius: var(--ck-border-radius);\n\t\t@mixin-content;\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-engine/theme/placeholder.css":
/*!***********************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-engine/theme/placeholder.css ***!
\***********************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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-placeholder,.ck .ck-placeholder{position:relative}.ck.ck-placeholder:before,.ck .ck-placeholder:before{position:absolute;left:0;right:0;content:attr(data-placeholder);pointer-events:none}.ck.ck-read-only .ck-placeholder:before{display:none}.ck.ck-reset_all .ck-placeholder{position:relative}.ck.ck-placeholder:before,.ck .ck-placeholder:before{cursor:text;color:var(--ck-color-engine-placeholder-text)}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-engine/theme/placeholder.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/ckeditor5-engine/placeholder.css"],"names":[],"mappings":"AAMA,uCAEC,iBAWD,CATC,qDACC,iBAAkB,CAClB,MAAO,CACP,OAAQ,CACR,8BAA+B,CAG/B,mBACD,CAKA,wCACC,YACD,CAQD,iCACC,iBACD,CC5BC,qDACC,WAAY,CACZ,6CACD","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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/* See ckeditor/ckeditor5#936. */\n.ck.ck-placeholder,\n.ck .ck-placeholder {\n\tposition: relative;\n\n\t&::before {\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\tright: 0;\n\t\tcontent: attr(data-placeholder);\n\n\t\t/* See ckeditor/ckeditor5#469. */\n\t\tpointer-events: none;\n\t}\n}\n\n/* See ckeditor/ckeditor5#1987. */\n.ck.ck-read-only .ck-placeholder {\n\t&::before {\n\t\tdisplay: none;\n\t}\n}\n\n/*\n * Rules for the `ck-placeholder` are loaded before the rules for `ck-reset_all` in the base CKEditor 5 DLL build.\n * This fix overwrites the incorrectly set `position: static` from `ck-reset_all`.\n * See https://github.com/ckeditor/ckeditor5/issues/11418.\n */\n.ck.ck-reset_all .ck-placeholder {\n\tposition: relative;\n}\n","/*\n * Copyright (c) 2003-2022, 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/* See ckeditor/ckeditor5#936. */\n.ck.ck-placeholder, .ck .ck-placeholder {\n\t&::before {\n\t\tcursor: text;\n\t\tcolor: var(--ck-color-engine-placeholder-text);\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-engine/theme/renderer.css":
/*!********************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-engine/theme/renderer.css ***!
\********************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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 span[data-ck-unsafe-element]{display:none}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-engine/theme/renderer.css"],"names":[],"mappings":"AAMA,qDACC,YACD","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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/* Elements marked by the Renderer as hidden should be invisible in the editor. */\n.ck.ck-editor__editable span[data-ck-unsafe-element] {\n\tdisplay: none;\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-find-and-replace/theme/findandreplace.css":
/*!************************************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-find-and-replace/theme/findandreplace.css ***!
\************************************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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-find-result{background:#ff0;color:var(--ck-color-text)}.ck-find-result_selected{background:#ff9633}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-find-and-replace/theme/findandreplace.css"],"names":[],"mappings":"AAKA,gBACC,eAA8B,CAC9B,0BACD,CAEA,yBACC,kBACD","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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-find-result {\n\tbackground: hsl(60, 100%, 50%);\n\tcolor: var(--ck-color-text);\n}\n\n.ck-find-result_selected {\n\tbackground: hsl(29, 100%, 60%);\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-find-and-replace/theme/findandreplaceform.css":
/*!****************************************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-find-and-replace/theme/findandreplaceform.css ***!
\****************************************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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-find-and-replace-form{max-width:100%}.ck.ck-find-and-replace-form fieldset{display:flex}.ck.ck-find-and-replace-form fieldset.ck-find-and-replace-form__find .ck-results-counter{position:absolute}.ck.ck-find-and-replace-form{width:400px}.ck.ck-find-and-replace-form:focus{outline:none}.ck.ck-find-and-replace-form fieldset{flex-direction:row;flex-wrap:nowrap;align-items:center;align-content:stretch;padding:var(--ck-spacing-large);border:0;margin:0}.ck.ck-find-and-replace-form fieldset>.ck-button{flex:0 0 auto}[dir=ltr] .ck.ck-find-and-replace-form fieldset>*+*{margin-left:var(--ck-spacing-standard)}[dir=rtl] .ck.ck-find-and-replace-form fieldset>*+*{margin-right:var(--ck-spacing-standard)}.ck.ck-find-and-replace-form fieldset .ck-labeled-field-view{flex:1 1 auto}.ck.ck-find-and-replace-form fieldset .ck-labeled-field-view .ck-input{width:100%;min-width:50px}.ck.ck-find-and-replace-form fieldset.ck-find-and-replace-form__find{align-items:flex-start}.ck.ck-find-and-replace-form fieldset.ck-find-and-replace-form__find>.ck-button-find{font-weight:700}.ck.ck-find-and-replace-form fieldset.ck-find-and-replace-form__find>.ck-button-find .ck-button__label{padding-left:var(--ck-spacing-large);padding-right:var(--ck-spacing-large)}.ck.ck-find-and-replace-form fieldset.ck-find-and-replace-form__find>.ck-button-prev>.ck-icon{transform:rotate(90deg)}.ck.ck-find-and-replace-form fieldset.ck-find-and-replace-form__find>.ck-button-next>.ck-icon{transform:rotate(-90deg)}.ck.ck-find-and-replace-form fieldset.ck-find-and-replace-form__find .ck-results-counter{top:50%;transform:translateY(-50%)}[dir=ltr] .ck.ck-find-and-replace-form fieldset.ck-find-and-replace-form__find .ck-results-counter{right:var(--ck-spacing-standard)}[dir=rtl] .ck.ck-find-and-replace-form fieldset.ck-find-and-replace-form__find .ck-results-counter{left:var(--ck-spacing-standard)}.ck.ck-find-and-replace-form fieldset.ck-find-and-replace-form__find .ck-results-counter{color:var(--ck-color-base-border)}.ck.ck-find-and-replace-form fieldset.ck-find-and-replace-form__replace{flex-wrap:wrap;justify-content:flex-end;margin-top:calc(var(--ck-spacing-large)*-1)}.ck.ck-find-and-replace-form fieldset.ck-find-and-replace-form__replace>.ck-labeled-field-view{margin-bottom:var(--ck-spacing-large)}.ck.ck-find-and-replace-form fieldset.ck-find-and-replace-form__replace>.ck-options-dropdown{margin-right:auto;margin-left:0}.ck.ck-find-and-replace-form fieldset.ck-find-and-replace-form__replace>.ck-labeled-field-view,.ck.ck-find-and-replace-form fieldset.ck-find-and-replace-form__replace>.ck-labeled-field-view .ck-input{width:100%}@media screen and (max-width:600px){.ck.ck-find-and-replace-form{width:300px}.ck.ck-find-and-replace-form fieldset{flex-wrap:wrap}.ck.ck-find-and-replace-form fieldset.ck-find-and-replace-form__find .ck-labeled-field-view{flex:1 0 auto;width:100%;margin-bottom:var(--ck-spacing-standard)}.ck.ck-find-and-replace-form fieldset.ck-find-and-replace-form__find>.ck-button{text-align:center}.ck.ck-find-and-replace-form fieldset.ck-find-and-replace-form__find>.ck-button:first-of-type{flex:1 1 auto}[dir=ltr] .ck.ck-find-and-replace-form fieldset.ck-find-and-replace-form__find>.ck-button:first-of-type{margin-left:0}[dir=rtl] .ck.ck-find-and-replace-form fieldset.ck-find-and-replace-form__find>.ck-button:first-of-type{margin-right:0}.ck.ck-find-and-replace-form fieldset.ck-find-and-replace-form__find>.ck-button:first-of-type .ck-button__label{width:100%;text-align:center}.ck.ck-find-and-replace-form fieldset.ck-find-and-replace-form__replace>:not(.ck-labeled-field-view){flex:1 1 auto}.ck.ck-find-and-replace-form fieldset.ck-find-and-replace-form__replace>:not(.ck-labeled-field-view).ck-dropdown{flex-grow:0}.ck.ck-find-and-replace-form fieldset.ck-find-and-replace-form__replace>:not(.ck-labeled-field-view).ck-button>.ck-button__label{width:100%;text-align:center}}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-find-and-replace/theme/findandreplaceform.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/ckeditor5-find-and-replace/findandreplaceform.css","webpack://./node_modules/@ckeditor/ckeditor5-ui/theme/mixins/_rwd.css"],"names":[],"mappings":"AAKA,6BACC,cAUD,CARC,sCACC,YAMD,CAHC,yFACC,iBACD,CCNF,6BACC,WAyGD,CAnGC,mCACC,YACD,CAEA,sCACC,kBAAmB,CACnB,gBAAiB,CACjB,kBAAmB,CACnB,qBAAsB,CAEtB,+BAAgC,CAChC,QAAS,CACT,QAsFD,CApFC,iDACC,aACD,CAGC,oDACC,sCACD,CAIA,oDACC,uCACD,CAGD,6DACC,aAMD,CAJC,uEACC,UAAW,CACX,cACD,CAID,qEAEC,sBAkCD,CAhCC,qFACC,eAOD,CAJC,uGACC,oCAAqC,CACrC,qCACD,CAGD,8FACC,uBACD,CAEA,8FACC,wBACD,CAEA,yFACC,OAAQ,CACR,0BAWD,CAbA,mGAKE,gCAQF,CAbA,mGASE,+BAIF,CAbA,yFAYC,iCACD,CAID,wEACC,cAAe,CACf,wBAAyB,CACzB,2CAeD,CAbC,+FACC,qCACD,CAEA,6FACC,iBAAkB,CAClB,aACD,CAEA,wMAEC,UACD,CCzGF,oCD+GA,6BACC,WAiDD,CA/CC,sCACC,cA6CD,CAzCE,4FACC,aAAc,CACd,UAAW,CACX,wCACD,CAEA,gFACC,iBAkBD,CAhBC,8FACC,aAcD,CAfA,wGAIE,aAWF,CAfA,wGAQE,cAOF,CAJC,gHACC,UAAW,CACX,iBACD,CAMH,qGACC,aAUD,CARC,iHACC,WACD,CAEA,iIACC,UAAW,CACX,iBACD,CC5JH","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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-find-and-replace-form {\n\tmax-width: 100%;\n\n\t& fieldset {\n\t\tdisplay: flex;\n\n\t\t/* The find fieldset */\n\t\t&.ck-find-and-replace-form__find .ck-results-counter {\n\t\t\tposition: absolute;\n\t\t}\n\t}\n}\n","/*\n * Copyright (c) 2003-2022, 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/_rwd.css\";\n@import \"@ckeditor/ckeditor5-ui/theme/mixins/_dir.css\";\n\n.ck.ck-find-and-replace-form {\n\twidth: 400px;\n\n\t/*\n\t * The <form> needs tabindex=\"-1\" for proper Esc handling after being clicked\n\t * but the side effect is that this creates a nasty focus outline in some browsers.\n\t */\n\t&:focus {\n\t\toutline: none;\n\t}\n\n\t& fieldset {\n\t\tflex-direction: row;\n\t\tflex-wrap: nowrap;\n\t\talign-items: center;\n\t\talign-content: stretch;\n\n\t\tpadding: var(--ck-spacing-large);\n\t\tborder: 0;\n\t\tmargin: 0;\n\n\t\t& > .ck-button {\n\t\t\tflex: 0 0 auto;\n\t\t}\n\n\t\t@mixin ck-dir ltr {\n\t\t\t& > * + * {\n\t\t\t\tmargin-left: var(--ck-spacing-standard);\n\t\t\t}\n\t\t}\n\n\t\t@mixin ck-dir rtl {\n\t\t\t& > * + * {\n\t\t\t\tmargin-right: var(--ck-spacing-standard);\n\t\t\t}\n\t\t}\n\n\t\t& .ck-labeled-field-view {\n\t\t\tflex: 1 1 auto;\n\n\t\t\t& .ck-input {\n\t\t\t\twidth: 100%;\n\t\t\t\tmin-width: 50px;\n\t\t\t}\n\t\t}\n\n\t\t/* The find fieldset */\n\t\t&.ck-find-and-replace-form__find {\n\t\t\t/* To display all controls in line when there's an error under the input */\n\t\t\talign-items: flex-start;\n\n\t\t\t& > .ck-button-find {\n\t\t\t\tfont-weight: bold;\n\n\t\t\t\t/* Beef the find button up a little. It's the main action button in the form */\n\t\t\t\t& .ck-button__label {\n\t\t\t\t\tpadding-left: var(--ck-spacing-large);\n\t\t\t\t\tpadding-right: var(--ck-spacing-large);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t& > .ck-button-prev > .ck-icon {\n\t\t\t\ttransform: rotate(90deg);\n\t\t\t}\n\n\t\t\t& > .ck-button-next > .ck-icon {\n\t\t\t\ttransform: rotate(-90deg);\n\t\t\t}\n\n\t\t\t& .ck-results-counter {\n\t\t\t\ttop: 50%;\n\t\t\t\ttransform: translateY(-50%);\n\n\t\t\t\t@mixin ck-dir ltr {\n\t\t\t\t\tright: var(--ck-spacing-standard);\n\t\t\t\t}\n\n\t\t\t\t@mixin ck-dir rtl {\n\t\t\t\t\tleft: var(--ck-spacing-standard);\n\t\t\t\t}\n\n\t\t\t\tcolor: var(--ck-color-base-border);\n\t\t\t}\n\t\t}\n\n\t\t/* The replace fieldset */\n\t\t&.ck-find-and-replace-form__replace {\n\t\t\tflex-wrap: wrap;\n\t\t\tjustify-content: flex-end;\n\t\t\tmargin-top: calc( -1 * var(--ck-spacing-large) );\n\n\t\t\t& > .ck-labeled-field-view {\n\t\t\t\tmargin-bottom: var(--ck-spacing-large);\n\t\t\t}\n\n\t\t\t& > .ck-options-dropdown {\n\t\t\t\tmargin-right: auto;\n\t\t\t\tmargin-left: 0;\n\t\t\t}\n\n\t\t\t& > .ck-labeled-field-view,\n\t\t\t& > .ck-labeled-field-view .ck-input {\n\t\t\t\twidth: 100%;\n\t\t\t}\n\t\t}\n\t}\n}\n\n@mixin ck-media-phone {\n\t.ck.ck-find-and-replace-form {\n\t\twidth: 300px;\n\n\t\t& fieldset {\n\t\t\tflex-wrap: wrap;\n\n\t\t\t/* The find fieldset */\n\t\t\t&.ck-find-and-replace-form__find {\n\t\t\t\t& .ck-labeled-field-view {\n\t\t\t\t\tflex: 1 0 auto;\n\t\t\t\t\twidth: 100%;\n\t\t\t\t\tmargin-bottom: var(--ck-spacing-standard);\n\t\t\t\t}\n\n\t\t\t\t& > .ck-button {\n\t\t\t\t\ttext-align: center;\n\n\t\t\t\t\t&:first-of-type {\n\t\t\t\t\t\tflex: 1 1 auto;\n\n\t\t\t\t\t\t@mixin ck-dir ltr {\n\t\t\t\t\t\t\tmargin-left: 0;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t@mixin ck-dir rtl {\n\t\t\t\t\t\t\tmargin-right: 0;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t& .ck-button__label {\n\t\t\t\t\t\t\twidth: 100%;\n\t\t\t\t\t\t\ttext-align: center;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t/* The replace fieldset */\n\t\t\t&.ck-find-and-replace-form__replace > :not(.ck-labeled-field-view) {\n\t\t\t\tflex: 1 1 auto;\n\n\t\t\t\t&.ck-dropdown {\n\t\t\t\t\tflex-grow: 0;\n\t\t\t\t}\n\n\t\t\t\t&.ck-button > .ck-button__label {\n\t\t\t\t\twidth: 100%;\n\t\t\t\t\ttext-align: center;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n","/*\n * Copyright (c) 2003-2022, 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@define-mixin ck-media-phone {\n\t@media screen and (max-width: 600px) {\n\t\t@mixin-content;\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-font/theme/fontcolor.css":
/*!*******************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-font/theme/fontcolor.css ***!
\*******************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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-button.ck-color-table__remove-color{display:flex;align-items:center;width:100%}label.ck.ck-color-grid__label{font-weight:unset}.ck .ck-button.ck-color-table__remove-color{padding:calc(var(--ck-spacing-standard)/2) var(--ck-spacing-standard);border-bottom-left-radius:0;border-bottom-right-radius:0}.ck .ck-button.ck-color-table__remove-color:not(:focus){border-bottom:1px solid var(--ck-color-base-border)}[dir=ltr] .ck .ck-button.ck-color-table__remove-color .ck.ck-icon{margin-right:var(--ck-spacing-standard)}[dir=rtl] .ck .ck-button.ck-color-table__remove-color .ck.ck-icon{margin-left:var(--ck-spacing-standard)}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-font/theme/fontcolor.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/ckeditor5-font/fontcolor.css"],"names":[],"mappings":"AAKA,4CACC,YAAa,CACb,kBAAmB,CACnB,UACD,CAEA,8BACC,iBACD,CCNA,4CACC,qEAAyE,CACzE,2BAA4B,CAC5B,4BAeD,CAbC,wDACC,mDACD,CAEA,kEAEE,uCAMF,CARA,kEAME,sCAEF","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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-button.ck-color-table__remove-color {\n\tdisplay: flex;\n\talign-items: center;\n\twidth: 100%;\n}\n\nlabel.ck.ck-color-grid__label {\n\tfont-weight: unset;\n}\n","/*\n * Copyright (c) 2003-2022, 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.ck .ck-button.ck-color-table__remove-color {\n\tpadding: calc(var(--ck-spacing-standard) / 2 ) var(--ck-spacing-standard);\n\tborder-bottom-left-radius: 0;\n\tborder-bottom-right-radius: 0;\n\n\t&:not(:focus) {\n\t\tborder-bottom: 1px solid var(--ck-color-base-border);\n\t}\n\n\t& .ck.ck-icon {\n\t\t@mixin ck-dir ltr {\n\t\t\tmargin-right: var(--ck-spacing-standard);\n\t\t}\n\n\t\t@mixin ck-dir rtl {\n\t\t\tmargin-left: var(--ck-spacing-standard);\n\t\t}\n\t}\n}\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-font/theme/fontsize.css":
/*!******************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-font/theme/fontsize.css ***!
\******************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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 .text-tiny{font-size:.7em}.ck-content .text-small{font-size:.85em}.ck-content .text-big{font-size:1.4em}.ck-content .text-huge{font-size:1.8em}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-font/theme/fontsize.css"],"names":[],"mappings":"AAUC,uBACC,cACD,CAEA,wBACC,eACD,CAEA,sBACC,eACD,CAEA,uBACC,eACD","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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/* The values should be synchronized with the \"FONT_SIZE_PRESET_UNITS\" object in the \"/src/fontsize/utils.js\" file. */\n\n/* Styles should be prefixed with the `.ck-content` class.\nSee https://github.com/ckeditor/ckeditor5/issues/6636 */\n.ck-content {\n\t& .text-tiny {\n\t\tfont-size: .7em;\n\t}\n\n\t& .text-small {\n\t\tfont-size: .85em;\n\t}\n\n\t& .text-big {\n\t\tfont-size: 1.4em;\n\t}\n\n\t& .text-huge {\n\t\tfont-size: 1.8em;\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-heading/theme/heading.css":
/*!********************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-heading/theme/heading.css ***!
\********************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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-heading_heading1{font-size:20px}.ck.ck-heading_heading2{font-size:17px}.ck.ck-heading_heading3{font-size:14px}.ck[class*=ck-heading_heading]{font-weight:700}.ck.ck-dropdown.ck-heading-dropdown .ck-dropdown__button .ck-button__label{width:8em}.ck.ck-dropdown.ck-heading-dropdown .ck-dropdown__panel .ck-list__item{min-width:18em}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-heading/theme/heading.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/ckeditor5-heading/heading.css"],"names":[],"mappings":"AAKA,wBACC,cACD,CAEA,wBACC,cACD,CAEA,wBACC,cACD,CAEA,+BACC,eACD,CCZC,2EACC,SACD,CAEA,uEACC,cACD","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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-heading_heading1 {\n\tfont-size: 20px;\n}\n\n.ck.ck-heading_heading2 {\n\tfont-size: 17px;\n}\n\n.ck.ck-heading_heading3 {\n\tfont-size: 14px;\n}\n\n.ck[class*=\"ck-heading_heading\"] {\n\tfont-weight: bold;\n}\n","/*\n * Copyright (c) 2003-2022, 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/* Resize dropdown's button label. */\n.ck.ck-dropdown.ck-heading-dropdown {\n\t& .ck-dropdown__button .ck-button__label {\n\t\twidth: 8em;\n\t}\n\n\t& .ck-dropdown__panel .ck-list__item {\n\t\tmin-width: 18em;\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-highlight/theme/highlight.css":
/*!************************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-highlight/theme/highlight.css ***!
\************************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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, ":root{--ck-highlight-marker-yellow:#fdfd77;--ck-highlight-marker-green:#62f962;--ck-highlight-marker-pink:#fc7899;--ck-highlight-marker-blue:#72ccfd;--ck-highlight-pen-red:#e71313;--ck-highlight-pen-green:#128a00}.ck-content .marker-yellow{background-color:var(--ck-highlight-marker-yellow)}.ck-content .marker-green{background-color:var(--ck-highlight-marker-green)}.ck-content .marker-pink{background-color:var(--ck-highlight-marker-pink)}.ck-content .marker-blue{background-color:var(--ck-highlight-marker-blue)}.ck-content .pen-red{color:var(--ck-highlight-pen-red);background-color:transparent}.ck-content .pen-green{color:var(--ck-highlight-pen-green);background-color:transparent}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-highlight/theme/highlight.css"],"names":[],"mappings":"AAKA,MACC,oCAA+C,CAC/C,mCAA+C,CAC/C,kCAA8C,CAC9C,kCAA8C,CAC9C,8BAAwC,CACxC,gCACD,CAGC,2BACC,kDACD,CAFA,0BACC,iDACD,CAFA,yBACC,gDACD,CAFA,yBACC,gDACD,CAIA,qBACC,iCAAqC,CAGrC,4BACD,CALA,uBACC,mCAAqC,CAGrC,4BACD","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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-highlight-marker-yellow: hsl(60, 97%, 73%);\n\t--ck-highlight-marker-green: hsl(120, 93%, 68%);\n\t--ck-highlight-marker-pink: hsl(345, 96%, 73%);\n\t--ck-highlight-marker-blue: hsl(201, 97%, 72%);\n\t--ck-highlight-pen-red: hsl(0, 85%, 49%);\n\t--ck-highlight-pen-green: hsl(112, 100%, 27%);\n}\n\n@define-mixin highlight-marker-color $color {\n\t.ck-content .marker-$color {\n\t\tbackground-color: var(--ck-highlight-marker-$color);\n\t}\n}\n\n@define-mixin highlight-pen-color $color {\n\t.ck-content .pen-$color {\n\t\tcolor: var(--ck-highlight-pen-$color);\n\n\t\t/* Override default yellow background of `<mark>` from user agent stylesheet */\n\t\tbackground-color: transparent;\n\t}\n}\n\n@mixin highlight-marker-color yellow;\n@mixin highlight-marker-color green;\n@mixin highlight-marker-color pink;\n@mixin highlight-marker-color blue;\n\n@mixin highlight-pen-color red;\n@mixin highlight-pen-color green;\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-horizontal-line/theme/horizontalline.css":
/*!***********************************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-horizontal-line/theme/horizontalline.css ***!
\***********************************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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-editor__editable .ck-horizontal-line{display:flow-root}.ck-content hr{margin:15px 0;height:4px;background:#dedede;border:0}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-horizontal-line/theme/horizontalline.css"],"names":[],"mappings":"AAMA,yCAEC,iBACD,CAEA,eACC,aAAc,CACd,UAAW,CACX,kBAA2B,CAC3B,QACD","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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\n.ck-editor__editable .ck-horizontal-line {\n\t/* Necessary to render properly next to floated objects, e.g. side image case. */\n\tdisplay: flow-root;\n}\n\n.ck-content hr {\n\tmargin: 15px 0;\n\theight: 4px;\n\tbackground: hsl(0, 0%, 87%);\n\tborder: 0;\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-html-embed/theme/htmlembed.css":
/*!*************************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-html-embed/theme/htmlembed.css ***!
\*************************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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-widget.raw-html-embed{margin:.9em auto;position:relative;display:flow-root;min-width:15em;font-style:normal}.ck-widget.raw-html-embed:before{position:absolute;z-index:1}.ck-widget.raw-html-embed .raw-html-embed__buttons-wrapper{position:absolute;display:flex;flex-direction:column}.ck-widget.raw-html-embed .raw-html-embed__preview{position:relative;overflow:hidden;display:flex}.ck-widget.raw-html-embed .raw-html-embed__preview-content{width:100%;position:relative;margin:auto;display:table;border-collapse:separate;border-spacing:7px}.ck-widget.raw-html-embed .raw-html-embed__preview-placeholder{position:absolute;left:0;top:0;right:0;bottom:0;display:flex;align-items:center;justify-content:center}:root{--ck-html-embed-content-width:calc(100% - var(--ck-icon-size)*1.5);--ck-html-embed-source-height:10em;--ck-html-embed-unfocused-outline-width:1px;--ck-html-embed-content-min-height:calc(var(--ck-icon-size) + var(--ck-spacing-standard));--ck-html-embed-source-disabled-background:var(--ck-color-base-foreground);--ck-html-embed-source-disabled-color:#737373}.ck-widget.raw-html-embed{font-size:var(--ck-font-size-base);background-color:var(--ck-color-base-foreground)}.ck-widget.raw-html-embed:not(.ck-widget_selected):not(:hover){outline:var(--ck-html-embed-unfocused-outline-width) dashed var(--ck-color-widget-blurred-border)}.ck-widget.raw-html-embed[dir=ltr]{text-align:left}.ck-widget.raw-html-embed[dir=rtl]{text-align:right}.ck-widget.raw-html-embed:before{content:attr(data-html-embed-label);top:calc(var(--ck-html-embed-unfocused-outline-width)*-1);left:var(--ck-spacing-standard);background:#999;transition:background var(--ck-widget-handler-animation-duration) var(--ck-widget-handler-animation-curve);padding:calc(var(--ck-spacing-tiny) + var(--ck-html-embed-unfocused-outline-width)) var(--ck-spacing-small) var(--ck-spacing-tiny);border-radius:0 0 var(--ck-border-radius) var(--ck-border-radius);color:var(--ck-color-base-background);font-size:var(--ck-font-size-tiny);font-family:var(--ck-font-face)}.ck-widget.raw-html-embed[dir=rtl]:before{left:auto;right:var(--ck-spacing-standard)}.ck-widget.raw-html-embed[dir=ltr] .ck-widget__type-around .ck-widget__type-around__button.ck-widget__type-around__button_before{margin-left:50px}.ck.ck-editor__editable.ck-blurred .ck-widget.raw-html-embed.ck-widget_selected:before{top:0;padding:var(--ck-spacing-tiny) var(--ck-spacing-small)}.ck.ck-editor__editable:not(.ck-blurred) .ck-widget.raw-html-embed.ck-widget_selected:before{top:0;padding:var(--ck-spacing-tiny) var(--ck-spacing-small);background:var(--ck-color-focus-border)}.ck.ck-editor__editable .ck-widget.raw-html-embed:not(.ck-widget_selected):hover:before{top:0;padding:var(--ck-spacing-tiny) var(--ck-spacing-small)}.ck-widget.raw-html-embed .raw-html-embed__content-wrapper{padding:var(--ck-spacing-standard)}.ck-widget.raw-html-embed .raw-html-embed__buttons-wrapper{top:var(--ck-spacing-standard);right:var(--ck-spacing-standard)}.ck-widget.raw-html-embed .raw-html-embed__buttons-wrapper .ck-button.raw-html-embed__save-button{color:var(--ck-color-button-save)}.ck-widget.raw-html-embed .raw-html-embed__buttons-wrapper .ck-button.raw-html-embed__cancel-button{color:var(--ck-color-button-cancel)}.ck-widget.raw-html-embed .raw-html-embed__buttons-wrapper .ck-button:not(:first-child){margin-top:var(--ck-spacing-small)}.ck-widget.raw-html-embed[dir=rtl] .raw-html-embed__buttons-wrapper{left:var(--ck-spacing-standard);right:auto}.ck-widget.raw-html-embed .raw-html-embed__source{box-sizing:border-box;height:var(--ck-html-embed-source-height);width:var(--ck-html-embed-content-width);resize:none;min-width:0;padding:var(--ck-spacing-standard);font-family:monospace;tab-size:4;white-space:pre-wrap;font-size:var(--ck-font-size-base);text-align:left;direction:ltr}.ck-widget.raw-html-embed .raw-html-embed__source[disabled]{background:var(--ck-html-embed-source-disabled-background);color:var(--ck-html-embed-source-disabled-color);-webkit-text-fill-color:var(--ck-html-embed-source-disabled-color);opacity:1}.ck-widget.raw-html-embed .raw-html-embed__preview{min-height:var(--ck-html-embed-content-min-height);width:var(--ck-html-embed-content-width)}.ck-editor__editable:not(.ck-read-only) .ck-widget.raw-html-embed .raw-html-embed__preview{pointer-events:none}.ck-widget.raw-html-embed .raw-html-embed__preview-content{box-sizing:border-box;background-color:var(--ck-color-base-foreground)}.ck-widget.raw-html-embed .raw-html-embed__preview-content>*{margin-left:auto;margin-right:auto}.ck-widget.raw-html-embed .raw-html-embed__preview-placeholder{color:var(--ck-html-embed-source-disabled-color)}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-html-embed/theme/htmlembed.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/ckeditor5-html-embed/htmlembed.css"],"names":[],"mappings":"AAMA,0BAIC,gBAAkB,CAClB,iBAAkB,CAClB,iBAAkB,CAIlB,cAAe,CAGf,iBAgDD,CA5CC,iCACC,iBAAkB,CAGlB,SACD,CAKA,2DACC,iBAAkB,CAClB,YAAa,CACb,qBACD,CAEA,mDACC,iBAAkB,CAClB,eAAgB,CAChB,YACD,CAEA,2DACC,UAAW,CACX,iBAAkB,CAClB,WAAY,CAGZ,aAAc,CACd,wBAAyB,CACzB,kBACD,CAEA,+DACC,iBAAkB,CAClB,MAAO,CACP,KAAM,CACN,OAAQ,CACR,QAAS,CAET,YAAa,CACb,kBAAmB,CACnB,sBACD,CC7DD,MACC,kEAAqE,CACrE,kCAAmC,CACnC,2CAA4C,CAC5C,yFAA0F,CAE1F,0EAA2E,CAC3E,6CACD,CAGA,0BACC,kCAAmC,CACnC,gDAyID,CAvIC,+DACC,iGACD,CAGA,mCACC,eACD,CAEA,mCACC,gBACD,CAIA,iCACC,mCAAoC,CACpC,yDAA4D,CAC5D,+BAAgC,CAChC,eAA4B,CAC5B,0GAA2G,CAC3G,kIAAmI,CACnI,iEAAkE,CAClE,qCAAsC,CACtC,kCAAmC,CACnC,+BACD,CAEA,0CACC,SAAU,CACV,gCACD,CAGA,iIACC,gBACD,CAxCD,uFA2CE,KAAQ,CACR,sDA+FF,CA3IA,6FAgDE,KAAM,CACN,sDAAuD,CACvD,uCAyFF,CA3IA,wFAsDE,KAAQ,CACR,sDAoFF,CA/EC,2DACC,kCACD,CAGA,2DACC,8BAA+B,CAC/B,gCAaD,CAXC,kGACC,iCACD,CAEA,oGACC,mCACD,CAEA,wFACC,kCACD,CAGD,oEACC,+BAAgC,CAChC,UACD,CAGA,kDACC,qBAAsB,CACtB,yCAA0C,CAC1C,wCAAyC,CACzC,WAAY,CACZ,WAAY,CACZ,kCAAmC,CAEnC,qBAAsB,CACtB,UAAW,CACX,oBAAqB,CACrB,kCAAmC,CAGnC,eAAgB,CAChB,aAUD,CARC,4DACC,0DAA2D,CAC3D,gDAAiD,CAGjD,kEAAmE,CACnE,SACD,CAID,mDACC,kDAAmD,CACnD,wCAMD,CARA,2FAME,mBAEF,CAEA,2DACC,qBAAsB,CACtB,gDAMD,CAJC,6DACC,gBAAiB,CACjB,iBACD,CAGD,+DACC,gDACD","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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/* The feature container. */\n.ck-widget.raw-html-embed {\n\t/* Give the embed some air. */\n\t/* The first value should be equal to --ck-spacing-large variable if used in the editor context\n\tto avoid the content jumping (See https://github.com/ckeditor/ckeditor5/issues/9825). */\n\tmargin: 0.9em auto;\n\tposition: relative;\n\tdisplay: flow-root;\n\n\t/* Give the html embed some minimal width in the content to prevent them\n\tfrom being \"squashed\" in tight spaces, e.g. in table cells (https://github.com/ckeditor/ckeditor5/issues/8331) */\n\tmin-width: 15em;\n\n\t/* Don't inherit the style, e.g. when in a block quote. */\n\tfont-style: normal;\n\n\t/* ----- Emebed label in the upper left corner ----------------------------------------------- */\n\n\t&::before {\n\t\tposition: absolute;\n\n\t\t/* Make sure the content does not cover the label. */\n\t\tz-index: 1;\n\t}\n\n\t/* ----- Emebed internals --------------------------------------------------------------------- */\n\n\t/* The switch mode button wrapper. */\n\t& .raw-html-embed__buttons-wrapper {\n\t\tposition: absolute;\n\t\tdisplay: flex;\n\t\tflex-direction: column;\n\t}\n\n\t& .raw-html-embed__preview {\n\t\tposition: relative;\n\t\toverflow: hidden;\n\t\tdisplay: flex;\n\t}\n\n\t& .raw-html-embed__preview-content {\n\t\twidth: 100%;\n\t\tposition: relative;\n\t\tmargin: auto;\n\n\t\t/* Gives spacing to the small renderable elements, so they always cover the placeholder. */\n\t\tdisplay: table;\n\t\tborder-collapse: separate;\n\t\tborder-spacing: 7px;\n\t}\n\n\t& .raw-html-embed__preview-placeholder {\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\ttop: 0;\n\t\tright: 0;\n\t\tbottom: 0;\n\n\t\tdisplay: flex;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t}\n}\n","/*\n * Copyright (c) 2003-2022, 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-html-embed-content-width: calc(100% - 1.5 * var(--ck-icon-size));\n\t--ck-html-embed-source-height: 10em;\n\t--ck-html-embed-unfocused-outline-width: 1px;\n\t--ck-html-embed-content-min-height: calc(var(--ck-icon-size) + var(--ck-spacing-standard));\n\n\t--ck-html-embed-source-disabled-background: var(--ck-color-base-foreground);\n\t--ck-html-embed-source-disabled-color: hsl(0deg 0% 45%);\n}\n\n/* The feature container. */\n.ck-widget.raw-html-embed {\n\tfont-size: var(--ck-font-size-base);\n\tbackground-color: var(--ck-color-base-foreground);\n\n\t&:not(.ck-widget_selected):not(:hover) {\n\t\toutline: var(--ck-html-embed-unfocused-outline-width) dashed var(--ck-color-widget-blurred-border);\n\t}\n\n\t/* HTML embed widget itself should respect UI language direction */\n\t&[dir=\"ltr\"] {\n\t\ttext-align: left;\n\t}\n\n\t&[dir=\"rtl\"] {\n\t\ttext-align: right;\n\t}\n\n\t/* ----- Embed label in the upper left corner ----------------------------------------------- */\n\n\t&::before {\n\t\tcontent: attr(data-html-embed-label);\n\t\ttop: calc(-1 * var(--ck-html-embed-unfocused-outline-width));\n\t\tleft: var(--ck-spacing-standard);\n\t\tbackground: hsl(0deg 0% 60%);\n\t\ttransition: background var(--ck-widget-handler-animation-duration) var(--ck-widget-handler-animation-curve);\n\t\tpadding: calc(var(--ck-spacing-tiny) + var(--ck-html-embed-unfocused-outline-width)) var(--ck-spacing-small) var(--ck-spacing-tiny);\n\t\tborder-radius: 0 0 var(--ck-border-radius) var(--ck-border-radius);\n\t\tcolor: var(--ck-color-base-background);\n\t\tfont-size: var(--ck-font-size-tiny);\n\t\tfont-family: var(--ck-font-face);\n\t}\n\n\t&[dir=\"rtl\"]::before {\n\t\tleft: auto;\n\t\tright: var(--ck-spacing-standard);\n\t}\n\n\t/* Make space for label but it only collides in LTR languages */\n\t&[dir=\"ltr\"] .ck-widget__type-around .ck-widget__type-around__button.ck-widget__type-around__button_before {\n\t\tmargin-left: 50px;\n\t}\n\n\t@nest .ck.ck-editor__editable.ck-blurred &.ck-widget_selected::before {\n\t\ttop: 0px;\n\t\tpadding: var(--ck-spacing-tiny) var(--ck-spacing-small);\n\t}\n\n\t@nest .ck.ck-editor__editable:not(.ck-blurred) &.ck-widget_selected::before {\n\t\ttop: 0;\n\t\tpadding: var(--ck-spacing-tiny) var(--ck-spacing-small);\n\t\tbackground: var(--ck-color-focus-border);\n\t}\n\n\t@nest .ck.ck-editor__editable &:not(.ck-widget_selected):hover::before {\n\t\ttop: 0px;\n\t\tpadding: var(--ck-spacing-tiny) var(--ck-spacing-small);\n\t}\n\n\t/* ----- Emebed internals --------------------------------------------------------------------- */\n\n\t& .raw-html-embed__content-wrapper {\n\t\tpadding: var(--ck-spacing-standard);\n\t}\n\n\t/* The switch mode button wrapper. */\n\t& .raw-html-embed__buttons-wrapper {\n\t\ttop: var(--ck-spacing-standard);\n\t\tright: var(--ck-spacing-standard);\n\n\t\t& .ck-button.raw-html-embed__save-button {\n\t\t\tcolor: var(--ck-color-button-save);\n\t\t}\n\n\t\t& .ck-button.raw-html-embed__cancel-button {\n\t\t\tcolor: var(--ck-color-button-cancel);\n\t\t}\n\n\t\t& .ck-button:not(:first-child) {\n\t\t\tmargin-top: var(--ck-spacing-small);\n\t\t}\n\t}\n\n\t&[dir=\"rtl\"] .raw-html-embed__buttons-wrapper {\n\t\tleft: var(--ck-spacing-standard);\n\t\tright: auto;\n\t}\n\n\t/* The edit source element. */\n\t& .raw-html-embed__source {\n\t\tbox-sizing: border-box;\n\t\theight: var(--ck-html-embed-source-height);\n\t\twidth: var(--ck-html-embed-content-width);\n\t\tresize: none;\n\t\tmin-width: 0;\n\t\tpadding: var(--ck-spacing-standard);\n\n\t\tfont-family: monospace;\n\t\ttab-size: 4;\n\t\twhite-space: pre-wrap;\n\t\tfont-size: var(--ck-font-size-base); /* Safari needs this. */\n\n\t\t/* HTML code is direction–agnostic. */\n\t\ttext-align: left;\n\t\tdirection: ltr;\n\n\t\t&[disabled] {\n\t\t\tbackground: var(--ck-html-embed-source-disabled-background);\n\t\t\tcolor: var(--ck-html-embed-source-disabled-color);\n\n\t\t\t/* Safari needs this for the proper text color in disabled input (https://github.com/ckeditor/ckeditor5/issues/8320). */\n\t\t\t-webkit-text-fill-color: var(--ck-html-embed-source-disabled-color);\n\t\t\topacity: 1;\n\t\t}\n\t}\n\n\t/* The preview data container. */\n\t& .raw-html-embed__preview {\n\t\tmin-height: var(--ck-html-embed-content-min-height);\n\t\twidth: var(--ck-html-embed-content-width);\n\n\t\t/* Disable all mouse interaction as long as the editor is not read–only. */\n\t\t@nest .ck-editor__editable:not(.ck-read-only) & {\n\t\t\tpointer-events: none;\n\t\t}\n\t}\n\n\t& .raw-html-embed__preview-content {\n\t\tbox-sizing: border-box;\n\t\tbackground-color: var(--ck-color-base-foreground);\n\n\t\t& > * {\n\t\t\tmargin-left: auto;\n\t\t\tmargin-right: auto;\n\t\t}\n\t}\n\n\t& .raw-html-embed__preview-placeholder {\n\t\tcolor: var(--ck-html-embed-source-disabled-color)\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-html-support/theme/datafilter.css":
/*!****************************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-html-support/theme/datafilter.css ***!
\****************************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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, ":root{--ck-html-object-embed-unfocused-outline-width:1px}.ck-widget.html-object-embed{font-size:var(--ck-font-size-base);background-color:var(--ck-color-base-foreground);padding:var(--ck-spacing-small);padding-top:calc(var(--ck-font-size-tiny) + var(--ck-spacing-large));min-width:calc(76px + var(--ck-spacing-standard))}.ck-widget.html-object-embed:not(.ck-widget_selected):not(:hover){outline:var(--ck-html-object-embed-unfocused-outline-width) dashed var(--ck-color-widget-blurred-border)}.ck-widget.html-object-embed:before{font-weight:400;font-style:normal;position:absolute;content:attr(data-html-object-embed-label);top:0;left:var(--ck-spacing-standard);background:#999;transition:background var(--ck-widget-handler-animation-duration) var(--ck-widget-handler-animation-curve);padding:calc(var(--ck-spacing-tiny) + var(--ck-html-object-embed-unfocused-outline-width)) var(--ck-spacing-small) var(--ck-spacing-tiny);border-radius:0 0 var(--ck-border-radius) var(--ck-border-radius);color:var(--ck-color-base-background);font-size:var(--ck-font-size-tiny);font-family:var(--ck-font-face)}.ck-widget.html-object-embed .ck-widget__type-around .ck-widget__type-around__button.ck-widget__type-around__button_before{margin-left:50px}.ck-widget.html-object-embed .html-object-embed__content{pointer-events:none}div.ck-widget.html-object-embed{margin:1em auto}span.ck-widget.html-object-embed{display:inline-block}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-html-support/theme/datafilter.css"],"names":[],"mappings":"AAAA,MACC,kDACD,CAEA,6BACC,kCAAmC,CACnC,gDAAiD,CACjD,+BAAgC,CAEhC,oEAAqE,CACrE,iDA+BD,CA7BC,kEACC,wGACD,CAEA,oCACC,eAAmB,CACnB,iBAAkB,CAClB,iBAAkB,CAClB,0CAA2C,CAC3C,KAAM,CACN,+BAAgC,CAChC,eAA4B,CAC5B,0GAA2G,CAC3G,yIAA0I,CAC1I,iEAAkE,CAClE,qCAAsC,CACtC,kCAAmC,CACnC,+BACD,CAGA,2HACC,gBACD,CAEA,yDAEC,mBACD,CAGD,gCACC,eACD,CAEA,iCACC,oBACD","sourcesContent":[":root {\n\t--ck-html-object-embed-unfocused-outline-width: 1px;\n}\n\n.ck-widget.html-object-embed {\n\tfont-size: var(--ck-font-size-base);\n\tbackground-color: var(--ck-color-base-foreground);\n\tpadding: var(--ck-spacing-small);\n\t/* Leave space for label */\n\tpadding-top: calc(var(--ck-font-size-tiny) + var(--ck-spacing-large));\n\tmin-width: calc(76px + var(--ck-spacing-standard));\n\n\t&:not(.ck-widget_selected):not(:hover) {\n\t\toutline: var(--ck-html-object-embed-unfocused-outline-width) dashed var(--ck-color-widget-blurred-border);\n\t}\n\n\t&::before {\n\t\tfont-weight: normal;\n\t\tfont-style: normal;\n\t\tposition: absolute;\n\t\tcontent: attr(data-html-object-embed-label);\n\t\ttop: 0;\n\t\tleft: var(--ck-spacing-standard);\n\t\tbackground: hsl(0deg 0% 60%);\n\t\ttransition: background var(--ck-widget-handler-animation-duration) var(--ck-widget-handler-animation-curve);\n\t\tpadding: calc(var(--ck-spacing-tiny) + var(--ck-html-object-embed-unfocused-outline-width)) var(--ck-spacing-small) var(--ck-spacing-tiny);\n\t\tborder-radius: 0 0 var(--ck-border-radius) var(--ck-border-radius);\n\t\tcolor: var(--ck-color-base-background);\n\t\tfont-size: var(--ck-font-size-tiny);\n\t\tfont-family: var(--ck-font-face);\n\t}\n\n\t/* Make space for label. */\n\t& .ck-widget__type-around .ck-widget__type-around__button.ck-widget__type-around__button_before {\n\t\tmargin-left: 50px;\n\t}\n\n\t& .html-object-embed__content {\n\t\t/* Disable user interaction with embed content */\n\t\tpointer-events: none;\n\t}\n}\n\ndiv.ck-widget.html-object-embed {\n\tmargin: 1em auto;\n}\n\nspan.ck-widget.html-object-embed {\n\tdisplay: inline-block;\n}\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-image/theme/image.css":
/*!****************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-image/theme/image.css ***!
\****************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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 .image{display:table;clear:both;text-align:center;margin:.9em auto;min-width:50px}.ck-content .image img{display:block;margin:0 auto;max-width:100%;min-width:100%}.ck-content .image-inline{display:inline-flex;max-width:100%;align-items:flex-start}.ck-content .image-inline picture{display:flex}.ck-content .image-inline img,.ck-content .image-inline picture{flex-grow:1;flex-shrink:1;max-width:100%}.ck.ck-editor__editable .image>figcaption.ck-placeholder:before{padding-left:inherit;padding-right:inherit;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.ck.ck-editor__editable .image-inline.ck-widget_selected,.ck.ck-editor__editable .image.ck-widget_selected{z-index:1}.ck.ck-editor__editable .image-inline.ck-widget_selected ::selection{display:none}.ck.ck-editor__editable td .image-inline img,.ck.ck-editor__editable th .image-inline img{max-width:none}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-image/theme/image.css"],"names":[],"mappings":"AAMC,mBACC,aAAc,CACd,UAAW,CACX,iBAAkB,CAKlB,gBAAkB,CAGlB,cAeD,CAbC,uBAEC,aAAc,CAGd,aAAc,CAGd,cAAe,CAGf,cACD,CAGD,0BAMC,mBAAoB,CAGpB,cAAe,CAGf,sBAiBD,CAdC,kCACC,YACD,CAGA,gEAGC,WAAY,CACZ,aAAc,CAGd,cACD,CAUD,gEACC,oBAAqB,CACrB,qBAAsB,CAMtB,kBAAmB,CACnB,eAAgB,CAChB,sBACD,CAWA,2GACC,SAUD,CAHC,qEACC,YACD,CAOA,0FACC,cACD","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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 {\n\t& .image {\n\t\tdisplay: table;\n\t\tclear: both;\n\t\ttext-align: center;\n\n\t\t/* Make sure there is some space between the content and the image. Center image by default. */\n\t\t/* The first value should be equal to --ck-spacing-large variable if used in the editor context\n\t \tto avoid the content jumping (See https://github.com/ckeditor/ckeditor5/issues/9825). */\n\t\tmargin: 0.9em auto;\n\n\t\t/* Make sure the caption will be displayed properly (See: https://github.com/ckeditor/ckeditor5/issues/1870). */\n\t\tmin-width: 50px;\n\n\t\t& img {\n\t\t\t/* Prevent unnecessary margins caused by line-height (see #44). */\n\t\t\tdisplay: block;\n\n\t\t\t/* Center the image if its width is smaller than the content's width. */\n\t\t\tmargin: 0 auto;\n\n\t\t\t/* Make sure the image never exceeds the size of the parent container (ckeditor/ckeditor5-ui#67). */\n\t\t\tmax-width: 100%;\n\n\t\t\t/* Make sure the image is never smaller than the parent container (See: https://github.com/ckeditor/ckeditor5/issues/9300). */\n\t\t\tmin-width: 100%\n\t\t}\n\t}\n\n\t& .image-inline {\n\t\t/*\n\t\t * Normally, the .image-inline would have \"display: inline-block\" and \"img { width: 100% }\" (to follow the wrapper while resizing).\n\t\t * Unfortunately, together with \"srcset\", it gets automatically stretched up to the width of the editing root.\n\t\t * This strange behavior does not happen with inline-flex.\n\t\t */\n\t\tdisplay: inline-flex;\n\n\t\t/* While being resized, don't allow the image to exceed the width of the editing root. */\n\t\tmax-width: 100%;\n\n\t\t/* This is required by Safari to resize images in a sensible way. Without this, the browser breaks the ratio. */\n\t\talign-items: flex-start;\n\n\t\t/* When the picture is present it must act as a flex container to let the img resize properly */\n\t\t& picture {\n\t\t\tdisplay: flex;\n\t\t}\n\n\t\t/* When the picture is present, it must act like a resizable img. */\n\t\t& picture,\n\t\t& img {\n\t\t\t/* This is necessary for the img to span the entire .image-inline wrapper and to resize properly. */\n\t\t\tflex-grow: 1;\n\t\t\tflex-shrink: 1;\n\n\t\t\t/* Prevents overflowing the editing root boundaries when an inline image is very wide. */\n\t\t\tmax-width: 100%;\n\t\t}\n\t}\n}\n\n.ck.ck-editor__editable {\n\t/*\n\t * Inhertit the content styles padding of the <figcaption> in case the integration overrides `text-align: center`\n\t * of `.image` (e.g. to the left/right). This ensures the placeholder stays at the padding just like the native\n\t * caret does, and not at the edge of <figcaption>.\n\t */\n\t& .image > figcaption.ck-placeholder::before {\n\t\tpadding-left: inherit;\n\t\tpadding-right: inherit;\n\n\t\t/*\n\t\t * Make sure the image caption placeholder doesn't overflow the placeholder area.\n\t\t * See https://github.com/ckeditor/ckeditor5/issues/9162.\n\t\t */\n\t\twhite-space: nowrap;\n\t\toverflow: hidden;\n\t\ttext-overflow: ellipsis;\n\t}\n\n\n\t/*\n\t * Make sure the selected inline image always stays on top of its siblings.\n\t * See https://github.com/ckeditor/ckeditor5/issues/9108.\n\t */\n\t& .image.ck-widget_selected {\n\t\tz-index: 1;\n\t}\n\n\t& .image-inline.ck-widget_selected {\n\t\tz-index: 1;\n\n\t\t/*\n\t\t * Make sure the native browser selection style is not displayed.\n\t\t * Inline image widgets have their own styles for the selected state and\n\t\t * leaving this up to the browser is asking for a visual collision.\n\t\t */\n\t\t& ::selection {\n\t\t\tdisplay: none;\n\t\t}\n\t}\n\n\t/* The inline image nested in the table should have its original size if not resized.\n\tSee https://github.com/ckeditor/ckeditor5/issues/9117. */\n\t& td,\n\t& th {\n\t\t& .image-inline img {\n\t\t\tmax-width: none;\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-image/theme/imagecaption.css":
/*!***********************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-image/theme/imagecaption.css ***!
\***********************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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, ":root{--ck-color-image-caption-background:#f7f7f7;--ck-color-image-caption-text:#333;--ck-color-image-caption-highligted-background:#fd0}.ck-content .image>figcaption{display:table-caption;caption-side:bottom;word-break:break-word;color:var(--ck-color-image-caption-text);background-color:var(--ck-color-image-caption-background);padding:.6em;font-size:.75em;outline-offset:-1px}.ck.ck-editor__editable .image>figcaption.image__caption_highlighted{animation:ck-image-caption-highlight .6s ease-out}@keyframes ck-image-caption-highlight{0%{background-color:var(--ck-color-image-caption-highligted-background)}to{background-color:var(--ck-color-image-caption-background)}}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-image/theme/imagecaption.css"],"names":[],"mappings":"AAKA,MACC,2CAAoD,CACpD,kCAA8C,CAC9C,mDACD,CAGA,8BACC,qBAAsB,CACtB,mBAAoB,CACpB,qBAAsB,CACtB,wCAAyC,CACzC,yDAA0D,CAC1D,YAAa,CACb,eAAgB,CAChB,mBACD,CAGA,qEACC,iDACD,CAEA,sCACC,GACC,oEACD,CAEA,GACC,yDACD,CACD","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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-image-caption-background: hsl(0, 0%, 97%);\n\t--ck-color-image-caption-text: hsl(0, 0%, 20%);\n\t--ck-color-image-caption-highligted-background: hsl(52deg 100% 50%);\n}\n\n/* Content styles */\n.ck-content .image > figcaption {\n\tdisplay: table-caption;\n\tcaption-side: bottom;\n\tword-break: break-word;\n\tcolor: var(--ck-color-image-caption-text);\n\tbackground-color: var(--ck-color-image-caption-background);\n\tpadding: .6em;\n\tfont-size: .75em;\n\toutline-offset: -1px;\n}\n\n/* Editing styles */\n.ck.ck-editor__editable .image > figcaption.image__caption_highlighted {\n\tanimation: ck-image-caption-highlight .6s ease-out;\n}\n\n@keyframes ck-image-caption-highlight {\n\t0% {\n\t\tbackground-color: var(--ck-color-image-caption-highligted-background);\n\t}\n\n\t100% {\n\t\tbackground-color: var(--ck-color-image-caption-background);\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-image/theme/imageinsert.css":
/*!**********************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-image/theme/imageinsert.css ***!
\**********************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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-image-insert__panel{padding:var(--ck-spacing-large)}.ck.ck-image-insert__ck-finder-button{display:block;width:100%;margin:var(--ck-spacing-standard) auto;border:1px solid #ccc;border-radius:var(--ck-border-radius)}.ck.ck-splitbutton>.ck-file-dialog-button.ck-button{padding:0;margin:0;border:none}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-image/theme/imageinsert.css"],"names":[],"mappings":"AAKA,2BACC,+BACD,CAEA,sCACC,aAAc,CACd,UAAW,CACX,sCAAuC,CACvC,qBAAiC,CACjC,qCACD,CAGA,oDACC,SAAU,CACV,QAAS,CACT,WACD","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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-image-insert__panel {\n\tpadding: var(--ck-spacing-large);\n}\n\n.ck.ck-image-insert__ck-finder-button {\n\tdisplay: block;\n\twidth: 100%;\n\tmargin: var(--ck-spacing-standard) auto;\n\tborder: 1px solid hsl(0, 0%, 80%);\n\tborder-radius: var(--ck-border-radius);\n}\n\n/* https://github.com/ckeditor/ckeditor5/issues/7986 */\n.ck.ck-splitbutton > .ck-file-dialog-button.ck-button {\n\tpadding: 0;\n\tmargin: 0;\n\tborder: none;\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-image/theme/imageinsertformrowview.css":
/*!*********************************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-image/theme/imageinsertformrowview.css ***!
\*********************************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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-image-insert-form:focus{outline:none}.ck.ck-form__row{display:flex;flex-direction:row;flex-wrap:nowrap;justify-content:space-between}.ck.ck-form__row>:not(.ck-label){flex-grow:1}.ck.ck-form__row.ck-image-insert-form__action-row{margin-top:var(--ck-spacing-standard)}.ck.ck-form__row.ck-image-insert-form__action-row .ck-button-cancel,.ck.ck-form__row.ck-image-insert-form__action-row .ck-button-save{justify-content:center}.ck.ck-form__row.ck-image-insert-form__action-row .ck-button .ck-button__label{color:var(--ck-color-text)}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-image/theme/imageinsertformrowview.css"],"names":[],"mappings":"AAMC,+BAEC,YACD,CAGD,iBACC,YAAa,CACb,kBAAmB,CACnB,gBAAiB,CACjB,6BAmBD,CAhBC,iCACC,WACD,CAEA,kDACC,qCAUD,CARC,sIAEC,sBACD,CAEA,+EACC,0BACD","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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-image-insert-form {\n\t&:focus {\n\t\t/* See: https://github.com/ckeditor/ckeditor5/issues/4773 */\n\t\toutline: none;\n\t}\n}\n\n.ck.ck-form__row {\n\tdisplay: flex;\n\tflex-direction: row;\n\tflex-wrap: nowrap;\n\tjustify-content: space-between;\n\n\t/* Ignore labels that work as fieldset legends */\n\t& > *:not(.ck-label) {\n\t\tflex-grow: 1;\n\t}\n\n\t&.ck-image-insert-form__action-row {\n\t\tmargin-top: var(--ck-spacing-standard);\n\n\t\t& .ck-button-save,\n\t\t& .ck-button-cancel {\n\t\t\tjustify-content: center;\n\t\t}\n\n\t\t& .ck-button .ck-button__label {\n\t\t\tcolor: var(--ck-color-text);\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-image/theme/imageresize.css":
/*!**********************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-image/theme/imageresize.css ***!
\**********************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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 .image.image_resized{max-width:100%;display:block;box-sizing:border-box}.ck-content .image.image_resized img{width:100%}.ck-content .image.image_resized>figcaption{display:block}.ck.ck-editor__editable td .image-inline.image_resized img,.ck.ck-editor__editable th .image-inline.image_resized img{max-width:100%}[dir=ltr] .ck.ck-button.ck-button_with-text.ck-resize-image-button .ck-button__icon{margin-right:var(--ck-spacing-standard)}[dir=rtl] .ck.ck-button.ck-button_with-text.ck-resize-image-button .ck-button__icon{margin-left:var(--ck-spacing-standard)}.ck.ck-dropdown .ck-button.ck-resize-image-button .ck-button__label{width:4em}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-image/theme/imageresize.css"],"names":[],"mappings":"AAKA,iCACC,cAAe,CAMf,aAAc,CACd,qBAWD,CATC,qCAEC,UACD,CAEA,4CAEC,aACD,CAQC,sHACC,cACD,CAIF,oFACC,uCACD,CAEA,oFACC,sCACD,CAEA,oEACC,SACD","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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 .image.image_resized {\n\tmax-width: 100%;\n\t/*\n\tThe `<figure>` element for resized images must not use `display:table` as browsers do not support `max-width` for it well.\n\tSee https://stackoverflow.com/questions/4019604/chrome-safari-ignoring-max-width-in-table/14420691#14420691 for more.\n\tFortunately, since we control the width, there is no risk that the image will look bad.\n\t*/\n\tdisplay: block;\n\tbox-sizing: border-box;\n\n\t& img {\n\t\t/* For resized images it is the `<figure>` element that determines the image width. */\n\t\twidth: 100%;\n\t}\n\n\t& > figcaption {\n\t\t/* The `<figure>` element uses `display:block`, so `<figcaption>` also has to. */\n\t\tdisplay: block;\n\t}\n}\n\n.ck.ck-editor__editable {\n\t/* The resized inline image nested in the table should respect its parent size.\n\tSee https://github.com/ckeditor/ckeditor5/issues/9117. */\n\t& td,\n\t& th {\n\t\t& .image-inline.image_resized img {\n\t\t\tmax-width: 100%;\n\t\t}\n\t}\n}\n\n[dir=\"ltr\"] .ck.ck-button.ck-button_with-text.ck-resize-image-button .ck-button__icon {\n\tmargin-right: var(--ck-spacing-standard);\n}\n\n[dir=\"rtl\"] .ck.ck-button.ck-button_with-text.ck-resize-image-button .ck-button__icon {\n\tmargin-left: var(--ck-spacing-standard);\n}\n\n.ck.ck-dropdown .ck-button.ck-resize-image-button .ck-button__label {\n\twidth: 4em;\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-image/theme/imagestyle.css":
/*!*********************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-image/theme/imagestyle.css ***!
\*********************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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, ":root{--ck-image-style-spacing:1.5em;--ck-inline-image-style-spacing:calc(var(--ck-image-style-spacing)/2)}.ck-content .image-style-block-align-left,.ck-content .image-style-block-align-right{max-width:calc(100% - var(--ck-image-style-spacing))}.ck-content .image-style-align-left,.ck-content .image-style-align-right{clear:none}.ck-content .image-style-side{float:right;margin-left:var(--ck-image-style-spacing);max-width:50%}.ck-content .image-style-align-left{float:left;margin-right:var(--ck-image-style-spacing)}.ck-content .image-style-align-center{margin-left:auto;margin-right:auto}.ck-content .image-style-align-right{float:right;margin-left:var(--ck-image-style-spacing)}.ck-content .image-style-block-align-right{margin-right:0;margin-left:auto}.ck-content .image-style-block-align-left{margin-left:0;margin-right:auto}.ck-content p+.image-style-align-left,.ck-content p+.image-style-align-right,.ck-content p+.image-style-side{margin-top:0}.ck-content .image-inline.image-style-align-left,.ck-content .image-inline.image-style-align-right{margin-top:var(--ck-inline-image-style-spacing);margin-bottom:var(--ck-inline-image-style-spacing)}.ck-content .image-inline.image-style-align-left{margin-right:var(--ck-inline-image-style-spacing)}.ck-content .image-inline.image-style-align-right{margin-left:var(--ck-inline-image-style-spacing)}.ck.ck-splitbutton.ck-splitbutton_flatten.ck-splitbutton_open>.ck-splitbutton__action:not(.ck-disabled),.ck.ck-splitbutton.ck-splitbutton_flatten.ck-splitbutton_open>.ck-splitbutton__arrow:not(.ck-disabled),.ck.ck-splitbutton.ck-splitbutton_flatten.ck-splitbutton_open>.ck-splitbutton__arrow:not(.ck-disabled):not(:hover),.ck.ck-splitbutton.ck-splitbutton_flatten:hover>.ck-splitbutton__action:not(.ck-disabled),.ck.ck-splitbutton.ck-splitbutton_flatten:hover>.ck-splitbutton__arrow:not(.ck-disabled),.ck.ck-splitbutton.ck-splitbutton_flatten:hover>.ck-splitbutton__arrow:not(.ck-disabled):not(:hover){background-color:var(--ck-color-button-on-background)}.ck.ck-splitbutton.ck-splitbutton_flatten.ck-splitbutton_open>.ck-splitbutton__action:not(.ck-disabled):after,.ck.ck-splitbutton.ck-splitbutton_flatten.ck-splitbutton_open>.ck-splitbutton__arrow:not(.ck-disabled):after,.ck.ck-splitbutton.ck-splitbutton_flatten.ck-splitbutton_open>.ck-splitbutton__arrow:not(.ck-disabled):not(:hover):after,.ck.ck-splitbutton.ck-splitbutton_flatten:hover>.ck-splitbutton__action:not(.ck-disabled):after,.ck.ck-splitbutton.ck-splitbutton_flatten:hover>.ck-splitbutton__arrow:not(.ck-disabled):after,.ck.ck-splitbutton.ck-splitbutton_flatten:hover>.ck-splitbutton__arrow:not(.ck-disabled):not(:hover):after{display:none}.ck.ck-splitbutton.ck-splitbutton_flatten.ck-splitbutton_open:hover>.ck-splitbutton__action:not(.ck-disabled),.ck.ck-splitbutton.ck-splitbutton_flatten.ck-splitbutton_open:hover>.ck-splitbutton__arrow:not(.ck-disabled),.ck.ck-splitbutton.ck-splitbutton_flatten.ck-splitbutton_open:hover>.ck-splitbutton__arrow:not(.ck-disabled):not(:hover){background-color:var(--ck-color-button-on-hover-background)}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-image/theme/imagestyle.css"],"names":[],"mappings":"AAKA,MACC,8BAA+B,CAC/B,qEACD,CAMC,qFAEC,oDACD,CAIA,yEAEC,UACD,CAEA,8BACC,WAAY,CACZ,yCAA0C,CAC1C,aACD,CAEA,oCACC,UAAW,CACX,0CACD,CAEA,sCACC,gBAAiB,CACjB,iBACD,CAEA,qCACC,WAAY,CACZ,yCACD,CAEA,2CACC,cAAe,CACf,gBACD,CAEA,0CACC,aAAc,CACd,iBACD,CAGA,6GAGC,YACD,CAGC,mGAEC,+CAAgD,CAChD,kDACD,CAEA,iDACC,iDACD,CAEA,kDACC,gDACD,CAUC,0lBAGC,qDAKD,CAHC,8nBACC,YACD,CAKD,oVAGC,2DACD","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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-image-style-spacing: 1.5em;\n\t--ck-inline-image-style-spacing: calc(var(--ck-image-style-spacing) / 2);\n}\n\n.ck-content {\n\t/* Provides a minimal side margin for the left and right aligned images, so that the user has a visual feedback\n\tconfirming successful application of the style if image width exceeds the editor's size.\n\tSee https://github.com/ckeditor/ckeditor5/issues/9342 */\n\t& .image-style-block-align-left,\n\t& .image-style-block-align-right {\n\t\tmax-width: calc(100% - var(--ck-image-style-spacing));\n\t}\n\n\t/* Allows displaying multiple floating images in the same line.\n\tSee https://github.com/ckeditor/ckeditor5/issues/9183#issuecomment-804988132 */\n\t& .image-style-align-left,\n\t& .image-style-align-right {\n\t\tclear: none;\n\t}\n\n\t& .image-style-side {\n\t\tfloat: right;\n\t\tmargin-left: var(--ck-image-style-spacing);\n\t\tmax-width: 50%;\n\t}\n\n\t& .image-style-align-left {\n\t\tfloat: left;\n\t\tmargin-right: var(--ck-image-style-spacing);\n\t}\n\n\t& .image-style-align-center {\n\t\tmargin-left: auto;\n\t\tmargin-right: auto;\n\t}\n\n\t& .image-style-align-right {\n\t\tfloat: right;\n\t\tmargin-left: var(--ck-image-style-spacing);\n\t}\n\n\t& .image-style-block-align-right {\n\t\tmargin-right: 0;\n\t\tmargin-left: auto;\n\t}\n\n\t& .image-style-block-align-left {\n\t\tmargin-left: 0;\n\t\tmargin-right: auto;\n\t}\n\n\t/* Simulates margin collapsing with the preceding paragraph, which does not work for the floating elements. */\n\t& p + .image-style-align-left,\n\t& p + .image-style-align-right,\n\t& p + .image-style-side {\n\t\tmargin-top: 0;\n\t}\n\n\t& .image-inline {\n\t\t&.image-style-align-left,\n\t\t&.image-style-align-right {\n\t\t\tmargin-top: var(--ck-inline-image-style-spacing);\n\t\t\tmargin-bottom: var(--ck-inline-image-style-spacing);\n\t\t}\n\n\t\t&.image-style-align-left {\n\t\t\tmargin-right: var(--ck-inline-image-style-spacing);\n\t\t}\n\n\t\t&.image-style-align-right {\n\t\t\tmargin-left: var(--ck-inline-image-style-spacing);\n\t\t}\n\t}\n}\n\n.ck.ck-splitbutton {\n\t/* The button should display as a regular drop-down if the action button\n\tis forced to fire the same action as the arrow button. */\n\t&.ck-splitbutton_flatten {\n\t\t&:hover,\n\t\t&.ck-splitbutton_open {\n\t\t\t& > .ck-splitbutton__action:not(.ck-disabled),\n\t\t\t& > .ck-splitbutton__arrow:not(.ck-disabled),\n\t\t\t& > .ck-splitbutton__arrow:not(.ck-disabled):not(:hover) {\n\t\t\t\tbackground-color: var(--ck-color-button-on-background);\n\n\t\t\t\t&::after {\n\t\t\t\t\tdisplay: none;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t&.ck-splitbutton_open:hover {\n\t\t\t& > .ck-splitbutton__action:not(.ck-disabled),\n\t\t\t& > .ck-splitbutton__arrow:not(.ck-disabled),\n\t\t\t& > .ck-splitbutton__arrow:not(.ck-disabled):not(:hover) {\n\t\t\t\tbackground-color: var(--ck-color-button-on-hover-background);\n\t\t\t}\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-image/theme/imageuploadicon.css":
/*!**************************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-image/theme/imageuploadicon.css ***!
\**************************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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-image-upload-complete-icon{display:block;position:absolute;top:min(var(--ck-spacing-medium),6%);right:min(var(--ck-spacing-medium),6%);border-radius:50%;z-index:1}.ck-image-upload-complete-icon:after{content:\"\";position:absolute}:root{--ck-color-image-upload-icon:#fff;--ck-color-image-upload-icon-background:#008a00;--ck-image-upload-icon-size:20;--ck-image-upload-icon-width:2px;--ck-image-upload-icon-is-visible:clamp(0px,100% - 50px,1px)}.ck-image-upload-complete-icon{opacity:0;background:var(--ck-color-image-upload-icon-background);animation-name:ck-upload-complete-icon-show,ck-upload-complete-icon-hide;animation-fill-mode:forwards,forwards;animation-duration:.5s,.5s;font-size:calc(1px*var(--ck-image-upload-icon-size));animation-delay:0ms,3s;overflow:hidden;width:calc(var(--ck-image-upload-icon-is-visible)*var(--ck-image-upload-icon-size));height:calc(var(--ck-image-upload-icon-is-visible)*var(--ck-image-upload-icon-size))}.ck-image-upload-complete-icon:after{left:25%;top:50%;opacity:0;height:0;width:0;transform:scaleX(-1) rotate(135deg);transform-origin:left top;border-top:var(--ck-image-upload-icon-width) solid var(--ck-color-image-upload-icon);border-right:var(--ck-image-upload-icon-width) solid var(--ck-color-image-upload-icon);animation-name:ck-upload-complete-icon-check;animation-duration:.5s;animation-delay:.5s;animation-fill-mode:forwards;box-sizing:border-box}@keyframes ck-upload-complete-icon-show{0%{opacity:0}to{opacity:1}}@keyframes ck-upload-complete-icon-hide{0%{opacity:1}to{opacity:0}}@keyframes ck-upload-complete-icon-check{0%{opacity:1;width:0;height:0}33%{width:.3em;height:0}to{opacity:1;width:.3em;height:.45em}}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-image/theme/imageuploadicon.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/ckeditor5-image/imageuploadicon.css"],"names":[],"mappings":"AAKA,+BACC,aAAc,CACd,iBAAkB,CAMlB,oCAAsC,CACtC,sCAAwC,CACxC,iBAAkB,CAClB,SAMD,CAJC,qCACC,UAAW,CACX,iBACD,CChBD,MACC,iCAA8C,CAC9C,+CAA4D,CAG5D,8BAA+B,CAC/B,gCAAiC,CACjC,4DACD,CAEA,+BACC,SAAU,CACV,uDAAwD,CACxD,wEAA0E,CAC1E,qCAAuC,CACvC,0BAAgC,CAGhC,oDAAuD,CAGvD,sBAA4B,CAM5B,eAAgB,CAChB,mFAAsF,CACtF,oFAyBD,CAtBC,qCAEC,QAAS,CAET,OAAQ,CACR,SAAU,CACV,QAAS,CACT,OAAQ,CAER,mCAAoC,CACpC,yBAA0B,CAC1B,oFAAqF,CACrF,sFAAuF,CAEvF,4CAA6C,CAC7C,sBAAyB,CACzB,mBAAsB,CACtB,4BAA6B,CAG7B,qBACD,CAGD,wCACC,GACC,SACD,CAEA,GACC,SACD,CACD,CAEA,wCACC,GACC,SACD,CAEA,GACC,SACD,CACD,CAEA,yCACC,GACC,SAAU,CACV,OAAQ,CACR,QACD,CACA,IACC,UAAY,CACZ,QACD,CACA,GACC,SAAU,CACV,UAAY,CACZ,YACD,CACD","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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-image-upload-complete-icon {\n\tdisplay: block;\n\tposition: absolute;\n\n\t/*\n\t * Smaller images should have the icon closer to the border.\n\t * Match the icon position with the linked image indicator brought by the link image feature.\n\t */\n\ttop: min(var(--ck-spacing-medium), 6%);\n\tright: min(var(--ck-spacing-medium), 6%);\n\tborder-radius: 50%;\n\tz-index: 1;\n\n\t&::after {\n\t\tcontent: \"\";\n\t\tposition: absolute;\n\t}\n}\n","/*\n * Copyright (c) 2003-2022, 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-image-upload-icon: hsl(0, 0%, 100%);\n\t--ck-color-image-upload-icon-background: hsl(120, 100%, 27%);\n\n\t/* Match the icon size with the linked image indicator brought by the link image feature. */\n\t--ck-image-upload-icon-size: 20;\n\t--ck-image-upload-icon-width: 2px;\n\t--ck-image-upload-icon-is-visible: clamp(0px, 100% - 50px, 1px);\n}\n\n.ck-image-upload-complete-icon {\n\topacity: 0;\n\tbackground: var(--ck-color-image-upload-icon-background);\n\tanimation-name: ck-upload-complete-icon-show, ck-upload-complete-icon-hide;\n\tanimation-fill-mode: forwards, forwards;\n\tanimation-duration: 500ms, 500ms;\n\n\t/* To make animation scalable. */\n\tfont-size: calc(1px * var(--ck-image-upload-icon-size));\n\n\t/* Hide completed upload icon after 3 seconds. */\n\tanimation-delay: 0ms, 3000ms;\n\n\t/*\n\t * Use CSS math to simulate container queries.\n\t * https://css-tricks.com/the-raven-technique-one-step-closer-to-container-queries/#what-about-showing-and-hiding-things\n\t */\n\toverflow: hidden;\n\twidth: calc(var(--ck-image-upload-icon-is-visible) * var(--ck-image-upload-icon-size));\n\theight: calc(var(--ck-image-upload-icon-is-visible) * var(--ck-image-upload-icon-size));\n\n\t/* This is check icon element made from border-width mixed with animations. */\n\t&::after {\n\t\t/* Because of border transformation we need to \"hard code\" left position. */\n\t\tleft: 25%;\n\n\t\ttop: 50%;\n\t\topacity: 0;\n\t\theight: 0;\n\t\twidth: 0;\n\n\t\ttransform: scaleX(-1) rotate(135deg);\n\t\ttransform-origin: left top;\n\t\tborder-top: var(--ck-image-upload-icon-width) solid var(--ck-color-image-upload-icon);\n\t\tborder-right: var(--ck-image-upload-icon-width) solid var(--ck-color-image-upload-icon);\n\n\t\tanimation-name: ck-upload-complete-icon-check;\n\t\tanimation-duration: 500ms;\n\t\tanimation-delay: 500ms;\n\t\tanimation-fill-mode: forwards;\n\n\t\t/* #1095. While reset is not providing proper box-sizing for pseudoelements, we need to handle it. */\n\t\tbox-sizing: border-box;\n\t}\n}\n\n@keyframes ck-upload-complete-icon-show {\n\tfrom {\n\t\topacity: 0;\n\t}\n\n\tto {\n\t\topacity: 1;\n\t}\n}\n\n@keyframes ck-upload-complete-icon-hide {\n\tfrom {\n\t\topacity: 1;\n\t}\n\n\tto {\n\t\topacity: 0;\n\t}\n}\n\n@keyframes ck-upload-complete-icon-check {\n\t0% {\n\t\topacity: 1;\n\t\twidth: 0;\n\t\theight: 0;\n\t}\n\t33% {\n\t\twidth: 0.3em;\n\t\theight: 0;\n\t}\n\t100% {\n\t\topacity: 1;\n\t\twidth: 0.3em;\n\t\theight: 0.45em;\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-image/theme/imageuploadloader.css":
/*!****************************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-image/theme/imageuploadloader.css ***!
\****************************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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-upload-placeholder-loader{position:absolute;display:flex;align-items:center;justify-content:center;top:0;left:0}.ck .ck-upload-placeholder-loader:before{content:\"\";position:relative}:root{--ck-color-upload-placeholder-loader:#b3b3b3;--ck-upload-placeholder-loader-size:32px;--ck-upload-placeholder-image-aspect-ratio:2.8}.ck .ck-image-upload-placeholder{width:100%;margin:0}.ck .ck-image-upload-placeholder.image-inline{width:calc(var(--ck-upload-placeholder-loader-size)*2*var(--ck-upload-placeholder-image-aspect-ratio))}.ck .ck-image-upload-placeholder img{aspect-ratio:var(--ck-upload-placeholder-image-aspect-ratio)}.ck .ck-upload-placeholder-loader{width:100%;height:100%}.ck .ck-upload-placeholder-loader:before{width:var(--ck-upload-placeholder-loader-size);height:var(--ck-upload-placeholder-loader-size);border-radius:50%;border-top:3px solid var(--ck-color-upload-placeholder-loader);border-right:2px solid transparent;animation:ck-upload-placeholder-loader 1s linear infinite}@keyframes ck-upload-placeholder-loader{to{transform:rotate(1turn)}}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-image/theme/imageuploadloader.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/ckeditor5-image/imageuploadloader.css"],"names":[],"mappings":"AAKA,kCACC,iBAAkB,CAClB,YAAa,CACb,kBAAmB,CACnB,sBAAuB,CACvB,KAAM,CACN,MAMD,CAJC,yCACC,UAAW,CACX,iBACD,CCXD,MACC,4CAAqD,CACrD,wCAAyC,CACzC,8CACD,CAEA,iCAEC,UAAW,CACX,QAeD,CAbC,8CACC,sGACD,CAEA,qCAOC,4DACD,CAGD,kCACC,UAAW,CACX,WAUD,CARC,yCACC,8CAA+C,CAC/C,+CAAgD,CAChD,iBAAkB,CAClB,8DAA+D,CAC/D,kCAAmC,CACnC,yDACD,CAGD,wCACC,GACC,uBACD,CACD","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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-upload-placeholder-loader {\n\tposition: absolute;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\ttop: 0;\n\tleft: 0;\n\n\t&::before {\n\t\tcontent: '';\n\t\tposition: relative;\n\t}\n}\n","/*\n * Copyright (c) 2003-2022, 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-upload-placeholder-loader: hsl(0, 0%, 70%);\n\t--ck-upload-placeholder-loader-size: 32px;\n\t--ck-upload-placeholder-image-aspect-ratio: 2.8;\n}\n\n.ck .ck-image-upload-placeholder {\n\t/* We need to control the full width of the SVG gray background. */\n\twidth: 100%;\n\tmargin: 0;\n\n\t&.image-inline {\n\t\twidth: calc( 2 * var(--ck-upload-placeholder-loader-size) * var(--ck-upload-placeholder-image-aspect-ratio) );\n\t}\n\n\t& img {\n\t\t/*\n\t\t * This is an arbitrary aspect for a 1x1 px GIF to display to the user. Not too tall, not too short.\n\t\t * There's nothing special about this number except that it should make the image placeholder look like\n\t\t * a real image during this short period after the upload started and before the image was read from the\n\t\t * file system (and a rich preview was loaded).\n\t\t */\n\t\taspect-ratio: var(--ck-upload-placeholder-image-aspect-ratio);\n\t}\n}\n\n.ck .ck-upload-placeholder-loader {\n\twidth: 100%;\n\theight: 100%;\n\n\t&::before {\n\t\twidth: var(--ck-upload-placeholder-loader-size);\n\t\theight: var(--ck-upload-placeholder-loader-size);\n\t\tborder-radius: 50%;\n\t\tborder-top: 3px solid var(--ck-color-upload-placeholder-loader);\n\t\tborder-right: 2px solid transparent;\n\t\tanimation: ck-upload-placeholder-loader 1s linear infinite;\n\t}\n}\n\n@keyframes ck-upload-placeholder-loader {\n\tto {\n\t\ttransform: rotate( 360deg );\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-image/theme/imageuploadprogress.css":
/*!******************************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-image/theme/imageuploadprogress.css ***!
\******************************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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 .image,.ck.ck-editor__editable .image-inline{position:relative}.ck.ck-editor__editable .image-inline .ck-progress-bar,.ck.ck-editor__editable .image .ck-progress-bar{position:absolute;top:0;left:0}.ck.ck-editor__editable .image-inline.ck-appear,.ck.ck-editor__editable .image.ck-appear{animation:fadeIn .7s}.ck.ck-editor__editable .image-inline .ck-progress-bar,.ck.ck-editor__editable .image .ck-progress-bar{height:2px;width:0;background:var(--ck-color-upload-bar-background);transition:width .1s}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-image/theme/imageuploadprogress.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/ckeditor5-image/imageuploadprogress.css"],"names":[],"mappings":"AAMC,qEAEC,iBACD,CAGA,uGAEC,iBAAkB,CAClB,KAAM,CACN,MACD,CCRC,yFACC,oBACD,CAID,uGAEC,UAAW,CACX,OAAQ,CACR,gDAAiD,CACjD,oBACD,CAGD,kBACC,GAAO,SAAY,CACnB,GAAO,SAAY,CACpB","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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& .image,\n\t& .image-inline {\n\t\tposition: relative;\n\t}\n\n\t/* Upload progress bar. */\n\t& .image .ck-progress-bar,\n\t& .image-inline .ck-progress-bar {\n\t\tposition: absolute;\n\t\ttop: 0;\n\t\tleft: 0;\n\t}\n}\n","/*\n * Copyright (c) 2003-2022, 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& .image,\n\t& .image-inline {\n\t\t/* Showing animation. */\n\t\t&.ck-appear {\n\t\t\tanimation: fadeIn 700ms;\n\t\t}\n\t}\n\n\t/* Upload progress bar. */\n\t& .image .ck-progress-bar,\n\t& .image-inline .ck-progress-bar {\n\t\theight: 2px;\n\t\twidth: 0;\n\t\tbackground: var(--ck-color-upload-bar-background);\n\t\ttransition: width 100ms;\n\t}\n}\n\n@keyframes fadeIn {\n\tfrom { opacity: 0; }\n\tto { opacity: 1; }\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-image/theme/textalternativeform.css":
/*!******************************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-image/theme/textalternativeform.css ***!
\******************************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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-text-alternative-form{display:flex;flex-direction:row;flex-wrap:nowrap}.ck.ck-text-alternative-form .ck-labeled-field-view{display:inline-block}.ck.ck-text-alternative-form .ck-label{display:none}@media screen and (max-width:600px){.ck.ck-text-alternative-form{flex-wrap:wrap}.ck.ck-text-alternative-form .ck-labeled-field-view{flex-basis:100%}.ck.ck-text-alternative-form .ck-button{flex-basis:50%}}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-image/theme/textalternativeform.css","webpack://./node_modules/@ckeditor/ckeditor5-ui/theme/mixins/_rwd.css"],"names":[],"mappings":"AAOA,6BACC,YAAa,CACb,kBAAmB,CACnB,gBAqBD,CAnBC,oDACC,oBACD,CAEA,uCACC,YACD,CCZA,oCDCD,6BAcE,cAUF,CARE,oDACC,eACD,CAEA,wCACC,cACD,CCrBD","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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/_rwd.css\";\n\n.ck.ck-text-alternative-form {\n\tdisplay: flex;\n\tflex-direction: row;\n\tflex-wrap: nowrap;\n\n\t& .ck-labeled-field-view {\n\t\tdisplay: inline-block;\n\t}\n\n\t& .ck-label {\n\t\tdisplay: none;\n\t}\n\n\t@mixin ck-media-phone {\n\t\tflex-wrap: wrap;\n\n\t\t& .ck-labeled-field-view {\n\t\t\tflex-basis: 100%;\n\t\t}\n\n\t\t& .ck-button {\n\t\t\tflex-basis: 50%;\n\t\t}\n\t}\n}\n","/*\n * Copyright (c) 2003-2022, 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@define-mixin ck-media-phone {\n\t@media screen and (max-width: 600px) {\n\t\t@mixin-content;\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-link/theme/link.css":
/*!**************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-link/theme/link.css ***!
\**************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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-link_selected{background:var(--ck-color-link-selected-background)}.ck .ck-link_selected span.image-inline{outline:var(--ck-widget-outline-thickness) solid var(--ck-color-link-selected-background)}.ck .ck-fake-link-selection{background:var(--ck-color-link-fake-selection)}.ck .ck-fake-link-selection_collapsed{height:100%;border-right:1px solid var(--ck-color-base-text);margin-right:-1px;outline:1px solid hsla(0,0%,100%,.5)}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/ckeditor5-link/link.css"],"names":[],"mappings":"AAMA,sBACC,mDAMD,CAHC,wCACC,yFACD,CAOD,4BACC,8CACD,CAGA,sCACC,WAAY,CACZ,gDAAiD,CACjD,iBAAkB,CAClB,oCACD","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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/* Class added to span element surrounding currently selected link. */\n.ck .ck-link_selected {\n\tbackground: var(--ck-color-link-selected-background);\n\n\t/* Give linked inline images some outline to let the user know they are also part of the link. */\n\t& span.image-inline {\n\t\toutline: var(--ck-widget-outline-thickness) solid var(--ck-color-link-selected-background);\n\t}\n}\n\n/*\n * Classes used by the \"fake visual selection\" displayed in the content when an input\n * in the link UI has focus (the browser does not render the native selection in this state).\n */\n.ck .ck-fake-link-selection {\n\tbackground: var(--ck-color-link-fake-selection);\n}\n\n/* A collapsed fake visual selection. */\n.ck .ck-fake-link-selection_collapsed {\n\theight: 100%;\n\tborder-right: 1px solid var(--ck-color-base-text);\n\tmargin-right: -1px;\n\toutline: solid 1px hsla(0, 0%, 100%, .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[1].use[2]!./node_modules/@ckeditor/ckeditor5-link/theme/linkactions.css":
/*!*********************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-link/theme/linkactions.css ***!
\*********************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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-link-actions{display:flex;flex-direction:row;flex-wrap:nowrap}.ck.ck-link-actions .ck-link-actions__preview{display:inline-block}.ck.ck-link-actions .ck-link-actions__preview .ck-button__label{overflow:hidden}@media screen and (max-width:600px){.ck.ck-link-actions{flex-wrap:wrap}.ck.ck-link-actions .ck-link-actions__preview{flex-basis:100%}.ck.ck-link-actions .ck-button:not(.ck-link-actions__preview){flex-basis:50%}}.ck.ck-link-actions .ck-button.ck-link-actions__preview{padding-left:0;padding-right:0}.ck.ck-link-actions .ck-button.ck-link-actions__preview .ck-button__label{padding:0 var(--ck-spacing-medium);color:var(--ck-color-link-default);text-overflow:ellipsis;cursor:pointer;max-width:var(--ck-input-width);min-width:3em;text-align:center}.ck.ck-link-actions .ck-button.ck-link-actions__preview .ck-button__label:hover{text-decoration:underline}.ck.ck-link-actions .ck-button.ck-link-actions__preview,.ck.ck-link-actions .ck-button.ck-link-actions__preview:active,.ck.ck-link-actions .ck-button.ck-link-actions__preview:focus,.ck.ck-link-actions .ck-button.ck-link-actions__preview:hover{background:none}.ck.ck-link-actions .ck-button.ck-link-actions__preview:active{box-shadow:none}.ck.ck-link-actions .ck-button.ck-link-actions__preview:focus .ck-button__label{text-decoration:underline}[dir=ltr] .ck.ck-link-actions .ck-button:not(:first-child),[dir=rtl] .ck.ck-link-actions .ck-button:not(:last-child){margin-left:var(--ck-spacing-standard)}@media screen and (max-width:600px){.ck.ck-link-actions .ck-button.ck-link-actions__preview{margin:var(--ck-spacing-standard) var(--ck-spacing-standard) 0}.ck.ck-link-actions .ck-button.ck-link-actions__preview .ck-button__label{min-width:0;max-width:100%}[dir=ltr] .ck.ck-link-actions .ck-button:not(.ck-link-actions__preview),[dir=rtl] .ck.ck-link-actions .ck-button:not(.ck-link-actions__preview){margin-left:0}}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-link/theme/linkactions.css","webpack://./node_modules/@ckeditor/ckeditor5-ui/theme/mixins/_rwd.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/ckeditor5-link/linkactions.css"],"names":[],"mappings":"AAOA,oBACC,YAAa,CACb,kBAAmB,CACnB,gBAqBD,CAnBC,8CACC,oBAKD,CAHC,gEACC,eACD,CCXD,oCDCD,oBAcE,cAUF,CARE,8CACC,eACD,CAEA,8DACC,cACD,CCrBD,CCKA,wDACC,cAAe,CACf,eAmCD,CAjCC,0EACC,kCAAmC,CACnC,kCAAmC,CACnC,sBAAuB,CACvB,cAAe,CAIf,+BAAgC,CAChC,aAAc,CACd,iBAKD,CAHC,gFACC,yBACD,CAGD,mPAIC,eACD,CAEA,+DACC,eACD,CAGC,gFACC,yBACD,CAWD,qHACC,sCACD,CDvDD,oCC2DC,wDACC,8DAMD,CAJC,0EACC,WAAY,CACZ,cACD,CAGD,gJAME,aAEF,CD1ED","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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/_rwd.css\";\n\n.ck.ck-link-actions {\n\tdisplay: flex;\n\tflex-direction: row;\n\tflex-wrap: nowrap;\n\n\t& .ck-link-actions__preview {\n\t\tdisplay: inline-block;\n\n\t\t& .ck-button__label {\n\t\t\toverflow: hidden;\n\t\t}\n\t}\n\n\t@mixin ck-media-phone {\n\t\tflex-wrap: wrap;\n\n\t\t& .ck-link-actions__preview {\n\t\t\tflex-basis: 100%;\n\t\t}\n\n\t\t& .ck-button:not(.ck-link-actions__preview) {\n\t\t\tflex-basis: 50%;\n\t\t}\n\t}\n}\n","/*\n * Copyright (c) 2003-2022, 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@define-mixin ck-media-phone {\n\t@media screen and (max-width: 600px) {\n\t\t@mixin-content;\n\t}\n}\n","/*\n * Copyright (c) 2003-2022, 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/components/tooltip/mixins/_tooltip.css\";\n@import \"@ckeditor/ckeditor5-ui/theme/mixins/_unselectable.css\";\n@import \"@ckeditor/ckeditor5-ui/theme/mixins/_dir.css\";\n@import \"../mixins/_focus.css\";\n@import \"../mixins/_shadow.css\";\n@import \"@ckeditor/ckeditor5-ui/theme/mixins/_rwd.css\";\n\n.ck.ck-link-actions {\n\t& .ck-button.ck-link-actions__preview {\n\t\tpadding-left: 0;\n\t\tpadding-right: 0;\n\n\t\t& .ck-button__label {\n\t\t\tpadding: 0 var(--ck-spacing-medium);\n\t\t\tcolor: var(--ck-color-link-default);\n\t\t\ttext-overflow: ellipsis;\n\t\t\tcursor: pointer;\n\n\t\t\t/* Match the box model of the link editor form's input so the balloon\n\t\t\tdoes not change width when moving between actions and the form. */\n\t\t\tmax-width: var(--ck-input-width);\n\t\t\tmin-width: 3em;\n\t\t\ttext-align: center;\n\n\t\t\t&:hover {\n\t\t\t\ttext-decoration: underline;\n\t\t\t}\n\t\t}\n\n\t\t&,\n\t\t&:hover,\n\t\t&:focus,\n\t\t&:active {\n\t\t\tbackground: none;\n\t\t}\n\n\t\t&:active {\n\t\t\tbox-shadow: none;\n\t\t}\n\n\t\t&:focus {\n\t\t\t& .ck-button__label {\n\t\t\t\ttext-decoration: underline;\n\t\t\t}\n\t\t}\n\t}\n\n\t@mixin ck-dir ltr {\n\t\t& .ck-button:not(:first-child) {\n\t\t\tmargin-left: var(--ck-spacing-standard);\n\t\t}\n\t}\n\n\t@mixin ck-dir rtl {\n\t\t& .ck-button:not(:last-child) {\n\t\t\tmargin-left: var(--ck-spacing-standard);\n\t\t}\n\t}\n\n\t@mixin ck-media-phone {\n\t\t& .ck-button.ck-link-actions__preview {\n\t\t\tmargin: var(--ck-spacing-standard) var(--ck-spacing-standard) 0;\n\n\t\t\t& .ck-button__label {\n\t\t\t\tmin-width: 0;\n\t\t\t\tmax-width: 100%;\n\t\t\t}\n\t\t}\n\n\t\t& .ck-button:not(.ck-link-actions__preview) {\n\t\t\t@mixin ck-dir ltr {\n\t\t\t\tmargin-left: 0;\n\t\t\t}\n\n\t\t\t@mixin ck-dir rtl {\n\t\t\t\tmargin-left: 0;\n\t\t\t}\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-link/theme/linkform.css":
/*!******************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-link/theme/linkform.css ***!
\******************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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-link-form{display:flex}.ck.ck-link-form .ck-label{display:none}@media screen and (max-width:600px){.ck.ck-link-form{flex-wrap:wrap}.ck.ck-link-form .ck-labeled-field-view{flex-basis:100%}.ck.ck-link-form .ck-button{flex-basis:50%}}.ck.ck-link-form_layout-vertical{display:block}.ck.ck-link-form_layout-vertical .ck-button.ck-button-cancel,.ck.ck-link-form_layout-vertical .ck-button.ck-button-save{margin-top:var(--ck-spacing-medium)}.ck.ck-link-form_layout-vertical{padding:0;min-width:var(--ck-input-width)}.ck.ck-link-form_layout-vertical .ck-labeled-field-view{margin:var(--ck-spacing-large) var(--ck-spacing-large) var(--ck-spacing-small)}.ck.ck-link-form_layout-vertical .ck-labeled-field-view .ck-input-text{min-width:0;width:100%}.ck.ck-link-form_layout-vertical .ck-button{padding:var(--ck-spacing-standard);margin:0;border-radius:0;border:0;border-top:1px solid var(--ck-color-base-border);width:50%}[dir=ltr] .ck.ck-link-form_layout-vertical .ck-button,[dir=rtl] .ck.ck-link-form_layout-vertical .ck-button{margin-left:0}[dir=rtl] .ck.ck-link-form_layout-vertical .ck-button:last-of-type{border-right:1px solid var(--ck-color-base-border)}.ck.ck-link-form_layout-vertical .ck.ck-list{margin:var(--ck-spacing-standard) var(--ck-spacing-large)}.ck.ck-link-form_layout-vertical .ck.ck-list .ck-button.ck-switchbutton{border:0;padding:0;width:100%}.ck.ck-link-form_layout-vertical .ck.ck-list .ck-button.ck-switchbutton:hover{background:none}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-link/theme/linkform.css","webpack://./node_modules/@ckeditor/ckeditor5-ui/theme/mixins/_rwd.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/ckeditor5-link/linkform.css"],"names":[],"mappings":"AAOA,iBACC,YAiBD,CAfC,2BACC,YACD,CCNA,oCDCD,iBAQE,cAUF,CARE,wCACC,eACD,CAEA,4BACC,cACD,CCfD,CDuBD,iCACC,aAYD,CALE,wHAEC,mCACD,CE/BF,iCACC,SAAU,CACV,+BA8CD,CA5CC,wDACC,8EAMD,CAJC,uEACC,WAAY,CACZ,UACD,CAGD,4CACC,kCAAmC,CACnC,QAAS,CACT,eAAgB,CAChB,QAAS,CACT,gDAAiD,CACjD,SAaD,CAnBA,4GAaE,aAMF,CAJE,mEACC,kDACD,CAKF,6CACC,yDAWD,CATC,wEACC,QAAS,CACT,SAAU,CACV,UAKD,CAHC,8EACC,eACD","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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/_rwd.css\";\n\n.ck.ck-link-form {\n\tdisplay: flex;\n\n\t& .ck-label {\n\t\tdisplay: none;\n\t}\n\n\t@mixin ck-media-phone {\n\t\tflex-wrap: wrap;\n\n\t\t& .ck-labeled-field-view {\n\t\t\tflex-basis: 100%;\n\t\t}\n\n\t\t& .ck-button {\n\t\t\tflex-basis: 50%;\n\t\t}\n\t}\n}\n\n/*\n * Style link form differently when manual decorators are available.\n * See: https://github.com/ckeditor/ckeditor5-link/issues/186.\n */\n.ck.ck-link-form_layout-vertical {\n\tdisplay: block;\n\n\t/*\n\t * Whether the form is in the responsive mode or not, if there are decorator buttons\n\t * keep the top margin of action buttons medium.\n\t */\n\t& .ck-button {\n\t\t&.ck-button-save,\n\t\t&.ck-button-cancel {\n\t\t\tmargin-top: var(--ck-spacing-medium);\n\t\t}\n\t}\n}\n","/*\n * Copyright (c) 2003-2022, 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@define-mixin ck-media-phone {\n\t@media screen and (max-width: 600px) {\n\t\t@mixin-content;\n\t}\n}\n","/*\n * Copyright (c) 2003-2022, 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/*\n * Style link form differently when manual decorators are available.\n * See: https://github.com/ckeditor/ckeditor5-link/issues/186.\n */\n.ck.ck-link-form_layout-vertical {\n\tpadding: 0;\n\tmin-width: var(--ck-input-width);\n\n\t& .ck-labeled-field-view {\n\t\tmargin: var(--ck-spacing-large) var(--ck-spacing-large) var(--ck-spacing-small);\n\n\t\t& .ck-input-text {\n\t\t\tmin-width: 0;\n\t\t\twidth: 100%;\n\t\t}\n\t}\n\n\t& .ck-button {\n\t\tpadding: var(--ck-spacing-standard);\n\t\tmargin: 0;\n\t\tborder-radius: 0;\n\t\tborder: 0;\n\t\tborder-top: 1px solid var(--ck-color-base-border);\n\t\twidth: 50%;\n\n\t\t@mixin ck-dir ltr {\n\t\t\tmargin-left: 0;\n\t\t}\n\n\t\t@mixin ck-dir rtl {\n\t\t\tmargin-left: 0;\n\n\t\t\t&:last-of-type {\n\t\t\t\tborder-right: 1px solid var(--ck-color-base-border);\n\t\t\t}\n\t\t}\n\t}\n\n\t/* Using additional `.ck` class for stronger CSS specificity than `.ck.ck-link-form > :not(:first-child)`. */\n\t& .ck.ck-list {\n\t\tmargin: var(--ck-spacing-standard) var(--ck-spacing-large);\n\n\t\t& .ck-button.ck-switchbutton {\n\t\t\tborder: 0;\n\t\t\tpadding: 0;\n\t\t\twidth: 100%;\n\n\t\t\t&:hover {\n\t\t\t\tbackground: none;\n\t\t\t}\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-link/theme/linkimage.css":
/*!*******************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-link/theme/linkimage.css ***!
\*******************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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 a span.image-inline:after,.ck.ck-editor__editable figure.image>a:after{display:block;position:absolute}:root{--ck-link-image-indicator-icon-size:20;--ck-link-image-indicator-icon-is-visible:clamp(0px,100% - 50px,1px)}.ck.ck-editor__editable a span.image-inline:after,.ck.ck-editor__editable figure.image>a:after{content:\"\";top:min(var(--ck-spacing-medium),6%);right:min(var(--ck-spacing-medium),6%);background-color:rgba(0,0,0,.4);background-image:url(\"data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjAgMjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTExLjA3NyAxNWwuOTkxLTEuNDE2YS43NS43NSAwIDExMS4yMjkuODZsLTEuMTQ4IDEuNjRhLjc0OC43NDggMCAwMS0uMjE3LjIwNiA1LjI1MSA1LjI1MSAwIDAxLTguNTAzLTUuOTU1Ljc0MS43NDEgMCAwMS4xMi0uMjc0bDEuMTQ3LTEuNjM5YS43NS43NSAwIDExMS4yMjguODZMNC45MzMgMTAuN2wuMDA2LjAwM2EzLjc1IDMuNzUgMCAwMDYuMTMyIDQuMjk0bC4wMDYuMDA0em01LjQ5NC01LjMzNWEuNzQ4Ljc0OCAwIDAxLS4xMi4yNzRsLTEuMTQ3IDEuNjM5YS43NS43NSAwIDExLTEuMjI4LS44NmwuODYtMS4yM2EzLjc1IDMuNzUgMCAwMC02LjE0NC00LjMwMWwtLjg2IDEuMjI5YS43NS43NSAwIDAxLTEuMjI5LS44NmwxLjE0OC0xLjY0YS43NDguNzQ4IDAgMDEuMjE3LS4yMDYgNS4yNTEgNS4yNTEgMCAwMTguNTAzIDUuOTU1em0tNC41NjMtMi41MzJhLjc1Ljc1IDAgMDEuMTg0IDEuMDQ1bC0zLjE1NSA0LjUwNWEuNzUuNzUgMCAxMS0xLjIyOS0uODZsMy4xNTUtNC41MDZhLjc1Ljc1IDAgMDExLjA0NS0uMTg0eiIvPjwvc3ZnPg==\");background-size:14px;background-repeat:no-repeat;background-position:50%;border-radius:100%;overflow:hidden;width:calc(var(--ck-link-image-indicator-icon-is-visible)*var(--ck-link-image-indicator-icon-size));height:calc(var(--ck-link-image-indicator-icon-is-visible)*var(--ck-link-image-indicator-icon-size))}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-link/theme/linkimage.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/ckeditor5-link/linkimage.css"],"names":[],"mappings":"AASE,+FACC,aAAc,CACd,iBACD,CCPF,MAEC,sCAAuC,CACvC,oEACD,CAME,+FACC,UAAW,CAMX,oCAAsC,CACtC,sCAAwC,CAExC,+BAAqC,CACrC,k2BAA+3B,CAC/3B,oBAAqB,CACrB,2BAA4B,CAC5B,uBAA2B,CAC3B,kBAAmB,CAMnB,eAAgB,CAChB,mGAAsG,CACtG,oGACD","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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/* Linked image indicator */\n\t& figure.image > a,\n\t& a span.image-inline {\n\t\t&::after {\n\t\t\tdisplay: block;\n\t\t\tposition: absolute;\n\t\t}\n\t}\n}\n\n","/*\n * Copyright (c) 2003-2022, 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/* Match the icon size with the upload indicator brought by the image upload feature. */\n\t--ck-link-image-indicator-icon-size: 20;\n\t--ck-link-image-indicator-icon-is-visible: clamp(0px, 100% - 50px, 1px);\n}\n\n.ck.ck-editor__editable {\n\t/* Linked image indicator */\n\t& figure.image > a,\n\t& a span.image-inline {\n\t\t&::after {\n\t\t\tcontent: \"\";\n\n\t\t\t/*\n\t\t\t * Smaller images should have the icon closer to the border.\n\t\t\t * Match the icon position with the upload indicator brought by the image upload feature.\n\t\t\t */\n\t\t\ttop: min(var(--ck-spacing-medium), 6%);\n\t\t\tright: min(var(--ck-spacing-medium), 6%);\n\n\t\t\tbackground-color: hsla(0, 0%, 0%, .4);\n\t\t\tbackground-image: url(\"data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjAgMjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbD0iI2ZmZiIgZD0ibTExLjA3NyAxNSAuOTkxLTEuNDE2YS43NS43NSAwIDEgMSAxLjIyOS44NmwtMS4xNDggMS42NGEuNzQ4Ljc0OCAwIDAgMS0uMjE3LjIwNiA1LjI1MSA1LjI1MSAwIDAgMS04LjUwMy01Ljk1NS43NDEuNzQxIDAgMCAxIC4xMi0uMjc0bDEuMTQ3LTEuNjM5YS43NS43NSAwIDEgMSAxLjIyOC44Nkw0LjkzMyAxMC43bC4wMDYuMDAzYTMuNzUgMy43NSAwIDAgMCA2LjEzMiA0LjI5NGwuMDA2LjAwNHptNS40OTQtNS4zMzVhLjc0OC43NDggMCAwIDEtLjEyLjI3NGwtMS4xNDcgMS42MzlhLjc1Ljc1IDAgMSAxLTEuMjI4LS44NmwuODYtMS4yM2EzLjc1IDMuNzUgMCAwIDAtNi4xNDQtNC4zMDFsLS44NiAxLjIyOWEuNzUuNzUgMCAwIDEtMS4yMjktLjg2bDEuMTQ4LTEuNjRhLjc0OC43NDggMCAwIDEgLjIxNy0uMjA2IDUuMjUxIDUuMjUxIDAgMCAxIDguNTAzIDUuOTU1em0tNC41NjMtMi41MzJhLjc1Ljc1IDAgMCAxIC4xODQgMS4wNDVsLTMuMTU1IDQuNTA1YS43NS43NSAwIDEgMS0xLjIyOS0uODZsMy4xNTUtNC41MDZhLjc1Ljc1IDAgMCAxIDEuMDQ1LS4xODR6Ii8+PC9zdmc+\");\n\t\t\tbackground-size: 14px;\n\t\t\tbackground-repeat: no-repeat;\n\t\t\tbackground-position: center;\n\t\t\tborder-radius: 100%;\n\n\t\t\t/*\n\t\t\t* Use CSS math to simulate container queries.\n\t\t\t* https://css-tricks.com/the-raven-technique-one-step-closer-to-container-queries/#what-about-showing-and-hiding-things\n\t\t\t*/\n\t\t\toverflow: hidden;\n\t\t\twidth: calc(var(--ck-link-image-indicator-icon-is-visible) * var(--ck-link-image-indicator-icon-size));\n\t\t\theight: calc(var(--ck-link-image-indicator-icon-is-visible) * var(--ck-link-image-indicator-icon-size));\n\t\t}\n\t}\n}\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-media-embed/theme/mediaembed.css":
/*!***************************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-media-embed/theme/mediaembed.css ***!
\***************************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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 .media{clear:both;margin:.9em 0;display:block;min-width:15em}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-media-embed/theme/mediaembed.css"],"names":[],"mappings":"AAKA,mBAGC,UAAW,CAKX,aAAe,CAIf,aAAc,CAId,cACD","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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 .media {\n\t/* Don't allow floated content overlap the media.\n\thttps://github.com/ckeditor/ckeditor5-media-embed/issues/53 */\n\tclear: both;\n\n\t/* Make sure there is some space between the content and the media. */\n\t/* The first value should be equal to --ck-spacing-large variable if used in the editor context\n\tto avoid the content jumping (See https://github.com/ckeditor/ckeditor5/issues/9825). */\n\tmargin: 0.9em 0;\n\n\t/* Make sure media is not overriden with Bootstrap default `flex` value.\n\tSee: https://github.com/ckeditor/ckeditor5/issues/1373. */\n\tdisplay: block;\n\n\t/* Give the media some minimal width in the content to prevent them\n\tfrom being \"squashed\" in tight spaces, e.g. in table cells (#44) */\n\tmin-width: 15em;\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-media-embed/theme/mediaembedediting.css":
/*!**********************************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-media-embed/theme/mediaembedediting.css ***!
\**********************************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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-media__wrapper .ck-media__placeholder{display:flex;flex-direction:column;align-items:center}.ck-media__wrapper .ck-media__placeholder .ck-media__placeholder__url .ck-tooltip{display:block}@media (hover:none){.ck-media__wrapper .ck-media__placeholder .ck-media__placeholder__url .ck-tooltip{display:none}}.ck-media__wrapper .ck-media__placeholder .ck-media__placeholder__url{max-width:100%;position:relative}.ck-media__wrapper .ck-media__placeholder .ck-media__placeholder__url:hover .ck-tooltip{visibility:visible;opacity:1}.ck-media__wrapper .ck-media__placeholder .ck-media__placeholder__url .ck-media__placeholder__url__text{overflow:hidden;display:block}.ck-media__wrapper[data-oembed-url*=\"facebook.com\"] .ck-media__placeholder__icon *,.ck-media__wrapper[data-oembed-url*=\"goo.gl/maps\"] .ck-media__placeholder__icon *,.ck-media__wrapper[data-oembed-url*=\"google.com/maps\"] .ck-media__placeholder__icon *,.ck-media__wrapper[data-oembed-url*=\"instagram.com\"] .ck-media__placeholder__icon *,.ck-media__wrapper[data-oembed-url*=\"maps.app.goo.gl\"] .ck-media__placeholder__icon *,.ck-media__wrapper[data-oembed-url*=\"maps.google.com\"] .ck-media__placeholder__icon *,.ck-media__wrapper[data-oembed-url*=\"twitter.com\"] .ck-media__placeholder__icon *{display:none}.ck-editor__editable:not(.ck-read-only) .ck-media__wrapper>:not(.ck-media__placeholder),.ck-editor__editable:not(.ck-read-only) .ck-widget:not(.ck-widget_selected) .ck-media__placeholder{pointer-events:none}:root{--ck-media-embed-placeholder-icon-size:3em;--ck-color-media-embed-placeholder-url-text:#757575;--ck-color-media-embed-placeholder-url-text-hover:var(--ck-color-base-text)}.ck-media__wrapper{margin:0 auto}.ck-media__wrapper .ck-media__placeholder{padding:calc(var(--ck-spacing-standard)*3);background:var(--ck-color-base-foreground)}.ck-media__wrapper .ck-media__placeholder .ck-media__placeholder__icon{min-width:var(--ck-media-embed-placeholder-icon-size);height:var(--ck-media-embed-placeholder-icon-size);margin-bottom:var(--ck-spacing-large);background-position:50%;background-size:cover}.ck-media__wrapper .ck-media__placeholder .ck-media__placeholder__icon .ck-icon{width:100%;height:100%}.ck-media__wrapper .ck-media__placeholder .ck-media__placeholder__url__text{color:var(--ck-color-media-embed-placeholder-url-text);white-space:nowrap;text-align:center;font-style:italic;text-overflow:ellipsis}.ck-media__wrapper .ck-media__placeholder .ck-media__placeholder__url__text:hover{color:var(--ck-color-media-embed-placeholder-url-text-hover);cursor:pointer;text-decoration:underline}.ck-media__wrapper[data-oembed-url*=\"open.spotify.com\"]{max-width:300px;max-height:380px}.ck-media__wrapper[data-oembed-url*=\"goo.gl/maps\"] .ck-media__placeholder__icon,.ck-media__wrapper[data-oembed-url*=\"google.com/maps\"] .ck-media__placeholder__icon,.ck-media__wrapper[data-oembed-url*=\"maps.app.goo.gl\"] .ck-media__placeholder__icon,.ck-media__wrapper[data-oembed-url*=\"maps.google.com\"] .ck-media__placeholder__icon{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNTAuMzc4IiBoZWlnaHQ9IjI1NC4xNjciIHZpZXdCb3g9IjAgMCA2Ni4yNDYgNjcuMjQ4Ij48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMTcyLjUzMSAtMjE4LjQ1NSkgc2NhbGUoLjk4MDEyKSI+PHJlY3Qgcnk9IjUuMjM4IiByeD0iNS4yMzgiIHk9IjIzMS4zOTkiIHg9IjE3Ni4wMzEiIGhlaWdodD0iNjAuMDk5IiB3aWR0aD0iNjAuMDk5IiBmaWxsPSIjMzRhNjY4IiBwYWludC1vcmRlcj0ibWFya2VycyBzdHJva2UgZmlsbCIvPjxwYXRoIGQ9Ik0yMDYuNDc3IDI2MC45bC0yOC45ODcgMjguOTg3YTUuMjE4IDUuMjE4IDAgMDAzLjc4IDEuNjFoNDkuNjIxYzEuNjk0IDAgMy4xOS0uNzk4IDQuMTQ2LTIuMDM3eiIgZmlsbD0iIzVjODhjNSIvPjxwYXRoIGQ9Ik0yMjYuNzQyIDIyMi45ODhjLTkuMjY2IDAtMTYuNzc3IDcuMTctMTYuNzc3IDE2LjAxNC4wMDcgMi43NjIuNjYzIDUuNDc0IDIuMDkzIDcuODc1LjQzLjcwMy44MyAxLjQwOCAxLjE5IDIuMTA3LjMzMy41MDIuNjUgMS4wMDUuOTUgMS41MDguMzQzLjQ3Ny42NzMuOTU3Ljk4OCAxLjQ0IDEuMzEgMS43NjkgMi41IDMuNTAyIDMuNjM3IDUuMTY4Ljc5MyAxLjI3NSAxLjY4MyAyLjY0IDIuNDY2IDMuOTkgMi4zNjMgNC4wOTQgNC4wMDcgOC4wOTIgNC42IDEzLjkxNHYuMDEyYy4xODIuNDEyLjUxNi42NjYuODc5LjY2Ny40MDMtLjAwMS43NjgtLjMxNC45My0uNzk5LjYwMy01Ljc1NiAyLjIzOC05LjcyOSA0LjU4NS0xMy43OTQuNzgyLTEuMzUgMS42NzMtMi43MTUgMi40NjUtMy45OSAxLjEzNy0xLjY2NiAyLjMyOC0zLjQgMy42MzgtNS4xNjkuMzE1LS40ODIuNjQ1LS45NjIuOTg4LTEuNDM5LjMtLjUwMy42MTctMS4wMDYuOTUtMS41MDguMzU5LS43Ljc2LTEuNDA0IDEuMTktMi4xMDcgMS40MjYtMi40MDIgMi01LjExNCAyLjAwNC03Ljg3NSAwLTguODQ0LTcuNTExLTE2LjAxNC0xNi43NzYtMTYuMDE0eiIgZmlsbD0iI2RkNGIzZSIgcGFpbnQtb3JkZXI9Im1hcmtlcnMgc3Ryb2tlIGZpbGwiLz48ZWxsaXBzZSByeT0iNS41NjQiIHJ4PSI1LjgyOCIgY3k9IjIzOS4wMDIiIGN4PSIyMjYuNzQyIiBmaWxsPSIjODAyZDI3IiBwYWludC1vcmRlcj0ibWFya2VycyBzdHJva2UgZmlsbCIvPjxwYXRoIGQ9Ik0xOTAuMzAxIDIzNy4yODNjLTQuNjcgMC04LjQ1NyAzLjg1My04LjQ1NyA4LjYwNnMzLjc4NiA4LjYwNyA4LjQ1NyA4LjYwN2MzLjA0MyAwIDQuODA2LS45NTggNi4zMzctMi41MTYgMS41My0xLjU1NyAyLjA4Ny0zLjkxMyAyLjA4Ny02LjI5IDAtLjM2Mi0uMDIzLS43MjItLjA2NC0xLjA3OWgtOC4yNTd2My4wNDNoNC44NWMtLjE5Ny43NTktLjUzMSAxLjQ1LTEuMDU4IDEuOTg2LS45NDIuOTU4LTIuMDI4IDEuNTQ4LTMuOTAxIDEuNTQ4LTIuODc2IDAtNS4yMDgtMi4zNzItNS4yMDgtNS4yOTkgMC0yLjkyNiAyLjMzMi01LjI5OSA1LjIwOC01LjI5OSAxLjM5OSAwIDIuNjE4LjQwNyAzLjU4NCAxLjI5M2wyLjM4MS0yLjM4YzAtLjAwMi0uMDAzLS4wMDQtLjAwNC0uMDA1LTEuNTg4LTEuNTI0LTMuNjItMi4yMTUtNS45NTUtMi4yMTV6bTQuNDMgNS42NmwuMDAzLjAwNnYtLjAwM3oiIGZpbGw9IiNmZmYiIHBhaW50LW9yZGVyPSJtYXJrZXJzIHN0cm9rZSBmaWxsIi8+PHBhdGggZD0iTTIxNS4xODQgMjUxLjkyOWwtNy45OCA3Ljk3OSAyOC40NzcgMjguNDc1YTUuMjMzIDUuMjMzIDAgMDAuNDQ5LTIuMTIzdi0zMS4xNjVjLS40NjkuNjc1LS45MzQgMS4zNDktMS4zODIgMi4wMDUtLjc5MiAxLjI3NS0xLjY4MiAyLjY0LTIuNDY1IDMuOTktMi4zNDcgNC4wNjUtMy45ODIgOC4wMzgtNC41ODUgMTMuNzk0LS4xNjIuNDg1LS41MjcuNzk4LS45My43OTktLjM2My0uMDAxLS42OTctLjI1NS0uODc5LS42Njd2LS4wMTJjLS41OTMtNS44MjItMi4yMzctOS44Mi00LjYtMTMuOTE0LS43ODMtMS4zNS0xLjY3My0yLjcxNS0yLjQ2Ni0zLjk5LTEuMTM3LTEuNjY2LTIuMzI3LTMuNC0zLjYzNy01LjE2OWwtLjAwMi0uMDAzeiIgZmlsbD0iI2MzYzNjMyIvPjxwYXRoIGQ9Ik0yMTIuOTgzIDI0OC40OTVsLTM2Ljk1MiAzNi45NTN2LjgxMmE1LjIyNyA1LjIyNyAwIDAwNS4yMzggNS4yMzhoMS4wMTVsMzUuNjY2LTM1LjY2NmExMzYuMjc1IDEzNi4yNzUgMCAwMC0yLjc2NC0zLjkgMzcuNTc1IDM3LjU3NSAwIDAwLS45ODktMS40NCAzNS4xMjcgMzUuMTI3IDAgMDAtLjk1LTEuNTA4Yy0uMDgzLS4xNjItLjE3Ni0uMzI2LS4yNjQtLjQ4OXoiIGZpbGw9IiNmZGRjNGYiIHBhaW50LW9yZGVyPSJtYXJrZXJzIHN0cm9rZSBmaWxsIi8+PHBhdGggZD0iTTIxMS45OTggMjYxLjA4M2wtNi4xNTIgNi4xNTEgMjQuMjY0IDI0LjI2NGguNzgxYTUuMjI3IDUuMjI3IDAgMDA1LjIzOS01LjIzOHYtMS4wNDV6IiBmaWxsPSIjZmZmIiBwYWludC1vcmRlcj0ibWFya2VycyBzdHJva2UgZmlsbCIvPjwvZz48L3N2Zz4=)}.ck-media__wrapper[data-oembed-url*=\"facebook.com\"] .ck-media__placeholder{background:#4268b3}.ck-media__wrapper[data-oembed-url*=\"facebook.com\"] .ck-media__placeholder .ck-media__placeholder__icon{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAyNCIgaGVpZ2h0PSIxMDI0IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Ik05NjcuNDg0IDBINTYuNTE3QzI1LjMwNCAwIDAgMjUuMzA0IDAgNTYuNTE3djkxMC45NjZDMCA5OTguNjk0IDI1LjI5NyAxMDI0IDU2LjUyMiAxMDI0SDU0N1Y2MjhINDE0VjQ3M2gxMzNWMzU5LjAyOWMwLTEzMi4yNjIgODAuNzczLTIwNC4yODIgMTk4Ljc1Ni0yMDQuMjgyIDU2LjUxMyAwIDEwNS4wODYgNC4yMDggMTE5LjI0NCA2LjA4OVYyOTlsLTgxLjYxNi4wMzdjLTYzLjk5MyAwLTc2LjM4NCAzMC40OTItNzYuMzg0IDc1LjIzNlY0NzNoMTUzLjQ4N2wtMTkuOTg2IDE1NUg3MDd2Mzk2aDI2MC40ODRjMzEuMjEzIDAgNTYuNTE2LTI1LjMwMyA1Ni41MTYtNTYuNTE2VjU2LjUxNUMxMDI0IDI1LjMwMyA5OTguNjk3IDAgOTY3LjQ4NCAwIiBmaWxsPSIjRkZGRkZFIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiLz48L3N2Zz4=)}.ck-media__wrapper[data-oembed-url*=\"facebook.com\"] .ck-media__placeholder .ck-media__placeholder__url__text{color:#cdf}.ck-media__wrapper[data-oembed-url*=\"facebook.com\"] .ck-media__placeholder .ck-media__placeholder__url__text:hover{color:#fff}.ck-media__wrapper[data-oembed-url*=\"instagram.com\"] .ck-media__placeholder{background:linear-gradient(-135deg,#1400c7,#b800b1,#f50000)}.ck-media__wrapper[data-oembed-url*=\"instagram.com\"] .ck-media__placeholder .ck-media__placeholder__icon{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTA0IiBoZWlnaHQ9IjUwNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+PGRlZnM+PHBhdGggaWQ9ImEiIGQ9Ik0wIC4xNTloNTAzLjg0MVY1MDMuOTRIMHoiLz48L2RlZnM+PGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj48bWFzayBpZD0iYiIgZmlsbD0iI2ZmZiI+PHVzZSB4bGluazpocmVmPSIjYSIvPjwvbWFzaz48cGF0aCBkPSJNMjUxLjkyMS4xNTljLTY4LjQxOCAwLTc2Ljk5Ny4yOS0xMDMuODY3IDEuNTE2LTI2LjgxNCAxLjIyMy00NS4xMjcgNS40ODItNjEuMTUxIDExLjcxLTE2LjU2NiA2LjQzNy0zMC42MTUgMTUuMDUxLTQ0LjYyMSAyOS4wNTYtMTQuMDA1IDE0LjAwNi0yMi42MTkgMjguMDU1LTI5LjA1NiA0NC42MjEtNi4yMjggMTYuMDI0LTEwLjQ4NyAzNC4zMzctMTEuNzEgNjEuMTUxQy4yOSAxNzUuMDgzIDAgMTgzLjY2MiAwIDI1Mi4wOGMwIDY4LjQxNy4yOSA3Ni45OTYgMS41MTYgMTAzLjg2NiAxLjIyMyAyNi44MTQgNS40ODIgNDUuMTI3IDExLjcxIDYxLjE1MSA2LjQzNyAxNi41NjYgMTUuMDUxIDMwLjYxNSAyOS4wNTYgNDQuNjIxIDE0LjAwNiAxNC4wMDUgMjguMDU1IDIyLjYxOSA0NC42MjEgMjkuMDU3IDE2LjAyNCA2LjIyNyAzNC4zMzcgMTAuNDg2IDYxLjE1MSAxMS43MDkgMjYuODcgMS4yMjYgMzUuNDQ5IDEuNTE2IDEwMy44NjcgMS41MTYgNjguNDE3IDAgNzYuOTk2LS4yOSAxMDMuODY2LTEuNTE2IDI2LjgxNC0xLjIyMyA0NS4xMjctNS40ODIgNjEuMTUxLTExLjcwOSAxNi41NjYtNi40MzggMzAuNjE1LTE1LjA1MiA0NC42MjEtMjkuMDU3IDE0LjAwNS0xNC4wMDYgMjIuNjE5LTI4LjA1NSAyOS4wNTctNDQuNjIxIDYuMjI3LTE2LjAyNCAxMC40ODYtMzQuMzM3IDExLjcwOS02MS4xNTEgMS4yMjYtMjYuODcgMS41MTYtMzUuNDQ5IDEuNTE2LTEwMy44NjYgMC02OC40MTgtLjI5LTc2Ljk5Ny0xLjUxNi0xMDMuODY3LTEuMjIzLTI2LjgxNC01LjQ4Mi00NS4xMjctMTEuNzA5LTYxLjE1MS02LjQzOC0xNi41NjYtMTUuMDUyLTMwLjYxNS0yOS4wNTctNDQuNjIxLTE0LjAwNi0xNC4wMDUtMjguMDU1LTIyLjYxOS00NC42MjEtMjkuMDU2LTE2LjAyNC02LjIyOC0zNC4zMzctMTAuNDg3LTYxLjE1MS0xMS43MUMzMjguOTE3LjQ0OSAzMjAuMzM4LjE1OSAyNTEuOTIxLjE1OXptMCA0NS4zOTFjNjcuMjY1IDAgNzUuMjMzLjI1NyAxMDEuNzk3IDEuNDY5IDI0LjU2MiAxLjEyIDM3LjkwMSA1LjIyNCA0Ni43NzggOC42NzQgMTEuNzU5IDQuNTcgMjAuMTUxIDEwLjAyOSAyOC45NjYgMTguODQ1IDguODE2IDguODE1IDE0LjI3NSAxNy4yMDcgMTguODQ1IDI4Ljk2NiAzLjQ1IDguODc3IDcuNTU0IDIyLjIxNiA4LjY3NCA0Ni43NzggMS4yMTIgMjYuNTY0IDEuNDY5IDM0LjUzMiAxLjQ2OSAxMDEuNzk4IDAgNjcuMjY1LS4yNTcgNzUuMjMzLTEuNDY5IDEwMS43OTctMS4xMiAyNC41NjItNS4yMjQgMzcuOTAxLTguNjc0IDQ2Ljc3OC00LjU3IDExLjc1OS0xMC4wMjkgMjAuMTUxLTE4Ljg0NSAyOC45NjYtOC44MTUgOC44MTYtMTcuMjA3IDE0LjI3NS0yOC45NjYgMTguODQ1LTguODc3IDMuNDUtMjIuMjE2IDcuNTU0LTQ2Ljc3OCA4LjY3NC0yNi41NiAxLjIxMi0zNC41MjcgMS40NjktMTAxLjc5NyAxLjQ2OS02Ny4yNzEgMC03NS4yMzctLjI1Ny0xMDEuNzk4LTEuNDY5LTI0LjU2Mi0xLjEyLTM3LjkwMS01LjIyNC00Ni43NzgtOC42NzQtMTEuNzU5LTQuNTctMjAuMTUxLTEwLjAyOS0yOC45NjYtMTguODQ1LTguODE1LTguODE1LTE0LjI3NS0xNy4yMDctMTguODQ1LTI4Ljk2Ni0zLjQ1LTguODc3LTcuNTU0LTIyLjIxNi04LjY3NC00Ni43NzgtMS4yMTItMjYuNTY0LTEuNDY5LTM0LjUzMi0xLjQ2OS0xMDEuNzk3IDAtNjcuMjY2LjI1Ny03NS4yMzQgMS40NjktMTAxLjc5OCAxLjEyLTI0LjU2MiA1LjIyNC0zNy45MDEgOC42NzQtNDYuNzc4IDQuNTctMTEuNzU5IDEwLjAyOS0yMC4xNTEgMTguODQ1LTI4Ljk2NiA4LjgxNS04LjgxNiAxNy4yMDctMTQuMjc1IDI4Ljk2Ni0xOC44NDUgOC44NzctMy40NSAyMi4yMTYtNy41NTQgNDYuNzc4LTguNjc0IDI2LjU2NC0xLjIxMiAzNC41MzItMS40NjkgMTAxLjc5OC0xLjQ2OXoiIGZpbGw9IiNGRkYiIG1hc2s9InVybCgjYikiLz48cGF0aCBkPSJNMjUxLjkyMSAzMzYuMDUzYy00Ni4zNzggMC04My45NzQtMzcuNTk2LTgzLjk3NC04My45NzMgMC00Ni4zNzggMzcuNTk2LTgzLjk3NCA4My45NzQtODMuOTc0IDQ2LjM3NyAwIDgzLjk3MyAzNy41OTYgODMuOTczIDgzLjk3NCAwIDQ2LjM3Ny0zNy41OTYgODMuOTczLTgzLjk3MyA4My45NzN6bTAtMjEzLjMzOGMtNzEuNDQ3IDAtMTI5LjM2NSA1Ny45MTgtMTI5LjM2NSAxMjkuMzY1IDAgNzEuNDQ2IDU3LjkxOCAxMjkuMzY0IDEyOS4zNjUgMTI5LjM2NCA3MS40NDYgMCAxMjkuMzY0LTU3LjkxOCAxMjkuMzY0LTEyOS4zNjQgMC03MS40NDctNTcuOTE4LTEyOS4zNjUtMTI5LjM2NC0xMjkuMzY1ek00MTYuNjI3IDExNy42MDRjMCAxNi42OTYtMTMuNTM1IDMwLjIzLTMwLjIzMSAzMC4yMy0xNi42OTUgMC0zMC4yMy0xMy41MzQtMzAuMjMtMzAuMjMgMC0xNi42OTYgMTMuNTM1LTMwLjIzMSAzMC4yMy0zMC4yMzEgMTYuNjk2IDAgMzAuMjMxIDEzLjUzNSAzMC4yMzEgMzAuMjMxIiBmaWxsPSIjRkZGIi8+PC9nPjwvc3ZnPg==)}.ck-media__wrapper[data-oembed-url*=\"instagram.com\"] .ck-media__placeholder .ck-media__placeholder__url__text{color:#ffe0fe}.ck-media__wrapper[data-oembed-url*=\"instagram.com\"] .ck-media__placeholder .ck-media__placeholder__url__text:hover{color:#fff}.ck-media__wrapper[data-oembed-url*=\"twitter.com\"] .ck.ck-media__placeholder{background:linear-gradient(90deg,#71c6f4,#0d70a5)}.ck-media__wrapper[data-oembed-url*=\"twitter.com\"] .ck.ck-media__placeholder .ck-media__placeholder__icon{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0MDAgNDAwIj48cGF0aCBkPSJNNDAwIDIwMGMwIDExMC41LTg5LjUgMjAwLTIwMCAyMDBTMCAzMTAuNSAwIDIwMCA4OS41IDAgMjAwIDBzMjAwIDg5LjUgMjAwIDIwMHpNMTYzLjQgMzA1LjVjODguNyAwIDEzNy4yLTczLjUgMTM3LjItMTM3LjIgMC0yLjEgMC00LjItLjEtNi4yIDkuNC02LjggMTcuNi0xNS4zIDI0LjEtMjUtOC42IDMuOC0xNy45IDYuNC0yNy43IDcuNiAxMC02IDE3LjYtMTUuNCAyMS4yLTI2LjctOS4zIDUuNS0xOS42IDkuNS0zMC42IDExLjctOC44LTkuNC0yMS4zLTE1LjItMzUuMi0xNS4yLTI2LjYgMC00OC4yIDIxLjYtNDguMiA0OC4yIDAgMy44LjQgNy41IDEuMyAxMS00MC4xLTItNzUuNi0yMS4yLTk5LjQtNTAuNC00LjEgNy4xLTYuNSAxNS40LTYuNSAyNC4yIDAgMTYuNyA4LjUgMzEuNSAyMS41IDQwLjEtNy45LS4yLTE1LjMtMi40LTIxLjgtNnYuNmMwIDIzLjQgMTYuNiA0Mi44IDM4LjcgNDcuMy00IDEuMS04LjMgMS43LTEyLjcgMS43LTMuMSAwLTYuMS0uMy05LjEtLjkgNi4xIDE5LjIgMjMuOSAzMy4xIDQ1IDMzLjUtMTYuNSAxMi45LTM3LjMgMjAuNi01OS45IDIwLjYtMy45IDAtNy43LS4yLTExLjUtLjcgMjEuMSAxMy44IDQ2LjUgMjEuOCA3My43IDIxLjgiIGZpbGw9IiNmZmYiLz48L3N2Zz4=)}.ck-media__wrapper[data-oembed-url*=\"twitter.com\"] .ck.ck-media__placeholder .ck-media__placeholder__url__text{color:#b8e6ff}.ck-media__wrapper[data-oembed-url*=\"twitter.com\"] .ck.ck-media__placeholder .ck-media__placeholder__url__text:hover{color:#fff}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-media-embed/theme/mediaembedediting.css","webpack://./node_modules/@ckeditor/ckeditor5-ui/theme/components/tooltip/mixins/_tooltip.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/ckeditor5-media-embed/mediaembedediting.css"],"names":[],"mappings":"AAQC,0CACC,YAAa,CACb,qBAAsB,CACtB,kBAmBD,CCpBA,kFACC,aAqBD,CAHC,oBAnBD,kFAoBE,YAEF,CADC,CDlBA,sEAIC,cAAe,CAEf,iBAUD,CCoBD,wFACC,kBAAmB,CACnB,SACD,CD3BE,wGACC,eAAgB,CAChB,aACD,CAWD,6kBACC,YACD,CAYF,2LACC,mBACD,CElDA,MACC,0CAA2C,CAE3C,mDAA4D,CAC5D,2EACD,CAEA,mBACC,aA+FD,CA7FC,0CACC,0CAA+C,CAC/C,0CA4BD,CA1BC,uEACC,qDAAsD,CACtD,kDAAmD,CACnD,qCAAsC,CACtC,uBAA2B,CAC3B,qBAMD,CAJC,gFACC,UAAW,CACX,WACD,CAGD,4EACC,sDAAuD,CACvD,kBAAmB,CACnB,iBAAkB,CAClB,iBAAkB,CAClB,sBAOD,CALC,kFACC,4DAA6D,CAC7D,cAAe,CACf,yBACD,CAIF,wDACC,eAAgB,CAChB,gBACD,CAEA,4UAIC,gvGACD,CAEA,2EACC,kBAaD,CAXC,wGACC,orBACD,CAEA,6GACC,UAKD,CAHC,mHACC,UACD,CAIF,4EACC,2DAcD,CAZC,yGACC,4jHACD,CAGA,8GACC,aAKD,CAHC,oHACC,UACD,CAIF,6EAEC,iDAaD,CAXC,0GACC,48BACD,CAEA,+GACC,aAKD,CAHC,qHACC,UACD","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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/components/tooltip/mixins/_tooltip.css\";\n\n.ck-media__wrapper {\n\t& .ck-media__placeholder {\n\t\tdisplay: flex;\n\t\tflex-direction: column;\n\t\talign-items: center;\n\n\t\t& .ck-media__placeholder__url {\n\t\t\t@mixin ck-tooltip_enabled;\n\n\t\t\t/* Otherwise the URL will overflow when the content is very narrow. */\n\t\t\tmax-width: 100%;\n\n\t\t\tposition: relative;\n\n\t\t\t&:hover {\n\t\t\t\t@mixin ck-tooltip_visible;\n\t\t\t}\n\n\t\t\t& .ck-media__placeholder__url__text {\n\t\t\t\toverflow: hidden;\n\t\t\t\tdisplay: block;\n\t\t\t}\n\t\t}\n\t}\n\n\t&[data-oembed-url*=\"twitter.com\"],\n\t&[data-oembed-url*=\"google.com/maps\"],\n\t&[data-oembed-url*=\"goo.gl/maps\"],\n\t&[data-oembed-url*=\"maps.google.com\"],\n\t&[data-oembed-url*=\"maps.app.goo.gl\"],\n\t&[data-oembed-url*=\"facebook.com\"],\n\t&[data-oembed-url*=\"instagram.com\"] {\n\t\t& .ck-media__placeholder__icon * {\n\t\t\tdisplay: none;\n\t\t}\n\t}\n}\n\n/* Disable all mouse interaction as long as the editor is not read–only.\n https://github.com/ckeditor/ckeditor5-media-embed/issues/58 */\n.ck-editor__editable:not(.ck-read-only) .ck-media__wrapper > *:not(.ck-media__placeholder) {\n\tpointer-events: none;\n}\n\n/* Disable all mouse interaction when the widget is not selected (e.g. to avoid opening links by accident).\n https://github.com/ckeditor/ckeditor5-media-embed/issues/18 */\n.ck-editor__editable:not(.ck-read-only) .ck-widget:not(.ck-widget_selected) .ck-media__placeholder {\n\tpointer-events: none;\n}\n","/*\n * Copyright (c) 2003-2022, 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/**\n * Enables the tooltip, which is the tooltip is in DOM but\n * not yet displayed.\n */\n@define-mixin ck-tooltip_enabled {\n\t& .ck-tooltip {\n\t\tdisplay: block;\n\n\t\t/*\n\t\t * Don't display tooltips in devices which don't support :hover.\n\t\t * In fact, it's all about iOS, which forces user to click UI elements twice to execute\n\t\t * the primary action, when tooltips are enabled.\n\t\t *\n\t\t * Q: OK, but why not the following query?\n\t\t *\n\t\t * @media (hover) {\n\t\t * display: block;\n\t\t * }\n\t\t *\n\t\t * A: Because FF does not support it and it would completely disable tooltips\n\t\t * in that browser.\n\t\t *\n\t\t * More in https://github.com/ckeditor/ckeditor5/issues/920.\n\t\t */\n\t\t@media (hover:none) {\n\t\t\tdisplay: none;\n\t\t}\n\t}\n}\n\n/**\n * Disables the tooltip making it disappear from DOM.\n */\n@define-mixin ck-tooltip_disabled {\n\t& .ck-tooltip {\n\t\tdisplay: none;\n\t}\n}\n\n/**\n * Shows the tooltip, which is already in DOM.\n * Requires `ck-tooltip_enabled` first.\n */\n@define-mixin ck-tooltip_visible {\n\t& .ck-tooltip {\n\t\tvisibility: visible;\n\t\topacity: 1;\n\t}\n}\n","/*\n * Copyright (c) 2003-2022, 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-media-embed-placeholder-icon-size: 3em;\n\n\t--ck-color-media-embed-placeholder-url-text: hsl(0, 0%, 46%);\n\t--ck-color-media-embed-placeholder-url-text-hover: var(--ck-color-base-text);\n}\n\n.ck-media__wrapper {\n\tmargin: 0 auto;\n\n\t& .ck-media__placeholder {\n\t\tpadding: calc( 3 * var(--ck-spacing-standard) );\n\t\tbackground: var(--ck-color-base-foreground);\n\n\t\t& .ck-media__placeholder__icon {\n\t\t\tmin-width: var(--ck-media-embed-placeholder-icon-size);\n\t\t\theight: var(--ck-media-embed-placeholder-icon-size);\n\t\t\tmargin-bottom: var(--ck-spacing-large);\n\t\t\tbackground-position: center;\n\t\t\tbackground-size: cover;\n\n\t\t\t& .ck-icon {\n\t\t\t\twidth: 100%;\n\t\t\t\theight: 100%;\n\t\t\t}\n\t\t}\n\n\t\t& .ck-media__placeholder__url__text {\n\t\t\tcolor: var(--ck-color-media-embed-placeholder-url-text);\n\t\t\twhite-space: nowrap;\n\t\t\ttext-align: center;\n\t\t\tfont-style: italic;\n\t\t\ttext-overflow: ellipsis;\n\n\t\t\t&:hover {\n\t\t\t\tcolor: var(--ck-color-media-embed-placeholder-url-text-hover);\n\t\t\t\tcursor: pointer;\n\t\t\t\ttext-decoration: underline;\n\t\t\t}\n\t\t}\n\t}\n\n\t&[data-oembed-url*=\"open.spotify.com\"] {\n\t\tmax-width: 300px;\n\t\tmax-height: 380px;\n\t}\n\n\t&[data-oembed-url*=\"google.com/maps\"] .ck-media__placeholder__icon,\n\t&[data-oembed-url*=\"goo.gl/maps\"] .ck-media__placeholder__icon,\n\t&[data-oembed-url*=\"maps.google.com\"] .ck-media__placeholder__icon,\n\t&[data-oembed-url*=\"maps.app.goo.gl\"] .ck-media__placeholder__icon {\n\t\tbackground-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNTAuMzc4IiBoZWlnaHQ9IjI1NC4xNjciIHZpZXdCb3g9IjAgMCA2Ni4yNDYgNjcuMjQ4Ij48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMTcyLjUzMSAtMjE4LjQ1NSkgc2NhbGUoLjk4MDEyKSI+PHJlY3Qgcnk9IjUuMjM4IiByeD0iNS4yMzgiIHk9IjIzMS4zOTkiIHg9IjE3Ni4wMzEiIGhlaWdodD0iNjAuMDk5IiB3aWR0aD0iNjAuMDk5IiBmaWxsPSIjMzRhNjY4IiBwYWludC1vcmRlcj0ibWFya2VycyBzdHJva2UgZmlsbCIvPjxwYXRoIGQ9Ik0yMDYuNDc3IDI2MC45bC0yOC45ODcgMjguOTg3YTUuMjE4IDUuMjE4IDAgMCAwIDMuNzggMS42MWg0OS42MjFjMS42OTQgMCAzLjE5LS43OTggNC4xNDYtMi4wMzd6IiBmaWxsPSIjNWM4OGM1Ii8+PHBhdGggZD0iTTIyNi43NDIgMjIyLjk4OGMtOS4yNjYgMC0xNi43NzcgNy4xNy0xNi43NzcgMTYuMDE0LjAwNyAyLjc2Mi42NjMgNS40NzQgMi4wOTMgNy44NzUuNDMuNzAzLjgzIDEuNDA4IDEuMTkgMi4xMDcuMzMzLjUwMi42NSAxLjAwNS45NSAxLjUwOC4zNDMuNDc3LjY3My45NTcuOTg4IDEuNDQgMS4zMSAxLjc2OSAyLjUgMy41MDIgMy42MzcgNS4xNjguNzkzIDEuMjc1IDEuNjgzIDIuNjQgMi40NjYgMy45OSAyLjM2MyA0LjA5NCA0LjAwNyA4LjA5MiA0LjYgMTMuOTE0di4wMTJjLjE4Mi40MTIuNTE2LjY2Ni44NzkuNjY3LjQwMy0uMDAxLjc2OC0uMzE0LjkzLS43OTkuNjAzLTUuNzU2IDIuMjM4LTkuNzI5IDQuNTg1LTEzLjc5NC43ODItMS4zNSAxLjY3My0yLjcxNSAyLjQ2NS0zLjk5IDEuMTM3LTEuNjY2IDIuMzI4LTMuNCAzLjYzOC01LjE2OS4zMTUtLjQ4Mi42NDUtLjk2Mi45ODgtMS40MzkuMy0uNTAzLjYxNy0xLjAwNi45NS0xLjUwOC4zNTktLjcuNzYtMS40MDQgMS4xOS0yLjEwNyAxLjQyNi0yLjQwMiAyLTUuMTE0IDIuMDA0LTcuODc1IDAtOC44NDQtNy41MTEtMTYuMDE0LTE2Ljc3Ni0xNi4wMTR6IiBmaWxsPSIjZGQ0YjNlIiBwYWludC1vcmRlcj0ibWFya2VycyBzdHJva2UgZmlsbCIvPjxlbGxpcHNlIHJ5PSI1LjU2NCIgcng9IjUuODI4IiBjeT0iMjM5LjAwMiIgY3g9IjIyNi43NDIiIGZpbGw9IiM4MDJkMjciIHBhaW50LW9yZGVyPSJtYXJrZXJzIHN0cm9rZSBmaWxsIi8+PHBhdGggZD0iTTE5MC4zMDEgMjM3LjI4M2MtNC42NyAwLTguNDU3IDMuODUzLTguNDU3IDguNjA2czMuNzg2IDguNjA3IDguNDU3IDguNjA3YzMuMDQzIDAgNC44MDYtLjk1OCA2LjMzNy0yLjUxNiAxLjUzLTEuNTU3IDIuMDg3LTMuOTEzIDIuMDg3LTYuMjkgMC0uMzYyLS4wMjMtLjcyMi0uMDY0LTEuMDc5aC04LjI1N3YzLjA0M2g0Ljg1Yy0uMTk3Ljc1OS0uNTMxIDEuNDUtMS4wNTggMS45ODYtLjk0Mi45NTgtMi4wMjggMS41NDgtMy45MDEgMS41NDgtMi44NzYgMC01LjIwOC0yLjM3Mi01LjIwOC01LjI5OSAwLTIuOTI2IDIuMzMyLTUuMjk5IDUuMjA4LTUuMjk5IDEuMzk5IDAgMi42MTguNDA3IDMuNTg0IDEuMjkzbDIuMzgxLTIuMzhjMC0uMDAyLS4wMDMtLjAwNC0uMDA0LS4wMDUtMS41ODgtMS41MjQtMy42Mi0yLjIxNS01Ljk1NS0yLjIxNXptNC40MyA1LjY2bC4wMDMuMDA2di0uMDAzeiIgZmlsbD0iI2ZmZiIgcGFpbnQtb3JkZXI9Im1hcmtlcnMgc3Ryb2tlIGZpbGwiLz48cGF0aCBkPSJNMjE1LjE4NCAyNTEuOTI5bC03Ljk4IDcuOTc5IDI4LjQ3NyAyOC40NzVjLjI4Ny0uNjQ5LjQ0OS0xLjM2Ni40NDktMi4xMjN2LTMxLjE2NWMtLjQ2OS42NzUtLjkzNCAxLjM0OS0xLjM4MiAyLjAwNS0uNzkyIDEuMjc1LTEuNjgyIDIuNjQtMi40NjUgMy45OS0yLjM0NyA0LjA2NS0zLjk4MiA4LjAzOC00LjU4NSAxMy43OTQtLjE2Mi40ODUtLjUyNy43OTgtLjkzLjc5OS0uMzYzLS4wMDEtLjY5Ny0uMjU1LS44NzktLjY2N3YtLjAxMmMtLjU5My01LjgyMi0yLjIzNy05LjgyLTQuNi0xMy45MTQtLjc4My0xLjM1LTEuNjczLTIuNzE1LTIuNDY2LTMuOTktMS4xMzctMS42NjYtMi4zMjctMy40LTMuNjM3LTUuMTY5bC0uMDAyLS4wMDN6IiBmaWxsPSIjYzNjM2MzIi8+PHBhdGggZD0iTTIxMi45ODMgMjQ4LjQ5NWwtMzYuOTUyIDM2Ljk1M3YuODEyYTUuMjI3IDUuMjI3IDAgMCAwIDUuMjM4IDUuMjM4aDEuMDE1bDM1LjY2Ni0zNS42NjZhMTM2LjI3NSAxMzYuMjc1IDAgMCAwLTIuNzY0LTMuOSAzNy41NzUgMzcuNTc1IDAgMCAwLS45ODktMS40NGMtLjI5OS0uNTAzLS42MTYtMS4wMDYtLjk1LTEuNTA4LS4wODMtLjE2Mi0uMTc2LS4zMjYtLjI2NC0uNDg5eiIgZmlsbD0iI2ZkZGM0ZiIgcGFpbnQtb3JkZXI9Im1hcmtlcnMgc3Ryb2tlIGZpbGwiLz48cGF0aCBkPSJNMjExLjk5OCAyNjEuMDgzbC02LjE1MiA2LjE1MSAyNC4yNjQgMjQuMjY0aC43ODFhNS4yMjcgNS4yMjcgMCAwIDAgNS4yMzktNS4yMzh2LTEuMDQ1eiIgZmlsbD0iI2ZmZiIgcGFpbnQtb3JkZXI9Im1hcmtlcnMgc3Ryb2tlIGZpbGwiLz48L2c+PC9zdmc+);\n\t}\n\n\t&[data-oembed-url*=\"facebook.com\"] .ck-media__placeholder {\n\t\tbackground: hsl(220, 46%, 48%);\n\n\t\t& .ck-media__placeholder__icon {\n\t\t\tbackground-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c3ZnIHdpZHRoPSIxMDI0cHgiIGhlaWdodD0iMTAyNHB4IiB2aWV3Qm94PSIwIDAgMTAyNCAxMDI0IiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPiAgICAgICAgPHRpdGxlPkZpbGwgMTwvdGl0bGU+ICAgIDxkZXNjPkNyZWF0ZWQgd2l0aCBTa2V0Y2guPC9kZXNjPiAgICA8ZGVmcz48L2RlZnM+ICAgIDxnIGlkPSJQYWdlLTEiIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxIiBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPiAgICAgICAgPGcgaWQ9ImZMb2dvX1doaXRlIiBmaWxsPSIjRkZGRkZFIj4gICAgICAgICAgICA8cGF0aCBkPSJNOTY3LjQ4NCwwIEw1Ni41MTcsMCBDMjUuMzA0LDAgMCwyNS4zMDQgMCw1Ni41MTcgTDAsOTY3LjQ4MyBDMCw5OTguNjk0IDI1LjI5NywxMDI0IDU2LjUyMiwxMDI0IEw1NDcsMTAyNCBMNTQ3LDYyOCBMNDE0LDYyOCBMNDE0LDQ3MyBMNTQ3LDQ3MyBMNTQ3LDM1OS4wMjkgQzU0NywyMjYuNzY3IDYyNy43NzMsMTU0Ljc0NyA3NDUuNzU2LDE1NC43NDcgQzgwMi4yNjksMTU0Ljc0NyA4NTAuODQyLDE1OC45NTUgODY1LDE2MC44MzYgTDg2NSwyOTkgTDc4My4zODQsMjk5LjAzNyBDNzE5LjM5MSwyOTkuMDM3IDcwNywzMjkuNTI5IDcwNywzNzQuMjczIEw3MDcsNDczIEw4NjAuNDg3LDQ3MyBMODQwLjUwMSw2MjggTDcwNyw2MjggTDcwNywxMDI0IEw5NjcuNDg0LDEwMjQgQzk5OC42OTcsMTAyNCAxMDI0LDk5OC42OTcgMTAyNCw5NjcuNDg0IEwxMDI0LDU2LjUxNSBDMTAyNCwyNS4zMDMgOTk4LjY5NywwIDk2Ny40ODQsMCIgaWQ9IkZpbGwtMSI+PC9wYXRoPiAgICAgICAgPC9nPiAgICA8L2c+PC9zdmc+);\n\t\t}\n\n\t\t& .ck-media__placeholder__url__text {\n\t\t\tcolor: hsl(220, 100%, 90%);\n\n\t\t\t&:hover {\n\t\t\t\tcolor: hsl(0, 0%, 100%);\n\t\t\t}\n\t\t}\n\t}\n\n\t&[data-oembed-url*=\"instagram.com\"] .ck-media__placeholder {\n\t\tbackground: linear-gradient(-135deg,hsl(246, 100%, 39%),hsl(302, 100%, 36%),hsl(0, 100%, 48%));\n\n\t\t& .ck-media__placeholder__icon {\n\t\t\tbackground-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c3ZnIHdpZHRoPSI1MDRweCIgaGVpZ2h0PSI1MDRweCIgdmlld0JveD0iMCAwIDUwNCA1MDQiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+ICAgICAgICA8dGl0bGU+Z2x5cGgtbG9nb19NYXkyMDE2PC90aXRsZT4gICAgPGRlc2M+Q3JlYXRlZCB3aXRoIFNrZXRjaC48L2Rlc2M+ICAgIDxkZWZzPiAgICAgICAgPHBvbHlnb24gaWQ9InBhdGgtMSIgcG9pbnRzPSIwIDAuMTU5IDUwMy44NDEgMC4xNTkgNTAzLjg0MSA1MDMuOTQgMCA1MDMuOTQiPjwvcG9seWdvbj4gICAgPC9kZWZzPiAgICA8ZyBpZD0iZ2x5cGgtbG9nb19NYXkyMDE2IiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj4gICAgICAgIDxnIGlkPSJHcm91cC0zIj4gICAgICAgICAgICA8bWFzayBpZD0ibWFzay0yIiBmaWxsPSJ3aGl0ZSI+ICAgICAgICAgICAgICAgIDx1c2UgeGxpbms6aHJlZj0iI3BhdGgtMSI+PC91c2U+ICAgICAgICAgICAgPC9tYXNrPiAgICAgICAgICAgIDxnIGlkPSJDbGlwLTIiPjwvZz4gICAgICAgICAgICA8cGF0aCBkPSJNMjUxLjkyMSwwLjE1OSBDMTgzLjUwMywwLjE1OSAxNzQuOTI0LDAuNDQ5IDE0OC4wNTQsMS42NzUgQzEyMS4yNCwyLjg5OCAxMDIuOTI3LDcuMTU3IDg2LjkwMywxMy4zODUgQzcwLjMzNywxOS44MjIgNTYuMjg4LDI4LjQzNiA0Mi4yODIsNDIuNDQxIEMyOC4yNzcsNTYuNDQ3IDE5LjY2Myw3MC40OTYgMTMuMjI2LDg3LjA2MiBDNi45OTgsMTAzLjA4NiAyLjczOSwxMjEuMzk5IDEuNTE2LDE0OC4yMTMgQzAuMjksMTc1LjA4MyAwLDE4My42NjIgMCwyNTIuMDggQzAsMzIwLjQ5NyAwLjI5LDMyOS4wNzYgMS41MTYsMzU1Ljk0NiBDMi43MzksMzgyLjc2IDYuOTk4LDQwMS4wNzMgMTMuMjI2LDQxNy4wOTcgQzE5LjY2Myw0MzMuNjYzIDI4LjI3Nyw0NDcuNzEyIDQyLjI4Miw0NjEuNzE4IEM1Ni4yODgsNDc1LjcyMyA3MC4zMzcsNDg0LjMzNyA4Ni45MDMsNDkwLjc3NSBDMTAyLjkyNyw0OTcuMDAyIDEyMS4yNCw1MDEuMjYxIDE0OC4wNTQsNTAyLjQ4NCBDMTc0LjkyNCw1MDMuNzEgMTgzLjUwMyw1MDQgMjUxLjkyMSw1MDQgQzMyMC4zMzgsNTA0IDMyOC45MTcsNTAzLjcxIDM1NS43ODcsNTAyLjQ4NCBDMzgyLjYwMSw1MDEuMjYxIDQwMC45MTQsNDk3LjAwMiA0MTYuOTM4LDQ5MC43NzUgQzQzMy41MDQsNDg0LjMzNyA0NDcuNTUzLDQ3NS43MjMgNDYxLjU1OSw0NjEuNzE4IEM0NzUuNTY0LDQ0Ny43MTIgNDg0LjE3OCw0MzMuNjYzIDQ5MC42MTYsNDE3LjA5NyBDNDk2Ljg0Myw0MDEuMDczIDUwMS4xMDIsMzgyLjc2IDUwMi4zMjUsMzU1Ljk0NiBDNTAzLjU1MSwzMjkuMDc2IDUwMy44NDEsMzIwLjQ5NyA1MDMuODQxLDI1Mi4wOCBDNTAzLjg0MSwxODMuNjYyIDUwMy41NTEsMTc1LjA4MyA1MDIuMzI1LDE0OC4yMTMgQzUwMS4xMDIsMTIxLjM5OSA0OTYuODQzLDEwMy4wODYgNDkwLjYxNiw4Ny4wNjIgQzQ4NC4xNzgsNzAuNDk2IDQ3NS41NjQsNTYuNDQ3IDQ2MS41NTksNDIuNDQxIEM0NDcuNTUzLDI4LjQzNiA0MzMuNTA0LDE5LjgyMiA0MTYuOTM4LDEzLjM4NSBDNDAwLjkxNCw3LjE1NyAzODIuNjAxLDIuODk4IDM1NS43ODcsMS42NzUgQzMyOC45MTcsMC40NDkgMzIwLjMzOCwwLjE1OSAyNTEuOTIxLDAuMTU5IFogTTI1MS45MjEsNDUuNTUgQzMxOS4xODYsNDUuNTUgMzI3LjE1NCw0NS44MDcgMzUzLjcxOCw0Ny4wMTkgQzM3OC4yOCw0OC4xMzkgMzkxLjYxOSw1Mi4yNDMgNDAwLjQ5Niw1NS42OTMgQzQxMi4yNTUsNjAuMjYzIDQyMC42NDcsNjUuNzIyIDQyOS40NjIsNzQuNTM4IEM0MzguMjc4LDgzLjM1MyA0NDMuNzM3LDkxLjc0NSA0NDguMzA3LDEwMy41MDQgQzQ1MS43NTcsMTEyLjM4MSA0NTUuODYxLDEyNS43MiA0NTYuOTgxLDE1MC4yODIgQzQ1OC4xOTMsMTc2Ljg0NiA0NTguNDUsMTg0LjgxNCA0NTguNDUsMjUyLjA4IEM0NTguNDUsMzE5LjM0NSA0NTguMTkzLDMyNy4zMTMgNDU2Ljk4MSwzNTMuODc3IEM0NTUuODYxLDM3OC40MzkgNDUxLjc1NywzOTEuNzc4IDQ0OC4zMDcsNDAwLjY1NSBDNDQzLjczNyw0MTIuNDE0IDQzOC4yNzgsNDIwLjgwNiA0MjkuNDYyLDQyOS42MjEgQzQyMC42NDcsNDM4LjQzNyA0MTIuMjU1LDQ0My44OTYgNDAwLjQ5Niw0NDguNDY2IEMzOTEuNjE5LDQ1MS45MTYgMzc4LjI4LDQ1Ni4wMiAzNTMuNzE4LDQ1Ny4xNCBDMzI3LjE1OCw0NTguMzUyIDMxOS4xOTEsNDU4LjYwOSAyNTEuOTIxLDQ1OC42MDkgQzE4NC42NSw0NTguNjA5IDE3Ni42ODQsNDU4LjM1MiAxNTAuMTIzLDQ1Ny4xNCBDMTI1LjU2MSw0NTYuMDIgMTEyLjIyMiw0NTEuOTE2IDEwMy4zNDUsNDQ4LjQ2NiBDOTEuNTg2LDQ0My44OTYgODMuMTk0LDQzOC40MzcgNzQuMzc5LDQyOS42MjEgQzY1LjU2NCw0MjAuODA2IDYwLjEwNCw0MTIuNDE0IDU1LjUzNCw0MDAuNjU1IEM1Mi4wODQsMzkxLjc3OCA0Ny45OCwzNzguNDM5IDQ2Ljg2LDM1My44NzcgQzQ1LjY0OCwzMjcuMzEzIDQ1LjM5MSwzMTkuMzQ1IDQ1LjM5MSwyNTIuMDggQzQ1LjM5MSwxODQuODE0IDQ1LjY0OCwxNzYuODQ2IDQ2Ljg2LDE1MC4yODIgQzQ3Ljk4LDEyNS43MiA1Mi4wODQsMTEyLjM4MSA1NS41MzQsMTAzLjUwNCBDNjAuMTA0LDkxLjc0NSA2NS41NjMsODMuMzUzIDc0LjM3OSw3NC41MzggQzgzLjE5NCw2NS43MjIgOTEuNTg2LDYwLjI2MyAxMDMuMzQ1LDU1LjY5MyBDMTEyLjIyMiw1Mi4yNDMgMTI1LjU2MSw0OC4xMzkgMTUwLjEyMyw0Ny4wMTkgQzE3Ni42ODcsNDUuODA3IDE4NC42NTUsNDUuNTUgMjUxLjkyMSw0NS41NSBaIiBpZD0iRmlsbC0xIiBmaWxsPSIjRkZGRkZGIiBtYXNrPSJ1cmwoI21hc2stMikiPjwvcGF0aD4gICAgICAgIDwvZz4gICAgICAgIDxwYXRoIGQ9Ik0yNTEuOTIxLDMzNi4wNTMgQzIwNS41NDMsMzM2LjA1MyAxNjcuOTQ3LDI5OC40NTcgMTY3Ljk0NywyNTIuMDggQzE2Ny45NDcsMjA1LjcwMiAyMDUuNTQzLDE2OC4xMDYgMjUxLjkyMSwxNjguMTA2IEMyOTguMjk4LDE2OC4xMDYgMzM1Ljg5NCwyMDUuNzAyIDMzNS44OTQsMjUyLjA4IEMzMzUuODk0LDI5OC40NTcgMjk4LjI5OCwzMzYuMDUzIDI1MS45MjEsMzM2LjA1MyBaIE0yNTEuOTIxLDEyMi43MTUgQzE4MC40NzQsMTIyLjcxNSAxMjIuNTU2LDE4MC42MzMgMTIyLjU1NiwyNTIuMDggQzEyMi41NTYsMzIzLjUyNiAxODAuNDc0LDM4MS40NDQgMjUxLjkyMSwzODEuNDQ0IEMzMjMuMzY3LDM4MS40NDQgMzgxLjI4NSwzMjMuNTI2IDM4MS4yODUsMjUyLjA4IEMzODEuMjg1LDE4MC42MzMgMzIzLjM2NywxMjIuNzE1IDI1MS45MjEsMTIyLjcxNSBaIiBpZD0iRmlsbC00IiBmaWxsPSIjRkZGRkZGIj48L3BhdGg+ICAgICAgICA8cGF0aCBkPSJNNDE2LjYyNywxMTcuNjA0IEM0MTYuNjI3LDEzNC4zIDQwMy4wOTIsMTQ3LjgzNCAzODYuMzk2LDE0Ny44MzQgQzM2OS43MDEsMTQ3LjgzNCAzNTYuMTY2LDEzNC4zIDM1Ni4xNjYsMTE3LjYwNCBDMzU2LjE2NiwxMDAuOTA4IDM2OS43MDEsODcuMzczIDM4Ni4zOTYsODcuMzczIEM0MDMuMDkyLDg3LjM3MyA0MTYuNjI3LDEwMC45MDggNDE2LjYyNywxMTcuNjA0IiBpZD0iRmlsbC01IiBmaWxsPSIjRkZGRkZGIj48L3BhdGg+ICAgIDwvZz48L3N2Zz4=);\n\t\t}\n\n\t\t/* stylelint-disable-next-line no-descending-specificity */\n\t\t& .ck-media__placeholder__url__text {\n\t\t\tcolor: hsl(302, 100%, 94%);\n\n\t\t\t&:hover {\n\t\t\t\tcolor: hsl(0, 0%, 100%);\n\t\t\t}\n\t\t}\n\t}\n\n\t&[data-oembed-url*=\"twitter.com\"] .ck.ck-media__placeholder {\n\t\t/* Use gradient to contrast with focused widget (ckeditor/ckeditor5-media-embed#22). */\n\t\tbackground: linear-gradient( to right, hsl(201, 85%, 70%), hsl(201, 85%, 35%) );\n\n\t\t& .ck-media__placeholder__icon {\n\t\t\tbackground-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48c3ZnIHZlcnNpb249IjEuMSIgaWQ9IldoaXRlIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4PSIwcHgiIHk9IjBweCIgdmlld0JveD0iMCAwIDQwMCA0MDAiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDQwMCA0MDA7IiB4bWw6c3BhY2U9InByZXNlcnZlIj48c3R5bGUgdHlwZT0idGV4dC9jc3MiPi5zdDB7ZmlsbDojRkZGRkZGO308L3N0eWxlPjxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik00MDAsMjAwYzAsMTEwLjUtODkuNSwyMDAtMjAwLDIwMFMwLDMxMC41LDAsMjAwUzg5LjUsMCwyMDAsMFM0MDAsODkuNSw0MDAsMjAweiBNMTYzLjQsMzA1LjVjODguNywwLDEzNy4yLTczLjUsMTM3LjItMTM3LjJjMC0yLjEsMC00LjItMC4xLTYuMmM5LjQtNi44LDE3LjYtMTUuMywyNC4xLTI1Yy04LjYsMy44LTE3LjksNi40LTI3LjcsNy42YzEwLTYsMTcuNi0xNS40LDIxLjItMjYuN2MtOS4zLDUuNS0xOS42LDkuNS0zMC42LDExLjdjLTguOC05LjQtMjEuMy0xNS4yLTM1LjItMTUuMmMtMjYuNiwwLTQ4LjIsMjEuNi00OC4yLDQ4LjJjMCwzLjgsMC40LDcuNSwxLjMsMTFjLTQwLjEtMi03NS42LTIxLjItOTkuNC01MC40Yy00LjEsNy4xLTYuNSwxNS40LTYuNSwyNC4yYzAsMTYuNyw4LjUsMzEuNSwyMS41LDQwLjFjLTcuOS0wLjItMTUuMy0yLjQtMjEuOC02YzAsMC4yLDAsMC40LDAsMC42YzAsMjMuNCwxNi42LDQyLjgsMzguNyw0Ny4zYy00LDEuMS04LjMsMS43LTEyLjcsMS43Yy0zLjEsMC02LjEtMC4zLTkuMS0wLjljNi4xLDE5LjIsMjMuOSwzMy4xLDQ1LDMzLjVjLTE2LjUsMTIuOS0zNy4zLDIwLjYtNTkuOSwyMC42Yy0zLjksMC03LjctMC4yLTExLjUtMC43QzExMC44LDI5Ny41LDEzNi4yLDMwNS41LDE2My40LDMwNS41Ii8+PC9zdmc+);\n\t\t}\n\n\t\t& .ck-media__placeholder__url__text {\n\t\t\tcolor: hsl(201, 100%, 86%);\n\n\t\t\t&:hover {\n\t\t\t\tcolor: hsl(0, 0%, 100%);\n\t\t\t}\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-media-embed/theme/mediaform.css":
/*!**************************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-media-embed/theme/mediaform.css ***!
\**************************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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-media-form{display:flex;align-items:flex-start;flex-direction:row;flex-wrap:nowrap}.ck.ck-media-form .ck-labeled-field-view{display:inline-block}.ck.ck-media-form .ck-label{display:none}@media screen and (max-width:600px){.ck.ck-media-form{flex-wrap:wrap}.ck.ck-media-form .ck-labeled-field-view{flex-basis:100%}.ck.ck-media-form .ck-button{flex-basis:50%}}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-media-embed/theme/mediaform.css","webpack://./node_modules/@ckeditor/ckeditor5-ui/theme/mixins/_rwd.css"],"names":[],"mappings":"AAOA,kBACC,YAAa,CACb,sBAAuB,CACvB,kBAAmB,CACnB,gBAqBD,CAnBC,yCACC,oBACD,CAEA,4BACC,YACD,CCbA,oCDCD,kBAeE,cAUF,CARE,yCACC,eACD,CAEA,6BACC,cACD,CCtBD","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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/_rwd.css\";\n\n.ck.ck-media-form {\n\tdisplay: flex;\n\talign-items: flex-start;\n\tflex-direction: row;\n\tflex-wrap: nowrap;\n\n\t& .ck-labeled-field-view {\n\t\tdisplay: inline-block;\n\t}\n\n\t& .ck-label {\n\t\tdisplay: none;\n\t}\n\n\t@mixin ck-media-phone {\n\t\tflex-wrap: wrap;\n\n\t\t& .ck-labeled-field-view {\n\t\t\tflex-basis: 100%;\n\t\t}\n\n\t\t& .ck-button {\n\t\t\tflex-basis: 50%;\n\t\t}\n\t}\n}\n","/*\n * Copyright (c) 2003-2022, 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@define-mixin ck-media-phone {\n\t@media screen and (max-width: 600px) {\n\t\t@mixin-content;\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-special-characters/theme/charactergrid.css":
/*!*************************************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-special-characters/theme/charactergrid.css ***!
\*************************************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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-character-grid{max-width:100%}.ck.ck-character-grid .ck-character-grid__tiles{display:grid}:root{--ck-character-grid-tile-size:24px}.ck.ck-character-grid{overflow-y:auto;overflow-x:hidden;width:350px;max-height:200px}.ck.ck-character-grid .ck-character-grid__tiles{grid-template-columns:repeat(auto-fit,minmax(var(--ck-character-grid-tile-size),1fr));margin:var(--ck-spacing-standard) var(--ck-spacing-large);grid-gap:var(--ck-spacing-standard)}.ck.ck-character-grid .ck-character-grid__tile{width:var(--ck-character-grid-tile-size);height:var(--ck-character-grid-tile-size);min-width:var(--ck-character-grid-tile-size);min-height:var(--ck-character-grid-tile-size);font-size:1.2em;padding:0;transition:box-shadow .2s ease;border:0}.ck.ck-character-grid .ck-character-grid__tile:focus:not(.ck-disabled),.ck.ck-character-grid .ck-character-grid__tile:hover:not(.ck-disabled){border:0;box-shadow:inset 0 0 0 1px var(--ck-color-base-background),0 0 0 2px var(--ck-color-focus-border)}.ck.ck-character-grid .ck-character-grid__tile .ck-button__label{line-height:var(--ck-character-grid-tile-size);width:100%;text-align:center}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-special-characters/theme/charactergrid.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/ckeditor5-special-characters/charactergrid.css"],"names":[],"mappings":"AAKA,sBACC,cAKD,CAHC,gDACC,YACD,CCHD,MACC,kCACD,CAEA,sBACC,eAAgB,CAChB,iBAAkB,CAClB,WAAY,CACZ,gBAgCD,CA9BC,gDACC,qFAAwF,CACxF,yDAA0D,CAC1D,mCACD,CAEA,+CACC,wCAAyC,CACzC,yCAA0C,CAC1C,4CAA6C,CAC7C,6CAA8C,CAC9C,eAAgB,CAChB,SAAU,CACV,8BAA+B,CAC/B,QAeD,CAbC,8IAGC,QAAS,CACT,iGACD,CAGA,iEACC,8CAA+C,CAC/C,UAAW,CACX,iBACD","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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-character-grid {\n\tmax-width: 100%;\n\t\n\t& .ck-character-grid__tiles {\n\t\tdisplay: grid;\n\t}\n}\n","/*\n * Copyright (c) 2003-2022, 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 \"../mixins/_rounded.css\";\n\n:root {\n\t--ck-character-grid-tile-size: 24px;\n}\n\n.ck.ck-character-grid {\n\toverflow-y: auto;\n\toverflow-x: hidden;\n\twidth: 350px;\n\tmax-height: 200px;\n\n\t& .ck-character-grid__tiles {\n\t\tgrid-template-columns: repeat(auto-fit, minmax(var(--ck-character-grid-tile-size), 1fr));\n\t\tmargin: var(--ck-spacing-standard) var(--ck-spacing-large);\n\t\tgrid-gap: var(--ck-spacing-standard);\n\t}\n\n\t& .ck-character-grid__tile {\n\t\twidth: var(--ck-character-grid-tile-size);\n\t\theight: var(--ck-character-grid-tile-size);\n\t\tmin-width: var(--ck-character-grid-tile-size);\n\t\tmin-height: var(--ck-character-grid-tile-size);\n\t\tfont-size: 1.2em;\n\t\tpadding: 0;\n\t\ttransition: .2s ease box-shadow;\n\t\tborder: 0;\n\n\t\t&:focus:not( .ck-disabled ),\n\t\t&:hover:not( .ck-disabled ) {\n\t\t\t/* Disable the default .ck-button's border ring. */\n\t\t\tborder: 0;\n\t\t\tbox-shadow: inset 0 0 0 1px var(--ck-color-base-background), 0 0 0 2px var(--ck-color-focus-border);\n\t\t}\n\n\t\t/* Make sure the glyph is rendered in the center of the button */\n\t\t& .ck-button__label {\n\t\t\tline-height: var(--ck-character-grid-tile-size);\n\t\t\twidth: 100%;\n\t\t\ttext-align: center;\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-special-characters/theme/characterinfo.css":
/*!*************************************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-special-characters/theme/characterinfo.css ***!
\*************************************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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-character-info{display:flex;justify-content:space-between;padding:var(--ck-spacing-small) var(--ck-spacing-large);border-top:1px solid var(--ck-color-base-border)}.ck.ck-character-info>*{text-transform:uppercase;font-size:var(--ck-font-size-small)}.ck.ck-character-info .ck-character-info__name{max-width:280px;text-overflow:ellipsis;overflow:hidden}.ck.ck-character-info .ck-character-info__code{opacity:.6}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-special-characters/theme/characterinfo.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/ckeditor5-special-characters/characterinfo.css"],"names":[],"mappings":"AAKA,sBACC,YAAa,CACb,6BAA8B,CCD9B,uDAAwD,CACxD,gDDCD,CCCC,wBACC,wBAAyB,CACzB,mCACD,CAEA,+CACC,eAAgB,CAChB,sBAAuB,CACvB,eACD,CAEA,+CACC,UACD","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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-character-info {\n\tdisplay: flex;\n\tjustify-content: space-between;\n}\n","/*\n * Copyright (c) 2003-2022, 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-character-info {\n\tpadding: var(--ck-spacing-small) var(--ck-spacing-large);\n\tborder-top: 1px solid var(--ck-color-base-border);\n\n\t& > * {\n\t\ttext-transform: uppercase;\n\t\tfont-size: var(--ck-font-size-small);\n\t}\n\n\t& .ck-character-info__name {\n\t\tmax-width: 280px;\n\t\ttext-overflow: ellipsis;\n\t\toverflow: hidden;\n\t}\n\n\t& .ck-character-info__code {\n\t\topacity: .6;\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-special-characters/theme/specialcharacters.css":
/*!*****************************************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-special-characters/theme/specialcharacters.css ***!
\*****************************************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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-special-characters-navigation>.ck-label{max-width:160px;text-overflow:ellipsis;overflow:hidden}.ck.ck-special-characters-navigation>.ck-dropdown .ck-dropdown__panel{max-height:250px;overflow-y:auto;overflow-x:hidden}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/ckeditor5-special-characters/specialcharacters.css"],"names":[],"mappings":"AASC,+CACC,eAAgB,CAChB,sBAAuB,CACvB,eACD,CAEA,sEAEC,gBAAiB,CACjB,eAAgB,CAChB,iBACD","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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.ck.ck-special-characters-navigation {\n\n\t& > .ck-label {\n\t\tmax-width: 160px;\n\t\ttext-overflow: ellipsis;\n\t\toverflow: hidden;\n\t}\n\n\t& > .ck-dropdown .ck-dropdown__panel {\n\t\t/* There could be dozens of categories available. Use scroll to prevent a 10e6px dropdown. */\n\t\tmax-height: 250px;\n\t\toverflow-y: auto;\n\t\toverflow-x: hidden;\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-table/theme/colorinput.css":
/*!*********************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-table/theme/colorinput.css ***!
\*********************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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-input-color{width:100%;display:flex;flex-direction:row-reverse}.ck.ck-input-color>input.ck.ck-input-text{min-width:auto;flex-grow:1}.ck.ck-input-color>div.ck.ck-dropdown{min-width:auto}.ck.ck-input-color>div.ck.ck-dropdown>.ck-input-color__button .ck-dropdown__arrow{display:none}.ck.ck-input-color .ck.ck-input-color__button{display:flex}.ck.ck-input-color .ck.ck-input-color__button .ck.ck-input-color__button__preview{position:relative;overflow:hidden}.ck.ck-input-color .ck.ck-input-color__button .ck.ck-input-color__button__preview>.ck.ck-input-color__button__preview__no-color-indicator{position:absolute;display:block}[dir=ltr] .ck.ck-input-color>.ck.ck-input-text{border-top-right-radius:0;border-bottom-right-radius:0}[dir=rtl] .ck.ck-input-color>.ck.ck-input-text{border-top-left-radius:0;border-bottom-left-radius:0}.ck.ck-input-color>.ck.ck-dropdown>.ck.ck-button.ck-input-color__button{padding:0}[dir=ltr] .ck.ck-input-color>.ck.ck-dropdown>.ck.ck-button.ck-input-color__button{border-left-width:0;border-top-left-radius:0;border-bottom-left-radius:0}[dir=rtl] .ck.ck-input-color>.ck.ck-dropdown>.ck.ck-button.ck-input-color__button{border-right-width:0;border-top-right-radius:0;border-bottom-right-radius:0}.ck.ck-input-color>.ck.ck-dropdown>.ck.ck-button.ck-input-color__button.ck-disabled{background:var(--ck-color-input-disabled-background)}.ck.ck-input-color>.ck.ck-dropdown>.ck.ck-button.ck-input-color__button>.ck.ck-input-color__button__preview{border-radius:0}.ck-rounded-corners .ck.ck-input-color>.ck.ck-dropdown>.ck.ck-button.ck-input-color__button>.ck.ck-input-color__button__preview,.ck.ck-input-color>.ck.ck-dropdown>.ck.ck-button.ck-input-color__button>.ck.ck-input-color__button__preview.ck-rounded-corners{border-radius:var(--ck-border-radius)}.ck.ck-input-color>.ck.ck-dropdown>.ck.ck-button.ck-input-color__button>.ck.ck-input-color__button__preview{width:20px;height:20px;border:1px solid var(--ck-color-input-border)}.ck.ck-input-color>.ck.ck-dropdown>.ck.ck-button.ck-input-color__button>.ck.ck-input-color__button__preview>.ck.ck-input-color__button__preview__no-color-indicator{top:-30%;left:50%;height:150%;width:8%;background:red;border-radius:2px;transform:rotate(45deg);transform-origin:50%}.ck.ck-input-color .ck.ck-input-color__remove-color{width:100%;border-bottom:1px solid var(--ck-color-input-border);padding:calc(var(--ck-spacing-standard)/2) var(--ck-spacing-standard);border-bottom-left-radius:0;border-bottom-right-radius:0}[dir=ltr] .ck.ck-input-color .ck.ck-input-color__remove-color{border-top-right-radius:0}[dir=rtl] .ck.ck-input-color .ck.ck-input-color__remove-color{border-top-left-radius:0}.ck.ck-input-color .ck.ck-input-color__remove-color .ck.ck-icon{margin-right:var(--ck-spacing-standard)}[dir=rtl] .ck.ck-input-color .ck.ck-input-color__remove-color .ck.ck-icon{margin-right:0;margin-left:var(--ck-spacing-standard)}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-table/theme/colorinput.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/ckeditor5-table/colorinput.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/mixins/_rounded.css"],"names":[],"mappings":"AAKA,mBACC,UAAW,CACX,YAAa,CACb,0BA8BD,CA5BC,0CACC,cAAe,CACf,WACD,CAEA,sCACC,cAMD,CAHC,kFACC,YACD,CAGD,8CAEC,YAWD,CATC,kFACC,iBAAkB,CAClB,eAMD,CAJC,0IACC,iBAAkB,CAClB,aACD,CC1BF,+CAEE,yBAA0B,CAC1B,4BAOF,CAVA,+CAOE,wBAAyB,CACzB,2BAEF,CAGC,wEACC,SAoCD,CArCA,kFAIE,mBAAoB,CACpB,wBAAyB,CACzB,2BA+BF,CArCA,kFAUE,oBAAqB,CACrB,yBAA0B,CAC1B,4BAyBF,CAtBC,oFACC,oDACD,CAEA,4GC9BF,eD+CE,CAjBA,+PC1BD,qCD2CC,CAjBA,4GAGC,UAAW,CACX,WAAY,CACZ,6CAYD,CAVC,oKACC,QAAS,CACT,QAAS,CACT,WAAY,CACZ,QAAS,CACT,cAA6B,CAC7B,iBAAkB,CAClB,uBAAwB,CACxB,oBACD,CAKH,oDACC,UAAW,CACX,oDAAqD,CACrD,qEAAwE,CAExE,2BAA4B,CAC5B,4BAkBD,CAxBA,8DASE,yBAeF,CAxBA,8DAaE,wBAWF,CARC,gEACC,uCAMD,CAPA,0EAIE,cAAe,CACf,sCAEF","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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-input-color {\n\twidth: 100%;\n\tdisplay: flex;\n\tflex-direction: row-reverse;\n\n\t& > input.ck.ck-input-text {\n\t\tmin-width: auto;\n\t\tflex-grow: 1;\n\t}\n\n\t& > div.ck.ck-dropdown {\n\t\tmin-width: auto;\n\n\t\t/* This dropdown has no arrow but a color preview instead. */\n\t\t& > .ck-input-color__button .ck-dropdown__arrow {\n\t\t\tdisplay: none;\n\t\t}\n\t}\n\n\t& .ck.ck-input-color__button {\n\t\t/* Resolving issue with misaligned buttons on Safari (see #10589) */\n\t\tdisplay: flex;\n\n\t\t& .ck.ck-input-color__button__preview {\n\t\t\tposition: relative;\n\t\t\toverflow: hidden;\n\n\t\t\t& > .ck.ck-input-color__button__preview__no-color-indicator {\n\t\t\t\tposition: absolute;\n\t\t\t\tdisplay: block;\n\t\t\t}\n\t\t}\n\t}\n}\n","/*\n * Copyright (c) 2003-2022, 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@import \"../mixins/_rounded.css\";\n\n.ck.ck-input-color {\n\t& > .ck.ck-input-text {\n\t\t@mixin ck-dir ltr {\n\t\t\tborder-top-right-radius: 0;\n\t\t\tborder-bottom-right-radius: 0;\n\t\t}\n\n\t\t@mixin ck-dir rtl {\n\t\t\tborder-top-left-radius: 0;\n\t\t\tborder-bottom-left-radius: 0;\n\t\t}\n\t}\n\n\t& > .ck.ck-dropdown {\n\t\t& > .ck.ck-button.ck-input-color__button {\n\t\t\tpadding: 0;\n\n\t\t\t@mixin ck-dir ltr {\n\t\t\t\tborder-left-width: 0;\n\t\t\t\tborder-top-left-radius: 0;\n\t\t\t\tborder-bottom-left-radius: 0;\n\t\t\t}\n\n\t\t\t@mixin ck-dir rtl {\n\t\t\t\tborder-right-width: 0;\n\t\t\t\tborder-top-right-radius: 0;\n\t\t\t\tborder-bottom-right-radius: 0;\n\t\t\t}\n\n\t\t\t&.ck-disabled {\n\t\t\t\tbackground: var(--ck-color-input-disabled-background);\n\t\t\t}\n\n\t\t\t& > .ck.ck-input-color__button__preview {\n\t\t\t\t@mixin ck-rounded-corners;\n\n\t\t\t\twidth: 20px;\n\t\t\t\theight: 20px;\n\t\t\t\tborder: 1px solid var(--ck-color-input-border);\n\n\t\t\t\t& > .ck.ck-input-color__button__preview__no-color-indicator {\n\t\t\t\t\ttop: -30%;\n\t\t\t\t\tleft: 50%;\n\t\t\t\t\theight: 150%;\n\t\t\t\t\twidth: 8%;\n\t\t\t\t\tbackground: hsl(0, 100%, 50%);\n\t\t\t\t\tborder-radius: 2px;\n\t\t\t\t\ttransform: rotate(45deg);\n\t\t\t\t\ttransform-origin: 50%;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t& .ck.ck-input-color__remove-color {\n\t\twidth: 100%;\n\t\tborder-bottom: 1px solid var(--ck-color-input-border);\n\t\tpadding: calc(var(--ck-spacing-standard) / 2) var(--ck-spacing-standard);\n\n\t\tborder-bottom-left-radius: 0;\n\t\tborder-bottom-right-radius: 0;\n\n\t\t@mixin ck-dir ltr {\n\t\t\tborder-top-right-radius: 0;\n\t\t}\n\n\t\t@mixin ck-dir rtl {\n\t\t\tborder-top-left-radius: 0;\n\t\t}\n\n\t\t& .ck.ck-icon {\n\t\t\tmargin-right: var(--ck-spacing-standard);\n\n\t\t\t@mixin ck-dir rtl {\n\t\t\t\tmargin-right: 0;\n\t\t\t\tmargin-left: var(--ck-spacing-standard);\n\t\t\t}\n\t\t}\n\t}\n}\n","/*\n * Copyright (c) 2003-2022, 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/**\n * Implements rounded corner interface for .ck-rounded-corners class.\n *\n * @see $ck-border-radius\n */\n@define-mixin ck-rounded-corners {\n\tborder-radius: 0;\n\n\t@nest .ck-rounded-corners &,\n\t&.ck-rounded-corners {\n\t\tborder-radius: var(--ck-border-radius);\n\t\t@mixin-content;\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-table/theme/form.css":
/*!***************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-table/theme/form.css ***!
\***************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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-form{padding:0 0 var(--ck-spacing-large)}.ck.ck-form:focus{outline:none}.ck.ck-form .ck.ck-input-text{min-width:100%;width:0}.ck.ck-form .ck.ck-dropdown{min-width:100%}.ck.ck-form .ck.ck-dropdown .ck-dropdown__button:not(:focus){border:1px solid var(--ck-color-base-border)}.ck.ck-form .ck.ck-dropdown .ck-dropdown__button .ck-button__label{width:100%}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/ckeditor5-table/form.css"],"names":[],"mappings":"AAKA,YACC,mCAyBD,CAvBC,kBAEC,YACD,CAEA,8BACC,cAAe,CACf,OACD,CAEA,4BACC,cAWD,CARE,6DACC,4CACD,CAEA,mEACC,UACD","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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-form {\n\tpadding: 0 0 var(--ck-spacing-large);\n\n\t&:focus {\n\t\t/* See: https://github.com/ckeditor/ckeditor5/issues/4773 */\n\t\toutline: none;\n\t}\n\n\t& .ck.ck-input-text {\n\t\tmin-width: 100%;\n\t\twidth: 0;\n\t}\n\n\t& .ck.ck-dropdown {\n\t\tmin-width: 100%;\n\n\t\t& .ck-dropdown__button {\n\t\t\t&:not(:focus) {\n\t\t\t\tborder: 1px solid var(--ck-color-base-border);\n\t\t\t}\n\n\t\t\t& .ck-button__label {\n\t\t\t\twidth: 100%;\n\t\t\t}\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-table/theme/formrow.css":
/*!******************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-table/theme/formrow.css ***!
\******************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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-form__row{display:flex;flex-direction:row;flex-wrap:nowrap;justify-content:space-between}.ck.ck-form__row>:not(.ck-label){flex-grow:1}.ck.ck-form__row.ck-table-form__action-row .ck-button-cancel,.ck.ck-form__row.ck-table-form__action-row .ck-button-save{justify-content:center}.ck.ck-form__row{padding:var(--ck-spacing-standard) var(--ck-spacing-large) 0}[dir=ltr] .ck.ck-form__row>:not(.ck-label)+*{margin-left:var(--ck-spacing-large)}[dir=rtl] .ck.ck-form__row>:not(.ck-label)+*{margin-right:var(--ck-spacing-large)}.ck.ck-form__row>.ck-label{width:100%;min-width:100%}.ck.ck-form__row.ck-table-form__action-row{margin-top:var(--ck-spacing-large)}.ck.ck-form__row.ck-table-form__action-row .ck-button .ck-button__label{color:var(--ck-color-text)}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-table/theme/formrow.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/ckeditor5-table/formrow.css"],"names":[],"mappings":"AAKA,iBACC,YAAa,CACb,kBAAmB,CACnB,gBAAiB,CACjB,6BAaD,CAVC,iCACC,WACD,CAGC,wHAEC,sBACD,CCbF,iBACC,4DA2BD,CAvBE,6CAEE,mCAMF,CARA,6CAME,oCAEF,CAGD,2BACC,UAAW,CACX,cACD,CAEA,2CACC,kCAKD,CAHC,wEACC,0BACD","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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-form__row {\n\tdisplay: flex;\n\tflex-direction: row;\n\tflex-wrap: nowrap;\n\tjustify-content: space-between;\n\n\t/* Ignore labels that work as fieldset legends */\n\t& > *:not(.ck-label) {\n\t\tflex-grow: 1;\n\t}\n\n\t&.ck-table-form__action-row {\n\t\t& .ck-button-save,\n\t\t& .ck-button-cancel {\n\t\t\tjustify-content: center;\n\t\t}\n\t}\n}\n","/*\n * Copyright (c) 2003-2022, 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.ck.ck-form__row {\n\tpadding: var(--ck-spacing-standard) var(--ck-spacing-large) 0;\n\n\t/* Ignore labels that work as fieldset legends */\n\t& > *:not(.ck-label) {\n\t\t& + * {\n\t\t\t@mixin ck-dir ltr {\n\t\t\t\tmargin-left: var(--ck-spacing-large);\n\t\t\t}\n\n\t\t\t@mixin ck-dir rtl {\n\t\t\t\tmargin-right: var(--ck-spacing-large);\n\t\t\t}\n\t\t}\n\t}\n\n\t& > .ck-label {\n\t\twidth: 100%;\n\t\tmin-width: 100%;\n\t}\n\n\t&.ck-table-form__action-row {\n\t\tmargin-top: var(--ck-spacing-large);\n\n\t\t& .ck-button .ck-button__label {\n\t\t\tcolor: var(--ck-color-text);\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-table/theme/inserttable.css":
/*!**********************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-table/theme/inserttable.css ***!
\**********************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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-insert-table-dropdown__grid{display:flex;flex-direction:row;flex-wrap:wrap}:root{--ck-insert-table-dropdown-padding:10px;--ck-insert-table-dropdown-box-height:11px;--ck-insert-table-dropdown-box-width:12px;--ck-insert-table-dropdown-box-margin:1px}.ck .ck-insert-table-dropdown__grid{width:calc(var(--ck-insert-table-dropdown-box-width)*10 + var(--ck-insert-table-dropdown-box-margin)*20 + var(--ck-insert-table-dropdown-padding)*2);padding:var(--ck-insert-table-dropdown-padding) var(--ck-insert-table-dropdown-padding) 0}.ck .ck-insert-table-dropdown__label{text-align:center}.ck .ck-insert-table-dropdown-grid-box{width:var(--ck-insert-table-dropdown-box-width);height:var(--ck-insert-table-dropdown-box-height);margin:var(--ck-insert-table-dropdown-box-margin);border:1px solid var(--ck-color-base-border);border-radius:1px}.ck .ck-insert-table-dropdown-grid-box.ck-on{border-color:var(--ck-color-focus-border);background:var(--ck-color-focus-outer-shadow)}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-table/theme/inserttable.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/ckeditor5-table/inserttable.css"],"names":[],"mappings":"AAKA,oCACC,YAAa,CACb,kBAAmB,CACnB,cACD,CCJA,MACC,uCAAwC,CACxC,0CAA2C,CAC3C,yCAA0C,CAC1C,yCACD,CAEA,oCAEC,oJAA2J,CAC3J,yFACD,CAEA,qCACC,iBACD,CAEA,uCACC,+CAAgD,CAChD,iDAAkD,CAClD,iDAAkD,CAClD,4CAA6C,CAC7C,iBAMD,CAJC,6CACC,yCAA0C,CAC1C,6CACD","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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-insert-table-dropdown__grid {\n\tdisplay: flex;\n\tflex-direction: row;\n\tflex-wrap: wrap;\n}\n","/*\n * Copyright (c) 2003-2022, 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-insert-table-dropdown-padding: 10px;\n\t--ck-insert-table-dropdown-box-height: 11px;\n\t--ck-insert-table-dropdown-box-width: 12px;\n\t--ck-insert-table-dropdown-box-margin: 1px;\n}\n\n.ck .ck-insert-table-dropdown__grid {\n\t/* The width of a container should match 10 items in a row so there will be a 10x10 grid. */\n\twidth: calc(var(--ck-insert-table-dropdown-box-width) * 10 + var(--ck-insert-table-dropdown-box-margin) * 20 + var(--ck-insert-table-dropdown-padding) * 2);\n\tpadding: var(--ck-insert-table-dropdown-padding) var(--ck-insert-table-dropdown-padding) 0;\n}\n\n.ck .ck-insert-table-dropdown__label {\n\ttext-align: center;\n}\n\n.ck .ck-insert-table-dropdown-grid-box {\n\twidth: var(--ck-insert-table-dropdown-box-width);\n\theight: var(--ck-insert-table-dropdown-box-height);\n\tmargin: var(--ck-insert-table-dropdown-box-margin);\n\tborder: 1px solid var(--ck-color-base-border);\n\tborder-radius: 1px;\n\n\t&.ck-on {\n\t\tborder-color: var(--ck-color-focus-border);\n\t\tbackground: var(--ck-color-focus-outer-shadow);\n\t}\n}\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-table/theme/table.css":
/*!****************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-table/theme/table.css ***!
\****************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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 .table{margin:.9em auto;display:table}.ck-content .table table{border-collapse:collapse;border-spacing:0;width:100%;height:100%;border:1px double #b3b3b3}.ck-content .table table td,.ck-content .table table th{min-width:2em;padding:.4em;border:1px solid #bfbfbf}.ck-content .table table th{font-weight:700;background:hsla(0,0%,0%,5%)}.ck-content[dir=rtl] .table th{text-align:right}.ck-content[dir=ltr] .table th{text-align:left}.ck-editor__editable .ck-table-bogus-paragraph{display:inline-block;width:100%}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-table/theme/table.css"],"names":[],"mappings":"AAKA,mBAIC,gBAAkB,CAClB,aAgCD,CA9BC,yBAEC,wBAAyB,CACzB,gBAAiB,CAIjB,UAAW,CACX,WAAY,CAIZ,yBAiBD,CAfC,wDAEC,aAAc,CACd,YAAa,CAKb,wBACD,CAEA,4BACC,eAAiB,CACjB,2BACD,CAMF,+BACC,gBACD,CAEA,+BACC,eACD,CAEA,+CAKC,oBAAqB,CAMrB,UACD","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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 .table {\n\t/* Give the table widget some air and center it horizontally */\n\t/* The first value should be equal to --ck-spacing-large variable if used in the editor context\n\tto avoid the content jumping (See https://github.com/ckeditor/ckeditor5/issues/9825). */\n\tmargin: 0.9em auto;\n\tdisplay: table;\n\n\t& table {\n\t\t/* The table cells should have slight borders */\n\t\tborder-collapse: collapse;\n\t\tborder-spacing: 0;\n\n\t\t/* Table width and height are set on the parent <figure>. Make sure the table inside stretches\n\t\tto the full dimensions of the container (https://github.com/ckeditor/ckeditor5/issues/6186). */\n\t\twidth: 100%;\n\t\theight: 100%;\n\n\t\t/* The outer border of the table should be slightly darker than the inner lines.\n\t\tAlso see https://github.com/ckeditor/ckeditor5-table/issues/50. */\n\t\tborder: 1px double hsl(0, 0%, 70%);\n\n\t\t& td,\n\t\t& th {\n\t\t\tmin-width: 2em;\n\t\t\tpadding: .4em;\n\n\t\t\t/* The border is inherited from .ck-editor__nested-editable styles, so theoretically it's not necessary here.\n\t\t\tHowever, the border is a content style, so it should use .ck-content (so it works outside the editor).\n\t\t\tHence, the duplication. See https://github.com/ckeditor/ckeditor5/issues/6314 */\n\t\t\tborder: 1px solid hsl(0, 0%, 75%);\n\t\t}\n\n\t\t& th {\n\t\t\tfont-weight: bold;\n\t\t\tbackground: hsla(0, 0%, 0%, 5%);\n\t\t}\n\t}\n}\n\n/* Text alignment of the table header should match the editor settings and override the native browser styling,\nwhen content is available outside the editor. See https://github.com/ckeditor/ckeditor5/issues/6638 */\n.ck-content[dir=\"rtl\"] .table th {\n\ttext-align: right;\n}\n\n.ck-content[dir=\"ltr\"] .table th {\n\ttext-align: left;\n}\n\n.ck-editor__editable .ck-table-bogus-paragraph {\n\t/*\n\t * Use display:inline-block to force Chrome/Safari to limit text mutations to this element.\n\t * See https://github.com/ckeditor/ckeditor5/issues/6062.\n\t */\n\tdisplay: inline-block;\n\n\t/*\n\t * Inline HTML elements nested in the span should always be dimensioned in relation to the whole cell width.\n\t * See https://github.com/ckeditor/ckeditor5/issues/9117.\n\t */\n\twidth: 100%;\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-table/theme/tablecaption.css":
/*!***********************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-table/theme/tablecaption.css ***!
\***********************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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, ":root{--ck-color-table-caption-background:#f7f7f7;--ck-color-table-caption-text:#333;--ck-color-table-caption-highlighted-background:#fd0}.ck-content .table>figcaption{display:table-caption;caption-side:top;word-break:break-word;text-align:center;color:var(--ck-color-table-caption-text);background-color:var(--ck-color-table-caption-background);padding:.6em;font-size:.75em;outline-offset:-1px}.ck.ck-editor__editable .table>figcaption.table__caption_highlighted{animation:ck-table-caption-highlight .6s ease-out}.ck.ck-editor__editable .table>figcaption.ck-placeholder:before{padding-left:inherit;padding-right:inherit;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}@keyframes ck-table-caption-highlight{0%{background-color:var(--ck-color-table-caption-highlighted-background)}to{background-color:var(--ck-color-table-caption-background)}}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-table/theme/tablecaption.css"],"names":[],"mappings":"AAKA,MACC,2CAAoD,CACpD,kCAA8C,CAC9C,oDACD,CAGA,8BACC,qBAAsB,CACtB,gBAAiB,CACjB,qBAAsB,CACtB,iBAAkB,CAClB,wCAAyC,CACzC,yDAA0D,CAC1D,YAAa,CACb,eAAgB,CAChB,mBACD,CAIC,qEACC,iDACD,CAEA,gEACC,oBAAqB,CACrB,qBAAsB,CAMtB,kBAAmB,CACnB,eAAgB,CAChB,sBACD,CAGD,sCACC,GACC,qEACD,CAEA,GACC,yDACD,CACD","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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-table-caption-background: hsl(0, 0%, 97%);\n\t--ck-color-table-caption-text: hsl(0, 0%, 20%);\n\t--ck-color-table-caption-highlighted-background: hsl(52deg 100% 50%);\n}\n\n/* Content styles */\n.ck-content .table > figcaption {\n\tdisplay: table-caption;\n\tcaption-side: top;\n\tword-break: break-word;\n\ttext-align: center;\n\tcolor: var(--ck-color-table-caption-text);\n\tbackground-color: var(--ck-color-table-caption-background);\n\tpadding: .6em;\n\tfont-size: .75em;\n\toutline-offset: -1px;\n}\n\n/* Editing styles */\n.ck.ck-editor__editable .table > figcaption {\n\t&.table__caption_highlighted {\n\t\tanimation: ck-table-caption-highlight .6s ease-out;\n\t}\n\n\t&.ck-placeholder::before {\n\t\tpadding-left: inherit;\n\t\tpadding-right: inherit;\n\n\t\t/*\n\t\t * Make sure the table caption placeholder doesn't overflow the placeholder area.\n\t\t * See https://github.com/ckeditor/ckeditor5/issues/9162.\n\t\t */\n\t\twhite-space: nowrap;\n\t\toverflow: hidden;\n\t\ttext-overflow: ellipsis;\n\t}\n}\n\n@keyframes ck-table-caption-highlight {\n\t0% {\n\t\tbackground-color: var(--ck-color-table-caption-highlighted-background);\n\t}\n\n\t100% {\n\t\tbackground-color: var(--ck-color-table-caption-background);\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-table/theme/tablecellproperties.css":
/*!******************************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-table/theme/tablecellproperties.css ***!
\******************************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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-table-cell-properties-form .ck-form__row.ck-table-cell-properties-form__alignment-row{flex-wrap:wrap}.ck.ck-table-cell-properties-form .ck-form__row.ck-table-cell-properties-form__alignment-row .ck.ck-toolbar:first-of-type{flex-grow:0.57}.ck.ck-table-cell-properties-form .ck-form__row.ck-table-cell-properties-form__alignment-row .ck.ck-toolbar:last-of-type{flex-grow:0.43}.ck.ck-table-cell-properties-form .ck-form__row.ck-table-cell-properties-form__alignment-row .ck.ck-toolbar .ck-button{flex-grow:1}.ck.ck-table-cell-properties-form{width:320px}.ck.ck-table-cell-properties-form .ck-form__row.ck-table-cell-properties-form__padding-row{align-self:flex-end;padding:0;width:25%}.ck.ck-table-cell-properties-form .ck-form__row.ck-table-cell-properties-form__alignment-row .ck.ck-toolbar{background:none;margin-top:var(--ck-spacing-standard)}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-table/theme/tablecellproperties.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/ckeditor5-table/tablecellproperties.css"],"names":[],"mappings":"AAOE,6FACC,cAiBD,CAdE,0HAEC,cACD,CAEA,yHAEC,cACD,CAEA,uHACC,WACD,CClBJ,kCACC,WAkBD,CAfE,2FACC,mBAAoB,CACpB,SAAU,CACV,SACD,CAGC,4GACC,eAAgB,CAGhB,qCACD","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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-table-cell-properties-form {\n\t& .ck-form__row {\n\t\t&.ck-table-cell-properties-form__alignment-row {\n\t\t\tflex-wrap: wrap;\n\n\t\t\t& .ck.ck-toolbar {\n\t\t\t\t&:first-of-type {\n\t\t\t\t\t/* 4 buttons out of 7 (h-alignment + v-alignment) = 0.57 */\n\t\t\t\t\tflex-grow: 0.57;\n\t\t\t\t}\n\n\t\t\t\t&:last-of-type {\n\t\t\t\t\t/* 3 buttons out of 7 (h-alignment + v-alignment) = 0.43 */\n\t\t\t\t\tflex-grow: 0.43;\n\t\t\t\t}\n\n\t\t\t\t& .ck-button {\n\t\t\t\t\tflex-grow: 1;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n","/*\n * Copyright (c) 2003-2022, 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-table-cell-properties-form {\n\twidth: 320px;\n\n\t& .ck-form__row {\n\t\t&.ck-table-cell-properties-form__padding-row {\n\t\t\talign-self: flex-end;\n\t\t\tpadding: 0;\n\t\t\twidth: 25%;\n\t\t}\n\n\t\t&.ck-table-cell-properties-form__alignment-row {\n\t\t\t& .ck.ck-toolbar {\n\t\t\t\tbackground: none;\n\n\t\t\t\t/* Compensate for missing input label that would push the margin (toolbar has no inputs). */\n\t\t\t\tmargin-top: var(--ck-spacing-standard);\n\t\t\t}\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-table/theme/tableediting.css":
/*!***********************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-table/theme/tableediting.css ***!
\***********************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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, ":root{--ck-color-table-focused-cell-background:rgba(158,207,250,0.3)}.ck-widget.table td.ck-editor__nested-editable.ck-editor__nested-editable_focused,.ck-widget.table td.ck-editor__nested-editable:focus,.ck-widget.table th.ck-editor__nested-editable.ck-editor__nested-editable_focused,.ck-widget.table th.ck-editor__nested-editable:focus{background:var(--ck-color-table-focused-cell-background);border-style:none;outline:1px solid var(--ck-color-focus-border);outline-offset:-1px}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/ckeditor5-table/tableediting.css"],"names":[],"mappings":"AAKA,MACC,8DACD,CAKE,8QAGC,wDAAyD,CAKzD,iBAAkB,CAClB,8CAA+C,CAC/C,mBACD","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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-table-focused-cell-background: hsla(208, 90%, 80%, .3);\n}\n\n.ck-widget.table {\n\t& td,\n\t& th {\n\t\t&.ck-editor__nested-editable.ck-editor__nested-editable_focused,\n\t\t&.ck-editor__nested-editable:focus {\n\t\t\t/* A very slight background to highlight the focused cell */\n\t\t\tbackground: var(--ck-color-table-focused-cell-background);\n\n\t\t\t/* Fixes the problem where surrounding cells cover the focused cell's border.\n\t\t\tIt does not fix the problem in all places but the UX is improved.\n\t\t\tSee https://github.com/ckeditor/ckeditor5-table/issues/29. */\n\t\t\tborder-style: none;\n\t\t\toutline: 1px solid var(--ck-color-focus-border);\n\t\t\toutline-offset: -1px; /* progressive enhancement - no IE support */\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-table/theme/tableform.css":
/*!********************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-table/theme/tableform.css ***!
\********************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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-table-form .ck-form__row.ck-table-form__background-row,.ck.ck-table-form .ck-form__row.ck-table-form__border-row{flex-wrap:wrap}.ck.ck-table-form .ck-form__row.ck-table-form__dimensions-row{flex-wrap:wrap;align-items:center}.ck.ck-table-form .ck-form__row.ck-table-form__dimensions-row .ck-labeled-field-view{display:flex;flex-direction:column-reverse;align-items:center}.ck.ck-table-form .ck-form__row.ck-table-form__dimensions-row .ck-labeled-field-view .ck.ck-dropdown,.ck.ck-table-form .ck-form__row.ck-table-form__dimensions-row .ck-table-form__dimension-operator{flex-grow:0}.ck.ck-table-form .ck.ck-labeled-field-view{position:relative}.ck.ck-table-form .ck.ck-labeled-field-view .ck.ck-labeled-field-view__status{position:absolute;left:50%;bottom:calc(var(--ck-table-properties-error-arrow-size)*-1);transform:translate(-50%,100%);z-index:1}.ck.ck-table-form .ck.ck-labeled-field-view .ck.ck-labeled-field-view__status:after{content:\"\";position:absolute;top:calc(var(--ck-table-properties-error-arrow-size)*-1);left:50%;transform:translateX(-50%)}:root{--ck-table-properties-error-arrow-size:6px;--ck-table-properties-min-error-width:150px}.ck.ck-table-form .ck-form__row.ck-table-form__border-row .ck-labeled-field-view>.ck-label{font-size:var(--ck-font-size-tiny);text-align:center}.ck.ck-table-form .ck-form__row.ck-table-form__border-row .ck-table-form__border-style,.ck.ck-table-form .ck-form__row.ck-table-form__border-row .ck-table-form__border-width{width:80px;min-width:80px;max-width:80px}.ck.ck-table-form .ck-form__row.ck-table-form__dimensions-row{padding:0}.ck.ck-table-form .ck-form__row.ck-table-form__dimensions-row .ck-table-form__dimensions-row__height,.ck.ck-table-form .ck-form__row.ck-table-form__dimensions-row .ck-table-form__dimensions-row__width{margin:0}.ck.ck-table-form .ck-form__row.ck-table-form__dimensions-row .ck-table-form__dimension-operator{align-self:flex-end;display:inline-block;height:var(--ck-ui-component-min-height);line-height:var(--ck-ui-component-min-height);margin:0 var(--ck-spacing-small)}.ck.ck-table-form .ck.ck-labeled-field-view{padding-top:var(--ck-spacing-standard)}.ck.ck-table-form .ck.ck-labeled-field-view .ck.ck-labeled-field-view__status{border-radius:0}.ck-rounded-corners .ck.ck-table-form .ck.ck-labeled-field-view .ck.ck-labeled-field-view__status,.ck.ck-table-form .ck.ck-labeled-field-view .ck.ck-labeled-field-view__status.ck-rounded-corners{border-radius:var(--ck-border-radius)}.ck.ck-table-form .ck.ck-labeled-field-view .ck.ck-labeled-field-view__status{background:var(--ck-color-base-error);color:var(--ck-color-base-background);padding:var(--ck-spacing-small) var(--ck-spacing-medium);min-width:var(--ck-table-properties-min-error-width);text-align:center}.ck.ck-table-form .ck.ck-labeled-field-view .ck.ck-labeled-field-view__status:after{border-left:var(--ck-table-properties-error-arrow-size) solid transparent;border-bottom:var(--ck-table-properties-error-arrow-size) solid var(--ck-color-base-error);border-right:var(--ck-table-properties-error-arrow-size) solid transparent;border-top:0 solid transparent}.ck.ck-table-form .ck.ck-labeled-field-view .ck.ck-labeled-field-view__status{animation:ck-table-form-labeled-view-status-appear .15s ease both}.ck.ck-table-form .ck.ck-labeled-field-view .ck-input.ck-error:not(:focus)+.ck.ck-labeled-field-view__status{display:none}@keyframes ck-table-form-labeled-view-status-appear{0%{opacity:0}to{opacity:1}}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-table/theme/tableform.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/ckeditor5-table/tableform.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/mixins/_rounded.css"],"names":[],"mappings":"AAWE,wHACC,cACD,CAEA,8DACC,cAAe,CACf,kBAeD,CAbC,qFACC,YAAa,CACb,6BAA8B,CAC9B,kBAKD,CAEA,sMACC,WACD,CAIF,4CAEC,iBAoBD,CAlBC,8EACC,iBAAkB,CAClB,QAAS,CACT,2DAAgE,CAChE,8BAA+B,CAG/B,SAUD,CAPC,oFACC,UAAW,CACX,iBAAkB,CAClB,wDAA6D,CAC7D,QAAS,CACT,0BACD,CChDH,MACC,0CAA2C,CAC3C,2CACD,CAMI,2FACC,kCAAmC,CACnC,iBACD,CAGD,8KAEC,UAAW,CACX,cAAe,CACf,cACD,CAGD,8DACC,SAcD,CAZC,yMAEC,QACD,CAEA,iGACC,mBAAoB,CACpB,oBAAqB,CACrB,wCAAyC,CACzC,6CAA8C,CAC9C,gCACD,CAIF,4CACC,sCAyBD,CAvBC,8ECxCD,eDyDC,CAjBA,mMCpCA,qCDqDA,CAjBA,8EAGC,qCAAsC,CACtC,qCAAsC,CACtC,wDAAyD,CACzD,oDAAqD,CACrD,iBAUD,CAPC,oFAGC,yEAAmB,CAAnB,0FAAmB,CAAnB,0EAAmB,CAAnB,8BACD,CAdD,8EAgBC,iEACD,CAGA,6GACC,YACD,CAIF,oDACC,GACC,SACD,CAEA,GACC,SACD,CACD","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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-table-form {\n\t& .ck-form__row {\n\t\t&.ck-table-form__border-row {\n\t\t\tflex-wrap: wrap;\n\t\t}\n\n\t\t&.ck-table-form__background-row {\n\t\t\tflex-wrap: wrap;\n\t\t}\n\n\t\t&.ck-table-form__dimensions-row {\n\t\t\tflex-wrap: wrap;\n\t\t\talign-items: center;\n\n\t\t\t& .ck-labeled-field-view {\n\t\t\t\tdisplay: flex;\n\t\t\t\tflex-direction: column-reverse;\n\t\t\t\talign-items: center;\n\n\t\t\t\t& .ck.ck-dropdown {\n\t\t\t\t\tflex-grow: 0;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t& .ck-table-form__dimension-operator {\n\t\t\t\tflex-grow: 0;\n\t\t\t}\n\t\t}\n\t}\n\n\t& .ck.ck-labeled-field-view {\n\t\t/* Allow absolute positioning of the status (error) balloons. */\n\t\tposition: relative;\n\n\t\t& .ck.ck-labeled-field-view__status {\n\t\t\tposition: absolute;\n\t\t\tleft: 50%;\n\t\t\tbottom: calc( -1 * var(--ck-table-properties-error-arrow-size) );\n\t\t\ttransform: translate(-50%,100%);\n\n\t\t\t/* Make sure the balloon status stays on top of other form elements. */\n\t\t\tz-index: 1;\n\n\t\t\t/* The arrow pointing towards the field. */\n\t\t\t&::after {\n\t\t\t\tcontent: \"\";\n\t\t\t\tposition: absolute;\n\t\t\t\ttop: calc( -1 * var(--ck-table-properties-error-arrow-size) );\n\t\t\t\tleft: 50%;\n\t\t\t\ttransform: translateX( -50% );\n\t\t\t}\n\t\t}\n\t}\n}\n","/*\n * Copyright (c) 2003-2022, 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 \"../mixins/_rounded.css\";\n\n:root {\n\t--ck-table-properties-error-arrow-size: 6px;\n\t--ck-table-properties-min-error-width: 150px;\n}\n\n.ck.ck-table-form {\n\t& .ck-form__row {\n\t\t&.ck-table-form__border-row {\n\t\t\t& .ck-labeled-field-view {\n\t\t\t\t& > .ck-label {\n\t\t\t\t\tfont-size: var(--ck-font-size-tiny);\n\t\t\t\t\ttext-align: center;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t& .ck-table-form__border-style,\n\t\t\t& .ck-table-form__border-width {\n\t\t\t\twidth: 80px;\n\t\t\t\tmin-width: 80px;\n\t\t\t\tmax-width: 80px;\n\t\t\t}\n\t\t}\n\n\t\t&.ck-table-form__dimensions-row {\n\t\t\tpadding: 0;\n\n\t\t\t& .ck-table-form__dimensions-row__width,\n\t\t\t& .ck-table-form__dimensions-row__height {\n\t\t\t\tmargin: 0\n\t\t\t}\n\n\t\t\t& .ck-table-form__dimension-operator {\n\t\t\t\talign-self: flex-end;\n\t\t\t\tdisplay: inline-block;\n\t\t\t\theight: var(--ck-ui-component-min-height);\n\t\t\t\tline-height: var(--ck-ui-component-min-height);\n\t\t\t\tmargin: 0 var(--ck-spacing-small);\n\t\t\t}\n\t\t}\n\t}\n\n\t& .ck.ck-labeled-field-view {\n\t\tpadding-top: var(--ck-spacing-standard);\n\n\t\t& .ck.ck-labeled-field-view__status {\n\t\t\t@mixin ck-rounded-corners;\n\n\t\t\tbackground: var(--ck-color-base-error);\n\t\t\tcolor: var(--ck-color-base-background);\n\t\t\tpadding: var(--ck-spacing-small) var(--ck-spacing-medium);\n\t\t\tmin-width: var(--ck-table-properties-min-error-width);\n\t\t\ttext-align: center;\n\n\t\t\t/* The arrow pointing towards the field. */\n\t\t\t&::after {\n\t\t\t\tborder-color: transparent transparent var(--ck-color-base-error) transparent;\n\t\t\t\tborder-width: 0 var(--ck-table-properties-error-arrow-size) var(--ck-table-properties-error-arrow-size) var(--ck-table-properties-error-arrow-size);\n\t\t\t\tborder-style: solid;\n\t\t\t}\n\n\t\t\tanimation: ck-table-form-labeled-view-status-appear .15s ease both;\n\t\t}\n\n\t\t/* Hide the error balloon when the field is blurred. Makes the experience much more clear. */\n\t\t& .ck-input.ck-error:not(:focus) + .ck.ck-labeled-field-view__status {\n\t\t\tdisplay: none;\n\t\t}\n\t}\n}\n\n@keyframes ck-table-form-labeled-view-status-appear {\n\t0% {\n\t\topacity: 0;\n\t}\n\n\t100% {\n\t\topacity: 1;\n\t}\n}\n","/*\n * Copyright (c) 2003-2022, 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/**\n * Implements rounded corner interface for .ck-rounded-corners class.\n *\n * @see $ck-border-radius\n */\n@define-mixin ck-rounded-corners {\n\tborder-radius: 0;\n\n\t@nest .ck-rounded-corners &,\n\t&.ck-rounded-corners {\n\t\tborder-radius: var(--ck-border-radius);\n\t\t@mixin-content;\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-table/theme/tableproperties.css":
/*!**************************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-table/theme/tableproperties.css ***!
\**************************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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-table-properties-form .ck-form__row.ck-table-properties-form__alignment-row{flex-wrap:wrap;flex-basis:0;align-content:baseline}.ck.ck-table-properties-form .ck-form__row.ck-table-properties-form__alignment-row .ck.ck-toolbar .ck-toolbar__items{flex-wrap:nowrap}.ck.ck-table-properties-form{width:320px}.ck.ck-table-properties-form .ck-form__row.ck-table-properties-form__alignment-row{align-self:flex-end;padding:0}.ck.ck-table-properties-form .ck-form__row.ck-table-properties-form__alignment-row .ck.ck-toolbar{background:none;margin-top:var(--ck-spacing-standard)}.ck.ck-table-properties-form .ck-form__row.ck-table-properties-form__alignment-row .ck.ck-toolbar .ck-toolbar__items>*{width:40px}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-table/theme/tableproperties.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/ckeditor5-table/tableproperties.css"],"names":[],"mappings":"AAOE,mFACC,cAAe,CACf,YAAa,CACb,sBAKD,CAHC,qHACC,gBACD,CCTH,6BACC,WAmBD,CAhBE,mFACC,mBAAoB,CACpB,SAYD,CAVC,kGACC,eAAgB,CAGhB,qCAKD,CAHC,uHACC,UACD","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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-table-properties-form {\n\t& .ck-form__row {\n\t\t&.ck-table-properties-form__alignment-row {\n\t\t\tflex-wrap: wrap;\n\t\t\tflex-basis: 0;\n\t\t\talign-content: baseline;\n\n\t\t\t& .ck.ck-toolbar .ck-toolbar__items {\n\t\t\t\tflex-wrap: nowrap;\n\t\t\t}\n\t\t}\n\t}\n}\n","/*\n * Copyright (c) 2003-2022, 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-table-properties-form {\n\twidth: 320px;\n\n\t& .ck-form__row {\n\t\t&.ck-table-properties-form__alignment-row {\n\t\t\talign-self: flex-end;\n\t\t\tpadding: 0;\n\n\t\t\t& .ck.ck-toolbar {\n\t\t\t\tbackground: none;\n\n\t\t\t\t/* Compensate for missing input label that would push the margin (toolbar has no inputs). */\n\t\t\t\tmargin-top: var(--ck-spacing-standard);\n\n\t\t\t\t& .ck-toolbar__items > * {\n\t\t\t\t\twidth: 40px;\n\t\t\t\t}\n\t\t\t}\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-table/theme/tableselection.css":
/*!*************************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-table/theme/tableselection.css ***!
\*************************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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, ":root{--ck-table-selected-cell-background:rgba(158,207,250,0.3)}.ck.ck-editor__editable .table table td.ck-editor__editable_selected,.ck.ck-editor__editable .table table th.ck-editor__editable_selected{position:relative;caret-color:transparent;outline:unset;box-shadow:unset}.ck.ck-editor__editable .table table td.ck-editor__editable_selected:after,.ck.ck-editor__editable .table table th.ck-editor__editable_selected:after{content:\"\";pointer-events:none;background-color:var(--ck-table-selected-cell-background);position:absolute;top:0;left:0;right:0;bottom:0}.ck.ck-editor__editable .table table td.ck-editor__editable_selected ::selection,.ck.ck-editor__editable .table table td.ck-editor__editable_selected:focus,.ck.ck-editor__editable .table table th.ck-editor__editable_selected ::selection,.ck.ck-editor__editable .table table th.ck-editor__editable_selected:focus{background-color:transparent}.ck.ck-editor__editable .table table td.ck-editor__editable_selected .ck-widget,.ck.ck-editor__editable .table table th.ck-editor__editable_selected .ck-widget{outline:unset}.ck.ck-editor__editable .table table td.ck-editor__editable_selected .ck-widget>.ck-widget__selection-handle,.ck.ck-editor__editable .table table th.ck-editor__editable_selected .ck-widget>.ck-widget__selection-handle{display:none}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/ckeditor5-table/tableselection.css"],"names":[],"mappings":"AAKA,MACC,yDACD,CAGC,0IAEC,iBAAkB,CAClB,uBAAwB,CACxB,aAAc,CACd,gBA8BD,CA3BC,sJACC,UAAW,CACX,mBAAoB,CACpB,yDAA0D,CAC1D,iBAAkB,CAClB,KAAM,CACN,MAAO,CACP,OAAQ,CACR,QACD,CAEA,wTAEC,4BACD,CAMA,gKACC,aAKD,CAHC,0NACC,YACD","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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-table-selected-cell-background: hsla(208, 90%, 80%, .3);\n}\n\n.ck.ck-editor__editable .table table {\n\t& td.ck-editor__editable_selected,\n\t& th.ck-editor__editable_selected {\n\t\tposition: relative;\n\t\tcaret-color: transparent;\n\t\toutline: unset;\n\t\tbox-shadow: unset;\n\n\t\t/* https://github.com/ckeditor/ckeditor5/issues/6446 */\n\t\t&:after {\n\t\t\tcontent: '';\n\t\t\tpointer-events: none;\n\t\t\tbackground-color: var(--ck-table-selected-cell-background);\n\t\t\tposition: absolute;\n\t\t\ttop: 0;\n\t\t\tleft: 0;\n\t\t\tright: 0;\n\t\t\tbottom: 0;\n\t\t}\n\n\t\t& ::selection,\n\t\t&:focus {\n\t\t\tbackground-color: transparent;\n\t\t}\n\n\t\t/*\n\t\t * To reduce the amount of noise, all widgets in the table selection have no outline and no selection handle.\n\t\t * See https://github.com/ckeditor/ckeditor5/issues/9491.\n\t\t */\n\t\t& .ck-widget {\n\t\t\toutline: unset;\n\n\t\t\t& > .ck-widget__selection-handle {\n\t\t\t\tdisplay: none;\n\t\t\t}\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/components/button/button.css":
/*!********************************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/components/button/button.css ***!
\********************************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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-button,a.ck.ck-button{-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none;user-select:none}.ck.ck-button .ck-tooltip,a.ck.ck-button .ck-tooltip{display:block}@media (hover:none){.ck.ck-button .ck-tooltip,a.ck.ck-button .ck-tooltip{display:none}}.ck.ck-button,a.ck.ck-button{position:relative;display:inline-flex;align-items:center;justify-content:left}.ck.ck-button .ck-button__label,a.ck.ck-button .ck-button__label{display:none}.ck.ck-button.ck-button_with-text .ck-button__label,a.ck.ck-button.ck-button_with-text .ck-button__label{display:inline-block}.ck.ck-button:not(.ck-button_with-text),a.ck.ck-button:not(.ck-button_with-text){justify-content:center}.ck.ck-button:hover .ck-tooltip,a.ck.ck-button:hover .ck-tooltip{visibility:visible;opacity:1}.ck.ck-button:focus:not(:hover) .ck-tooltip,a.ck.ck-button:focus:not(:hover) .ck-tooltip{display:none}.ck.ck-button,a.ck.ck-button{background:var(--ck-color-button-default-background)}.ck.ck-button:not(.ck-disabled):hover,a.ck.ck-button:not(.ck-disabled):hover{background:var(--ck-color-button-default-hover-background)}.ck.ck-button:not(.ck-disabled):active,a.ck.ck-button:not(.ck-disabled):active{background:var(--ck-color-button-default-active-background);box-shadow:inset 0 2px 2px var(--ck-color-button-default-active-shadow)}.ck.ck-button.ck-disabled,a.ck.ck-button.ck-disabled{background:var(--ck-color-button-default-disabled-background)}.ck.ck-button,a.ck.ck-button{border-radius:0}.ck-rounded-corners .ck.ck-button,.ck-rounded-corners a.ck.ck-button,.ck.ck-button.ck-rounded-corners,a.ck.ck-button.ck-rounded-corners{border-radius:var(--ck-border-radius)}.ck.ck-button,a.ck.ck-button{white-space:nowrap;cursor:default;vertical-align:middle;padding:var(--ck-spacing-tiny);text-align:center;min-width:var(--ck-ui-component-min-height);min-height:var(--ck-ui-component-min-height);line-height:1;font-size:inherit;border:1px solid transparent;transition:box-shadow .2s ease-in-out,border .2s ease-in-out;-webkit-appearance:none}.ck.ck-button:active,.ck.ck-button:focus,a.ck.ck-button:active,a.ck.ck-button:focus{outline:none;border:var(--ck-focus-ring);box-shadow:var(--ck-focus-outer-shadow),0 0}.ck.ck-button .ck-button__icon use,.ck.ck-button .ck-button__icon use *,a.ck.ck-button .ck-button__icon use,a.ck.ck-button .ck-button__icon use *{color:inherit}.ck.ck-button .ck-button__label,a.ck.ck-button .ck-button__label{font-size:inherit;font-weight:inherit;color:inherit;cursor:inherit;vertical-align:middle}[dir=ltr] .ck.ck-button .ck-button__label,[dir=ltr] a.ck.ck-button .ck-button__label{text-align:left}[dir=rtl] .ck.ck-button .ck-button__label,[dir=rtl] a.ck.ck-button .ck-button__label{text-align:right}.ck.ck-button .ck-button__keystroke,a.ck.ck-button .ck-button__keystroke{color:inherit}[dir=ltr] .ck.ck-button .ck-button__keystroke,[dir=ltr] a.ck.ck-button .ck-button__keystroke{margin-left:var(--ck-spacing-large)}[dir=rtl] .ck.ck-button .ck-button__keystroke,[dir=rtl] a.ck.ck-button .ck-button__keystroke{margin-right:var(--ck-spacing-large)}.ck.ck-button .ck-button__keystroke,a.ck.ck-button .ck-button__keystroke{font-weight:700;opacity:.7}.ck.ck-button.ck-disabled:active,.ck.ck-button.ck-disabled:focus,a.ck.ck-button.ck-disabled:active,a.ck.ck-button.ck-disabled:focus{box-shadow:var(--ck-focus-disabled-outer-shadow),0 0}.ck.ck-button.ck-disabled .ck-button__icon,a.ck.ck-button.ck-disabled .ck-button__icon{opacity:var(--ck-disabled-opacity)}.ck.ck-button.ck-disabled .ck-button__label,a.ck.ck-button.ck-disabled .ck-button__label{opacity:var(--ck-disabled-opacity)}.ck.ck-button.ck-disabled .ck-button__keystroke,a.ck.ck-button.ck-disabled .ck-button__keystroke{opacity:.3}.ck.ck-button.ck-button_with-text,a.ck.ck-button.ck-button_with-text{padding:var(--ck-spacing-tiny) var(--ck-spacing-standard)}[dir=ltr] .ck.ck-button.ck-button_with-text .ck-button__icon,[dir=ltr] a.ck.ck-button.ck-button_with-text .ck-button__icon{margin-left:calc(var(--ck-spacing-small)*-1);margin-right:var(--ck-spacing-small)}[dir=rtl] .ck.ck-button.ck-button_with-text .ck-button__icon,[dir=rtl] a.ck.ck-button.ck-button_with-text .ck-button__icon{margin-right:calc(var(--ck-spacing-small)*-1);margin-left:var(--ck-spacing-small)}.ck.ck-button.ck-button_with-keystroke .ck-button__label,a.ck.ck-button.ck-button_with-keystroke .ck-button__label{flex-grow:1}.ck.ck-button.ck-on,a.ck.ck-button.ck-on{background:var(--ck-color-button-on-background)}.ck.ck-button.ck-on:not(.ck-disabled):hover,a.ck.ck-button.ck-on:not(.ck-disabled):hover{background:var(--ck-color-button-on-hover-background)}.ck.ck-button.ck-on:not(.ck-disabled):active,a.ck.ck-button.ck-on:not(.ck-disabled):active{background:var(--ck-color-button-on-active-background);box-shadow:inset 0 2px 2px var(--ck-color-button-on-active-shadow)}.ck.ck-button.ck-on.ck-disabled,a.ck.ck-button.ck-on.ck-disabled{background:var(--ck-color-button-on-disabled-background)}.ck.ck-button.ck-button-save,a.ck.ck-button.ck-button-save{color:var(--ck-color-button-save)}.ck.ck-button.ck-button-cancel,a.ck.ck-button.ck-button-cancel{color:var(--ck-color-button-cancel)}.ck.ck-button-action,a.ck.ck-button-action{background:var(--ck-color-button-action-background)}.ck.ck-button-action:not(.ck-disabled):hover,a.ck.ck-button-action:not(.ck-disabled):hover{background:var(--ck-color-button-action-hover-background)}.ck.ck-button-action:not(.ck-disabled):active,a.ck.ck-button-action:not(.ck-disabled):active{background:var(--ck-color-button-action-active-background);box-shadow:inset 0 2px 2px var(--ck-color-button-action-active-shadow)}.ck.ck-button-action.ck-disabled,a.ck.ck-button-action.ck-disabled{background:var(--ck-color-button-action-disabled-background)}.ck.ck-button-action,a.ck.ck-button-action{color:var(--ck-color-button-action-text)}.ck.ck-button-bold,a.ck.ck-button-bold{font-weight:700}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-ui/theme/components/button/button.css","webpack://./node_modules/@ckeditor/ckeditor5-ui/theme/mixins/_unselectable.css","webpack://./node_modules/@ckeditor/ckeditor5-ui/theme/components/tooltip/mixins/_tooltip.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/ckeditor5-ui/components/button/button.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/ckeditor5-ui/mixins/_button.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/mixins/_rounded.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/mixins/_focus.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/mixins/_shadow.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/mixins/_disabled.css"],"names":[],"mappings":"AAQA,6BCCC,qBAAsB,CACtB,wBAAyB,CACzB,oBAAqB,CACrB,gBD6BD,CE/BC,qDACC,aAqBD,CAHC,oBAnBD,qDAoBE,YAEF,CADC,CFvBF,6BAKC,iBAAkB,CAClB,mBAAoB,CACpB,kBAAmB,CACnB,oBAyBD,CAvBC,iEACC,YACD,CAGC,yGACC,oBACD,CAID,iFACC,sBACD,CEkBA,iEACC,kBAAmB,CACnB,SACD,CAbA,yFACC,YACD,CC7BD,6BCAC,oDD0ID,CCvIE,6EACC,0DACD,CAEA,+EACC,2DAA4C,CAC5C,uEACD,CAID,qDACC,6DACD,CDhBD,6BEDC,eF2ID,CA1IA,wIEGE,qCFuIF,CA1IA,6BAKC,kBAAmB,CACnB,cAAe,CACf,qBAAsB,CACtB,8BAA+B,CAC/B,iBAAkB,CAGlB,2CAA4C,CAC5C,4CAA6C,CAI7C,aAAc,CAGd,iBAAkB,CAGlB,4BAA6B,CAG7B,4DAA8D,CAG9D,uBA6GD,CA3GC,oFGjCA,YAAa,CACb,2BAA2B,CCF3B,2CJsCA,CAIC,kJAEC,aACD,CAGD,iEAEC,iBAAkB,CAClB,mBAAoB,CACpB,aAAc,CACd,cAAe,CAIf,qBASD,CAlBA,qFAYE,eAMF,CAlBA,qFAgBE,gBAEF,CAEA,yEACC,aAYD,CAbA,6FAIE,mCASF,CAbA,6FAQE,oCAKF,CAbA,yEAWC,eAAiB,CACjB,UACD,CAIC,oIIrFD,oDJyFC,CAEA,uFK3FD,kCL6FC,CAGA,yFKhGD,kCLkGC,CAEA,iGACC,UACD,CAGD,qEACC,yDAcD,CAXC,2HAEE,4CAA+C,CAC/C,oCAOF,CAVA,2HAOE,6CAAgD,CAChD,mCAEF,CAKA,mHACC,WACD,CAID,yCC/HA,+CDiIA,CC9HC,yFACC,qDACD,CAEA,2FACC,sDAA4C,CAC5C,kEACD,CAID,iEACC,wDACD,CDmHA,2DACC,iCACD,CAEA,+DACC,mCACD,CAID,2CC7IC,mDDkJD,CC/IE,2FACC,yDACD,CAEA,6FACC,0DAA4C,CAC5C,sEACD,CAID,mEACC,4DACD,CD6HD,2CAIC,wCACD,CAEA,uCAEC,eACD","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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 \"../../mixins/_unselectable.css\";\n@import \"../tooltip/mixins/_tooltip.css\";\n\n.ck.ck-button,\na.ck.ck-button {\n\t@mixin ck-unselectable;\n\t@mixin ck-tooltip_enabled;\n\n\tposition: relative;\n\tdisplay: inline-flex;\n\talign-items: center;\n\tjustify-content: left;\n\n\t& .ck-button__label {\n\t\tdisplay: none;\n\t}\n\n\t&.ck-button_with-text {\n\t\t& .ck-button__label {\n\t\t\tdisplay: inline-block;\n\t\t}\n\t}\n\n\t/* Center the icon horizontally in a button without text. */\n\t&:not(.ck-button_with-text) {\n\t\tjustify-content: center;\n\t}\n\n\t&:hover {\n\t\t@mixin ck-tooltip_visible;\n\t}\n\n\t/* Get rid of the native focus outline around the tooltip when focused (but not :hover). */\n\t&:focus:not(:hover) {\n\t\t@mixin ck-tooltip_disabled;\n\t}\n}\n","/*\n * Copyright (c) 2003-2022, 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/**\n * Makes element unselectable.\n */\n@define-mixin ck-unselectable {\n\t-moz-user-select: none;\n\t-webkit-user-select: none;\n\t-ms-user-select: none;\n\tuser-select: none\n}\n","/*\n * Copyright (c) 2003-2022, 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/**\n * Enables the tooltip, which is the tooltip is in DOM but\n * not yet displayed.\n */\n@define-mixin ck-tooltip_enabled {\n\t& .ck-tooltip {\n\t\tdisplay: block;\n\n\t\t/*\n\t\t * Don't display tooltips in devices which don't support :hover.\n\t\t * In fact, it's all about iOS, which forces user to click UI elements twice to execute\n\t\t * the primary action, when tooltips are enabled.\n\t\t *\n\t\t * Q: OK, but why not the following query?\n\t\t *\n\t\t * @media (hover) {\n\t\t * display: block;\n\t\t * }\n\t\t *\n\t\t * A: Because FF does not support it and it would completely disable tooltips\n\t\t * in that browser.\n\t\t *\n\t\t * More in https://github.com/ckeditor/ckeditor5/issues/920.\n\t\t */\n\t\t@media (hover:none) {\n\t\t\tdisplay: none;\n\t\t}\n\t}\n}\n\n/**\n * Disables the tooltip making it disappear from DOM.\n */\n@define-mixin ck-tooltip_disabled {\n\t& .ck-tooltip {\n\t\tdisplay: none;\n\t}\n}\n\n/**\n * Shows the tooltip, which is already in DOM.\n * Requires `ck-tooltip_enabled` first.\n */\n@define-mixin ck-tooltip_visible {\n\t& .ck-tooltip {\n\t\tvisibility: visible;\n\t\topacity: 1;\n\t}\n}\n","/*\n * Copyright (c) 2003-2022, 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 \"../../../mixins/_focus.css\";\n@import \"../../../mixins/_shadow.css\";\n@import \"../../../mixins/_disabled.css\";\n@import \"../../../mixins/_rounded.css\";\n@import \"../../mixins/_button.css\";\n@import \"@ckeditor/ckeditor5-ui/theme/mixins/_dir.css\";\n\n.ck.ck-button,\na.ck.ck-button {\n\t@mixin ck-button-colors --ck-color-button-default;\n\t@mixin ck-rounded-corners;\n\n\twhite-space: nowrap;\n\tcursor: default;\n\tvertical-align: middle;\n\tpadding: var(--ck-spacing-tiny);\n\ttext-align: center;\n\n\t/* A very important piece of styling. Go to variable declaration to learn more. */\n\tmin-width: var(--ck-ui-component-min-height);\n\tmin-height: var(--ck-ui-component-min-height);\n\n\t/* Normalize the height of the line. Removing this will break consistent height\n\tamong text and text-less buttons (with icons). */\n\tline-height: 1;\n\n\t/* Enable font size inheritance, which allows fluid UI scaling. */\n\tfont-size: inherit;\n\n\t/* Avoid flickering when the foucs border shows up. */\n\tborder: 1px solid transparent;\n\n\t/* Apply some smooth transition to the box-shadow and border. */\n\ttransition: box-shadow .2s ease-in-out, border .2s ease-in-out;\n\n\t/* https://github.com/ckeditor/ckeditor5-theme-lark/issues/189 */\n\t-webkit-appearance: none;\n\n\t&:active,\n\t&:focus {\n\t\t@mixin ck-focus-ring;\n\t\t@mixin ck-box-shadow var(--ck-focus-outer-shadow);\n\t}\n\n\t/* Allow icon coloring using the text \"color\" property. */\n\t& .ck-button__icon {\n\t\t& use,\n\t\t& use * {\n\t\t\tcolor: inherit;\n\t\t}\n\t}\n\n\t& .ck-button__label {\n\t\t/* Enable font size inheritance, which allows fluid UI scaling. */\n\t\tfont-size: inherit;\n\t\tfont-weight: inherit;\n\t\tcolor: inherit;\n\t\tcursor: inherit;\n\n\t\t/* Must be consistent with .ck-icon's vertical align. Otherwise, buttons with and\n\t\twithout labels (but with icons) have different sizes in Chrome */\n\t\tvertical-align: middle;\n\n\t\t@mixin ck-dir ltr {\n\t\t\ttext-align: left;\n\t\t}\n\n\t\t@mixin ck-dir rtl {\n\t\t\ttext-align: right;\n\t\t}\n\t}\n\n\t& .ck-button__keystroke {\n\t\tcolor: inherit;\n\n\t\t@mixin ck-dir ltr {\n\t\t\tmargin-left: var(--ck-spacing-large);\n\t\t}\n\n\t\t@mixin ck-dir rtl {\n\t\t\tmargin-right: var(--ck-spacing-large);\n\t\t}\n\n\t\tfont-weight: bold;\n\t\topacity: .7;\n\t}\n\n\t/* https://github.com/ckeditor/ckeditor5-theme-lark/issues/70 */\n\t&.ck-disabled {\n\t\t&:active,\n\t\t&:focus {\n\t\t\t/* The disabled button should have a slightly less visible shadow when focused. */\n\t\t\t@mixin ck-box-shadow var(--ck-focus-disabled-outer-shadow);\n\t\t}\n\n\t\t& .ck-button__icon {\n\t\t\t@mixin ck-disabled;\n\t\t}\n\n\t\t/* https://github.com/ckeditor/ckeditor5-theme-lark/issues/98 */\n\t\t& .ck-button__label {\n\t\t\t@mixin ck-disabled;\n\t\t}\n\n\t\t& .ck-button__keystroke {\n\t\t\topacity: .3;\n\t\t}\n\t}\n\n\t&.ck-button_with-text {\n\t\tpadding: var(--ck-spacing-tiny) var(--ck-spacing-standard);\n\n\t\t/* stylelint-disable-next-line no-descending-specificity */\n\t\t& .ck-button__icon {\n\t\t\t@mixin ck-dir ltr {\n\t\t\t\tmargin-left: calc(-1 * var(--ck-spacing-small));\n\t\t\t\tmargin-right: var(--ck-spacing-small);\n\t\t\t}\n\n\t\t\t@mixin ck-dir rtl {\n\t\t\t\tmargin-right: calc(-1 * var(--ck-spacing-small));\n\t\t\t\tmargin-left: var(--ck-spacing-small);\n\t\t\t}\n\t\t}\n\t}\n\n\t&.ck-button_with-keystroke {\n\t\t/* stylelint-disable-next-line no-descending-specificity */\n\t\t& .ck-button__label {\n\t\t\tflex-grow: 1;\n\t\t}\n\t}\n\n\t/* A style of the button which is currently on, e.g. its feature is active. */\n\t&.ck-on {\n\t\t@mixin ck-button-colors --ck-color-button-on;\n\t}\n\n\t&.ck-button-save {\n\t\tcolor: var(--ck-color-button-save);\n\t}\n\n\t&.ck-button-cancel {\n\t\tcolor: var(--ck-color-button-cancel);\n\t}\n}\n\n/* A style of the button which handles the primary action. */\n.ck.ck-button-action,\na.ck.ck-button-action {\n\t@mixin ck-button-colors --ck-color-button-action;\n\n\tcolor: var(--ck-color-button-action-text);\n}\n\n.ck.ck-button-bold,\na.ck.ck-button-bold {\n\tfont-weight: bold;\n}\n","/*\n * Copyright (c) 2003-2022, 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/**\n * Implements a button of given background color.\n *\n * @param {String} $background - Background color of the button.\n * @param {String} $border - Border color of the button.\n */\n@define-mixin ck-button-colors $prefix {\n\tbackground: var($(prefix)-background);\n\n\t&:not(.ck-disabled) {\n\t\t&:hover {\n\t\t\tbackground: var($(prefix)-hover-background);\n\t\t}\n\n\t\t&:active {\n\t\t\tbackground: var($(prefix)-active-background);\n\t\t\tbox-shadow: inset 0 2px 2px var($(prefix)-active-shadow);\n\t\t}\n\t}\n\n\t/* https://github.com/ckeditor/ckeditor5-theme-lark/issues/98 */\n\t&.ck-disabled {\n\t\tbackground: var($(prefix)-disabled-background);\n\t}\n}\n","/*\n * Copyright (c) 2003-2022, 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/**\n * Implements rounded corner interface for .ck-rounded-corners class.\n *\n * @see $ck-border-radius\n */\n@define-mixin ck-rounded-corners {\n\tborder-radius: 0;\n\n\t@nest .ck-rounded-corners &,\n\t&.ck-rounded-corners {\n\t\tborder-radius: var(--ck-border-radius);\n\t\t@mixin-content;\n\t}\n}\n","/*\n * Copyright (c) 2003-2022, 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/**\n * A visual style of focused element's border.\n */\n@define-mixin ck-focus-ring {\n\t/* Disable native outline. */\n\toutline: none;\n\tborder: var(--ck-focus-ring)\n}\n","/*\n * Copyright (c) 2003-2022, 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/**\n * A helper to combine multiple shadows.\n */\n@define-mixin ck-box-shadow $shadowA, $shadowB: 0 0 {\n\tbox-shadow: $shadowA, $shadowB;\n}\n\n/**\n * Gives an element a drop shadow so it looks like a floating panel.\n */\n@define-mixin ck-drop-shadow {\n\t@mixin ck-box-shadow var(--ck-drop-shadow);\n}\n","/*\n * Copyright (c) 2003-2022, 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/**\n * A class which indicates that an element holding it is disabled.\n */\n@define-mixin ck-disabled {\n\topacity: var(--ck-disabled-opacity);\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/components/button/switchbutton.css":
/*!**************************************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/components/button/switchbutton.css ***!
\**************************************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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-button.ck-switchbutton .ck-button__toggle,.ck.ck-button.ck-switchbutton .ck-button__toggle .ck-button__toggle__inner{display:block}:root{--ck-switch-button-toggle-width:2.6153846154em;--ck-switch-button-toggle-inner-size:1.0769230769em;--ck-switch-button-toggle-spacing:1px;--ck-switch-button-translation:calc(var(--ck-switch-button-toggle-width) - var(--ck-switch-button-toggle-inner-size) - var(--ck-switch-button-toggle-spacing)*2)}[dir=ltr] .ck.ck-button.ck-switchbutton .ck-button__label{margin-right:calc(var(--ck-spacing-large)*2)}[dir=rtl] .ck.ck-button.ck-switchbutton .ck-button__label{margin-left:calc(var(--ck-spacing-large)*2)}.ck.ck-button.ck-switchbutton .ck-button__toggle{border-radius:0}.ck-rounded-corners .ck.ck-button.ck-switchbutton .ck-button__toggle,.ck.ck-button.ck-switchbutton .ck-button__toggle.ck-rounded-corners{border-radius:var(--ck-border-radius)}[dir=ltr] .ck.ck-button.ck-switchbutton .ck-button__toggle{margin-left:auto}[dir=rtl] .ck.ck-button.ck-switchbutton .ck-button__toggle{margin-right:auto}.ck.ck-button.ck-switchbutton .ck-button__toggle{transition:background .4s ease;width:var(--ck-switch-button-toggle-width);background:var(--ck-color-switch-button-off-background)}.ck.ck-button.ck-switchbutton .ck-button__toggle .ck-button__toggle__inner{border-radius:0}.ck-rounded-corners .ck.ck-button.ck-switchbutton .ck-button__toggle .ck-button__toggle__inner,.ck.ck-button.ck-switchbutton .ck-button__toggle .ck-button__toggle__inner.ck-rounded-corners{border-radius:var(--ck-border-radius);border-radius:calc(var(--ck-border-radius)*0.5)}.ck.ck-button.ck-switchbutton .ck-button__toggle .ck-button__toggle__inner{margin:var(--ck-switch-button-toggle-spacing);width:var(--ck-switch-button-toggle-inner-size);height:var(--ck-switch-button-toggle-inner-size);background:var(--ck-color-switch-button-inner-background);transition:all .3s ease}.ck.ck-button.ck-switchbutton .ck-button__toggle:hover{background:var(--ck-color-switch-button-off-hover-background)}.ck.ck-button.ck-switchbutton .ck-button__toggle:hover .ck-button__toggle__inner{box-shadow:0 0 0 5px var(--ck-color-switch-button-inner-shadow)}.ck.ck-button.ck-switchbutton.ck-disabled .ck-button__toggle{opacity:var(--ck-disabled-opacity)}.ck.ck-button.ck-switchbutton.ck-on .ck-button__toggle{background:var(--ck-color-switch-button-on-background)}.ck.ck-button.ck-switchbutton.ck-on .ck-button__toggle:hover{background:var(--ck-color-switch-button-on-hover-background)}[dir=ltr] .ck.ck-button.ck-switchbutton.ck-on .ck-button__toggle .ck-button__toggle__inner{transform:translateX(var(--ck-switch-button-translation))}[dir=rtl] .ck.ck-button.ck-switchbutton.ck-on .ck-button__toggle .ck-button__toggle__inner{transform:translateX(calc(var(--ck-switch-button-translation)*-1))}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-ui/theme/components/button/switchbutton.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/ckeditor5-ui/components/button/switchbutton.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/mixins/_rounded.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/mixins/_disabled.css"],"names":[],"mappings":"AASE,4HACC,aACD,CCCF,MAEC,8CAA+C,CAE/C,mDAAoD,CACpD,qCAAsC,CACtC,gKAKD,CAGC,0DAGE,4CAOF,CAVA,0DAQE,2CAEF,CAEA,iDC3BA,eDoEA,CAzCA,yICvBC,qCDgED,CAzCA,2DAKE,gBAoCF,CAzCA,2DAUE,iBA+BF,CAzCA,iDAcC,8BAAiC,CAEjC,0CAA2C,CAC3C,uDAwBD,CAtBC,2EC9CD,eD2DC,CAbA,6LC1CA,qCAAsC,CD4CpC,+CAWF,CAbA,2EAMC,6CAA8C,CAC9C,+CAAgD,CAChD,gDAAiD,CACjD,yDAA0D,CAG1D,uBACD,CAEA,uDACC,6DAKD,CAHC,iFACC,+DACD,CAIF,6DExEA,kCF0EA,CAEA,uDACC,sDAkBD,CAhBC,6DACC,4DACD,CAEA,2FAKE,yDAMF,CAXA,2FASE,kEAEF","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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-button.ck-switchbutton {\n\t& .ck-button__toggle {\n\t\tdisplay: block;\n\n\t\t& .ck-button__toggle__inner {\n\t\t\tdisplay: block;\n\t\t}\n\t}\n}\n","/*\n * Copyright (c) 2003-2022, 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 \"../../../mixins/_rounded.css\";\n@import \"../../../mixins/_disabled.css\";\n@import \"@ckeditor/ckeditor5-ui/theme/mixins/_dir.css\";\n\n/* Note: To avoid rendering issues (aliasing) but to preserve the responsive nature\nof the component, floating–point numbers have been used which, for the default font size\n(see: --ck-font-size-base), will generate simple integers. */\n:root {\n\t/* 34px at 13px font-size */\n\t--ck-switch-button-toggle-width: 2.6153846154em;\n\t/* 14px at 13px font-size */\n\t--ck-switch-button-toggle-inner-size: 1.0769230769em;\n\t--ck-switch-button-toggle-spacing: 1px;\n\t--ck-switch-button-translation: calc(\n\t\tvar(--ck-switch-button-toggle-width) -\n\t\tvar(--ck-switch-button-toggle-inner-size) -\n\t\t2 * var(--ck-switch-button-toggle-spacing)\n\t);\n}\n\n.ck.ck-button.ck-switchbutton {\n\t& .ck-button__label {\n\t\t@mixin ck-dir ltr {\n\t\t\t/* Separate the label from the switch */\n\t\t\tmargin-right: calc(2 * var(--ck-spacing-large));\n\t\t}\n\n\t\t@mixin ck-dir rtl {\n\t\t\t/* Separate the label from the switch */\n\t\t\tmargin-left: calc(2 * var(--ck-spacing-large));\n\t\t}\n\t}\n\n\t& .ck-button__toggle {\n\t\t@mixin ck-rounded-corners;\n\n\t\t@mixin ck-dir ltr {\n\t\t\t/* Make sure the toggle is always to the right as far as possible. */\n\t\t\tmargin-left: auto;\n\t\t}\n\n\t\t@mixin ck-dir rtl {\n\t\t\t/* Make sure the toggle is always to the left as far as possible. */\n\t\t\tmargin-right: auto;\n\t\t}\n\n\t\t/* Gently animate the background color of the toggle switch */\n\t\ttransition: background 400ms ease;\n\n\t\twidth: var(--ck-switch-button-toggle-width);\n\t\tbackground: var(--ck-color-switch-button-off-background);\n\n\t\t& .ck-button__toggle__inner {\n\t\t\t@mixin ck-rounded-corners {\n\t\t\t\tborder-radius: calc(.5 * var(--ck-border-radius));\n\t\t\t}\n\n\t\t\t/* Leave some tiny bit of space around the inner part of the switch */\n\t\t\tmargin: var(--ck-switch-button-toggle-spacing);\n\t\t\twidth: var(--ck-switch-button-toggle-inner-size);\n\t\t\theight: var(--ck-switch-button-toggle-inner-size);\n\t\t\tbackground: var(--ck-color-switch-button-inner-background);\n\n\t\t\t/* Gently animate the inner part of the toggle switch */\n\t\t\ttransition: all 300ms ease;\n\t\t}\n\n\t\t&:hover {\n\t\t\tbackground: var(--ck-color-switch-button-off-hover-background);\n\n\t\t\t& .ck-button__toggle__inner {\n\t\t\t\tbox-shadow: 0 0 0 5px var(--ck-color-switch-button-inner-shadow);\n\t\t\t}\n\t\t}\n\t}\n\n\t&.ck-disabled .ck-button__toggle {\n\t\t@mixin ck-disabled;\n\t}\n\n\t&.ck-on .ck-button__toggle {\n\t\tbackground: var(--ck-color-switch-button-on-background);\n\n\t\t&:hover {\n\t\t\tbackground: var(--ck-color-switch-button-on-hover-background);\n\t\t}\n\n\t\t& .ck-button__toggle__inner {\n\t\t\t/*\n\t\t\t * Move the toggle switch to the right. It will be animated.\n\t\t\t */\n\t\t\t@mixin ck-dir ltr {\n\t\t\t\ttransform: translateX( var( --ck-switch-button-translation ) );\n\t\t\t}\n\n\t\t\t@mixin ck-dir rtl {\n\t\t\t\ttransform: translateX( calc( -1 * var( --ck-switch-button-translation ) ) );\n\t\t\t}\n\t\t}\n\t}\n}\n","/*\n * Copyright (c) 2003-2022, 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/**\n * Implements rounded corner interface for .ck-rounded-corners class.\n *\n * @see $ck-border-radius\n */\n@define-mixin ck-rounded-corners {\n\tborder-radius: 0;\n\n\t@nest .ck-rounded-corners &,\n\t&.ck-rounded-corners {\n\t\tborder-radius: var(--ck-border-radius);\n\t\t@mixin-content;\n\t}\n}\n","/*\n * Copyright (c) 2003-2022, 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/**\n * A class which indicates that an element holding it is disabled.\n */\n@define-mixin ck-disabled {\n\topacity: var(--ck-disabled-opacity);\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/components/colorgrid/colorgrid.css":
/*!**************************************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/components/colorgrid/colorgrid.css ***!
\**************************************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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-color-grid{display:grid}:root{--ck-color-grid-tile-size:24px;--ck-color-color-grid-check-icon:#000}.ck.ck-color-grid{grid-gap:5px;padding:8px}.ck.ck-color-grid__tile{width:var(--ck-color-grid-tile-size);height:var(--ck-color-grid-tile-size);min-width:var(--ck-color-grid-tile-size);min-height:var(--ck-color-grid-tile-size);padding:0;transition:box-shadow .2s ease;border:0}.ck.ck-color-grid__tile.ck-disabled{cursor:unset;transition:unset}.ck.ck-color-grid__tile.ck-color-table__color-tile_bordered{box-shadow:0 0 0 1px var(--ck-color-base-border)}.ck.ck-color-grid__tile .ck.ck-icon{display:none;color:var(--ck-color-color-grid-check-icon)}.ck.ck-color-grid__tile.ck-on{box-shadow:inset 0 0 0 1px var(--ck-color-base-background),0 0 0 2px var(--ck-color-base-text)}.ck.ck-color-grid__tile.ck-on .ck.ck-icon{display:block}.ck.ck-color-grid__tile.ck-on,.ck.ck-color-grid__tile:focus:not(.ck-disabled),.ck.ck-color-grid__tile:hover:not(.ck-disabled){border:0}.ck.ck-color-grid__tile:focus:not(.ck-disabled),.ck.ck-color-grid__tile:hover:not(.ck-disabled){box-shadow:inset 0 0 0 1px var(--ck-color-base-background),0 0 0 2px var(--ck-color-focus-border)}.ck.ck-color-grid__label{padding:0 var(--ck-spacing-standard)}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-ui/theme/components/colorgrid/colorgrid.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/ckeditor5-ui/components/colorgrid/colorgrid.css"],"names":[],"mappings":"AAKA,kBACC,YACD,CCAA,MACC,8BAA+B,CAK/B,qCACD,CAEA,kBACC,YAAa,CACb,WACD,CAEA,wBACC,oCAAqC,CACrC,qCAAsC,CACtC,wCAAyC,CACzC,yCAA0C,CAC1C,SAAU,CACV,8BAA+B,CAC/B,QAmCD,CAjCC,oCACC,YAAa,CACb,gBACD,CAEA,4DACC,gDACD,CAEA,oCACC,YAAa,CACb,2CACD,CAEA,8BACC,8FAKD,CAHC,0CACC,aACD,CAGD,8HAIC,QACD,CAEA,gGAEC,iGACD,CAGD,yBACC,oCACD","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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-color-grid {\n\tdisplay: grid;\n}\n","/*\n * Copyright (c) 2003-2022, 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 \"../../../mixins/_rounded.css\";\n\n:root {\n\t--ck-color-grid-tile-size: 24px;\n\n\t/* Not using global colors here because these may change but some colors in a pallette\n\t * require special treatment. For instance, this ensures no matter what the UI text color is,\n\t * the check icon will look good on the black color tile. */\n\t--ck-color-color-grid-check-icon: hsl(0, 0%, 0%);\n}\n\n.ck.ck-color-grid {\n\tgrid-gap: 5px;\n\tpadding: 8px;\n}\n\n.ck.ck-color-grid__tile {\n\twidth: var(--ck-color-grid-tile-size);\n\theight: var(--ck-color-grid-tile-size);\n\tmin-width: var(--ck-color-grid-tile-size);\n\tmin-height: var(--ck-color-grid-tile-size);\n\tpadding: 0;\n\ttransition: .2s ease box-shadow;\n\tborder: 0;\n\n\t&.ck-disabled {\n\t\tcursor: unset;\n\t\ttransition: unset;\n\t}\n\n\t&.ck-color-table__color-tile_bordered {\n\t\tbox-shadow: 0 0 0 1px var(--ck-color-base-border);\n\t}\n\n\t& .ck.ck-icon {\n\t\tdisplay: none;\n\t\tcolor: var(--ck-color-color-grid-check-icon);\n\t}\n\n\t&.ck-on {\n\t\tbox-shadow: inset 0 0 0 1px var(--ck-color-base-background), 0 0 0 2px var(--ck-color-base-text);\n\n\t\t& .ck.ck-icon {\n\t\t\tdisplay: block;\n\t\t}\n\t}\n\n\t&.ck-on,\n\t&:focus:not( .ck-disabled ),\n\t&:hover:not( .ck-disabled ) {\n\t\t/* Disable the default .ck-button's border ring. */\n\t\tborder: 0;\n\t}\n\n\t&:focus:not( .ck-disabled ),\n\t&:hover:not( .ck-disabled ) {\n\t\tbox-shadow: inset 0 0 0 1px var(--ck-color-base-background), 0 0 0 2px var(--ck-color-focus-border);\n\t}\n}\n\n.ck.ck-color-grid__label {\n\tpadding: 0 var(--ck-spacing-standard);\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/components/dropdown/dropdown.css":
/*!************************************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/components/dropdown/dropdown.css ***!
\************************************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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, ":root{--ck-dropdown-max-width:75vw}.ck.ck-dropdown{display:inline-block;position:relative}.ck.ck-dropdown .ck-dropdown__arrow{pointer-events:none;z-index:var(--ck-z-default)}.ck.ck-dropdown .ck-button.ck-dropdown__button{width:100%}.ck.ck-dropdown .ck-button.ck-dropdown__button.ck-on .ck-tooltip{display:none}.ck.ck-dropdown .ck-dropdown__panel{-webkit-backface-visibility:hidden;display:none;z-index:var(--ck-z-modal);max-width:var(--ck-dropdown-max-width);position:absolute}.ck.ck-dropdown .ck-dropdown__panel.ck-dropdown__panel-visible{display:inline-block}.ck.ck-dropdown .ck-dropdown__panel.ck-dropdown__panel_n,.ck.ck-dropdown .ck-dropdown__panel.ck-dropdown__panel_ne,.ck.ck-dropdown .ck-dropdown__panel.ck-dropdown__panel_nme,.ck.ck-dropdown .ck-dropdown__panel.ck-dropdown__panel_nmw,.ck.ck-dropdown .ck-dropdown__panel.ck-dropdown__panel_nw{bottom:100%}.ck.ck-dropdown .ck-dropdown__panel.ck-dropdown__panel_s,.ck.ck-dropdown .ck-dropdown__panel.ck-dropdown__panel_se,.ck.ck-dropdown .ck-dropdown__panel.ck-dropdown__panel_sme,.ck.ck-dropdown .ck-dropdown__panel.ck-dropdown__panel_smw,.ck.ck-dropdown .ck-dropdown__panel.ck-dropdown__panel_sw{top:100%;bottom:auto}.ck.ck-dropdown .ck-dropdown__panel.ck-dropdown__panel_ne,.ck.ck-dropdown .ck-dropdown__panel.ck-dropdown__panel_se{left:0}.ck.ck-dropdown .ck-dropdown__panel.ck-dropdown__panel_nw,.ck.ck-dropdown .ck-dropdown__panel.ck-dropdown__panel_sw{right:0}.ck.ck-dropdown .ck-dropdown__panel.ck-dropdown__panel_n,.ck.ck-dropdown .ck-dropdown__panel.ck-dropdown__panel_s{left:50%;transform:translateX(-50%)}.ck.ck-dropdown .ck-dropdown__panel.ck-dropdown__panel_nmw,.ck.ck-dropdown .ck-dropdown__panel.ck-dropdown__panel_smw{left:75%;transform:translateX(-75%)}.ck.ck-dropdown .ck-dropdown__panel.ck-dropdown__panel_nme,.ck.ck-dropdown .ck-dropdown__panel.ck-dropdown__panel_sme{left:25%;transform:translateX(-25%)}.ck.ck-toolbar .ck-dropdown__panel{z-index:calc(var(--ck-z-modal) + 1)}:root{--ck-dropdown-arrow-size:calc(var(--ck-icon-size)*0.5)}.ck.ck-dropdown{font-size:inherit}.ck.ck-dropdown .ck-dropdown__arrow{width:var(--ck-dropdown-arrow-size)}[dir=ltr] .ck.ck-dropdown .ck-dropdown__arrow{right:var(--ck-spacing-standard);margin-left:var(--ck-spacing-standard)}[dir=rtl] .ck.ck-dropdown .ck-dropdown__arrow{left:var(--ck-spacing-standard);margin-right:var(--ck-spacing-small)}.ck.ck-dropdown.ck-disabled .ck-dropdown__arrow{opacity:var(--ck-disabled-opacity)}[dir=ltr] .ck.ck-dropdown .ck-button.ck-dropdown__button:not(.ck-button_with-text){padding-left:var(--ck-spacing-small)}[dir=rtl] .ck.ck-dropdown .ck-button.ck-dropdown__button:not(.ck-button_with-text){padding-right:var(--ck-spacing-small)}.ck.ck-dropdown .ck-button.ck-dropdown__button .ck-button__label{width:7em;overflow:hidden;text-overflow:ellipsis}.ck.ck-dropdown .ck-button.ck-dropdown__button.ck-disabled .ck-button__label{opacity:var(--ck-disabled-opacity)}.ck.ck-dropdown .ck-button.ck-dropdown__button.ck-on{border-bottom-left-radius:0;border-bottom-right-radius:0}.ck.ck-dropdown .ck-button.ck-dropdown__button.ck-dropdown__button_label-width_auto .ck-button__label{width:auto}.ck.ck-dropdown .ck-button.ck-dropdown__button.ck-off:active,.ck.ck-dropdown .ck-button.ck-dropdown__button.ck-on:active{box-shadow:none}.ck.ck-dropdown .ck-button.ck-dropdown__button.ck-off:active:focus,.ck.ck-dropdown .ck-button.ck-dropdown__button.ck-on:active:focus{box-shadow:var(--ck-focus-outer-shadow),0 0}.ck.ck-dropdown__panel{border-radius:0}.ck-rounded-corners .ck.ck-dropdown__panel,.ck.ck-dropdown__panel.ck-rounded-corners{border-radius:var(--ck-border-radius)}.ck.ck-dropdown__panel{box-shadow:var(--ck-drop-shadow),0 0;background:var(--ck-color-dropdown-panel-background);border:1px solid var(--ck-color-dropdown-panel-border);bottom:0;min-width:100%}.ck.ck-dropdown__panel.ck-dropdown__panel_se{border-top-left-radius:0}.ck.ck-dropdown__panel.ck-dropdown__panel_sw{border-top-right-radius:0}.ck.ck-dropdown__panel.ck-dropdown__panel_ne{border-bottom-left-radius:0}.ck.ck-dropdown__panel.ck-dropdown__panel_nw{border-bottom-right-radius:0}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-ui/theme/components/dropdown/dropdown.css","webpack://./node_modules/@ckeditor/ckeditor5-ui/theme/components/tooltip/mixins/_tooltip.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/ckeditor5-ui/components/dropdown/dropdown.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/mixins/_disabled.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/mixins/_shadow.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/mixins/_rounded.css"],"names":[],"mappings":"AAOA,MACC,4BACD,CAEA,gBACC,oBAAqB,CACrB,iBAqFD,CAnFC,oCACC,mBAAoB,CACpB,2BACD,CAGA,+CACC,UAOD,CCUA,iEACC,YACD,CDVA,oCAGC,kCAAmC,CAEnC,YAAa,CACb,yBAA0B,CAC1B,sCAAuC,CAEvC,iBAyDD,CAvDC,+DACC,oBACD,CAEA,mSAKC,WACD,CAEA,mSASC,QAAS,CACT,WACD,CAEA,oHAEC,MACD,CAEA,oHAEC,OACD,CAEA,kHAGC,QAAS,CACT,0BACD,CAEA,sHAGC,QAAS,CACT,0BACD,CAEA,sHAGC,QAAS,CACT,0BACD,CAQF,mCACC,mCACD,CEhGA,MACC,sDACD,CAEA,gBAEC,iBA2ED,CAzEC,oCACC,mCACD,CAGC,8CACC,gCAAiC,CAGjC,sCACD,CAIA,8CACC,+BAAgC,CAGhC,oCACD,CAGD,gDC/BA,kCDiCA,CAIE,mFAEC,oCACD,CAIA,mFAEC,qCACD,CAID,iEACC,SAAU,CACV,eAAgB,CAChB,sBACD,CAGA,6EC1DD,kCD4DC,CAGA,qDACC,2BAA4B,CAC5B,4BACD,CAEA,sGACC,UACD,CAGA,yHAEC,eAKD,CAHC,qIE7EF,2CF+EE,CAKH,uBGlFC,eH8GD,CA5BA,qFG9EE,qCH0GF,CA5BA,uBEpFC,oCAA8B,CFwF9B,oDAAqD,CACrD,sDAAuD,CACvD,QAAS,CAGT,cAmBD,CAfC,6CACC,wBACD,CAEA,6CACC,yBACD,CAEA,6CACC,2BACD,CAEA,6CACC,4BACD","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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 \"../tooltip/mixins/_tooltip.css\";\n\n:root {\n\t--ck-dropdown-max-width: 75vw;\n}\n\n.ck.ck-dropdown {\n\tdisplay: inline-block;\n\tposition: relative;\n\n\t& .ck-dropdown__arrow {\n\t\tpointer-events: none;\n\t\tz-index: var(--ck-z-default);\n\t}\n\n\t/* Dropdown button should span horizontally, e.g. in vertical toolbars */\n\t& .ck-button.ck-dropdown__button {\n\t\twidth: 100%;\n\n\t\t/* Disable main button's tooltip when the dropdown is open. Otherwise the panel may\n\t\tpartially cover the tooltip */\n\t\t&.ck-on {\n\t\t\t@mixin ck-tooltip_disabled;\n\t\t}\n\t}\n\n\t& .ck-dropdown__panel {\n\t\t/* This is to get rid of flickering when the tooltip is shown under the panel,\n\t\twhich looks like the panel moves vertically a pixel down and up. */\n\t\t-webkit-backface-visibility: hidden;\n\n\t\tdisplay: none;\n\t\tz-index: var(--ck-z-modal);\n\t\tmax-width: var(--ck-dropdown-max-width);\n\n\t\tposition: absolute;\n\n\t\t&.ck-dropdown__panel-visible {\n\t\t\tdisplay: inline-block;\n\t\t}\n\n\t\t&.ck-dropdown__panel_ne,\n\t\t&.ck-dropdown__panel_nw,\n\t\t&.ck-dropdown__panel_n,\n\t\t&.ck-dropdown__panel_nmw,\n\t\t&.ck-dropdown__panel_nme {\n\t\t\tbottom: 100%;\n\t\t}\n\n\t\t&.ck-dropdown__panel_se,\n\t\t&.ck-dropdown__panel_sw,\n\t\t&.ck-dropdown__panel_smw,\n\t\t&.ck-dropdown__panel_sme,\n\t\t&.ck-dropdown__panel_s {\n\t\t\t/*\n\t\t\t * Using transform: translate3d( 0, 100%, 0 ) causes blurry dropdown on Chrome 67-78+ on non-retina displays.\n\t\t\t * See https://github.com/ckeditor/ckeditor5/issues/1053.\n\t\t\t */\n\t\t\ttop: 100%;\n\t\t\tbottom: auto;\n\t\t}\n\n\t\t&.ck-dropdown__panel_ne,\n\t\t&.ck-dropdown__panel_se {\n\t\t\tleft: 0px;\n\t\t}\n\n\t\t&.ck-dropdown__panel_nw,\n\t\t&.ck-dropdown__panel_sw {\n\t\t\tright: 0px;\n\t\t}\n\n\t\t&.ck-dropdown__panel_s,\n\t\t&.ck-dropdown__panel_n {\n\t\t\t/* Positioning panels relative to the center of the button */\n\t\t\tleft: 50%;\n\t\t\ttransform: translateX(-50%);\n\t\t}\n\n\t\t&.ck-dropdown__panel_nmw,\n\t\t&.ck-dropdown__panel_smw {\n\t\t\t/* Positioning panels relative to the middle-west of the button */\n\t\t\tleft: 75%;\n\t\t\ttransform: translateX(-75%);\n\t\t}\n\n\t\t&.ck-dropdown__panel_nme,\n\t\t&.ck-dropdown__panel_sme {\n\t\t\t/* Positioning panels relative to the middle-east of the button */\n\t\t\tleft: 25%;\n\t\t\ttransform: translateX(-25%);\n\t\t}\n\t}\n}\n\n/*\n * Toolbar dropdown panels should be always above the UI (eg. other dropdown panels) from the editor's content.\n * See https://github.com/ckeditor/ckeditor5/issues/7874\n */\n.ck.ck-toolbar .ck-dropdown__panel {\n\tz-index: calc( var(--ck-z-modal) + 1 );\n}\n","/*\n * Copyright (c) 2003-2022, 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/**\n * Enables the tooltip, which is the tooltip is in DOM but\n * not yet displayed.\n */\n@define-mixin ck-tooltip_enabled {\n\t& .ck-tooltip {\n\t\tdisplay: block;\n\n\t\t/*\n\t\t * Don't display tooltips in devices which don't support :hover.\n\t\t * In fact, it's all about iOS, which forces user to click UI elements twice to execute\n\t\t * the primary action, when tooltips are enabled.\n\t\t *\n\t\t * Q: OK, but why not the following query?\n\t\t *\n\t\t * @media (hover) {\n\t\t * display: block;\n\t\t * }\n\t\t *\n\t\t * A: Because FF does not support it and it would completely disable tooltips\n\t\t * in that browser.\n\t\t *\n\t\t * More in https://github.com/ckeditor/ckeditor5/issues/920.\n\t\t */\n\t\t@media (hover:none) {\n\t\t\tdisplay: none;\n\t\t}\n\t}\n}\n\n/**\n * Disables the tooltip making it disappear from DOM.\n */\n@define-mixin ck-tooltip_disabled {\n\t& .ck-tooltip {\n\t\tdisplay: none;\n\t}\n}\n\n/**\n * Shows the tooltip, which is already in DOM.\n * Requires `ck-tooltip_enabled` first.\n */\n@define-mixin ck-tooltip_visible {\n\t& .ck-tooltip {\n\t\tvisibility: visible;\n\t\topacity: 1;\n\t}\n}\n","/*\n * Copyright (c) 2003-2022, 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 \"../../../mixins/_rounded.css\";\n@import \"../../../mixins/_disabled.css\";\n@import \"../../../mixins/_shadow.css\";\n@import \"@ckeditor/ckeditor5-ui/theme/mixins/_dir.css\";\n\n:root {\n\t--ck-dropdown-arrow-size: calc(0.5 * var(--ck-icon-size));\n}\n\n.ck.ck-dropdown {\n\t/* Enable font size inheritance, which allows fluid UI scaling. */\n\tfont-size: inherit;\n\n\t& .ck-dropdown__arrow {\n\t\twidth: var(--ck-dropdown-arrow-size);\n\t}\n\n\t@mixin ck-dir ltr {\n\t\t& .ck-dropdown__arrow {\n\t\t\tright: var(--ck-spacing-standard);\n\n\t\t\t/* A space to accommodate the triangle. */\n\t\t\tmargin-left: var(--ck-spacing-standard);\n\t\t}\n\t}\n\n\t@mixin ck-dir rtl {\n\t\t& .ck-dropdown__arrow {\n\t\t\tleft: var(--ck-spacing-standard);\n\n\t\t\t/* A space to accommodate the triangle. */\n\t\t\tmargin-right: var(--ck-spacing-small);\n\t\t}\n\t}\n\n\t&.ck-disabled .ck-dropdown__arrow {\n\t\t@mixin ck-disabled;\n\t}\n\n\t& .ck-button.ck-dropdown__button {\n\t\t@mixin ck-dir ltr {\n\t\t\t&:not(.ck-button_with-text) {\n\t\t\t\t/* Make sure dropdowns with just an icon have the right inner spacing */\n\t\t\t\tpadding-left: var(--ck-spacing-small);\n\t\t\t}\n\t\t}\n\n\t\t@mixin ck-dir rtl {\n\t\t\t&:not(.ck-button_with-text) {\n\t\t\t\t/* Make sure dropdowns with just an icon have the right inner spacing */\n\t\t\t\tpadding-right: var(--ck-spacing-small);\n\t\t\t}\n\t\t}\n\n\t\t/* #23 */\n\t\t& .ck-button__label {\n\t\t\twidth: 7em;\n\t\t\toverflow: hidden;\n\t\t\ttext-overflow: ellipsis;\n\t\t}\n\n\t\t/* https://github.com/ckeditor/ckeditor5-theme-lark/issues/70 */\n\t\t&.ck-disabled .ck-button__label {\n\t\t\t@mixin ck-disabled;\n\t\t}\n\n\t\t/* https://github.com/ckeditor/ckeditor5/issues/816 */\n\t\t&.ck-on {\n\t\t\tborder-bottom-left-radius: 0;\n\t\t\tborder-bottom-right-radius: 0;\n\t\t}\n\n\t\t&.ck-dropdown__button_label-width_auto .ck-button__label {\n\t\t\twidth: auto;\n\t\t}\n\n\t\t/* https://github.com/ckeditor/ckeditor5/issues/8699 */\n\t\t&.ck-off:active,\n\t\t&.ck-on:active {\n\t\t\tbox-shadow: none;\n\t\t\t\n\t\t\t&:focus {\n\t\t\t\t@mixin ck-box-shadow var(--ck-focus-outer-shadow);\n\t\t\t}\n\t\t}\n\t}\n}\n\n.ck.ck-dropdown__panel {\n\t@mixin ck-rounded-corners;\n\t@mixin ck-drop-shadow;\n\n\tbackground: var(--ck-color-dropdown-panel-background);\n\tborder: 1px solid var(--ck-color-dropdown-panel-border);\n\tbottom: 0;\n\n\t/* Make sure the panel is at least as wide as the drop-down's button. */\n\tmin-width: 100%;\n\n\t/* Disabled corner border radius to be consistent with the .dropdown__button\n\thttps://github.com/ckeditor/ckeditor5/issues/816 */\n\t&.ck-dropdown__panel_se {\n\t\tborder-top-left-radius: 0;\n\t}\n\n\t&.ck-dropdown__panel_sw {\n\t\tborder-top-right-radius: 0;\n\t}\n\n\t&.ck-dropdown__panel_ne {\n\t\tborder-bottom-left-radius: 0;\n\t}\n\n\t&.ck-dropdown__panel_nw {\n\t\tborder-bottom-right-radius: 0;\n\t}\n}\n","/*\n * Copyright (c) 2003-2022, 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/**\n * A class which indicates that an element holding it is disabled.\n */\n@define-mixin ck-disabled {\n\topacity: var(--ck-disabled-opacity);\n}\n","/*\n * Copyright (c) 2003-2022, 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/**\n * A helper to combine multiple shadows.\n */\n@define-mixin ck-box-shadow $shadowA, $shadowB: 0 0 {\n\tbox-shadow: $shadowA, $shadowB;\n}\n\n/**\n * Gives an element a drop shadow so it looks like a floating panel.\n */\n@define-mixin ck-drop-shadow {\n\t@mixin ck-box-shadow var(--ck-drop-shadow);\n}\n","/*\n * Copyright (c) 2003-2022, 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/**\n * Implements rounded corner interface for .ck-rounded-corners class.\n *\n * @see $ck-border-radius\n */\n@define-mixin ck-rounded-corners {\n\tborder-radius: 0;\n\n\t@nest .ck-rounded-corners &,\n\t&.ck-rounded-corners {\n\t\tborder-radius: var(--ck-border-radius);\n\t\t@mixin-content;\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/components/dropdown/listdropdown.css":
/*!****************************************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/components/dropdown/listdropdown.css ***!
\****************************************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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-dropdown .ck-dropdown__panel .ck-list{border-radius:0}.ck-rounded-corners .ck.ck-dropdown .ck-dropdown__panel .ck-list,.ck.ck-dropdown .ck-dropdown__panel .ck-list.ck-rounded-corners{border-radius:var(--ck-border-radius);border-top-left-radius:0}.ck.ck-dropdown .ck-dropdown__panel .ck-list .ck-list__item:first-child .ck-button{border-radius:0}.ck-rounded-corners .ck.ck-dropdown .ck-dropdown__panel .ck-list .ck-list__item:first-child .ck-button,.ck.ck-dropdown .ck-dropdown__panel .ck-list .ck-list__item:first-child .ck-button.ck-rounded-corners{border-radius:var(--ck-border-radius);border-top-left-radius:0;border-bottom-left-radius:0;border-bottom-right-radius:0}.ck.ck-dropdown .ck-dropdown__panel .ck-list .ck-list__item:last-child .ck-button{border-radius:0}.ck-rounded-corners .ck.ck-dropdown .ck-dropdown__panel .ck-list .ck-list__item:last-child .ck-button,.ck.ck-dropdown .ck-dropdown__panel .ck-list .ck-list__item:last-child .ck-button.ck-rounded-corners{border-radius:var(--ck-border-radius);border-top-left-radius:0;border-top-right-radius:0}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/ckeditor5-ui/components/dropdown/listdropdown.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/mixins/_rounded.css"],"names":[],"mappings":"AAOA,6CCIC,eDqBD,CAzBA,iICQE,qCAAsC,CDJtC,wBAqBF,CAfE,mFCND,eDYC,CANA,6MCFA,qCAAsC,CDIpC,wBAAyB,CACzB,2BAA4B,CAC5B,4BAEF,CAEA,kFCdD,eDmBC,CALA,2MCVA,qCAAsC,CDYpC,wBAAyB,CACzB,yBAEF","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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 \"../../../mixins/_rounded.css\";\n\n.ck.ck-dropdown .ck-dropdown__panel .ck-list {\n\t/* Disabled radius of top-left border to be consistent with .dropdown__button\n\thttps://github.com/ckeditor/ckeditor5/issues/816 */\n\t@mixin ck-rounded-corners {\n\t\tborder-top-left-radius: 0;\n\t}\n\n\t/* Make sure the button belonging to the first/last child of the list goes well with the\n\tborder radius of the entire panel. */\n\t& .ck-list__item {\n\t\t&:first-child .ck-button {\n\t\t\t@mixin ck-rounded-corners {\n\t\t\t\tborder-top-left-radius: 0;\n\t\t\t\tborder-bottom-left-radius: 0;\n\t\t\t\tborder-bottom-right-radius: 0;\n\t\t\t}\n\t\t}\n\n\t\t&:last-child .ck-button {\n\t\t\t@mixin ck-rounded-corners {\n\t\t\t\tborder-top-left-radius: 0;\n\t\t\t\tborder-top-right-radius: 0;\n\t\t\t}\n\t\t}\n\t}\n}\n","/*\n * Copyright (c) 2003-2022, 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/**\n * Implements rounded corner interface for .ck-rounded-corners class.\n *\n * @see $ck-border-radius\n */\n@define-mixin ck-rounded-corners {\n\tborder-radius: 0;\n\n\t@nest .ck-rounded-corners &,\n\t&.ck-rounded-corners {\n\t\tborder-radius: var(--ck-border-radius);\n\t\t@mixin-content;\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/components/dropdown/splitbutton.css":
/*!***************************************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/components/dropdown/splitbutton.css ***!
\***************************************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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-splitbutton{font-size:inherit}.ck.ck-splitbutton .ck-splitbutton__action:focus{z-index:calc(var(--ck-z-default) + 1)}.ck.ck-splitbutton.ck-splitbutton_open>.ck-button .ck-tooltip{display:none}:root{--ck-color-split-button-hover-background:#ebebeb;--ck-color-split-button-hover-border:#b3b3b3}[dir=ltr] .ck.ck-splitbutton.ck-splitbutton_open>.ck-splitbutton__action,[dir=ltr] .ck.ck-splitbutton:hover>.ck-splitbutton__action{border-top-right-radius:unset;border-bottom-right-radius:unset}[dir=rtl] .ck.ck-splitbutton.ck-splitbutton_open>.ck-splitbutton__action,[dir=rtl] .ck.ck-splitbutton:hover>.ck-splitbutton__action{border-top-left-radius:unset;border-bottom-left-radius:unset}.ck.ck-splitbutton>.ck-splitbutton__arrow{min-width:unset}[dir=ltr] .ck.ck-splitbutton>.ck-splitbutton__arrow{border-top-left-radius:unset;border-bottom-left-radius:unset}[dir=rtl] .ck.ck-splitbutton>.ck-splitbutton__arrow{border-top-right-radius:unset;border-bottom-right-radius:unset}.ck.ck-splitbutton>.ck-splitbutton__arrow svg{width:var(--ck-dropdown-arrow-size)}.ck.ck-splitbutton.ck-splitbutton_open>.ck-button:not(.ck-on):not(.ck-disabled):not(:hover),.ck.ck-splitbutton:hover>.ck-button:not(.ck-on):not(.ck-disabled):not(:hover){background:var(--ck-color-split-button-hover-background)}.ck.ck-splitbutton.ck-splitbutton_open>.ck-splitbutton__arrow:not(.ck-disabled):after,.ck.ck-splitbutton:hover>.ck-splitbutton__arrow:not(.ck-disabled):after{content:\"\";position:absolute;width:1px;height:100%;background-color:var(--ck-color-split-button-hover-border)}[dir=ltr] .ck.ck-splitbutton.ck-splitbutton_open>.ck-splitbutton__arrow:not(.ck-disabled):after,[dir=ltr] .ck.ck-splitbutton:hover>.ck-splitbutton__arrow:not(.ck-disabled):after{left:-1px}[dir=rtl] .ck.ck-splitbutton.ck-splitbutton_open>.ck-splitbutton__arrow:not(.ck-disabled):after,[dir=rtl] .ck.ck-splitbutton:hover>.ck-splitbutton__arrow:not(.ck-disabled):after{right:-1px}.ck.ck-splitbutton.ck-splitbutton_open{border-radius:0}.ck-rounded-corners .ck.ck-splitbutton.ck-splitbutton_open,.ck.ck-splitbutton.ck-splitbutton_open.ck-rounded-corners{border-radius:var(--ck-border-radius)}.ck-rounded-corners .ck.ck-splitbutton.ck-splitbutton_open>.ck-splitbutton__action,.ck.ck-splitbutton.ck-splitbutton_open.ck-rounded-corners>.ck-splitbutton__action{border-bottom-left-radius:0}.ck-rounded-corners .ck.ck-splitbutton.ck-splitbutton_open>.ck-splitbutton__arrow,.ck.ck-splitbutton.ck-splitbutton_open.ck-rounded-corners>.ck-splitbutton__arrow{border-bottom-right-radius:0}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-ui/theme/components/dropdown/splitbutton.css","webpack://./node_modules/@ckeditor/ckeditor5-ui/theme/components/tooltip/mixins/_tooltip.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/ckeditor5-ui/components/dropdown/splitbutton.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/mixins/_rounded.css"],"names":[],"mappings":"AAOA,mBAEC,iBAUD,CARC,iDACC,qCACD,CC0BA,8DACC,YACD,CClCD,MACC,gDAAyD,CACzD,4CACD,CAMC,oIAIE,6BAA8B,CAC9B,gCAQF,CAbA,oIAUE,4BAA6B,CAC7B,+BAEF,CAEA,0CAGC,eAiBD,CApBA,oDAOE,4BAA6B,CAC7B,+BAYF,CApBA,oDAaE,6BAA8B,CAC9B,gCAMF,CAHC,8CACC,mCACD,CASA,0KACC,wDACD,CAIA,8JACC,UAAW,CACX,iBAAkB,CAClB,SAAU,CACV,WAAY,CACZ,0DACD,CAGC,kLACC,SACD,CAIA,kLACC,UACD,CAMF,uCC7EA,eDuFA,CAVA,qHCzEC,qCDmFD,CARE,qKACC,2BACD,CAEA,mKACC,4BACD","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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 \"../tooltip/mixins/_tooltip.css\";\n\n.ck.ck-splitbutton {\n\t/* Enable font size inheritance, which allows fluid UI scaling. */\n\tfont-size: inherit;\n\n\t& .ck-splitbutton__action:focus {\n\t\tz-index: calc(var(--ck-z-default) + 1);\n\t}\n\n\t/* Disable tooltips for the buttons when the button is \"open\" */\n\t&.ck-splitbutton_open > .ck-button {\n\t\t@mixin ck-tooltip_disabled;\n\t}\n}\n\n","/*\n * Copyright (c) 2003-2022, 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/**\n * Enables the tooltip, which is the tooltip is in DOM but\n * not yet displayed.\n */\n@define-mixin ck-tooltip_enabled {\n\t& .ck-tooltip {\n\t\tdisplay: block;\n\n\t\t/*\n\t\t * Don't display tooltips in devices which don't support :hover.\n\t\t * In fact, it's all about iOS, which forces user to click UI elements twice to execute\n\t\t * the primary action, when tooltips are enabled.\n\t\t *\n\t\t * Q: OK, but why not the following query?\n\t\t *\n\t\t * @media (hover) {\n\t\t * display: block;\n\t\t * }\n\t\t *\n\t\t * A: Because FF does not support it and it would completely disable tooltips\n\t\t * in that browser.\n\t\t *\n\t\t * More in https://github.com/ckeditor/ckeditor5/issues/920.\n\t\t */\n\t\t@media (hover:none) {\n\t\t\tdisplay: none;\n\t\t}\n\t}\n}\n\n/**\n * Disables the tooltip making it disappear from DOM.\n */\n@define-mixin ck-tooltip_disabled {\n\t& .ck-tooltip {\n\t\tdisplay: none;\n\t}\n}\n\n/**\n * Shows the tooltip, which is already in DOM.\n * Requires `ck-tooltip_enabled` first.\n */\n@define-mixin ck-tooltip_visible {\n\t& .ck-tooltip {\n\t\tvisibility: visible;\n\t\topacity: 1;\n\t}\n}\n","/*\n * Copyright (c) 2003-2022, 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 \"../../../mixins/_rounded.css\";\n\n:root {\n\t--ck-color-split-button-hover-background: hsl(0, 0%, 92%);\n\t--ck-color-split-button-hover-border: hsl(0, 0%, 70%);\n}\n\n.ck.ck-splitbutton {\n\t/*\n\t * Note: ck-rounded and ck-dir mixins don't go together (because they both use @nest).\n\t */\n\t&:hover > .ck-splitbutton__action,\n\t&.ck-splitbutton_open > .ck-splitbutton__action {\n\t\t@nest [dir=\"ltr\"] & {\n\t\t\t/* Don't round the action button on the right side */\n\t\t\tborder-top-right-radius: unset;\n\t\t\tborder-bottom-right-radius: unset;\n\t\t}\n\n\t\t@nest [dir=\"rtl\"] & {\n\t\t\t/* Don't round the action button on the left side */\n\t\t\tborder-top-left-radius: unset;\n\t\t\tborder-bottom-left-radius: unset;\n\t\t}\n\t}\n\n\t& > .ck-splitbutton__arrow {\n\t\t/* It's a text-less button and since the icon is positioned absolutely in such situation,\n\t\tit must get some arbitrary min-width. */\n\t\tmin-width: unset;\n\n\t\t@nest [dir=\"ltr\"] & {\n\t\t\t/* Don't round the arrow button on the left side */\n\t\t\tborder-top-left-radius: unset;\n\t\t\tborder-bottom-left-radius: unset;\n\t\t}\n\n\t\t@nest [dir=\"rtl\"] & {\n\t\t\t/* Don't round the arrow button on the right side */\n\t\t\tborder-top-right-radius: unset;\n\t\t\tborder-bottom-right-radius: unset;\n\t\t}\n\n\t\t& svg {\n\t\t\twidth: var(--ck-dropdown-arrow-size);\n\t\t}\n\t}\n\n\t/* When the split button is \"open\" (the arrow is on) or being hovered, it should get some styling\n\tas a whole. The background of both buttons should stand out and there should be a visual\n\tseparation between both buttons. */\n\t&.ck-splitbutton_open,\n\t&:hover {\n\t\t/* When the split button hovered as a whole, not as individual buttons. */\n\t\t& > .ck-button:not(.ck-on):not(.ck-disabled):not(:hover) {\n\t\t\tbackground: var(--ck-color-split-button-hover-background);\n\t\t}\n\n\t\t/* Splitbutton separator needs to be set with the ::after pseudoselector\n\t\tto display properly the borders on focus */\n\t\t& > .ck-splitbutton__arrow:not(.ck-disabled)::after {\n\t\t\tcontent: '';\n\t\t\tposition: absolute;\n\t\t\twidth: 1px;\n\t\t\theight: 100%;\n\t\t\tbackground-color: var(--ck-color-split-button-hover-border);\n\t\t}\n\n\t\t@nest [dir=\"ltr\"] & {\n\t\t\t& > .ck-splitbutton__arrow:not(.ck-disabled)::after {\n\t\t\t\tleft: -1px;\n\t\t\t}\n\t\t}\n\n\t\t@nest [dir=\"rtl\"] & {\n\t\t\t& > .ck-splitbutton__arrow:not(.ck-disabled)::after {\n\t\t\t\tright: -1px;\n\t\t\t}\n\t\t}\n\t}\n\n\t/* Don't round the bottom left and right corners of the buttons when \"open\"\n\thttps://github.com/ckeditor/ckeditor5/issues/816 */\n\t&.ck-splitbutton_open {\n\t\t@mixin ck-rounded-corners {\n\t\t\t& > .ck-splitbutton__action {\n\t\t\t\tborder-bottom-left-radius: 0;\n\t\t\t}\n\n\t\t\t& > .ck-splitbutton__arrow {\n\t\t\t\tborder-bottom-right-radius: 0;\n\t\t\t}\n\t\t}\n\t}\n}\n","/*\n * Copyright (c) 2003-2022, 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/**\n * Implements rounded corner interface for .ck-rounded-corners class.\n *\n * @see $ck-border-radius\n */\n@define-mixin ck-rounded-corners {\n\tborder-radius: 0;\n\n\t@nest .ck-rounded-corners &,\n\t&.ck-rounded-corners {\n\t\tborder-radius: var(--ck-border-radius);\n\t\t@mixin-content;\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/components/dropdown/toolbardropdown.css":
/*!*******************************************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/components/dropdown/toolbardropdown.css ***!
\*******************************************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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, ":root{--ck-toolbar-dropdown-max-width:60vw}.ck.ck-toolbar-dropdown>.ck-dropdown__panel{width:max-content;max-width:var(--ck-toolbar-dropdown-max-width)}.ck.ck-toolbar-dropdown>.ck-dropdown__panel .ck-button:focus{z-index:calc(var(--ck-z-default) + 1)}.ck.ck-toolbar-dropdown .ck-toolbar{border:0}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-ui/theme/components/dropdown/toolbardropdown.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/ckeditor5-ui/components/dropdown/toolbardropdown.css"],"names":[],"mappings":"AAKA,MACC,oCACD,CAEA,4CAEC,iBAAkB,CAClB,8CAOD,CAJE,6DACC,qCACD,CCZF,oCACC,QACD","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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-toolbar-dropdown-max-width: 60vw;\n}\n\n.ck.ck-toolbar-dropdown > .ck-dropdown__panel {\n\t/* https://github.com/ckeditor/ckeditor5/issues/5586 */\n\twidth: max-content;\n\tmax-width: var(--ck-toolbar-dropdown-max-width);\n\n\t& .ck-button {\n\t\t&:focus {\n\t\t\tz-index: calc(var(--ck-z-default) + 1);\n\t\t}\n\t}\n}\n","/*\n * Copyright (c) 2003-2022, 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-toolbar-dropdown .ck-toolbar {\n\tborder: 0;\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/components/editorui/editorui.css":
/*!************************************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/components/editorui/editorui.css ***!
\************************************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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, ":root{--ck-color-editable-blur-selection:#d9d9d9}.ck.ck-editor__editable:not(.ck-editor__nested-editable){border-radius:0}.ck-rounded-corners .ck.ck-editor__editable:not(.ck-editor__nested-editable),.ck.ck-editor__editable:not(.ck-editor__nested-editable).ck-rounded-corners{border-radius:var(--ck-border-radius)}.ck.ck-editor__editable:not(.ck-editor__nested-editable).ck-focused{outline:none;border:var(--ck-focus-ring);box-shadow:var(--ck-inner-shadow),0 0}.ck.ck-editor__editable_inline{overflow:auto;padding:0 var(--ck-spacing-standard);border:1px solid transparent}.ck.ck-editor__editable_inline[dir=ltr]{text-align:left}.ck.ck-editor__editable_inline[dir=rtl]{text-align:right}.ck.ck-editor__editable_inline>:first-child{margin-top:var(--ck-spacing-large)}.ck.ck-editor__editable_inline>:last-child{margin-bottom:var(--ck-spacing-large)}.ck.ck-editor__editable_inline.ck-blurred ::selection{background:var(--ck-color-editable-blur-selection)}.ck.ck-balloon-panel.ck-toolbar-container[class*=arrow_n]:after{border-bottom-color:var(--ck-color-base-foreground)}.ck.ck-balloon-panel.ck-toolbar-container[class*=arrow_s]:after{border-top-color:var(--ck-color-base-foreground)}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/ckeditor5-ui/components/editorui/editorui.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/mixins/_rounded.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/mixins/_focus.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/mixins/_shadow.css"],"names":[],"mappings":"AAWA,MACC,0CACD,CAEA,yDCJC,eDWD,CAPA,yJCAE,qCDOF,CAJC,oEERA,YAAa,CACb,2BAA2B,CCF3B,qCHYA,CAGD,+BACC,aAAc,CACd,oCAAqC,CACrC,4BA4BD,CA1BC,wCACC,eACD,CAEA,wCACC,gBACD,CAGA,4CACC,kCACD,CAGA,2CAKC,qCACD,CAGA,sDACC,kDACD,CAKA,gEACC,mDACD,CAIA,gEACC,gDACD","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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 \"../../../mixins/_rounded.css\";\n@import \"../../../mixins/_disabled.css\";\n@import \"../../../mixins/_shadow.css\";\n@import \"../../../mixins/_focus.css\";\n@import \"../../mixins/_button.css\";\n\n:root {\n\t--ck-color-editable-blur-selection: hsl(0, 0%, 85%);\n}\n\n.ck.ck-editor__editable:not(.ck-editor__nested-editable) {\n\t@mixin ck-rounded-corners;\n\n\t&.ck-focused {\n\t\t@mixin ck-focus-ring;\n\t\t@mixin ck-box-shadow var(--ck-inner-shadow);\n\t}\n}\n\n.ck.ck-editor__editable_inline {\n\toverflow: auto;\n\tpadding: 0 var(--ck-spacing-standard);\n\tborder: 1px solid transparent;\n\n\t&[dir=\"ltr\"] {\n\t\ttext-align: left;\n\t}\n\n\t&[dir=\"rtl\"] {\n\t\ttext-align: right;\n\t}\n\n\t/* https://github.com/ckeditor/ckeditor5-theme-lark/issues/116 */\n\t& > *:first-child {\n\t\tmargin-top: var(--ck-spacing-large);\n\t}\n\n\t/* https://github.com/ckeditor/ckeditor5/issues/847 */\n\t& > *:last-child {\n\t\t/*\n\t\t * This value should match with the default margins of the block elements (like .media or .image)\n\t\t * to avoid a content jumping when the fake selection container shows up (See https://github.com/ckeditor/ckeditor5/issues/9825).\n\t\t */\n\t\tmargin-bottom: var(--ck-spacing-large);\n\t}\n\n\t/* https://github.com/ckeditor/ckeditor5/issues/6517 */\n\t&.ck-blurred ::selection {\n\t\tbackground: var(--ck-color-editable-blur-selection);\n\t}\n}\n\n/* https://github.com/ckeditor/ckeditor5-theme-lark/issues/111 */\n.ck.ck-balloon-panel.ck-toolbar-container[class*=\"arrow_n\"] {\n\t&::after {\n\t\tborder-bottom-color: var(--ck-color-base-foreground);\n\t}\n}\n\n.ck.ck-balloon-panel.ck-toolbar-container[class*=\"arrow_s\"] {\n\t&::after {\n\t\tborder-top-color: var(--ck-color-base-foreground);\n\t}\n}\n","/*\n * Copyright (c) 2003-2022, 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/**\n * Implements rounded corner interface for .ck-rounded-corners class.\n *\n * @see $ck-border-radius\n */\n@define-mixin ck-rounded-corners {\n\tborder-radius: 0;\n\n\t@nest .ck-rounded-corners &,\n\t&.ck-rounded-corners {\n\t\tborder-radius: var(--ck-border-radius);\n\t\t@mixin-content;\n\t}\n}\n","/*\n * Copyright (c) 2003-2022, 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/**\n * A visual style of focused element's border.\n */\n@define-mixin ck-focus-ring {\n\t/* Disable native outline. */\n\toutline: none;\n\tborder: var(--ck-focus-ring)\n}\n","/*\n * Copyright (c) 2003-2022, 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/**\n * A helper to combine multiple shadows.\n */\n@define-mixin ck-box-shadow $shadowA, $shadowB: 0 0 {\n\tbox-shadow: $shadowA, $shadowB;\n}\n\n/**\n * Gives an element a drop shadow so it looks like a floating panel.\n */\n@define-mixin ck-drop-shadow {\n\t@mixin ck-box-shadow var(--ck-drop-shadow);\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/components/formheader/formheader.css":
/*!****************************************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/components/formheader/formheader.css ***!
\****************************************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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-form__header{display:flex;flex-direction:row;flex-wrap:nowrap;align-items:center;justify-content:space-between}:root{--ck-form-header-height:38px}.ck.ck-form__header{padding:var(--ck-spacing-small) var(--ck-spacing-large);height:var(--ck-form-header-height);line-height:var(--ck-form-header-height);border-bottom:1px solid var(--ck-color-base-border)}.ck.ck-form__header .ck-form__header__label{font-weight:700}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-ui/theme/components/formheader/formheader.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/ckeditor5-ui/components/formheader/formheader.css"],"names":[],"mappings":"AAKA,oBACC,YAAa,CACb,kBAAmB,CACnB,gBAAiB,CACjB,kBAAmB,CACnB,6BACD,CCNA,MACC,4BACD,CAEA,oBACC,uDAAwD,CACxD,mCAAoC,CACpC,wCAAyC,CACzC,mDAKD,CAHC,4CACC,eACD","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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-form__header {\n\tdisplay: flex;\n\tflex-direction: row;\n\tflex-wrap: nowrap;\n\talign-items: center;\n\tjustify-content: space-between;\n}\n","/*\n * Copyright (c) 2003-2022, 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-form-header-height: 38px;\n}\n\n.ck.ck-form__header {\n\tpadding: var(--ck-spacing-small) var(--ck-spacing-large);\n\theight: var(--ck-form-header-height);\n\tline-height: var(--ck-form-header-height);\n\tborder-bottom: 1px solid var(--ck-color-base-border);\n\n\t& .ck-form__header__label {\n\t\tfont-weight: bold;\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/components/icon/icon.css":
/*!****************************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/components/icon/icon.css ***!
\****************************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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-icon{vertical-align:middle}:root{--ck-icon-size:calc(var(--ck-line-height-base)*var(--ck-font-size-normal))}.ck.ck-icon{width:var(--ck-icon-size);height:var(--ck-icon-size);font-size:.8333350694em;will-change:transform}.ck.ck-icon,.ck.ck-icon *{color:inherit;cursor:inherit}.ck.ck-icon :not([fill]){fill:currentColor}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-ui/theme/components/icon/icon.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/ckeditor5-ui/components/icon/icon.css"],"names":[],"mappings":"AAKA,YACC,qBACD,CCFA,MACC,0EACD,CAEA,YACC,yBAA0B,CAC1B,0BAA2B,CAG3B,uBAAwB,CAQxB,qBAcD,CAZC,0BARA,aAAc,CAGd,cAgBA,CAJC,yBAEC,iBACD","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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-icon {\n\tvertical-align: middle;\n}\n","/*\n * Copyright (c) 2003-2022, 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-icon-size: calc(var(--ck-line-height-base) * var(--ck-font-size-normal));\n}\n\n.ck.ck-icon {\n\twidth: var(--ck-icon-size);\n\theight: var(--ck-icon-size);\n\n\t/* Multiplied by the height of the line in \"px\" should give SVG \"viewport\" dimensions */\n\tfont-size: .8333350694em;\n\n\tcolor: inherit;\n\n\t/* Inherit cursor style (#5). */\n\tcursor: inherit;\n\n\t/* This will prevent blurry icons on Firefox. See #340. */\n\twill-change: transform;\n\n\t& * {\n\t\t/* Inherit cursor style (#5). */\n\t\tcursor: inherit;\n\n\t\t/* Allows dynamic coloring of the icons. */\n\t\tcolor: inherit;\n\n\t\t&:not([fill]) {\n\t\t\t/* Needed by FF. */\n\t\t\tfill: currentColor;\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/components/input/input.css":
/*!******************************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/components/input/input.css ***!
\******************************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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, ":root{--ck-input-width:18em;--ck-input-text-width:var(--ck-input-width)}.ck.ck-input{border-radius:0}.ck-rounded-corners .ck.ck-input,.ck.ck-input.ck-rounded-corners{border-radius:var(--ck-border-radius)}.ck.ck-input{background:var(--ck-color-input-background);border:1px solid var(--ck-color-input-border);padding:var(--ck-spacing-extra-tiny) var(--ck-spacing-medium);min-width:var(--ck-input-width);min-height:var(--ck-ui-component-min-height);transition:box-shadow .1s ease-in-out,border .1s ease-in-out}.ck.ck-input:focus{outline:none;border:var(--ck-focus-ring);box-shadow:var(--ck-focus-outer-shadow),0 0}.ck.ck-input[readonly]{border:1px solid var(--ck-color-input-disabled-border);background:var(--ck-color-input-disabled-background);color:var(--ck-color-input-disabled-text)}.ck.ck-input[readonly]:focus{box-shadow:var(--ck-focus-disabled-outer-shadow),0 0}.ck.ck-input.ck-error{border-color:var(--ck-color-input-error-border);animation:ck-input-shake .3s ease both}.ck.ck-input.ck-error:focus{box-shadow:var(--ck-focus-error-outer-shadow),0 0}@keyframes ck-input-shake{20%{transform:translateX(-2px)}40%{transform:translateX(2px)}60%{transform:translateX(-1px)}80%{transform:translateX(1px)}}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/ckeditor5-ui/components/input/input.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/mixins/_rounded.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/mixins/_focus.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/mixins/_shadow.css"],"names":[],"mappings":"AASA,MACC,qBAAsB,CAGtB,2CACD,CAEA,aCLC,eD2CD,CAtCA,iECDE,qCDuCF,CAtCA,aAGC,2CAA4C,CAC5C,6CAA8C,CAC9C,6DAA8D,CAC9D,+BAAgC,CAGhC,4CAA6C,CAG7C,4DA0BD,CAxBC,mBEpBA,YAAa,CACb,2BAA2B,CCF3B,2CHwBA,CAEA,uBACC,sDAAuD,CACvD,oDAAqD,CACrD,yCAMD,CAJC,6BG/BD,oDHkCC,CAGD,sBACC,+CAAgD,CAChD,sCAKD,CAHC,4BGzCD,iDH2CC,CAIF,0BACC,IACC,0BACD,CAEA,IACC,yBACD,CAEA,IACC,0BACD,CAEA,IACC,yBACD,CACD","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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 \"../../../mixins/_rounded.css\";\n@import \"../../../mixins/_focus.css\";\n@import \"../../../mixins/_shadow.css\";\n\n:root {\n\t--ck-input-width: 18em;\n\n\t/* Backward compatibility. */\n\t--ck-input-text-width: var(--ck-input-width);\n}\n\n.ck.ck-input {\n\t@mixin ck-rounded-corners;\n\n\tbackground: var(--ck-color-input-background);\n\tborder: 1px solid var(--ck-color-input-border);\n\tpadding: var(--ck-spacing-extra-tiny) var(--ck-spacing-medium);\n\tmin-width: var(--ck-input-width);\n\n\t/* This is important to stay of the same height as surrounding buttons */\n\tmin-height: var(--ck-ui-component-min-height);\n\n\t/* Apply some smooth transition to the box-shadow and border. */\n\ttransition: box-shadow .1s ease-in-out, border .1s ease-in-out;\n\n\t&:focus {\n\t\t@mixin ck-focus-ring;\n\t\t@mixin ck-box-shadow var(--ck-focus-outer-shadow);\n\t}\n\n\t&[readonly] {\n\t\tborder: 1px solid var(--ck-color-input-disabled-border);\n\t\tbackground: var(--ck-color-input-disabled-background);\n\t\tcolor: var(--ck-color-input-disabled-text);\n\n\t\t&:focus {\n\t\t\t/* The read-only input should have a slightly less visible shadow when focused. */\n\t\t\t@mixin ck-box-shadow var(--ck-focus-disabled-outer-shadow);\n\t\t}\n\t}\n\n\t&.ck-error {\n\t\tborder-color: var(--ck-color-input-error-border);\n\t\tanimation: ck-input-shake .3s ease both;\n\n\t\t&:focus {\n\t\t\t@mixin ck-box-shadow var(--ck-focus-error-outer-shadow);\n\t\t}\n\t}\n}\n\n@keyframes ck-input-shake {\n\t20% {\n\t\ttransform: translateX(-2px);\n\t}\n\n\t40% {\n\t\ttransform: translateX(2px);\n\t}\n\n\t60% {\n\t\ttransform: translateX(-1px);\n\t}\n\n\t80% {\n\t\ttransform: translateX(1px);\n\t}\n}\n","/*\n * Copyright (c) 2003-2022, 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/**\n * Implements rounded corner interface for .ck-rounded-corners class.\n *\n * @see $ck-border-radius\n */\n@define-mixin ck-rounded-corners {\n\tborder-radius: 0;\n\n\t@nest .ck-rounded-corners &,\n\t&.ck-rounded-corners {\n\t\tborder-radius: var(--ck-border-radius);\n\t\t@mixin-content;\n\t}\n}\n","/*\n * Copyright (c) 2003-2022, 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/**\n * A visual style of focused element's border.\n */\n@define-mixin ck-focus-ring {\n\t/* Disable native outline. */\n\toutline: none;\n\tborder: var(--ck-focus-ring)\n}\n","/*\n * Copyright (c) 2003-2022, 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/**\n * A helper to combine multiple shadows.\n */\n@define-mixin ck-box-shadow $shadowA, $shadowB: 0 0 {\n\tbox-shadow: $shadowA, $shadowB;\n}\n\n/**\n * Gives an element a drop shadow so it looks like a floating panel.\n */\n@define-mixin ck-drop-shadow {\n\t@mixin ck-box-shadow var(--ck-drop-shadow);\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/components/label/label.css":
/*!******************************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/components/label/label.css ***!
\******************************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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-label{display:block}.ck.ck-voice-label{display:none}.ck.ck-label{font-weight:700}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-ui/theme/components/label/label.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/ckeditor5-ui/components/label/label.css"],"names":[],"mappings":"AAKA,aACC,aACD,CAEA,mBACC,YACD,CCNA,aACC,eACD","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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-label {\n\tdisplay: block;\n}\n\n.ck.ck-voice-label {\n\tdisplay: none;\n}\n","/*\n * Copyright (c) 2003-2022, 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-label {\n\tfont-weight: bold;\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/components/labeledfield/labeledfieldview.css":
/*!************************************************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/components/labeledfield/labeledfieldview.css ***!
\************************************************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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-labeled-field-view>.ck.ck-labeled-field-view__input-wrapper{display:flex;position:relative}.ck.ck-labeled-field-view .ck.ck-label{display:block;position:absolute}:root{--ck-labeled-field-view-transition:.1s cubic-bezier(0,0,0.24,0.95);--ck-labeled-field-empty-unfocused-max-width:100% - 2 * var(--ck-spacing-medium);--ck-color-labeled-field-label-background:var(--ck-color-base-background)}.ck.ck-labeled-field-view{border-radius:0}.ck-rounded-corners .ck.ck-labeled-field-view,.ck.ck-labeled-field-view.ck-rounded-corners{border-radius:var(--ck-border-radius)}.ck.ck-labeled-field-view>.ck.ck-labeled-field-view__input-wrapper{width:100%}.ck.ck-labeled-field-view>.ck.ck-labeled-field-view__input-wrapper>.ck.ck-label{top:0}[dir=ltr] .ck.ck-labeled-field-view>.ck.ck-labeled-field-view__input-wrapper>.ck.ck-label{left:0}[dir=rtl] .ck.ck-labeled-field-view>.ck.ck-labeled-field-view__input-wrapper>.ck.ck-label{right:0}.ck.ck-labeled-field-view>.ck.ck-labeled-field-view__input-wrapper>.ck.ck-label{pointer-events:none;transform-origin:0 0;transform:translate(var(--ck-spacing-medium),-6px) scale(.75);background:var(--ck-color-labeled-field-label-background);padding:0 calc(var(--ck-font-size-tiny)*0.5);line-height:normal;font-weight:400;text-overflow:ellipsis;overflow:hidden;max-width:100%;transition:transform var(--ck-labeled-field-view-transition),padding var(--ck-labeled-field-view-transition),background var(--ck-labeled-field-view-transition)}.ck.ck-labeled-field-view.ck-error .ck-input:not([readonly])+.ck.ck-label,.ck.ck-labeled-field-view.ck-error>.ck.ck-labeled-field-view__input-wrapper>.ck.ck-label{color:var(--ck-color-base-error)}.ck.ck-labeled-field-view .ck-labeled-field-view__status{font-size:var(--ck-font-size-small);margin-top:var(--ck-spacing-small);white-space:normal}.ck.ck-labeled-field-view .ck-labeled-field-view__status.ck-labeled-field-view__status_error{color:var(--ck-color-base-error)}.ck.ck-labeled-field-view.ck-disabled>.ck.ck-labeled-field-view__input-wrapper>.ck.ck-label,.ck.ck-labeled-field-view.ck-labeled-field-view_empty:not(.ck-labeled-field-view_focused)>.ck.ck-labeled-field-view__input-wrapper>.ck.ck-label{color:var(--ck-color-input-disabled-text)}[dir=ltr] .ck.ck-labeled-field-view.ck-disabled.ck-labeled-field-view_empty>.ck.ck-labeled-field-view__input-wrapper>.ck.ck-label,[dir=ltr] .ck.ck-labeled-field-view.ck-labeled-field-view_empty:not(.ck-labeled-field-view_focused):not(.ck-labeled-field-view_placeholder)>.ck.ck-labeled-field-view__input-wrapper>.ck.ck-label{transform:translate(var(--ck-spacing-medium),calc(var(--ck-font-size-base)*0.6)) scale(1)}[dir=rtl] .ck.ck-labeled-field-view.ck-disabled.ck-labeled-field-view_empty>.ck.ck-labeled-field-view__input-wrapper>.ck.ck-label,[dir=rtl] .ck.ck-labeled-field-view.ck-labeled-field-view_empty:not(.ck-labeled-field-view_focused):not(.ck-labeled-field-view_placeholder)>.ck.ck-labeled-field-view__input-wrapper>.ck.ck-label{transform:translate(calc(var(--ck-spacing-medium)*-1),calc(var(--ck-font-size-base)*0.6)) scale(1)}.ck.ck-labeled-field-view.ck-disabled.ck-labeled-field-view_empty>.ck.ck-labeled-field-view__input-wrapper>.ck.ck-label,.ck.ck-labeled-field-view.ck-labeled-field-view_empty:not(.ck-labeled-field-view_focused):not(.ck-labeled-field-view_placeholder)>.ck.ck-labeled-field-view__input-wrapper>.ck.ck-label{max-width:calc(var(--ck-labeled-field-empty-unfocused-max-width));background:transparent;padding:0}.ck.ck-labeled-field-view>.ck.ck-labeled-field-view__input-wrapper>.ck-dropdown>.ck.ck-button{background:transparent}.ck.ck-labeled-field-view.ck-labeled-field-view_empty>.ck.ck-labeled-field-view__input-wrapper>.ck-dropdown>.ck-button>.ck-button__label{opacity:0}.ck.ck-labeled-field-view.ck-labeled-field-view_empty:not(.ck-labeled-field-view_focused):not(.ck-labeled-field-view_placeholder)>.ck.ck-labeled-field-view__input-wrapper>.ck-dropdown+.ck-label{max-width:calc(var(--ck-labeled-field-empty-unfocused-max-width) - var(--ck-dropdown-arrow-size) - var(--ck-spacing-standard))}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-ui/theme/components/labeledfield/labeledfieldview.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/ckeditor5-ui/components/labeledfield/labeledfieldview.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/mixins/_rounded.css"],"names":[],"mappings":"AAMC,mEACC,YAAa,CACb,iBACD,CAEA,uCACC,aAAc,CACd,iBACD,CCND,MACC,kEAAsE,CACtE,gFAAiF,CACjF,yEACD,CAEA,0BCHC,eD4GD,CAzGA,2FCCE,qCDwGF,CAtGC,mEACC,UAmCD,CAjCC,gFACC,KA+BD,CAhCA,0FAIE,MA4BF,CAhCA,0FAQE,OAwBF,CAhCA,gFAWC,mBAAoB,CACpB,oBAAqB,CAGrB,6DAA+D,CAE/D,yDAA0D,CAC1D,4CAA8C,CAC9C,kBAAoB,CACpB,eAAmB,CAGnB,sBAAuB,CACvB,eAAgB,CAEhB,cAAe,CAEf,+JAID,CAQA,mKACC,gCACD,CAGD,yDACC,mCAAoC,CACpC,kCAAmC,CAInC,kBAKD,CAHC,6FACC,gCACD,CAID,4OAEC,yCACD,CAIA,oUAGE,yFAYF,CAfA,oUAOE,kGAQF,CAfA,gTAWC,iEAAkE,CAElE,sBAAuB,CACvB,SACD,CAKA,8FACC,sBACD,CAGA,yIACC,SACD,CAGA,kMACC,8HACD","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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-labeled-field-view {\n\t& > .ck.ck-labeled-field-view__input-wrapper {\n\t\tdisplay: flex;\n\t\tposition: relative;\n\t}\n\n\t& .ck.ck-label {\n\t\tdisplay: block;\n\t\tposition: absolute;\n\t}\n}\n","/*\n * Copyright (c) 2003-2022, 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@import \"../../../mixins/_rounded.css\";\n\n:root {\n\t--ck-labeled-field-view-transition: .1s cubic-bezier(0, 0, 0.24, 0.95);\n\t--ck-labeled-field-empty-unfocused-max-width: 100% - 2 * var(--ck-spacing-medium);\n\t--ck-color-labeled-field-label-background: var(--ck-color-base-background);\n}\n\n.ck.ck-labeled-field-view {\n\t@mixin ck-rounded-corners;\n\n\t& > .ck.ck-labeled-field-view__input-wrapper {\n\t\twidth: 100%;\n\n\t\t& > .ck.ck-label {\n\t\t\ttop: 0px;\n\n\t\t\t@mixin ck-dir ltr {\n\t\t\t\tleft: 0px;\n\t\t\t}\n\n\t\t\t@mixin ck-dir rtl {\n\t\t\t\tright: 0px;\n\t\t\t}\n\n\t\t\tpointer-events: none;\n\t\t\ttransform-origin: 0 0;\n\n\t\t\t/* By default, display the label scaled down above the field. */\n\t\t\ttransform: translate(var(--ck-spacing-medium), -6px) scale(.75);\n\n\t\t\tbackground: var(--ck-color-labeled-field-label-background);\n\t\t\tpadding: 0 calc(.5 * var(--ck-font-size-tiny));\n\t\t\tline-height: initial;\n\t\t\tfont-weight: normal;\n\n\t\t\t/* Prevent overflow when the label is longer than the input */\n\t\t\ttext-overflow: ellipsis;\n\t\t\toverflow: hidden;\n\n\t\t\tmax-width: 100%;\n\n\t\t\ttransition:\n\t\t\t\ttransform var(--ck-labeled-field-view-transition),\n\t\t\t\tpadding var(--ck-labeled-field-view-transition),\n\t\t\t\tbackground var(--ck-labeled-field-view-transition);\n\t\t}\n\t}\n\n\t&.ck-error {\n\t\t& > .ck.ck-labeled-field-view__input-wrapper > .ck.ck-label {\n\t\t\tcolor: var(--ck-color-base-error);\n\t\t}\n\n\t\t& .ck-input:not([readonly]) + .ck.ck-label {\n\t\t\tcolor: var(--ck-color-base-error);\n\t\t}\n\t}\n\n\t& .ck-labeled-field-view__status {\n\t\tfont-size: var(--ck-font-size-small);\n\t\tmargin-top: var(--ck-spacing-small);\n\n\t\t/* Let the info wrap to the next line to avoid stretching the layout horizontally.\n\t\tThe status could be very long. */\n\t\twhite-space: normal;\n\n\t\t&.ck-labeled-field-view__status_error {\n\t\t\tcolor: var(--ck-color-base-error);\n\t\t}\n\t}\n\n\t/* Disabled fields and fields that have no focus should fade out. */\n\t&.ck-disabled > .ck.ck-labeled-field-view__input-wrapper > .ck.ck-label,\n\t&.ck-labeled-field-view_empty:not(.ck-labeled-field-view_focused) > .ck.ck-labeled-field-view__input-wrapper > .ck.ck-label {\n\t\tcolor: var(--ck-color-input-disabled-text);\n\t}\n\n\t/* Fields that are disabled or not focused and without a placeholder should have full-sized labels. */\n\t/* stylelint-disable-next-line no-descending-specificity */\n\t&.ck-disabled.ck-labeled-field-view_empty > .ck.ck-labeled-field-view__input-wrapper > .ck.ck-label,\n\t&.ck-labeled-field-view_empty:not(.ck-labeled-field-view_focused):not(.ck-labeled-field-view_placeholder) > .ck.ck-labeled-field-view__input-wrapper > .ck.ck-label {\n\t\t@mixin ck-dir ltr {\n\t\t\ttransform: translate(var(--ck-spacing-medium), calc(0.6 * var(--ck-font-size-base))) scale(1);\n\t\t}\n\n\t\t@mixin ck-dir rtl {\n\t\t\ttransform: translate(calc(-1 * var(--ck-spacing-medium)), calc(0.6 * var(--ck-font-size-base))) scale(1);\n\t\t}\n\n\t\t/* Compensate for the default translate position. */\n\t\tmax-width: calc(var(--ck-labeled-field-empty-unfocused-max-width));\n\n\t\tbackground: transparent;\n\t\tpadding: 0;\n\t}\n\n\t/*------ DropdownView integration ----------------------------------------------------------------------------------- */\n\n\t/* Make sure dropdown' background color in any of dropdown's state does not collide with labeled field. */\n\t& > .ck.ck-labeled-field-view__input-wrapper > .ck-dropdown > .ck.ck-button {\n\t\tbackground: transparent;\n\t}\n\n\t/* When the dropdown is \"empty\", the labeled field label replaces its label. */\n\t&.ck-labeled-field-view_empty > .ck.ck-labeled-field-view__input-wrapper > .ck-dropdown > .ck-button > .ck-button__label {\n\t\topacity: 0;\n\t}\n\n\t/* Make sure the label of the empty, unfocused input does not cover the dropdown arrow. */\n\t&.ck-labeled-field-view_empty:not(.ck-labeled-field-view_focused):not(.ck-labeled-field-view_placeholder) > .ck.ck-labeled-field-view__input-wrapper > .ck-dropdown + .ck-label {\n\t\tmax-width: calc(var(--ck-labeled-field-empty-unfocused-max-width) - var(--ck-dropdown-arrow-size) - var(--ck-spacing-standard));\n\t}\n}\n","/*\n * Copyright (c) 2003-2022, 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/**\n * Implements rounded corner interface for .ck-rounded-corners class.\n *\n * @see $ck-border-radius\n */\n@define-mixin ck-rounded-corners {\n\tborder-radius: 0;\n\n\t@nest .ck-rounded-corners &,\n\t&.ck-rounded-corners {\n\t\tborder-radius: var(--ck-border-radius);\n\t\t@mixin-content;\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/components/list/list.css":
/*!****************************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/components/list/list.css ***!
\****************************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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-list{-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none;user-select:none;display:flex;flex-direction:column}.ck.ck-list .ck-list__item,.ck.ck-list .ck-list__separator{display:block}.ck.ck-list .ck-list__item>:focus{position:relative;z-index:var(--ck-z-default)}.ck.ck-list{border-radius:0}.ck-rounded-corners .ck.ck-list,.ck.ck-list.ck-rounded-corners{border-radius:var(--ck-border-radius)}.ck.ck-list{list-style-type:none;background:var(--ck-color-list-background)}.ck.ck-list__item{cursor:default;min-width:12em}.ck.ck-list__item .ck-button{min-height:unset;width:100%;text-align:left;border-radius:0;padding:calc(var(--ck-line-height-base)*0.2*var(--ck-font-size-base)) calc(var(--ck-line-height-base)*0.4*var(--ck-font-size-base))}.ck.ck-list__item .ck-button .ck-button__label{line-height:calc(var(--ck-line-height-base)*1.2*var(--ck-font-size-base))}.ck.ck-list__item .ck-button:active{box-shadow:none}.ck.ck-list__item .ck-button.ck-on{background:var(--ck-color-list-button-on-background);color:var(--ck-color-list-button-on-text)}.ck.ck-list__item .ck-button.ck-on:active{box-shadow:none}.ck.ck-list__item .ck-button.ck-on:hover:not(.ck-disabled){background:var(--ck-color-list-button-on-background-focus)}.ck.ck-list__item .ck-button.ck-on:focus:not(.ck-disabled){border-color:var(--ck-color-base-background)}.ck.ck-list__item .ck-button:hover:not(.ck-disabled){background:var(--ck-color-list-button-hover-background)}.ck.ck-list__item .ck-switchbutton.ck-on{background:var(--ck-color-list-background);color:inherit}.ck.ck-list__item .ck-switchbutton.ck-on:hover:not(.ck-disabled){background:var(--ck-color-list-button-hover-background);color:inherit}.ck.ck-list__separator{height:1px;width:100%;background:var(--ck-color-base-border)}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-ui/theme/components/list/list.css","webpack://./node_modules/@ckeditor/ckeditor5-ui/theme/mixins/_unselectable.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/ckeditor5-ui/components/list/list.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/mixins/_rounded.css"],"names":[],"mappings":"AAOA,YCEC,qBAAsB,CACtB,wBAAyB,CACzB,oBAAqB,CACrB,gBAAgB,CDFhB,YAAa,CACb,qBAcD,CAZC,2DAEC,aACD,CAKA,kCACC,iBAAkB,CAClB,2BACD,CEfD,YCEC,eDGD,CALA,+DCME,qCDDF,CALA,YAGC,oBAAqB,CACrB,0CACD,CAEA,kBACC,cAAe,CACf,cA2DD,CAzDC,6BACC,gBAAiB,CACjB,UAAW,CACX,eAAgB,CAChB,eAAgB,CAKhB,mIAiCD,CA7BC,+CAEC,yEACD,CAEA,oCACC,eACD,CAEA,mCACC,oDAAqD,CACrD,yCAaD,CAXC,0CACC,eACD,CAEA,2DACC,0DACD,CAEA,2DACC,4CACD,CAGD,qDACC,uDACD,CAMA,yCACC,0CAA2C,CAC3C,aAMD,CAJC,iEACC,uDAAwD,CACxD,aACD,CAKH,uBACC,UAAW,CACX,UAAW,CACX,sCACD","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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 \"../../mixins/_unselectable.css\";\n\n.ck.ck-list {\n\t@mixin ck-unselectable;\n\n\tdisplay: flex;\n\tflex-direction: column;\n\n\t& .ck-list__item,\n\t& .ck-list__separator {\n\t\tdisplay: block;\n\t}\n\n\t/* Make sure that whatever child of the list item gets focus, it remains on the\n\ttop. Thanks to that, styles like box-shadow, outline, etc. are not masked by\n\tadjacent list items. */\n\t& .ck-list__item > *:focus {\n\t\tposition: relative;\n\t\tz-index: var(--ck-z-default);\n\t}\n}\n","/*\n * Copyright (c) 2003-2022, 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/**\n * Makes element unselectable.\n */\n@define-mixin ck-unselectable {\n\t-moz-user-select: none;\n\t-webkit-user-select: none;\n\t-ms-user-select: none;\n\tuser-select: none\n}\n","/*\n * Copyright (c) 2003-2022, 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 \"../../../mixins/_disabled.css\";\n@import \"../../../mixins/_rounded.css\";\n@import \"../../../mixins/_shadow.css\";\n\n.ck.ck-list {\n\t@mixin ck-rounded-corners;\n\n\tlist-style-type: none;\n\tbackground: var(--ck-color-list-background);\n}\n\n.ck.ck-list__item {\n\tcursor: default;\n\tmin-width: 12em;\n\n\t& .ck-button {\n\t\tmin-height: unset;\n\t\twidth: 100%;\n\t\ttext-align: left;\n\t\tborder-radius: 0;\n\n\t\t/* List items should have the same height. Use absolute units to make sure it is so\n\t\t because e.g. different heading styles may have different height\n\t\t https://github.com/ckeditor/ckeditor5-heading/issues/63 */\n\t\tpadding:\n\t\t\tcalc(.2 * var(--ck-line-height-base) * var(--ck-font-size-base))\n\t\t\tcalc(.4 * var(--ck-line-height-base) * var(--ck-font-size-base));\n\n\t\t& .ck-button__label {\n\t\t\t/* https://github.com/ckeditor/ckeditor5-heading/issues/63 */\n\t\t\tline-height: calc(1.2 * var(--ck-line-height-base) * var(--ck-font-size-base));\n\t\t}\n\n\t\t&:active {\n\t\t\tbox-shadow: none;\n\t\t}\n\n\t\t&.ck-on {\n\t\t\tbackground: var(--ck-color-list-button-on-background);\n\t\t\tcolor: var(--ck-color-list-button-on-text);\n\n\t\t\t&:active {\n\t\t\t\tbox-shadow: none;\n\t\t\t}\n\n\t\t\t&:hover:not(.ck-disabled) {\n\t\t\t\tbackground: var(--ck-color-list-button-on-background-focus);\n\t\t\t}\n\n\t\t\t&:focus:not(.ck-disabled) {\n\t\t\t\tborder-color: var(--ck-color-base-background);\n\t\t\t}\n\t\t}\n\n\t\t&:hover:not(.ck-disabled) {\n\t\t\tbackground: var(--ck-color-list-button-hover-background);\n\t\t}\n\t}\n\n\t/* It's unnecessary to change the background/text of a switch toggle; it has different ways\n\tof conveying its state (like the switcher) */\n\t& .ck-switchbutton {\n\t\t&.ck-on {\n\t\t\tbackground: var(--ck-color-list-background);\n\t\t\tcolor: inherit;\n\n\t\t\t&:hover:not(.ck-disabled) {\n\t\t\t\tbackground: var(--ck-color-list-button-hover-background);\n\t\t\t\tcolor: inherit;\n\t\t\t}\n\t\t}\n\t}\n}\n\n.ck.ck-list__separator {\n\theight: 1px;\n\twidth: 100%;\n\tbackground: var(--ck-color-base-border);\n}\n","/*\n * Copyright (c) 2003-2022, 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/**\n * Implements rounded corner interface for .ck-rounded-corners class.\n *\n * @see $ck-border-radius\n */\n@define-mixin ck-rounded-corners {\n\tborder-radius: 0;\n\n\t@nest .ck-rounded-corners &,\n\t&.ck-rounded-corners {\n\t\tborder-radius: var(--ck-border-radius);\n\t\t@mixin-content;\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/components/panel/balloonpanel.css":
/*!*************************************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/components/panel/balloonpanel.css ***!
\*************************************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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, ":root{--ck-balloon-panel-arrow-z-index:calc(var(--ck-z-default) - 3)}.ck.ck-balloon-panel{display:none;position:absolute;z-index:var(--ck-z-modal)}.ck.ck-balloon-panel.ck-balloon-panel_with-arrow:after,.ck.ck-balloon-panel.ck-balloon-panel_with-arrow:before{content:\"\";position:absolute}.ck.ck-balloon-panel.ck-balloon-panel_with-arrow:before{z-index:var(--ck-balloon-panel-arrow-z-index)}.ck.ck-balloon-panel.ck-balloon-panel_with-arrow:after{z-index:calc(var(--ck-balloon-panel-arrow-z-index) + 1)}.ck.ck-balloon-panel[class*=arrow_n]:before{z-index:var(--ck-balloon-panel-arrow-z-index)}.ck.ck-balloon-panel[class*=arrow_n]:after{z-index:calc(var(--ck-balloon-panel-arrow-z-index) + 1)}.ck.ck-balloon-panel[class*=arrow_s]:before{z-index:var(--ck-balloon-panel-arrow-z-index)}.ck.ck-balloon-panel[class*=arrow_s]:after{z-index:calc(var(--ck-balloon-panel-arrow-z-index) + 1)}.ck.ck-balloon-panel.ck-balloon-panel_visible{display:block}:root{--ck-balloon-arrow-offset:2px;--ck-balloon-arrow-height:10px;--ck-balloon-arrow-half-width:8px;--ck-balloon-arrow-drop-shadow:0 2px 2px var(--ck-color-shadow-drop)}.ck.ck-balloon-panel{border-radius:0}.ck-rounded-corners .ck.ck-balloon-panel,.ck.ck-balloon-panel.ck-rounded-corners{border-radius:var(--ck-border-radius)}.ck.ck-balloon-panel{box-shadow:var(--ck-drop-shadow),0 0;min-height:15px;background:var(--ck-color-panel-background);border:1px solid var(--ck-color-panel-border)}.ck.ck-balloon-panel.ck-balloon-panel_with-arrow:after,.ck.ck-balloon-panel.ck-balloon-panel_with-arrow:before{width:0;height:0;border-style:solid}.ck.ck-balloon-panel[class*=arrow_n]:after,.ck.ck-balloon-panel[class*=arrow_n]:before{border-left-width:var(--ck-balloon-arrow-half-width);border-bottom-width:var(--ck-balloon-arrow-height);border-right-width:var(--ck-balloon-arrow-half-width);border-top-width:0}.ck.ck-balloon-panel[class*=arrow_n]:before{border-bottom-color:var(--ck-color-panel-border)}.ck.ck-balloon-panel[class*=arrow_n]:after,.ck.ck-balloon-panel[class*=arrow_n]:before{border-left-color:transparent;border-right-color:transparent;border-top-color:transparent}.ck.ck-balloon-panel[class*=arrow_n]:after{border-bottom-color:var(--ck-color-panel-background);margin-top:var(--ck-balloon-arrow-offset)}.ck.ck-balloon-panel[class*=arrow_s]:after,.ck.ck-balloon-panel[class*=arrow_s]:before{border-left-width:var(--ck-balloon-arrow-half-width);border-bottom-width:0;border-right-width:var(--ck-balloon-arrow-half-width);border-top-width:var(--ck-balloon-arrow-height)}.ck.ck-balloon-panel[class*=arrow_s]:before{border-top-color:var(--ck-color-panel-border);filter:drop-shadow(var(--ck-balloon-arrow-drop-shadow))}.ck.ck-balloon-panel[class*=arrow_s]:after,.ck.ck-balloon-panel[class*=arrow_s]:before{border-left-color:transparent;border-bottom-color:transparent;border-right-color:transparent}.ck.ck-balloon-panel[class*=arrow_s]:after{border-top-color:var(--ck-color-panel-background);margin-bottom:var(--ck-balloon-arrow-offset)}.ck.ck-balloon-panel.ck-balloon-panel_arrow_n:after,.ck.ck-balloon-panel.ck-balloon-panel_arrow_n:before{left:50%;margin-left:calc(var(--ck-balloon-arrow-half-width)*-1);top:calc(var(--ck-balloon-arrow-height)*-1)}.ck.ck-balloon-panel.ck-balloon-panel_arrow_nw:after,.ck.ck-balloon-panel.ck-balloon-panel_arrow_nw:before{left:calc(var(--ck-balloon-arrow-half-width)*2);top:calc(var(--ck-balloon-arrow-height)*-1)}.ck.ck-balloon-panel.ck-balloon-panel_arrow_ne:after,.ck.ck-balloon-panel.ck-balloon-panel_arrow_ne:before{right:calc(var(--ck-balloon-arrow-half-width)*2);top:calc(var(--ck-balloon-arrow-height)*-1)}.ck.ck-balloon-panel.ck-balloon-panel_arrow_s:after,.ck.ck-balloon-panel.ck-balloon-panel_arrow_s:before{left:50%;margin-left:calc(var(--ck-balloon-arrow-half-width)*-1);bottom:calc(var(--ck-balloon-arrow-height)*-1)}.ck.ck-balloon-panel.ck-balloon-panel_arrow_sw:after,.ck.ck-balloon-panel.ck-balloon-panel_arrow_sw:before{left:calc(var(--ck-balloon-arrow-half-width)*2);bottom:calc(var(--ck-balloon-arrow-height)*-1)}.ck.ck-balloon-panel.ck-balloon-panel_arrow_se:after,.ck.ck-balloon-panel.ck-balloon-panel_arrow_se:before{right:calc(var(--ck-balloon-arrow-half-width)*2);bottom:calc(var(--ck-balloon-arrow-height)*-1)}.ck.ck-balloon-panel.ck-balloon-panel_arrow_sme:after,.ck.ck-balloon-panel.ck-balloon-panel_arrow_sme:before{right:25%;margin-right:calc(var(--ck-balloon-arrow-half-width)*2);bottom:calc(var(--ck-balloon-arrow-height)*-1)}.ck.ck-balloon-panel.ck-balloon-panel_arrow_smw:after,.ck.ck-balloon-panel.ck-balloon-panel_arrow_smw:before{left:25%;margin-left:calc(var(--ck-balloon-arrow-half-width)*2);bottom:calc(var(--ck-balloon-arrow-height)*-1)}.ck.ck-balloon-panel.ck-balloon-panel_arrow_nme:after,.ck.ck-balloon-panel.ck-balloon-panel_arrow_nme:before{right:25%;margin-right:calc(var(--ck-balloon-arrow-half-width)*2);top:calc(var(--ck-balloon-arrow-height)*-1)}.ck.ck-balloon-panel.ck-balloon-panel_arrow_nmw:after,.ck.ck-balloon-panel.ck-balloon-panel_arrow_nmw:before{left:25%;margin-left:calc(var(--ck-balloon-arrow-half-width)*2);top:calc(var(--ck-balloon-arrow-height)*-1)}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-ui/theme/components/panel/balloonpanel.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/ckeditor5-ui/components/panel/balloonpanel.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/mixins/_rounded.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/mixins/_shadow.css"],"names":[],"mappings":"AAKA,MAEC,8DACD,CAEA,qBACC,YAAa,CACb,iBAAkB,CAElB,yBAyCD,CAtCE,+GAEC,UAAW,CACX,iBACD,CAEA,wDACC,6CACD,CAEA,uDACC,uDACD,CAIA,4CACC,6CACD,CAEA,2CACC,uDACD,CAIA,4CACC,6CACD,CAEA,2CACC,uDACD,CAGD,8CACC,aACD,CC9CD,MACC,6BAA8B,CAC9B,8BAA+B,CAC/B,iCAAkC,CAClC,oEACD,CAEA,qBCJC,eD4ID,CAxIA,iFCAE,qCDwIF,CAxIA,qBENC,oCAA8B,CFU9B,eAAgB,CAEhB,2CAA4C,CAC5C,6CAiID,CA9HE,+GAEC,OAAQ,CACR,QAAS,CACT,kBACD,CAIA,uFAEC,oDAAoH,CAApH,kDAAoH,CAApH,qDAAoH,CAApH,kBACD,CAEA,4CACC,gDACD,CAEA,uFAHC,6BAA8E,CAA9E,8BAA8E,CAA9E,4BAMD,CAHA,2CACC,oDAAkF,CAClF,yCACD,CAIA,uFAEC,oDAAoH,CAApH,qBAAoH,CAApH,qDAAoH,CAApH,+CACD,CAEA,4CACC,6CAAkE,CAClE,uDACD,CAEA,uFAJC,6BAAkE,CAAlE,+BAAkE,CAAlE,8BAOD,CAHA,2CACC,iDAAkF,CAClF,4CACD,CAIA,yGAEC,QAAS,CACT,uDAA0D,CAC1D,2CACD,CAIA,2GAEC,+CAAkD,CAClD,2CACD,CAIA,2GAEC,gDAAmD,CACnD,2CACD,CAIA,yGAEC,QAAS,CACT,uDAA0D,CAC1D,8CACD,CAIA,2GAEC,+CAAkD,CAClD,8CACD,CAIA,2GAEC,gDAAmD,CACnD,8CACD,CAIA,6GAEC,SAAU,CACV,uDAA0D,CAC1D,8CACD,CAIA,6GAEC,QAAS,CACT,sDAAyD,CACzD,8CACD,CAIA,6GAEC,SAAU,CACV,uDAA0D,CAC1D,2CACD,CAIA,6GAEC,QAAS,CACT,sDAAyD,CACzD,2CACD","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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/* Make sure the balloon arrow does not float over its children. */\n\t--ck-balloon-panel-arrow-z-index: calc(var(--ck-z-default) - 3);\n}\n\n.ck.ck-balloon-panel {\n\tdisplay: none;\n\tposition: absolute;\n\n\tz-index: var(--ck-z-modal);\n\n\t&.ck-balloon-panel_with-arrow {\n\t\t&::before,\n\t\t&::after {\n\t\t\tcontent: \"\";\n\t\t\tposition: absolute;\n\t\t}\n\n\t\t&::before {\n\t\t\tz-index: var(--ck-balloon-panel-arrow-z-index);\n\t\t}\n\n\t\t&::after {\n\t\t\tz-index: calc(var(--ck-balloon-panel-arrow-z-index) + 1);\n\t\t}\n\t}\n\n\t&[class*=\"arrow_n\"] {\n\t\t&::before {\n\t\t\tz-index: var(--ck-balloon-panel-arrow-z-index);\n\t\t}\n\n\t\t&::after {\n\t\t\tz-index: calc(var(--ck-balloon-panel-arrow-z-index) + 1);\n\t\t}\n\t}\n\n\t&[class*=\"arrow_s\"] {\n\t\t&::before {\n\t\t\tz-index: var(--ck-balloon-panel-arrow-z-index);\n\t\t}\n\n\t\t&::after {\n\t\t\tz-index: calc(var(--ck-balloon-panel-arrow-z-index) + 1);\n\t\t}\n\t}\n\n\t&.ck-balloon-panel_visible {\n\t\tdisplay: block;\n\t}\n}\n","/*\n * Copyright (c) 2003-2022, 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 \"../../../mixins/_rounded.css\";\n@import \"../../../mixins/_shadow.css\";\n\n:root {\n\t--ck-balloon-arrow-offset: 2px;\n\t--ck-balloon-arrow-height: 10px;\n\t--ck-balloon-arrow-half-width: 8px;\n\t--ck-balloon-arrow-drop-shadow: 0 2px 2px var(--ck-color-shadow-drop);\n}\n\n.ck.ck-balloon-panel {\n\t@mixin ck-rounded-corners;\n\t@mixin ck-drop-shadow;\n\n\tmin-height: 15px;\n\n\tbackground: var(--ck-color-panel-background);\n\tborder: 1px solid var(--ck-color-panel-border);\n\n\t&.ck-balloon-panel_with-arrow {\n\t\t&::before,\n\t\t&::after {\n\t\t\twidth: 0;\n\t\t\theight: 0;\n\t\t\tborder-style: solid;\n\t\t}\n\t}\n\n\t&[class*=\"arrow_n\"] {\n\t\t&::before,\n\t\t&::after {\n\t\t\tborder-width: 0 var(--ck-balloon-arrow-half-width) var(--ck-balloon-arrow-height) var(--ck-balloon-arrow-half-width);\n\t\t}\n\n\t\t&::before {\n\t\t\tborder-color: transparent transparent var(--ck-color-panel-border) transparent;\n\t\t}\n\n\t\t&::after {\n\t\t\tborder-color: transparent transparent var(--ck-color-panel-background) transparent;\n\t\t\tmargin-top: var(--ck-balloon-arrow-offset);\n\t\t}\n\t}\n\n\t&[class*=\"arrow_s\"] {\n\t\t&::before,\n\t\t&::after {\n\t\t\tborder-width: var(--ck-balloon-arrow-height) var(--ck-balloon-arrow-half-width) 0 var(--ck-balloon-arrow-half-width);\n\t\t}\n\n\t\t&::before {\n\t\t\tborder-color: var(--ck-color-panel-border) transparent transparent;\n\t\t\tfilter: drop-shadow(var(--ck-balloon-arrow-drop-shadow));\n\t\t}\n\n\t\t&::after {\n\t\t\tborder-color: var(--ck-color-panel-background) transparent transparent transparent;\n\t\t\tmargin-bottom: var(--ck-balloon-arrow-offset);\n\t\t}\n\t}\n\n\t&.ck-balloon-panel_arrow_n {\n\t\t&::before,\n\t\t&::after {\n\t\t\tleft: 50%;\n\t\t\tmargin-left: calc(-1 * var(--ck-balloon-arrow-half-width));\n\t\t\ttop: calc(-1 * var(--ck-balloon-arrow-height));\n\t\t}\n\t}\n\n\t&.ck-balloon-panel_arrow_nw {\n\t\t&::before,\n\t\t&::after {\n\t\t\tleft: calc(2 * var(--ck-balloon-arrow-half-width));\n\t\t\ttop: calc(-1 * var(--ck-balloon-arrow-height));\n\t\t}\n\t}\n\n\t&.ck-balloon-panel_arrow_ne {\n\t\t&::before,\n\t\t&::after {\n\t\t\tright: calc(2 * var(--ck-balloon-arrow-half-width));\n\t\t\ttop: calc(-1 * var(--ck-balloon-arrow-height));\n\t\t}\n\t}\n\n\t&.ck-balloon-panel_arrow_s {\n\t\t&::before,\n\t\t&::after {\n\t\t\tleft: 50%;\n\t\t\tmargin-left: calc(-1 * var(--ck-balloon-arrow-half-width));\n\t\t\tbottom: calc(-1 * var(--ck-balloon-arrow-height));\n\t\t}\n\t}\n\n\t&.ck-balloon-panel_arrow_sw {\n\t\t&::before,\n\t\t&::after {\n\t\t\tleft: calc(2 * var(--ck-balloon-arrow-half-width));\n\t\t\tbottom: calc(-1 * var(--ck-balloon-arrow-height));\n\t\t}\n\t}\n\n\t&.ck-balloon-panel_arrow_se {\n\t\t&::before,\n\t\t&::after {\n\t\t\tright: calc(2 * var(--ck-balloon-arrow-half-width));\n\t\t\tbottom: calc(-1 * var(--ck-balloon-arrow-height));\n\t\t}\n\t}\n\n\t&.ck-balloon-panel_arrow_sme {\n\t\t&::before,\n\t\t&::after {\n\t\t\tright: 25%;\n\t\t\tmargin-right: calc(2 * var(--ck-balloon-arrow-half-width));\n\t\t\tbottom: calc(-1 * var(--ck-balloon-arrow-height));\n\t\t}\n\t}\n\n\t&.ck-balloon-panel_arrow_smw {\n\t\t&::before,\n\t\t&::after {\n\t\t\tleft: 25%;\n\t\t\tmargin-left: calc(2 * var(--ck-balloon-arrow-half-width));\n\t\t\tbottom: calc(-1 * var(--ck-balloon-arrow-height));\n\t\t}\n\t}\n\n\t&.ck-balloon-panel_arrow_nme {\n\t\t&::before,\n\t\t&::after {\n\t\t\tright: 25%;\n\t\t\tmargin-right: calc(2 * var(--ck-balloon-arrow-half-width));\n\t\t\ttop: calc(-1 * var(--ck-balloon-arrow-height));\n\t\t}\n\t}\n\n\t&.ck-balloon-panel_arrow_nmw {\n\t\t&::before,\n\t\t&::after {\n\t\t\tleft: 25%;\n\t\t\tmargin-left: calc(2 * var(--ck-balloon-arrow-half-width));\n\t\t\ttop: calc(-1 * var(--ck-balloon-arrow-height));\n\t\t}\n\t}\n}\n","/*\n * Copyright (c) 2003-2022, 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/**\n * Implements rounded corner interface for .ck-rounded-corners class.\n *\n * @see $ck-border-radius\n */\n@define-mixin ck-rounded-corners {\n\tborder-radius: 0;\n\n\t@nest .ck-rounded-corners &,\n\t&.ck-rounded-corners {\n\t\tborder-radius: var(--ck-border-radius);\n\t\t@mixin-content;\n\t}\n}\n","/*\n * Copyright (c) 2003-2022, 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/**\n * A helper to combine multiple shadows.\n */\n@define-mixin ck-box-shadow $shadowA, $shadowB: 0 0 {\n\tbox-shadow: $shadowA, $shadowB;\n}\n\n/**\n * Gives an element a drop shadow so it looks like a floating panel.\n */\n@define-mixin ck-drop-shadow {\n\t@mixin ck-box-shadow var(--ck-drop-shadow);\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/components/panel/balloonrotator.css":
/*!***************************************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/components/panel/balloonrotator.css ***!
\***************************************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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-balloon-rotator__navigation{display:flex;align-items:center;justify-content:center}.ck .ck-balloon-rotator__content .ck-toolbar{justify-content:center}.ck .ck-balloon-rotator__navigation{background:var(--ck-color-toolbar-background);border-bottom:1px solid var(--ck-color-toolbar-border);padding:0 var(--ck-spacing-small)}.ck .ck-balloon-rotator__navigation>*{margin-right:var(--ck-spacing-small);margin-top:var(--ck-spacing-small);margin-bottom:var(--ck-spacing-small)}.ck .ck-balloon-rotator__navigation .ck-balloon-rotator__counter{margin-right:var(--ck-spacing-standard);margin-left:var(--ck-spacing-small)}.ck .ck-balloon-rotator__content .ck.ck-annotation-wrapper{box-shadow:none}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-ui/theme/components/panel/balloonrotator.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/ckeditor5-ui/components/panel/balloonrotator.css"],"names":[],"mappings":"AAKA,oCACC,YAAa,CACb,kBAAmB,CACnB,sBACD,CAKA,6CACC,sBACD,CCXA,oCACC,6CAA8C,CAC9C,sDAAuD,CACvD,iCAgBD,CAbC,sCACC,oCAAqC,CACrC,kCAAmC,CACnC,qCACD,CAGA,iEACC,uCAAwC,CAGxC,mCACD,CAMA,2DACC,eACD","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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-balloon-rotator__navigation {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n}\n\n/* Buttons inside a toolbar should be centered when rotator bar is wider.\n * See: https://github.com/ckeditor/ckeditor5-ui/issues/495\n */\n.ck .ck-balloon-rotator__content .ck-toolbar {\n\tjustify-content: center;\n}\n","/*\n * Copyright (c) 2003-2022, 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-balloon-rotator__navigation {\n\tbackground: var(--ck-color-toolbar-background);\n\tborder-bottom: 1px solid var(--ck-color-toolbar-border);\n\tpadding: 0 var(--ck-spacing-small);\n\n\t/* Let's keep similar appearance to `ck-toolbar`. */\n\t& > * {\n\t\tmargin-right: var(--ck-spacing-small);\n\t\tmargin-top: var(--ck-spacing-small);\n\t\tmargin-bottom: var(--ck-spacing-small);\n\t}\n\n\t/* Gives counter more breath than buttons. */\n\t& .ck-balloon-rotator__counter {\n\t\tmargin-right: var(--ck-spacing-standard);\n\n\t\t/* We need to use smaller margin because of previous button's right margin. */\n\t\tmargin-left: var(--ck-spacing-small);\n\t}\n}\n\n.ck .ck-balloon-rotator__content {\n\n\t/* Disable default annotation shadow inside rotator with fake panels. */\n\t& .ck.ck-annotation-wrapper {\n\t\tbox-shadow: none;\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/components/panel/fakepanel.css":
/*!**********************************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/components/panel/fakepanel.css ***!
\**********************************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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-fake-panel{position:absolute;z-index:calc(var(--ck-z-modal) - 1)}.ck .ck-fake-panel div{position:absolute}.ck .ck-fake-panel div:first-child{z-index:2}.ck .ck-fake-panel div:nth-child(2){z-index:1}:root{--ck-balloon-fake-panel-offset-horizontal:6px;--ck-balloon-fake-panel-offset-vertical:6px}.ck .ck-fake-panel div{box-shadow:var(--ck-drop-shadow),0 0;min-height:15px;background:var(--ck-color-panel-background);border:1px solid var(--ck-color-panel-border);border-radius:var(--ck-border-radius);width:100%;height:100%}.ck .ck-fake-panel div:first-child{margin-left:var(--ck-balloon-fake-panel-offset-horizontal);margin-top:var(--ck-balloon-fake-panel-offset-vertical)}.ck .ck-fake-panel div:nth-child(2){margin-left:calc(var(--ck-balloon-fake-panel-offset-horizontal)*2);margin-top:calc(var(--ck-balloon-fake-panel-offset-vertical)*2)}.ck .ck-fake-panel div:nth-child(3){margin-left:calc(var(--ck-balloon-fake-panel-offset-horizontal)*3);margin-top:calc(var(--ck-balloon-fake-panel-offset-vertical)*3)}.ck .ck-balloon-panel_arrow_s+.ck-fake-panel,.ck .ck-balloon-panel_arrow_se+.ck-fake-panel,.ck .ck-balloon-panel_arrow_sw+.ck-fake-panel{--ck-balloon-fake-panel-offset-vertical:-6px}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-ui/theme/components/panel/fakepanel.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/ckeditor5-ui/components/panel/fakepanel.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/mixins/_shadow.css"],"names":[],"mappings":"AAKA,mBACC,iBAAkB,CAGlB,mCACD,CAEA,uBACC,iBACD,CAEA,mCACC,SACD,CAEA,oCACC,SACD,CCfA,MACC,6CAA8C,CAC9C,2CACD,CAGA,uBCJC,oCAA8B,CDO9B,eAAgB,CAEhB,2CAA4C,CAC5C,6CAA8C,CAC9C,qCAAsC,CAEtC,UAAW,CACX,WACD,CAEA,mCACC,0DAA2D,CAC3D,uDACD,CAEA,oCACC,kEAAqE,CACrE,+DACD,CACA,oCACC,kEAAqE,CACrE,+DACD,CAGA,yIAGC,4CACD","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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-fake-panel {\n\tposition: absolute;\n\n\t/* Fake panels should be placed under main balloon content. */\n\tz-index: calc(var(--ck-z-modal) - 1);\n}\n\n.ck .ck-fake-panel div {\n\tposition: absolute;\n}\n\n.ck .ck-fake-panel div:nth-child( 1 ) {\n\tz-index: 2;\n}\n\n.ck .ck-fake-panel div:nth-child( 2 ) {\n\tz-index: 1;\n}\n","/*\n * Copyright (c) 2003-2022, 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 \"../../../mixins/_shadow.css\";\n\n:root {\n\t--ck-balloon-fake-panel-offset-horizontal: 6px;\n\t--ck-balloon-fake-panel-offset-vertical: 6px;\n}\n\n/* Let's use `.ck-balloon-panel` appearance. See: balloonpanel.css. */\n.ck .ck-fake-panel div {\n\t@mixin ck-drop-shadow;\n\n\tmin-height: 15px;\n\n\tbackground: var(--ck-color-panel-background);\n\tborder: 1px solid var(--ck-color-panel-border);\n\tborder-radius: var(--ck-border-radius);\n\n\twidth: 100%;\n\theight: 100%;\n}\n\n.ck .ck-fake-panel div:nth-child( 1 ) {\n\tmargin-left: var(--ck-balloon-fake-panel-offset-horizontal);\n\tmargin-top: var(--ck-balloon-fake-panel-offset-vertical);\n}\n\n.ck .ck-fake-panel div:nth-child( 2 ) {\n\tmargin-left: calc(var(--ck-balloon-fake-panel-offset-horizontal) * 2);\n\tmargin-top: calc(var(--ck-balloon-fake-panel-offset-vertical) * 2);\n}\n.ck .ck-fake-panel div:nth-child( 3 ) {\n\tmargin-left: calc(var(--ck-balloon-fake-panel-offset-horizontal) * 3);\n\tmargin-top: calc(var(--ck-balloon-fake-panel-offset-vertical) * 3);\n}\n\n/* If balloon is positioned above element, we need to move fake panel to the top. */\n.ck .ck-balloon-panel_arrow_s + .ck-fake-panel,\n.ck .ck-balloon-panel_arrow_se + .ck-fake-panel,\n.ck .ck-balloon-panel_arrow_sw + .ck-fake-panel {\n\t--ck-balloon-fake-panel-offset-vertical: -6px;\n}\n","/*\n * Copyright (c) 2003-2022, 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/**\n * A helper to combine multiple shadows.\n */\n@define-mixin ck-box-shadow $shadowA, $shadowB: 0 0 {\n\tbox-shadow: $shadowA, $shadowB;\n}\n\n/**\n * Gives an element a drop shadow so it looks like a floating panel.\n */\n@define-mixin ck-drop-shadow {\n\t@mixin ck-box-shadow var(--ck-drop-shadow);\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/components/panel/stickypanel.css":
/*!************************************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/components/panel/stickypanel.css ***!
\************************************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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-sticky-panel .ck-sticky-panel__content_sticky{z-index:var(--ck-z-modal);position:fixed;top:0}.ck.ck-sticky-panel .ck-sticky-panel__content_sticky_bottom-limit{top:auto;position:absolute}.ck.ck-sticky-panel .ck-sticky-panel__content_sticky{box-shadow:var(--ck-drop-shadow),0 0;border-width:0 1px 1px;border-top-left-radius:0;border-top-right-radius:0}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-ui/theme/components/panel/stickypanel.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/ckeditor5-ui/components/panel/stickypanel.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/mixins/_shadow.css"],"names":[],"mappings":"AAMC,qDACC,yBAA0B,CAC1B,cAAe,CACf,KACD,CAEA,kEACC,QAAS,CACT,iBACD,CCPA,qDCCA,oCAA8B,CDE7B,sBAAuB,CACvB,wBAAyB,CACzB,yBACD","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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-sticky-panel {\n\t& .ck-sticky-panel__content_sticky {\n\t\tz-index: var(--ck-z-modal); /* #315 */\n\t\tposition: fixed;\n\t\ttop: 0;\n\t}\n\n\t& .ck-sticky-panel__content_sticky_bottom-limit {\n\t\ttop: auto;\n\t\tposition: absolute;\n\t}\n}\n","/*\n * Copyright (c) 2003-2022, 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 \"../../../mixins/_shadow.css\";\n\n.ck.ck-sticky-panel {\n\t& .ck-sticky-panel__content_sticky {\n\t\t@mixin ck-drop-shadow;\n\n\t\tborder-width: 0 1px 1px;\n\t\tborder-top-left-radius: 0;\n\t\tborder-top-right-radius: 0;\n\t}\n}\n","/*\n * Copyright (c) 2003-2022, 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/**\n * A helper to combine multiple shadows.\n */\n@define-mixin ck-box-shadow $shadowA, $shadowB: 0 0 {\n\tbox-shadow: $shadowA, $shadowB;\n}\n\n/**\n * Gives an element a drop shadow so it looks like a floating panel.\n */\n@define-mixin ck-drop-shadow {\n\t@mixin ck-box-shadow var(--ck-drop-shadow);\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/components/responsive-form/responsiveform.css":
/*!*************************************************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/components/responsive-form/responsiveform.css ***!
\*************************************************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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-vertical-form .ck-button:after{content:\"\";width:0;position:absolute;right:-1px;top:var(--ck-spacing-small);bottom:var(--ck-spacing-small);z-index:1}@media screen and (max-width:600px){.ck.ck-responsive-form .ck-button:after{content:\"\";width:0;position:absolute;right:-1px;top:var(--ck-spacing-small);bottom:var(--ck-spacing-small);z-index:1}}.ck-vertical-form>.ck-button:nth-last-child(2):after{border-right:1px solid var(--ck-color-base-border)}.ck.ck-responsive-form{padding:var(--ck-spacing-large)}.ck.ck-responsive-form:focus{outline:none}[dir=ltr] .ck.ck-responsive-form>:not(:first-child),[dir=rtl] .ck.ck-responsive-form>:not(:last-child){margin-left:var(--ck-spacing-standard)}@media screen and (max-width:600px){.ck.ck-responsive-form{padding:0;width:calc(var(--ck-input-width)*0.8)}.ck.ck-responsive-form .ck-labeled-field-view{margin:var(--ck-spacing-large) var(--ck-spacing-large) 0}.ck.ck-responsive-form .ck-labeled-field-view .ck-input-text{min-width:0;width:100%}.ck.ck-responsive-form .ck-labeled-field-view .ck-labeled-field-view__error{white-space:normal}.ck.ck-responsive-form>.ck-button:last-child,.ck.ck-responsive-form>.ck-button:nth-last-child(2){padding:var(--ck-spacing-standard);margin-top:var(--ck-spacing-large);border-radius:0;border:0;border-top:1px solid var(--ck-color-base-border)}[dir=ltr] .ck.ck-responsive-form>.ck-button:last-child,[dir=ltr] .ck.ck-responsive-form>.ck-button:nth-last-child(2),[dir=rtl] .ck.ck-responsive-form>.ck-button:last-child,[dir=rtl] .ck.ck-responsive-form>.ck-button:nth-last-child(2){margin-left:0}.ck.ck-responsive-form>.ck-button:nth-last-child(2):after,[dir=rtl] .ck.ck-responsive-form>.ck-button:last-child:last-of-type,[dir=rtl] .ck.ck-responsive-form>.ck-button:nth-last-child(2):last-of-type{border-right:1px solid var(--ck-color-base-border)}}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-ui/theme/components/responsive-form/responsiveform.css","webpack://./node_modules/@ckeditor/ckeditor5-ui/theme/mixins/_rwd.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/ckeditor5-ui/components/responsive-form/responsiveform.css"],"names":[],"mappings":"AAOA,mCACC,UAAW,CACX,OAAQ,CACR,iBAAkB,CAClB,UAAW,CACX,2BAA4B,CAC5B,8BAA+B,CAC/B,SACD,CCTC,oCDaC,wCACC,UAAW,CACX,OAAQ,CACR,iBAAkB,CAClB,UAAW,CACX,2BAA4B,CAC5B,8BAA+B,CAC/B,SACD,CCnBD,CCAD,qDACC,kDACD,CAEA,uBACC,+BAkED,CAhEC,6BAEC,YACD,CASC,uGACC,sCACD,CDvBD,oCCMD,uBAqBE,SAAU,CACV,qCA6CF,CA3CE,8CACC,wDAWD,CATC,6DACC,WAAY,CACZ,UACD,CAGA,4EACC,kBACD,CAID,iGAEC,kCAAmC,CACnC,kCAAmC,CAEnC,eAAgB,CAChB,QAAS,CACT,gDAaD,CApBA,0OAcE,aAMF,CAGC,yMACC,kDACD,CDpEF","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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/_rwd.css\";\n\n.ck-vertical-form .ck-button::after {\n\tcontent: \"\";\n\twidth: 0;\n\tposition: absolute;\n\tright: -1px;\n\ttop: var(--ck-spacing-small);\n\tbottom: var(--ck-spacing-small);\n\tz-index: 1;\n}\n\n.ck.ck-responsive-form {\n\t@mixin ck-media-phone {\n\t\t& .ck-button::after {\n\t\t\tcontent: \"\";\n\t\t\twidth: 0;\n\t\t\tposition: absolute;\n\t\t\tright: -1px;\n\t\t\ttop: var(--ck-spacing-small);\n\t\t\tbottom: var(--ck-spacing-small);\n\t\t\tz-index: 1;\n\t\t}\n\t}\n}\n","/*\n * Copyright (c) 2003-2022, 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@define-mixin ck-media-phone {\n\t@media screen and (max-width: 600px) {\n\t\t@mixin-content;\n\t}\n}\n","/*\n * Copyright (c) 2003-2022, 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/_rwd.css\";\n@import \"@ckeditor/ckeditor5-ui/theme/mixins/_dir.css\";\n\n.ck-vertical-form > .ck-button:nth-last-child(2)::after {\n\tborder-right: 1px solid var(--ck-color-base-border);\n}\n\n.ck.ck-responsive-form {\n\tpadding: var(--ck-spacing-large);\n\n\t&:focus {\n\t\t/* See: https://github.com/ckeditor/ckeditor5/issues/4773 */\n\t\toutline: none;\n\t}\n\n\t@mixin ck-dir ltr {\n\t\t& > :not(:first-child) {\n\t\t\tmargin-left: var(--ck-spacing-standard);\n\t\t}\n\t}\n\n\t@mixin ck-dir rtl {\n\t\t& > :not(:last-child) {\n\t\t\tmargin-left: var(--ck-spacing-standard);\n\t\t}\n\t}\n\n\t@mixin ck-media-phone {\n\t\tpadding: 0;\n\t\twidth: calc(.8 * var(--ck-input-width));\n\n\t\t& .ck-labeled-field-view {\n\t\t\tmargin: var(--ck-spacing-large) var(--ck-spacing-large) 0;\n\n\t\t\t& .ck-input-text {\n\t\t\t\tmin-width: 0;\n\t\t\t\twidth: 100%;\n\t\t\t}\n\n\t\t\t/* Let the long error messages wrap in the narrow form. */\n\t\t\t& .ck-labeled-field-view__error {\n\t\t\t\twhite-space: normal;\n\t\t\t}\n\t\t}\n\n\t\t/* Styles for two last buttons in the form (save&cancel, edit&unlink, etc.). */\n\t\t& > .ck-button:nth-last-child(1),\n\t\t& > .ck-button:nth-last-child(2) {\n\t\t\tpadding: var(--ck-spacing-standard);\n\t\t\tmargin-top: var(--ck-spacing-large);\n\n\t\t\tborder-radius: 0;\n\t\t\tborder: 0;\n\t\t\tborder-top: 1px solid var(--ck-color-base-border);\n\n\t\t\t@mixin ck-dir ltr {\n\t\t\t\tmargin-left: 0;\n\t\t\t}\n\n\t\t\t@mixin ck-dir rtl {\n\t\t\t\tmargin-left: 0;\n\n\t\t\t\t&:last-of-type {\n\t\t\t\t\tborder-right: 1px solid var(--ck-color-base-border);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t& > .ck-button:nth-last-child(2) {\n\t\t\t&::after {\n\t\t\t\tborder-right: 1px solid var(--ck-color-base-border);\n\t\t\t}\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/components/toolbar/blocktoolbar.css":
/*!***************************************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/components/toolbar/blocktoolbar.css ***!
\***************************************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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-block-toolbar-button{position:absolute;z-index:var(--ck-z-default)}:root{--ck-color-block-toolbar-button:var(--ck-color-text);--ck-block-toolbar-button-size:var(--ck-font-size-normal)}.ck.ck-block-toolbar-button{color:var(--ck-color-block-toolbar-button);font-size:var(--ck-block-toolbar-size)}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-ui/theme/components/toolbar/blocktoolbar.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/ckeditor5-ui/components/toolbar/blocktoolbar.css"],"names":[],"mappings":"AAKA,4BACC,iBAAkB,CAClB,2BACD,CCHA,MACC,oDAAqD,CACrD,yDACD,CAEA,4BACC,0CAA2C,CAC3C,sCACD","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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-block-toolbar-button {\n\tposition: absolute;\n\tz-index: var(--ck-z-default);\n}\n","/*\n * Copyright (c) 2003-2022, 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-block-toolbar-button: var(--ck-color-text);\n\t--ck-block-toolbar-button-size: var(--ck-font-size-normal);\n}\n\n.ck.ck-block-toolbar-button {\n\tcolor: var(--ck-color-block-toolbar-button);\n\tfont-size: var(--ck-block-toolbar-size);\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/components/toolbar/toolbar.css":
/*!**********************************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/components/toolbar/toolbar.css ***!
\**********************************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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-toolbar{-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none;user-select:none;display:flex;flex-flow:row nowrap;align-items:center}.ck.ck-toolbar>.ck-toolbar__items{display:flex;flex-flow:row wrap;align-items:center;flex-grow:1}.ck.ck-toolbar .ck.ck-toolbar__separator{display:inline-block}.ck.ck-toolbar .ck.ck-toolbar__separator:first-child,.ck.ck-toolbar .ck.ck-toolbar__separator:last-child{display:none}.ck.ck-toolbar .ck-toolbar__line-break{flex-basis:100%}.ck.ck-toolbar.ck-toolbar_grouping>.ck-toolbar__items{flex-wrap:nowrap}.ck.ck-toolbar.ck-toolbar_vertical>.ck-toolbar__items{flex-direction:column}.ck.ck-toolbar.ck-toolbar_floating>.ck-toolbar__items{flex-wrap:nowrap}.ck.ck-toolbar>.ck.ck-toolbar__grouped-dropdown>.ck-dropdown__button .ck-dropdown__arrow{display:none}.ck.ck-toolbar{border-radius:0}.ck-rounded-corners .ck.ck-toolbar,.ck.ck-toolbar.ck-rounded-corners{border-radius:var(--ck-border-radius)}.ck.ck-toolbar{background:var(--ck-color-toolbar-background);padding:0 var(--ck-spacing-small);border:1px solid var(--ck-color-toolbar-border)}.ck.ck-toolbar .ck.ck-toolbar__separator{align-self:stretch;width:1px;min-width:1px;background:var(--ck-color-toolbar-border);margin-top:var(--ck-spacing-small);margin-bottom:var(--ck-spacing-small)}.ck.ck-toolbar .ck-toolbar__line-break{height:0}.ck.ck-toolbar>.ck-toolbar__items>:not(.ck-toolbar__line-break){margin-right:var(--ck-spacing-small)}.ck.ck-toolbar>.ck-toolbar__items:empty+.ck.ck-toolbar__separator{display:none}.ck.ck-toolbar>.ck-toolbar__items>:not(.ck-toolbar__line-break),.ck.ck-toolbar>.ck.ck-toolbar__grouped-dropdown{margin-top:var(--ck-spacing-small);margin-bottom:var(--ck-spacing-small)}.ck.ck-toolbar.ck-toolbar_vertical{padding:0}.ck.ck-toolbar.ck-toolbar_vertical>.ck-toolbar__items>.ck{width:100%;margin:0;border-radius:0;border:0}.ck.ck-toolbar.ck-toolbar_compact{padding:0}.ck.ck-toolbar.ck-toolbar_compact>.ck-toolbar__items>*{margin:0}.ck.ck-toolbar.ck-toolbar_compact>.ck-toolbar__items>:not(:first-child):not(:last-child){border-radius:0}.ck.ck-toolbar>.ck.ck-toolbar__grouped-dropdown>.ck.ck-button.ck-dropdown__button{padding-left:var(--ck-spacing-tiny)}.ck-toolbar-container .ck.ck-toolbar{border:0}.ck.ck-toolbar[dir=rtl]>.ck-toolbar__items>.ck,[dir=rtl] .ck.ck-toolbar>.ck-toolbar__items>.ck{margin-right:0}.ck.ck-toolbar[dir=rtl]:not(.ck-toolbar_compact)>.ck-toolbar__items>.ck,[dir=rtl] .ck.ck-toolbar:not(.ck-toolbar_compact)>.ck-toolbar__items>.ck{margin-left:var(--ck-spacing-small)}.ck.ck-toolbar[dir=rtl]>.ck-toolbar__items>.ck:last-child,[dir=rtl] .ck.ck-toolbar>.ck-toolbar__items>.ck:last-child{margin-left:0}.ck.ck-toolbar[dir=rtl].ck-toolbar_compact>.ck-toolbar__items>.ck:first-child,[dir=rtl] .ck.ck-toolbar.ck-toolbar_compact>.ck-toolbar__items>.ck:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.ck.ck-toolbar[dir=rtl].ck-toolbar_compact>.ck-toolbar__items>.ck:last-child,[dir=rtl] .ck.ck-toolbar.ck-toolbar_compact>.ck-toolbar__items>.ck:last-child{border-top-right-radius:0;border-bottom-right-radius:0}.ck.ck-toolbar[dir=rtl]>.ck.ck-toolbar__separator,[dir=rtl] .ck.ck-toolbar>.ck.ck-toolbar__separator{margin-left:var(--ck-spacing-small)}.ck.ck-toolbar[dir=rtl].ck-toolbar_grouping>.ck-toolbar__items:not(:empty):not(:only-child),[dir=rtl] .ck.ck-toolbar.ck-toolbar_grouping>.ck-toolbar__items:not(:empty):not(:only-child){margin-left:var(--ck-spacing-small)}.ck.ck-toolbar[dir=ltr]>.ck-toolbar__items>.ck:last-child,[dir=ltr] .ck.ck-toolbar>.ck-toolbar__items>.ck:last-child{margin-right:0}.ck.ck-toolbar[dir=ltr].ck-toolbar_compact>.ck-toolbar__items>.ck:first-child,[dir=ltr] .ck.ck-toolbar.ck-toolbar_compact>.ck-toolbar__items>.ck:first-child{border-top-right-radius:0;border-bottom-right-radius:0}.ck.ck-toolbar[dir=ltr].ck-toolbar_compact>.ck-toolbar__items>.ck:last-child,[dir=ltr] .ck.ck-toolbar.ck-toolbar_compact>.ck-toolbar__items>.ck:last-child{border-top-left-radius:0;border-bottom-left-radius:0}.ck.ck-toolbar[dir=ltr]>.ck.ck-toolbar__separator,[dir=ltr] .ck.ck-toolbar>.ck.ck-toolbar__separator{margin-right:var(--ck-spacing-small)}.ck.ck-toolbar[dir=ltr].ck-toolbar_grouping>.ck-toolbar__items:not(:empty):not(:only-child),[dir=ltr] .ck.ck-toolbar.ck-toolbar_grouping>.ck-toolbar__items:not(:empty):not(:only-child){margin-right:var(--ck-spacing-small)}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-ui/theme/components/toolbar/toolbar.css","webpack://./node_modules/@ckeditor/ckeditor5-ui/theme/mixins/_unselectable.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/ckeditor5-ui/components/toolbar/toolbar.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/mixins/_rounded.css"],"names":[],"mappings":"AAOA,eCEC,qBAAsB,CACtB,wBAAyB,CACzB,oBAAqB,CACrB,gBAAgB,CDFhB,YAAa,CACb,oBAAqB,CACrB,kBA6CD,CA3CC,kCACC,YAAa,CACb,kBAAmB,CACnB,kBAAmB,CACnB,WAED,CAEA,yCACC,oBAWD,CAJC,yGAEC,YACD,CAGD,uCACC,eACD,CAEA,sDACC,gBACD,CAEA,sDACC,qBACD,CAEA,sDACC,gBACD,CAGC,yFACC,YACD,CE/CF,eCGC,eD0FD,CA7FA,qECOE,qCDsFF,CA7FA,eAGC,6CAA8C,CAC9C,iCAAkC,CAClC,+CAwFD,CAtFC,yCACC,kBAAmB,CACnB,SAAU,CACV,aAAc,CACd,yCAA0C,CAM1C,kCAAmC,CACnC,qCACD,CAEA,uCACC,QACD,CAGC,gEAEC,oCACD,CAIA,kEACC,YACD,CAGD,gHAGC,kCAAmC,CACnC,qCACD,CAEA,mCAEC,SAgBD,CAbC,0DAEC,UAAW,CAGX,QAAS,CAGT,eAAgB,CAGhB,QACD,CAGD,kCAEC,SAWD,CATC,uDAEC,QAMD,CAHC,yFACC,eACD,CASD,kFACC,mCACD,CAvFF,qCA2FE,QAEF,CAYC,+FACC,cACD,CAEA,iJAEC,mCACD,CAEA,qHACC,aACD,CAIC,6JACC,wBAAyB,CACzB,2BACD,CAGA,2JACC,yBAA0B,CAC1B,4BACD,CAID,qGACC,mCACD,CAGA,yLACC,mCACD,CAWA,qHACC,cACD,CAIC,6JACC,yBAA0B,CAC1B,4BACD,CAGA,2JACC,wBAAyB,CACzB,2BACD,CAID,qGACC,oCACD,CAGA,yLACC,oCACD","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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 \"../../mixins/_unselectable.css\";\n\n.ck.ck-toolbar {\n\t@mixin ck-unselectable;\n\n\tdisplay: flex;\n\tflex-flow: row nowrap;\n\talign-items: center;\n\n\t& > .ck-toolbar__items {\n\t\tdisplay: flex;\n\t\tflex-flow: row wrap;\n\t\talign-items: center;\n\t\tflex-grow: 1;\n\n\t}\n\n\t& .ck.ck-toolbar__separator {\n\t\tdisplay: inline-block;\n\n\t\t/*\n\t\t * A leading or trailing separator makes no sense (separates from nothing on one side).\n\t\t * For instance, it can happen when toolbar items (also separators) are getting grouped one by one and\n\t\t * moved to another toolbar in the dropdown.\n\t\t */\n\t\t&:first-child,\n\t\t&:last-child {\n\t\t\tdisplay: none;\n\t\t}\n\t}\n\n\t& .ck-toolbar__line-break {\n\t\tflex-basis: 100%;\n\t}\n\n\t&.ck-toolbar_grouping > .ck-toolbar__items {\n\t\tflex-wrap: nowrap;\n\t}\n\n\t&.ck-toolbar_vertical > .ck-toolbar__items {\n\t\tflex-direction: column;\n\t}\n\n\t&.ck-toolbar_floating > .ck-toolbar__items {\n\t\tflex-wrap: nowrap;\n\t}\n\n\t& > .ck.ck-toolbar__grouped-dropdown {\n\t\t& > .ck-dropdown__button .ck-dropdown__arrow {\n\t\t\tdisplay: none;\n\t\t}\n\t}\n}\n","/*\n * Copyright (c) 2003-2022, 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/**\n * Makes element unselectable.\n */\n@define-mixin ck-unselectable {\n\t-moz-user-select: none;\n\t-webkit-user-select: none;\n\t-ms-user-select: none;\n\tuser-select: none\n}\n","/*\n * Copyright (c) 2003-2022, 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 \"../../../mixins/_rounded.css\";\n@import \"@ckeditor/ckeditor5-ui/theme/mixins/_dir.css\";\n\n.ck.ck-toolbar {\n\t@mixin ck-rounded-corners;\n\n\tbackground: var(--ck-color-toolbar-background);\n\tpadding: 0 var(--ck-spacing-small);\n\tborder: 1px solid var(--ck-color-toolbar-border);\n\n\t& .ck.ck-toolbar__separator {\n\t\talign-self: stretch;\n\t\twidth: 1px;\n\t\tmin-width: 1px;\n\t\tbackground: var(--ck-color-toolbar-border);\n\n\t\t/*\n\t\t * These margins make the separators look better in balloon toolbars (when aligned with the \"tip\").\n\t\t * See https://github.com/ckeditor/ckeditor5/issues/7493.\n\t\t */\n\t\tmargin-top: var(--ck-spacing-small);\n\t\tmargin-bottom: var(--ck-spacing-small);\n\t}\n\n\t& .ck-toolbar__line-break {\n\t\theight: 0;\n\t}\n\n\t& > .ck-toolbar__items {\n\t\t& > *:not(.ck-toolbar__line-break) {\n\t\t\t/* (#11) Separate toolbar items. */\n\t\t\tmargin-right: var(--ck-spacing-small);\n\t\t}\n\n\t\t/* Don't display a separator after an empty items container, for instance,\n\t\twhen all items were grouped */\n\t\t&:empty + .ck.ck-toolbar__separator {\n\t\t\tdisplay: none;\n\t\t}\n\t}\n\n\t& > .ck-toolbar__items > *:not(.ck-toolbar__line-break),\n\t& > .ck.ck-toolbar__grouped-dropdown {\n\t\t/* Make sure items wrapped to the next line have v-spacing */\n\t\tmargin-top: var(--ck-spacing-small);\n\t\tmargin-bottom: var(--ck-spacing-small);\n\t}\n\n\t&.ck-toolbar_vertical {\n\t\t/* Items in a vertical toolbar span the entire width. */\n\t\tpadding: 0;\n\n\t\t/* Specificity matters here. See https://github.com/ckeditor/ckeditor5-theme-lark/issues/168. */\n\t\t& > .ck-toolbar__items > .ck {\n\t\t\t/* Items in a vertical toolbar should span the horizontal space. */\n\t\t\twidth: 100%;\n\n\t\t\t/* Items in a vertical toolbar should have no margin. */\n\t\t\tmargin: 0;\n\n\t\t\t/* Items in a vertical toolbar span the entire width so rounded corners are pointless. */\n\t\t\tborder-radius: 0;\n\n\t\t\t/* Items in a vertical toolbar span the entire width so any border is pointless. */\n\t\t\tborder: 0;\n\t\t}\n\t}\n\n\t&.ck-toolbar_compact {\n\t\t/* No spacing around items. */\n\t\tpadding: 0;\n\n\t\t& > .ck-toolbar__items > * {\n\t\t\t/* Compact toolbar items have no spacing between them. */\n\t\t\tmargin: 0;\n\n\t\t\t/* \"Middle\" children should have no rounded corners. */\n\t\t\t&:not(:first-child):not(:last-child) {\n\t\t\t\tborder-radius: 0;\n\t\t\t}\n\t\t}\n\t}\n\n\t& > .ck.ck-toolbar__grouped-dropdown {\n\t\t/*\n\t\t * Dropdown button has asymmetric padding to fit the arrow.\n\t\t * This button has no arrow so let's revert that padding back to normal.\n\t\t */\n\t\t& > .ck.ck-button.ck-dropdown__button {\n\t\t\tpadding-left: var(--ck-spacing-tiny);\n\t\t}\n\t}\n\n\t@nest .ck-toolbar-container & {\n\t\tborder: 0;\n\t}\n}\n\n/* stylelint-disable */\n\n/*\n * Styles for RTL toolbars.\n *\n * Note: In some cases (e.g. a decoupled editor), the toolbar has its own \"dir\"\n * because its parent is not controlled by the editor framework.\n */\n[dir=\"rtl\"] .ck.ck-toolbar,\n.ck.ck-toolbar[dir=\"rtl\"] {\n\t& > .ck-toolbar__items > .ck {\n\t\tmargin-right: 0;\n\t}\n\n\t&:not(.ck-toolbar_compact) > .ck-toolbar__items > .ck {\n\t\t/* (#11) Separate toolbar items. */\n\t\tmargin-left: var(--ck-spacing-small);\n\t}\n\n\t& > .ck-toolbar__items > .ck:last-child {\n\t\tmargin-left: 0;\n\t}\n\n\t&.ck-toolbar_compact > .ck-toolbar__items > .ck {\n\t\t/* No rounded corners on the right side of the first child. */\n\t\t&:first-child {\n\t\t\tborder-top-left-radius: 0;\n\t\t\tborder-bottom-left-radius: 0;\n\t\t}\n\n\t\t/* No rounded corners on the left side of the last child. */\n\t\t&:last-child {\n\t\t\tborder-top-right-radius: 0;\n\t\t\tborder-bottom-right-radius: 0;\n\t\t}\n\t}\n\n\t/* Separate the the separator form the grouping dropdown when some items are grouped. */\n\t& > .ck.ck-toolbar__separator {\n\t\tmargin-left: var(--ck-spacing-small);\n\t}\n\n\t/* Some spacing between the items and the separator before the grouped items dropdown. */\n\t&.ck-toolbar_grouping > .ck-toolbar__items:not(:empty):not(:only-child) {\n\t\tmargin-left: var(--ck-spacing-small);\n\t}\n}\n\n/*\n * Styles for LTR toolbars.\n *\n * Note: In some cases (e.g. a decoupled editor), the toolbar has its own \"dir\"\n * because its parent is not controlled by the editor framework.\n */\n[dir=\"ltr\"] .ck.ck-toolbar,\n.ck.ck-toolbar[dir=\"ltr\"] {\n\t& > .ck-toolbar__items > .ck:last-child {\n\t\tmargin-right: 0;\n\t}\n\n\t&.ck-toolbar_compact > .ck-toolbar__items > .ck {\n\t\t/* No rounded corners on the right side of the first child. */\n\t\t&:first-child {\n\t\t\tborder-top-right-radius: 0;\n\t\t\tborder-bottom-right-radius: 0;\n\t\t}\n\n\t\t/* No rounded corners on the left side of the last child. */\n\t\t&:last-child {\n\t\t\tborder-top-left-radius: 0;\n\t\t\tborder-bottom-left-radius: 0;\n\t\t}\n\t}\n\n\t/* Separate the the separator form the grouping dropdown when some items are grouped. */\n\t& > .ck.ck-toolbar__separator {\n\t\tmargin-right: var(--ck-spacing-small);\n\t}\n\n\t/* Some spacing between the items and the separator before the grouped items dropdown. */\n\t&.ck-toolbar_grouping > .ck-toolbar__items:not(:empty):not(:only-child) {\n\t\tmargin-right: var(--ck-spacing-small);\n\t}\n}\n\n/* stylelint-enable */\n","/*\n * Copyright (c) 2003-2022, 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/**\n * Implements rounded corner interface for .ck-rounded-corners class.\n *\n * @see $ck-border-radius\n */\n@define-mixin ck-rounded-corners {\n\tborder-radius: 0;\n\n\t@nest .ck-rounded-corners &,\n\t&.ck-rounded-corners {\n\t\tborder-radius: var(--ck-border-radius);\n\t\t@mixin-content;\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/components/tooltip/tooltip.css":
/*!**********************************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/components/tooltip/tooltip.css ***!
\**********************************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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-tooltip,.ck.ck-tooltip .ck-tooltip__text:after{position:absolute;pointer-events:none;-webkit-backface-visibility:hidden}.ck.ck-tooltip{visibility:hidden;opacity:0;display:none;z-index:var(--ck-z-modal)}.ck.ck-tooltip .ck-tooltip__text{display:inline-block}.ck.ck-tooltip .ck-tooltip__text:after{content:\"\";width:0;height:0}:root{--ck-tooltip-arrow-size:5px}.ck.ck-tooltip{left:50%;top:0;transition:opacity .2s ease-in-out .2s}.ck.ck-tooltip .ck-tooltip__text{border-radius:0}.ck-rounded-corners .ck.ck-tooltip .ck-tooltip__text,.ck.ck-tooltip .ck-tooltip__text.ck-rounded-corners{border-radius:var(--ck-border-radius)}.ck.ck-tooltip .ck-tooltip__text{font-size:.9em;line-height:1.5;color:var(--ck-color-tooltip-text);padding:var(--ck-spacing-small) var(--ck-spacing-medium);background:var(--ck-color-tooltip-background);position:relative;left:-50%}.ck.ck-tooltip .ck-tooltip__text:after{transition:opacity .2s ease-in-out .2s;border-style:solid;left:50%}.ck.ck-tooltip.ck-tooltip_s,.ck.ck-tooltip.ck-tooltip_se,.ck.ck-tooltip.ck-tooltip_sw{bottom:calc(var(--ck-tooltip-arrow-size)*-1);transform:translateY(100%)}.ck.ck-tooltip.ck-tooltip_s .ck-tooltip__text:after,.ck.ck-tooltip.ck-tooltip_se .ck-tooltip__text:after,.ck.ck-tooltip.ck-tooltip_sw .ck-tooltip__text:after{top:calc(var(--ck-tooltip-arrow-size)*-1 + 1px);transform:translateX(-50%);border-left-color:transparent;border-bottom-color:var(--ck-color-tooltip-background);border-right-color:transparent;border-top-color:transparent;border-left-width:var(--ck-tooltip-arrow-size);border-bottom-width:var(--ck-tooltip-arrow-size);border-right-width:var(--ck-tooltip-arrow-size);border-top-width:0}.ck.ck-tooltip.ck-tooltip_sw{right:50%;left:auto}.ck.ck-tooltip.ck-tooltip_sw .ck-tooltip__text{left:auto;right:calc(var(--ck-tooltip-arrow-size)*-2)}.ck.ck-tooltip.ck-tooltip_sw .ck-tooltip__text:after{left:auto;right:0}.ck.ck-tooltip.ck-tooltip_se{left:50%;right:auto}.ck.ck-tooltip.ck-tooltip_se .ck-tooltip__text{right:auto;left:calc(var(--ck-tooltip-arrow-size)*-2)}.ck.ck-tooltip.ck-tooltip_se .ck-tooltip__text:after{right:auto;left:0;transform:translateX(50%)}.ck.ck-tooltip.ck-tooltip_n{top:calc(var(--ck-tooltip-arrow-size)*-1);transform:translateY(-100%)}.ck.ck-tooltip.ck-tooltip_n .ck-tooltip__text:after{bottom:calc(var(--ck-tooltip-arrow-size)*-1);transform:translateX(-50%);border-left-color:transparent;border-bottom-color:transparent;border-right-color:transparent;border-top-color:var(--ck-color-tooltip-background);border-left-width:var(--ck-tooltip-arrow-size);border-bottom-width:0;border-right-width:var(--ck-tooltip-arrow-size);border-top-width:var(--ck-tooltip-arrow-size)}.ck.ck-tooltip.ck-tooltip_e{left:calc(100% + var(--ck-tooltip-arrow-size));top:50%}.ck.ck-tooltip.ck-tooltip_e .ck-tooltip__text{left:0;transform:translateY(-50%)}.ck.ck-tooltip.ck-tooltip_e .ck-tooltip__text:after{left:calc(var(--ck-tooltip-arrow-size)*-1);top:calc(50% - var(--ck-tooltip-arrow-size)*1);border-left-color:transparent;border-bottom-color:transparent;border-right-color:var(--ck-color-tooltip-background);border-top-color:transparent;border-left-width:0;border-bottom-width:var(--ck-tooltip-arrow-size);border-right-width:var(--ck-tooltip-arrow-size);border-top-width:var(--ck-tooltip-arrow-size)}.ck.ck-tooltip.ck-tooltip_w{right:calc(100% + var(--ck-tooltip-arrow-size));left:auto;top:50%}.ck.ck-tooltip.ck-tooltip_w .ck-tooltip__text{left:0;transform:translateY(-50%)}.ck.ck-tooltip.ck-tooltip_w .ck-tooltip__text:after{left:100%;top:calc(50% - var(--ck-tooltip-arrow-size)*1);border-left-color:var(--ck-color-tooltip-background);border-bottom-color:transparent;border-right-color:transparent;border-top-color:transparent;border-left-width:var(--ck-tooltip-arrow-size);border-bottom-width:var(--ck-tooltip-arrow-size);border-right-width:0;border-top-width:var(--ck-tooltip-arrow-size)}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-ui/theme/components/tooltip/tooltip.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/ckeditor5-ui/components/tooltip/tooltip.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/mixins/_rounded.css"],"names":[],"mappings":"AAKA,sDAEC,iBAAkB,CAGlB,mBAAoB,CAIpB,kCACD,CAEA,eAEC,iBAAkB,CAClB,SAAU,CACV,YAAa,CACb,yBAWD,CATC,iCACC,oBAOD,CALC,uCACC,UAAW,CACX,OAAQ,CACR,QACD,CCxBF,MACC,2BACD,CAEA,eACC,QAAS,CAMT,KAAM,CAON,sCAwKD,CAtKC,iCChBA,eDqCA,CArBA,yGCZC,qCDiCD,CArBA,iCAGC,cAAe,CACf,eAAgB,CAChB,kCAAmC,CACnC,wDAAyD,CACzD,6CAA8C,CAC9C,iBAAkB,CAClB,SAYD,CAVC,uCAMC,sCAAuC,CACvC,kBAAmB,CACnB,QACD,CAYD,sFAGC,4CAA+C,CAC/C,0BASD,CAPC,8JAEC,+CAAkD,CAClD,0BAA6B,CAC7B,6BAAoF,CAApF,sDAAoF,CAApF,8BAAoF,CAApF,4BAAoF,CACpF,8CAAsG,CAAtG,gDAAsG,CAAtG,+CAAsG,CAAtG,kBACD,CAaD,6BACC,SAAU,CACV,SAWD,CATC,+CACC,SAAU,CACV,2CACD,CAEA,qDACC,SAAU,CACV,OACD,CAYD,6BACC,QAAS,CACT,UAYD,CAVC,+CACC,UAAW,CACX,0CACD,CAEA,qDACC,UAAW,CACX,MAAO,CACP,yBACD,CAYD,4BACC,yCAA4C,CAC5C,2BAQD,CANC,oDACC,4CAA+C,CAC/C,0BAA6B,CAC7B,6BAAoF,CAApF,+BAAoF,CAApF,8BAAoF,CAApF,mDAAoF,CACpF,8CAAsG,CAAtG,qBAAsG,CAAtG,+CAAsG,CAAtG,6CACD,CAUD,4BACC,8CAA+C,CAC/C,OAaD,CAXC,8CACC,MAAO,CACP,0BAQD,CANC,oDACC,0CAA6C,CAC7C,8CAAiD,CACjD,6BAAoF,CAApF,+BAAoF,CAApF,qDAAoF,CAApF,4BAAoF,CACpF,mBAAsG,CAAtG,gDAAsG,CAAtG,+CAAsG,CAAtG,6CACD,CAWF,4BACC,+CAAgD,CAChD,SAAU,CACV,OAaD,CAXC,8CACC,MAAO,CACP,0BAQD,CANC,oDACC,SAAU,CACV,8CAAiD,CACjD,oDAAoF,CAApF,+BAAoF,CAApF,8BAAoF,CAApF,4BAAoF,CACpF,8CAAsG,CAAtG,gDAAsG,CAAtG,oBAAsG,CAAtG,6CACD","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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-tooltip,\n.ck.ck-tooltip .ck-tooltip__text::after {\n\tposition: absolute;\n\n\t/* Without this, hovering the tooltip could keep it visible. */\n\tpointer-events: none;\n\n\t/* This is to get rid of flickering when transitioning opacity in Chrome.\n\tIt's weird but it works. */\n\t-webkit-backface-visibility: hidden;\n}\n\n.ck.ck-tooltip {\n\t/* Tooltip is hidden by default. */\n\tvisibility: hidden;\n\topacity: 0;\n\tdisplay: none;\n\tz-index: var(--ck-z-modal);\n\n\t& .ck-tooltip__text {\n\t\tdisplay: inline-block;\n\n\t\t&::after {\n\t\t\tcontent: \"\";\n\t\t\twidth: 0;\n\t\t\theight: 0;\n\t\t}\n\t}\n}\n","/*\n * Copyright (c) 2003-2022, 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 \"../../../mixins/_rounded.css\";\n\n:root {\n\t--ck-tooltip-arrow-size: 5px;\n}\n\n.ck.ck-tooltip {\n\tleft: 50%;\n\n\t/*\n\t * Prevent blurry tooltips in LoDPI environments.\n\t * See https://github.com/ckeditor/ckeditor5/issues/1802.\n\t */\n\ttop: 0;\n\n\t/*\n\t * For the transition to work, the tooltip must be controlled\n\t * using visibility+opacity. A delay prevents a \"tooltip avalanche\"\n\t * i.e. when scanning the toolbar with mouse cursor.\n\t */\n\ttransition: opacity .2s ease-in-out .2s;\n\n\t& .ck-tooltip__text {\n\t\t@mixin ck-rounded-corners;\n\n\t\tfont-size: .9em;\n\t\tline-height: 1.5;\n\t\tcolor: var(--ck-color-tooltip-text);\n\t\tpadding: var(--ck-spacing-small) var(--ck-spacing-medium);\n\t\tbackground: var(--ck-color-tooltip-background);\n\t\tposition: relative;\n\t\tleft: -50%;\n\n\t\t&::after {\n\t\t\t/*\n\t\t\t * For the transition to work, the tooltip must be controlled\n\t\t\t * using visibility+opacity. A delay prevents a \"tooltip avalanche\"\n\t\t\t * i.e. when scanning the toolbar with mouse cursor.\n\t\t\t */\n\t\t\ttransition: opacity .2s ease-in-out .2s;\n\t\t\tborder-style: solid;\n\t\t\tleft: 50%;\n\t\t}\n\t}\n\n\t/**\n\t * A class that displays the tooltip south of the element.\n\t *\n\t * [element]\n\t * ^\n\t * +-----------+\n\t * | Tooltip |\n\t * +-----------+\n\t */\n\t&.ck-tooltip_s,\n\t&.ck-tooltip_sw,\n\t&.ck-tooltip_se {\n\t\tbottom: calc(-1 * var(--ck-tooltip-arrow-size));\n\t\ttransform: translateY( 100% );\n\n\t\t& .ck-tooltip__text::after {\n\t\t\t/* 1px addresses gliches in rendering causing gap between the triangle and the text */\n\t\t\ttop: calc(-1 * var(--ck-tooltip-arrow-size) + 1px);\n\t\t\ttransform: translateX( -50% );\n\t\t\tborder-color: transparent transparent var(--ck-color-tooltip-background) transparent;\n\t\t\tborder-width: 0 var(--ck-tooltip-arrow-size) var(--ck-tooltip-arrow-size) var(--ck-tooltip-arrow-size);\n\t\t}\n\t}\n\n\t/**\n\t * A class that displays the tooltip south-west of the element.\n\t *\n\t * [element]\n\t * ^\n\t * +-----------+\n\t * | Tooltip |\n\t * +-----------+\n\t */\n\n\t&.ck-tooltip_sw {\n\t\tright: 50%;\n\t\tleft: auto;\n\n\t\t& .ck-tooltip__text {\n\t\t\tleft: auto;\n\t\t\tright: calc( -2 * var(--ck-tooltip-arrow-size));\n\t\t}\n\n\t\t& .ck-tooltip__text::after {\n\t\t\tleft: auto;\n\t\t\tright: 0;\n\t\t}\n\t}\n\n\t/**\n\t * A class that displays the tooltip south-east of the element.\n\t *\n\t * [element]\n\t * ^\n\t * +-----------+\n\t * | Tooltip |\n\t * +-----------+\n\t */\n\t&.ck-tooltip_se {\n\t\tleft: 50%;\n\t\tright: auto;\n\n\t\t& .ck-tooltip__text {\n\t\t\tright: auto;\n\t\t\tleft: calc( -2 * var(--ck-tooltip-arrow-size));\n\t\t}\n\n\t\t& .ck-tooltip__text::after {\n\t\t\tright: auto;\n\t\t\tleft: 0;\n\t\t\ttransform: translateX( 50% );\n\t\t}\n\t}\n\n\t/**\n\t * A class that displays the tooltip north of the element.\n\t *\n\t * +-----------+\n\t * | Tooltip |\n\t * +-----------+\n\t * V\n\t * [element]\n\t */\n\t&.ck-tooltip_n {\n\t\ttop: calc(-1 * var(--ck-tooltip-arrow-size));\n\t\ttransform: translateY( -100% );\n\n\t\t& .ck-tooltip__text::after {\n\t\t\tbottom: calc(-1 * var(--ck-tooltip-arrow-size));\n\t\t\ttransform: translateX( -50% );\n\t\t\tborder-color: var(--ck-color-tooltip-background) transparent transparent transparent;\n\t\t\tborder-width: var(--ck-tooltip-arrow-size) var(--ck-tooltip-arrow-size) 0 var(--ck-tooltip-arrow-size);\n\t\t}\n\t}\n\n\t/**\n\t * A class that displays the tooltip east of the element.\n\t *\n\t * +----------+\n\t * [element] < | east |\n\t * +----------+\n\t */\n\t&.ck-tooltip_e {\n\t\tleft: calc(100% + var(--ck-tooltip-arrow-size));\n\t\ttop: 50%;\n\n\t\t& .ck-tooltip__text {\n\t\t\tleft: 0;\n\t\t\ttransform: translateY( -50% );\n\n\t\t\t&::after {\n\t\t\t\tleft: calc(-1 * var(--ck-tooltip-arrow-size));\n\t\t\t\ttop: calc(50% - 1 * var(--ck-tooltip-arrow-size));\n\t\t\t\tborder-color: transparent var(--ck-color-tooltip-background) transparent transparent;\n\t\t\t\tborder-width: var(--ck-tooltip-arrow-size) var(--ck-tooltip-arrow-size) var(--ck-tooltip-arrow-size) 0;\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * A class that displays the tooltip west of the element.\n\t *\n\t * +----------+\n\t * | west | > [element]\n\t * +----------+\n\t */\n\t&.ck-tooltip_w {\n\t\tright: calc(100% + var(--ck-tooltip-arrow-size));\n\t\tleft: auto;\n\t\ttop: 50%;\n\n\t\t& .ck-tooltip__text {\n\t\t\tleft: 0;\n\t\t\ttransform: translateY( -50% );\n\n\t\t\t&::after {\n\t\t\t\tleft: 100%;\n\t\t\t\ttop: calc(50% - 1 * var(--ck-tooltip-arrow-size));\n\t\t\t\tborder-color: transparent transparent transparent var(--ck-color-tooltip-background);\n\t\t\t\tborder-width: var(--ck-tooltip-arrow-size) 0 var(--ck-tooltip-arrow-size) var(--ck-tooltip-arrow-size);\n\t\t\t}\n\t\t}\n\t}\n}\n","/*\n * Copyright (c) 2003-2022, 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/**\n * Implements rounded corner interface for .ck-rounded-corners class.\n *\n * @see $ck-border-radius\n */\n@define-mixin ck-rounded-corners {\n\tborder-radius: 0;\n\n\t@nest .ck-rounded-corners &,\n\t&.ck-rounded-corners {\n\t\tborder-radius: var(--ck-border-radius);\n\t\t@mixin-content;\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/globals/globals.css":
/*!***********************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/globals/globals.css ***!
\***********************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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-hidden{display:none!important}.ck.ck-reset,.ck.ck-reset_all,.ck.ck-reset_all *{box-sizing:border-box;width:auto;height:auto;position:static}:root{--ck-z-default:1;--ck-z-modal:calc(var(--ck-z-default) + 999)}.ck-transitions-disabled,.ck-transitions-disabled *{transition:none!important}:root{--ck-color-base-foreground:#fafafa;--ck-color-base-background:#fff;--ck-color-base-border:#c4c4c4;--ck-color-base-action:#61b045;--ck-color-base-focus:#6cb5f9;--ck-color-base-text:#333;--ck-color-base-active:#198cf0;--ck-color-base-active-focus:#0e7fe1;--ck-color-base-error:#db3700;--ck-color-focus-border-coordinates:208,79%,51%;--ck-color-focus-border:hsl(var(--ck-color-focus-border-coordinates));--ck-color-focus-outer-shadow:#bcdefb;--ck-color-focus-disabled-shadow:rgba(119,186,248,0.3);--ck-color-focus-error-shadow:rgba(255,64,31,0.3);--ck-color-text:var(--ck-color-base-text);--ck-color-shadow-drop:rgba(0,0,0,0.15);--ck-color-shadow-drop-active:rgba(0,0,0,0.2);--ck-color-shadow-inner:rgba(0,0,0,0.1);--ck-color-button-default-background:transparent;--ck-color-button-default-hover-background:#e6e6e6;--ck-color-button-default-active-background:#d9d9d9;--ck-color-button-default-active-shadow:#bfbfbf;--ck-color-button-default-disabled-background:transparent;--ck-color-button-on-background:#dedede;--ck-color-button-on-hover-background:#c4c4c4;--ck-color-button-on-active-background:#bababa;--ck-color-button-on-active-shadow:#a1a1a1;--ck-color-button-on-disabled-background:#dedede;--ck-color-button-action-background:var(--ck-color-base-action);--ck-color-button-action-hover-background:#579e3d;--ck-color-button-action-active-background:#53973b;--ck-color-button-action-active-shadow:#498433;--ck-color-button-action-disabled-background:#7ec365;--ck-color-button-action-text:var(--ck-color-base-background);--ck-color-button-save:#008a00;--ck-color-button-cancel:#db3700;--ck-color-switch-button-off-background:#b0b0b0;--ck-color-switch-button-off-hover-background:#a3a3a3;--ck-color-switch-button-on-background:var(--ck-color-button-action-background);--ck-color-switch-button-on-hover-background:#579e3d;--ck-color-switch-button-inner-background:var(--ck-color-base-background);--ck-color-switch-button-inner-shadow:rgba(0,0,0,0.1);--ck-color-dropdown-panel-background:var(--ck-color-base-background);--ck-color-dropdown-panel-border:var(--ck-color-base-border);--ck-color-input-background:var(--ck-color-base-background);--ck-color-input-border:#c7c7c7;--ck-color-input-error-border:var(--ck-color-base-error);--ck-color-input-text:var(--ck-color-base-text);--ck-color-input-disabled-background:#f2f2f2;--ck-color-input-disabled-border:#c7c7c7;--ck-color-input-disabled-text:#757575;--ck-color-list-background:var(--ck-color-base-background);--ck-color-list-button-hover-background:var(--ck-color-button-default-hover-background);--ck-color-list-button-on-background:var(--ck-color-base-active);--ck-color-list-button-on-background-focus:var(--ck-color-base-active-focus);--ck-color-list-button-on-text:var(--ck-color-base-background);--ck-color-panel-background:var(--ck-color-base-background);--ck-color-panel-border:var(--ck-color-base-border);--ck-color-toolbar-background:var(--ck-color-base-foreground);--ck-color-toolbar-border:var(--ck-color-base-border);--ck-color-tooltip-background:var(--ck-color-base-text);--ck-color-tooltip-text:var(--ck-color-base-background);--ck-color-engine-placeholder-text:#707070;--ck-color-upload-bar-background:#6cb5f9;--ck-color-link-default:#0000f0;--ck-color-link-selected-background:rgba(31,177,255,0.1);--ck-color-link-fake-selection:rgba(31,177,255,0.3);--ck-disabled-opacity:.5;--ck-focus-outer-shadow-geometry:0 0 0 3px;--ck-focus-outer-shadow:var(--ck-focus-outer-shadow-geometry) var(--ck-color-focus-outer-shadow);--ck-focus-disabled-outer-shadow:var(--ck-focus-outer-shadow-geometry) var(--ck-color-focus-disabled-shadow);--ck-focus-error-outer-shadow:var(--ck-focus-outer-shadow-geometry) var(--ck-color-focus-error-shadow);--ck-focus-ring:1px solid var(--ck-color-focus-border);--ck-font-size-base:13px;--ck-line-height-base:1.84615;--ck-font-face:Helvetica,Arial,Tahoma,Verdana,Sans-Serif;--ck-font-size-tiny:0.7em;--ck-font-size-small:0.75em;--ck-font-size-normal:1em;--ck-font-size-big:1.4em;--ck-font-size-large:1.8em;--ck-ui-component-min-height:2.3em}.ck.ck-reset,.ck.ck-reset_all,.ck.ck-reset_all *{margin:0;padding:0;border:0;background:transparent;text-decoration:none;vertical-align:middle;transition:none;word-wrap:break-word}.ck.ck-reset_all,.ck.ck-reset_all *{border-collapse:collapse;font:normal normal normal var(--ck-font-size-base)/var(--ck-line-height-base) var(--ck-font-face);color:var(--ck-color-text);text-align:left;white-space:nowrap;cursor:auto;float:none}.ck.ck-reset_all .ck-rtl *{text-align:right}.ck.ck-reset_all iframe{vertical-align:inherit}.ck.ck-reset_all textarea{white-space:pre-wrap}.ck.ck-reset_all input[type=password],.ck.ck-reset_all input[type=text],.ck.ck-reset_all textarea{cursor:text}.ck.ck-reset_all input[type=password][disabled],.ck.ck-reset_all input[type=text][disabled],.ck.ck-reset_all textarea[disabled]{cursor:default}.ck.ck-reset_all fieldset{padding:10px;border:2px groove #dfdee3}.ck.ck-reset_all button::-moz-focus-inner{padding:0;border:0}.ck[dir=rtl],.ck[dir=rtl] .ck{text-align:right}:root{--ck-border-radius:2px;--ck-inner-shadow:2px 2px 3px var(--ck-color-shadow-inner) inset;--ck-drop-shadow:0 1px 2px 1px var(--ck-color-shadow-drop);--ck-drop-shadow-active:0 3px 6px 1px var(--ck-color-shadow-drop-active);--ck-spacing-unit:0.6em;--ck-spacing-large:calc(var(--ck-spacing-unit)*1.5);--ck-spacing-standard:var(--ck-spacing-unit);--ck-spacing-medium:calc(var(--ck-spacing-unit)*0.8);--ck-spacing-small:calc(var(--ck-spacing-unit)*0.5);--ck-spacing-tiny:calc(var(--ck-spacing-unit)*0.3);--ck-spacing-extra-tiny:calc(var(--ck-spacing-unit)*0.16)}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-ui/theme/globals/_hidden.css","webpack://./node_modules/@ckeditor/ckeditor5-ui/theme/globals/_reset.css","webpack://./node_modules/@ckeditor/ckeditor5-ui/theme/globals/_zindex.css","webpack://./node_modules/@ckeditor/ckeditor5-ui/theme/globals/_transition.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/ckeditor5-ui/globals/_colors.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/ckeditor5-ui/globals/_disabled.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/ckeditor5-ui/globals/_focus.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/ckeditor5-ui/globals/_fonts.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/ckeditor5-ui/globals/_reset.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/ckeditor5-ui/globals/_rounded.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/ckeditor5-ui/globals/_shadow.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/ckeditor5-ui/globals/_spacing.css"],"names":[],"mappings":"AAQA,WAGC,sBACD,CCPA,iDAGC,qBAAsB,CACtB,UAAW,CACX,WAAY,CACZ,eACD,CCPA,MACC,gBAAiB,CACjB,4CACD,CCAA,oDAEC,yBACD,CCNA,MACC,kCAAmD,CACnD,+BAAoD,CACpD,8BAAgD,CAChD,8BAAmD,CACnD,6BAAmD,CACnD,yBAA+C,CAC/C,8BAAmD,CACnD,oCAAuD,CACvD,6BAAkD,CAIlD,+CAAwD,CACxD,qEAA+E,CAC/E,qCAAwD,CACxD,sDAA8D,CAC9D,iDAAyD,CACzD,yCAAqD,CACrD,uCAAsD,CACtD,6CAA0D,CAC1D,uCAAsD,CAItD,gDAAuD,CACvD,kDAA+D,CAC/D,mDAAgE,CAChE,+CAA6D,CAC7D,yDAA8D,CAE9D,uCAAuD,CACvD,6CAA4D,CAC5D,8CAA4D,CAC5D,0CAAyD,CACzD,gDAA8D,CAE9D,+DAAsE,CACtE,iDAAkE,CAClE,kDAAkE,CAClE,8CAA+D,CAC/D,oDAAoE,CACpE,6DAAsE,CAEtE,8BAAoD,CACpD,gCAAqD,CAErD,+CAA4D,CAC5D,qDAAiE,CACjE,+EAAqF,CACrF,oDAAmE,CACnE,yEAA8E,CAC9E,qDAAgE,CAIhE,oEAA2E,CAC3E,4DAAoE,CAIpE,2DAAoE,CACpE,+BAAiD,CACjD,wDAAgE,CAChE,+CAA0D,CAC1D,4CAA2D,CAC3D,wCAAwD,CACxD,sCAAsD,CAItD,0DAAmE,CACnE,uFAA6F,CAC7F,gEAAuE,CACvE,4EAAiF,CACjF,8DAAsE,CAItE,2DAAoE,CACpE,mDAA6D,CAI7D,6DAAsE,CACtE,qDAA+D,CAI/D,uDAAgE,CAChE,uDAAiE,CAIjE,0CAAyD,CAIzD,wCAA2D,CAI3D,+BAAoD,CACpD,wDAAmE,CACnE,mDAAgE,CCpGhE,wBAAyB,CCAzB,0CAA2C,CAK3C,gGAAiG,CAKjG,4GAA6G,CAK7G,sGAAuG,CAKvG,sDAAuD,CCvBvD,wBAAyB,CACzB,6BAA8B,CAC9B,wDAA6D,CAE7D,yBAA0B,CAC1B,2BAA4B,CAC5B,yBAA0B,CAC1B,wBAAyB,CACzB,0BAA2B,CCJ3B,kCJoGD,CI9FA,iDAIC,QAAS,CACT,SAAU,CACV,QAAS,CACT,sBAAuB,CACvB,oBAAqB,CACrB,qBAAsB,CACtB,eAAgB,CAGhB,oBACD,CAKA,oCAGC,wBAAyB,CACzB,iGAAkG,CAClG,0BAA2B,CAC3B,eAAgB,CAChB,kBAAmB,CACnB,WAAY,CACZ,UACD,CAGC,2BACC,gBACD,CAEA,wBAEC,sBACD,CAEA,0BACC,oBACD,CAEA,kGAGC,WACD,CAEA,gIAGC,cACD,CAEA,0BACC,YAAa,CACb,yBACD,CAEA,0CAEC,SAAU,CACV,QACD,CAMD,8BAEC,gBACD,CCnFA,MACC,sBAAuB,CCAvB,gEAAiE,CAKjE,0DAA2D,CAK3D,wEAAyE,CCbzE,uBAA8B,CAC9B,mDAA2D,CAC3D,4CAAkD,CAClD,oDAA4D,CAC5D,mDAA2D,CAC3D,kDAA2D,CAC3D,yDFFD","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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/**\n * A class which hides an element in DOM.\n */\n.ck-hidden {\n\t/* Override selector specificity. Otherwise, all elements with some display\n\tstyle defined will override this one, which is not a desired result. */\n\tdisplay: none !important;\n}\n","/*\n * Copyright (c) 2003-2022, 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-reset,\n.ck.ck-reset_all,\n.ck.ck-reset_all * {\n\tbox-sizing: border-box;\n\twidth: auto;\n\theight: auto;\n\tposition: static;\n}\n","/*\n * Copyright (c) 2003-2022, 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-z-default: 1;\n\t--ck-z-modal: calc( var(--ck-z-default) + 999 );\n}\n","/*\n * Copyright (c) 2003-2022, 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/**\n * A class that disables all transitions of the element and its children.\n */\n.ck-transitions-disabled,\n.ck-transitions-disabled * {\n\ttransition: none !important;\n}\n","/*\n * Copyright (c) 2003-2022, 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-base-foreground: \t\t\t\t\t\t\t\thsl(0, 0%, 98%);\n\t--ck-color-base-background: \t\t\t\t\t\t\t\thsl(0, 0%, 100%);\n\t--ck-color-base-border: \t\t\t\t\t\t\t\t\thsl(0, 0%, 77%);\n\t--ck-color-base-action: \t\t\t\t\t\t\t\t\thsl(104, 44%, 48%);\n\t--ck-color-base-focus: \t\t\t\t\t\t\t\t\t\thsl(209, 92%, 70%);\n\t--ck-color-base-text: \t\t\t\t\t\t\t\t\t\thsl(0, 0%, 20%);\n\t--ck-color-base-active: \t\t\t\t\t\t\t\t\thsl(208, 88%, 52%);\n\t--ck-color-base-active-focus:\t\t\t\t\t\t\t\thsl(208, 88%, 47%);\n\t--ck-color-base-error:\t\t\t\t\t\t\t\t\t\thsl(15, 100%, 43%);\n\n\t/* -- Generic colors ------------------------------------------------------------------------ */\n\n\t--ck-color-focus-border-coordinates: \t\t\t\t\t\t208, 79%, 51%;\n\t--ck-color-focus-border: \t\t\t\t\t\t\t\t\thsl(var(--ck-color-focus-border-coordinates));\n\t--ck-color-focus-outer-shadow:\t\t\t\t\t\t\t\thsl(207, 89%, 86%);\n\t--ck-color-focus-disabled-shadow:\t\t\t\t\t\t\thsla(209, 90%, 72%,.3);\n\t--ck-color-focus-error-shadow:\t\t\t\t\t\t\t\thsla(9,100%,56%,.3);\n\t--ck-color-text: \t\t\t\t\t\t\t\t\t\t\tvar(--ck-color-base-text);\n\t--ck-color-shadow-drop: \t\t\t\t\t\t\t\t\thsla(0, 0%, 0%, 0.15);\n\t--ck-color-shadow-drop-active:\t\t\t\t\t\t\t\thsla(0, 0%, 0%, 0.2);\n\t--ck-color-shadow-inner: \t\t\t\t\t\t\t\t\thsla(0, 0%, 0%, 0.1);\n\n\t/* -- Buttons ------------------------------------------------------------------------------- */\n\n\t--ck-color-button-default-background: \t\t\t\t\t\ttransparent;\n\t--ck-color-button-default-hover-background: \t\t\t\thsl(0, 0%, 90%);\n\t--ck-color-button-default-active-background: \t\t\t\thsl(0, 0%, 85%);\n\t--ck-color-button-default-active-shadow: \t\t\t\t\thsl(0, 0%, 75%);\n\t--ck-color-button-default-disabled-background: \t\t\t\ttransparent;\n\n\t--ck-color-button-on-background: \t\t\t\t\t\t\thsl(0, 0%, 87%);\n\t--ck-color-button-on-hover-background: \t\t\t\t\t\thsl(0, 0%, 77%);\n\t--ck-color-button-on-active-background: \t\t\t\t\thsl(0, 0%, 73%);\n\t--ck-color-button-on-active-shadow: \t\t\t\t\t\thsl(0, 0%, 63%);\n\t--ck-color-button-on-disabled-background: \t\t\t\t\thsl(0, 0%, 87%);\n\n\t--ck-color-button-action-background: \t\t\t\t\t\tvar(--ck-color-base-action);\n\t--ck-color-button-action-hover-background: \t\t\t\t\thsl(104, 44%, 43%);\n\t--ck-color-button-action-active-background: \t\t\t\thsl(104, 44%, 41%);\n\t--ck-color-button-action-active-shadow: \t\t\t\t\thsl(104, 44%, 36%);\n\t--ck-color-button-action-disabled-background: \t\t\t\thsl(104, 44%, 58%);\n\t--ck-color-button-action-text: \t\t\t\t\t\t\t\tvar(--ck-color-base-background);\n\n\t--ck-color-button-save: \t\t\t\t\t\t\t\t\thsl(120, 100%, 27%);\n\t--ck-color-button-cancel: \t\t\t\t\t\t\t\t\thsl(15, 100%, 43%);\n\n\t--ck-color-switch-button-off-background:\t\t\t\t\thsl(0, 0%, 69%);\n\t--ck-color-switch-button-off-hover-background:\t\t\t\thsl(0, 0%, 64%);\n\t--ck-color-switch-button-on-background:\t\t\t\t\t\tvar(--ck-color-button-action-background);\n\t--ck-color-switch-button-on-hover-background:\t\t\t\thsl(104, 44%, 43%);\n\t--ck-color-switch-button-inner-background:\t\t\t\t\tvar(--ck-color-base-background);\n\t--ck-color-switch-button-inner-shadow:\t\t\t\t\t\thsla(0, 0%, 0%, 0.1);\n\n\t/* -- Dropdown ------------------------------------------------------------------------------ */\n\n\t--ck-color-dropdown-panel-background: \t\t\t\t\t\tvar(--ck-color-base-background);\n\t--ck-color-dropdown-panel-border: \t\t\t\t\t\t\tvar(--ck-color-base-border);\n\n\t/* -- Input --------------------------------------------------------------------------------- */\n\n\t--ck-color-input-background: \t\t\t\t\t\t\t\tvar(--ck-color-base-background);\n\t--ck-color-input-border: \t\t\t\t\t\t\t\t\thsl(0, 0%, 78%);\n\t--ck-color-input-error-border:\t\t\t\t\t\t\t\tvar(--ck-color-base-error);\n\t--ck-color-input-text: \t\t\t\t\t\t\t\t\t\tvar(--ck-color-base-text);\n\t--ck-color-input-disabled-background: \t\t\t\t\t\thsl(0, 0%, 95%);\n\t--ck-color-input-disabled-border: \t\t\t\t\t\t\thsl(0, 0%, 78%);\n\t--ck-color-input-disabled-text: \t\t\t\t\t\t\thsl(0, 0%, 46%);\n\n\t/* -- List ---------------------------------------------------------------------------------- */\n\n\t--ck-color-list-background: \t\t\t\t\t\t\t\tvar(--ck-color-base-background);\n\t--ck-color-list-button-hover-background: \t\t\t\t\tvar(--ck-color-button-default-hover-background);\n\t--ck-color-list-button-on-background: \t\t\t\t\t\tvar(--ck-color-base-active);\n\t--ck-color-list-button-on-background-focus: \t\t\t\tvar(--ck-color-base-active-focus);\n\t--ck-color-list-button-on-text:\t\t\t\t\t\t\t\tvar(--ck-color-base-background);\n\n\t/* -- Panel --------------------------------------------------------------------------------- */\n\n\t--ck-color-panel-background: \t\t\t\t\t\t\t\tvar(--ck-color-base-background);\n\t--ck-color-panel-border: \t\t\t\t\t\t\t\t\tvar(--ck-color-base-border);\n\n\t/* -- Toolbar ------------------------------------------------------------------------------- */\n\n\t--ck-color-toolbar-background: \t\t\t\t\t\t\t\tvar(--ck-color-base-foreground);\n\t--ck-color-toolbar-border: \t\t\t\t\t\t\t\t\tvar(--ck-color-base-border);\n\n\t/* -- Tooltip ------------------------------------------------------------------------------- */\n\n\t--ck-color-tooltip-background: \t\t\t\t\t\t\t\tvar(--ck-color-base-text);\n\t--ck-color-tooltip-text: \t\t\t\t\t\t\t\t\tvar(--ck-color-base-background);\n\n\t/* -- Engine -------------------------------------------------------------------------------- */\n\n\t--ck-color-engine-placeholder-text: \t\t\t\t\t\thsl(0, 0%, 44%);\n\n\t/* -- Upload -------------------------------------------------------------------------------- */\n\n\t--ck-color-upload-bar-background:\t\t \t\t\t\t\thsl(209, 92%, 70%);\n\n\t/* -- Link -------------------------------------------------------------------------------- */\n\n\t--ck-color-link-default:\t\t\t\t\t\t\t\t\thsl(240, 100%, 47%);\n\t--ck-color-link-selected-background:\t\t\t\t\t\thsla(201, 100%, 56%, 0.1);\n\t--ck-color-link-fake-selection:\t\t\t\t\t\t\t\thsla(201, 100%, 56%, 0.3);\n}\n","/*\n * Copyright (c) 2003-2022, 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/**\n\t * An opacity value of disabled UI item.\n\t */\n\t--ck-disabled-opacity: .5;\n}\n","/*\n * Copyright (c) 2003-2022, 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/**\n\t * The geometry of the of focused element's outer shadow.\n\t */\n\t--ck-focus-outer-shadow-geometry: 0 0 0 3px;\n\n\t/**\n\t * A visual style of focused element's outer shadow.\n\t */\n\t--ck-focus-outer-shadow: var(--ck-focus-outer-shadow-geometry) var(--ck-color-focus-outer-shadow);\n\n\t/**\n\t * A visual style of focused element's outer shadow (when disabled).\n\t */\n\t--ck-focus-disabled-outer-shadow: var(--ck-focus-outer-shadow-geometry) var(--ck-color-focus-disabled-shadow);\n\n\t/**\n\t * A visual style of focused element's outer shadow (when has errors).\n\t */\n\t--ck-focus-error-outer-shadow: var(--ck-focus-outer-shadow-geometry) var(--ck-color-focus-error-shadow);\n\n\t/**\n\t * A visual style of focused element's border or outline.\n\t */\n\t--ck-focus-ring: 1px solid var(--ck-color-focus-border);\n}\n","/*\n * Copyright (c) 2003-2022, 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-font-size-base: 13px;\n\t--ck-line-height-base: 1.84615;\n\t--ck-font-face: Helvetica, Arial, Tahoma, Verdana, Sans-Serif;\n\n\t--ck-font-size-tiny: 0.7em;\n\t--ck-font-size-small: 0.75em;\n\t--ck-font-size-normal: 1em;\n\t--ck-font-size-big: 1.4em;\n\t--ck-font-size-large: 1.8em;\n}\n","/*\n * Copyright (c) 2003-2022, 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/* This is super-important. This is **manually** adjusted so a button without an icon\n\tis never smaller than a button with icon, additionally making sure that text-less buttons\n\tare perfect squares. The value is also shared by other components which should stay \"in-line\"\n\twith buttons. */\n\t--ck-ui-component-min-height: 2.3em;\n}\n\n/**\n * Resets an element, ignoring its children.\n */\n.ck.ck-reset,\n.ck.ck-reset_all,\n.ck.ck-reset_all * {\n\t/* Do not include inheritable rules here. */\n\tmargin: 0;\n\tpadding: 0;\n\tborder: 0;\n\tbackground: transparent;\n\ttext-decoration: none;\n\tvertical-align: middle;\n\ttransition: none;\n\n\t/* https://github.com/ckeditor/ckeditor5-theme-lark/issues/105 */\n\tword-wrap: break-word;\n}\n\n/**\n * Resets an element AND its children.\n */\n.ck.ck-reset_all,\n.ck.ck-reset_all * {\n\t/* These are rule inherited by all children elements. */\n\tborder-collapse: collapse;\n\tfont: normal normal normal var(--ck-font-size-base)/var(--ck-line-height-base) var(--ck-font-face);\n\tcolor: var(--ck-color-text);\n\ttext-align: left;\n\twhite-space: nowrap;\n\tcursor: auto;\n\tfloat: none;\n}\n\n.ck.ck-reset_all {\n\t& .ck-rtl * {\n\t\ttext-align: right;\n\t}\n\n\t& iframe {\n\t\t/* For IE */\n\t\tvertical-align: inherit;\n\t}\n\n\t& textarea {\n\t\twhite-space: pre-wrap;\n\t}\n\n\t& textarea,\n\t& input[type=\"text\"],\n\t& input[type=\"password\"] {\n\t\tcursor: text;\n\t}\n\n\t& textarea[disabled],\n\t& input[type=\"text\"][disabled],\n\t& input[type=\"password\"][disabled] {\n\t\tcursor: default;\n\t}\n\n\t& fieldset {\n\t\tpadding: 10px;\n\t\tborder: 2px groove hsl(255, 7%, 88%);\n\t}\n\n\t& button::-moz-focus-inner {\n\t\t/* See http://stackoverflow.com/questions/5517744/remove-extra-button-spacing-padding-in-firefox */\n\t\tpadding: 0;\n\t\tborder: 0\n\t}\n}\n\n/**\n * Default UI rules for RTL languages.\n */\n.ck[dir=\"rtl\"],\n.ck[dir=\"rtl\"] .ck {\n\ttext-align: right;\n}\n","/*\n * Copyright (c) 2003-2022, 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/**\n * Default border-radius value.\n */\n:root{\n\t--ck-border-radius: 2px;\n}\n","/*\n * Copyright (c) 2003-2022, 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/**\n\t * A visual style of element's inner shadow (i.e. input).\n\t */\n\t--ck-inner-shadow: 2px 2px 3px var(--ck-color-shadow-inner) inset;\n\n\t/**\n\t * A visual style of element's drop shadow (i.e. panel).\n\t */\n\t--ck-drop-shadow: 0 1px 2px 1px var(--ck-color-shadow-drop);\n\n\t/**\n\t * A visual style of element's active shadow (i.e. comment or suggestion).\n\t */\n\t--ck-drop-shadow-active: 0 3px 6px 1px var(--ck-color-shadow-drop-active);\n}\n","/*\n * Copyright (c) 2003-2022, 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-spacing-unit: \t\t\t\t\t\t0.6em;\n\t--ck-spacing-large: \t\t\t\t\tcalc(var(--ck-spacing-unit) * 1.5);\n\t--ck-spacing-standard: \t\t\t\t\tvar(--ck-spacing-unit);\n\t--ck-spacing-medium: \t\t\t\t\tcalc(var(--ck-spacing-unit) * 0.8);\n\t--ck-spacing-small: \t\t\t\t\tcalc(var(--ck-spacing-unit) * 0.5);\n\t--ck-spacing-tiny: \t\t\t\t\t\tcalc(var(--ck-spacing-unit) * 0.3);\n\t--ck-spacing-extra-tiny: \t\t\t\tcalc(var(--ck-spacing-unit) * 0.16);\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-widget/theme/widget.css":
/*!******************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-widget/theme/widget.css ***!
\******************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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, ":root{--ck-color-resizer:var(--ck-color-focus-border);--ck-color-resizer-tooltip-background:#262626;--ck-color-resizer-tooltip-text:#f2f2f2;--ck-resizer-border-radius:var(--ck-border-radius);--ck-resizer-tooltip-offset:10px;--ck-resizer-tooltip-height:calc(var(--ck-spacing-small)*2 + 10px)}.ck .ck-widget,.ck .ck-widget.ck-widget_with-selection-handle{position:relative}.ck .ck-widget.ck-widget_with-selection-handle .ck-widget__selection-handle{position:absolute}.ck .ck-widget.ck-widget_with-selection-handle .ck-widget__selection-handle .ck-icon{display:block}.ck .ck-widget.ck-widget_with-selection-handle.ck-widget_selected>.ck-widget__selection-handle,.ck .ck-widget.ck-widget_with-selection-handle:hover>.ck-widget__selection-handle{visibility:visible}.ck .ck-size-view{background:var(--ck-color-resizer-tooltip-background);color:var(--ck-color-resizer-tooltip-text);border:1px solid var(--ck-color-resizer-tooltip-text);border-radius:var(--ck-resizer-border-radius);font-size:var(--ck-font-size-tiny);display:block;padding:0 var(--ck-spacing-small);height:var(--ck-resizer-tooltip-height);line-height:var(--ck-resizer-tooltip-height)}.ck .ck-size-view.ck-orientation-above-center,.ck .ck-size-view.ck-orientation-bottom-left,.ck .ck-size-view.ck-orientation-bottom-right,.ck .ck-size-view.ck-orientation-top-left,.ck .ck-size-view.ck-orientation-top-right{position:absolute}.ck .ck-size-view.ck-orientation-top-left{top:var(--ck-resizer-tooltip-offset);left:var(--ck-resizer-tooltip-offset)}.ck .ck-size-view.ck-orientation-top-right{top:var(--ck-resizer-tooltip-offset);right:var(--ck-resizer-tooltip-offset)}.ck .ck-size-view.ck-orientation-bottom-right{bottom:var(--ck-resizer-tooltip-offset);right:var(--ck-resizer-tooltip-offset)}.ck .ck-size-view.ck-orientation-bottom-left{bottom:var(--ck-resizer-tooltip-offset);left:var(--ck-resizer-tooltip-offset)}.ck .ck-size-view.ck-orientation-above-center{top:calc(var(--ck-resizer-tooltip-height)*-1);left:50%;transform:translate(-50%)}:root{--ck-widget-outline-thickness:3px;--ck-widget-handler-icon-size:16px;--ck-widget-handler-animation-duration:200ms;--ck-widget-handler-animation-curve:ease;--ck-color-widget-blurred-border:#dedede;--ck-color-widget-hover-border:#ffc83d;--ck-color-widget-editable-focus-background:var(--ck-color-base-background);--ck-color-widget-drag-handler-icon-color:var(--ck-color-base-background)}.ck .ck-widget{outline-width:var(--ck-widget-outline-thickness);outline-style:solid;outline-color:transparent;transition:outline-color var(--ck-widget-handler-animation-duration) var(--ck-widget-handler-animation-curve)}.ck .ck-widget.ck-widget_selected,.ck .ck-widget.ck-widget_selected:hover{outline:var(--ck-widget-outline-thickness) solid var(--ck-color-focus-border)}.ck .ck-widget:hover{outline-color:var(--ck-color-widget-hover-border)}.ck .ck-editor__nested-editable{border:1px solid transparent}.ck .ck-editor__nested-editable.ck-editor__nested-editable_focused,.ck .ck-editor__nested-editable:focus{outline:none;border:var(--ck-focus-ring);box-shadow:var(--ck-inner-shadow),0 0;background-color:var(--ck-color-widget-editable-focus-background)}.ck .ck-widget.ck-widget_with-selection-handle .ck-widget__selection-handle{padding:4px;box-sizing:border-box;background-color:transparent;opacity:0;transition:background-color var(--ck-widget-handler-animation-duration) var(--ck-widget-handler-animation-curve),visibility var(--ck-widget-handler-animation-duration) var(--ck-widget-handler-animation-curve),opacity var(--ck-widget-handler-animation-duration) var(--ck-widget-handler-animation-curve);border-radius:var(--ck-border-radius) var(--ck-border-radius) 0 0;transform:translateY(-100%);left:calc(0px - var(--ck-widget-outline-thickness));top:0}.ck .ck-widget.ck-widget_with-selection-handle .ck-widget__selection-handle .ck-icon{width:var(--ck-widget-handler-icon-size);height:var(--ck-widget-handler-icon-size);color:var(--ck-color-widget-drag-handler-icon-color)}.ck .ck-widget.ck-widget_with-selection-handle .ck-widget__selection-handle .ck-icon .ck-icon__selected-indicator{opacity:0;transition:opacity .3s var(--ck-widget-handler-animation-curve)}.ck .ck-widget.ck-widget_with-selection-handle .ck-widget__selection-handle:hover .ck-icon .ck-icon__selected-indicator{opacity:1}.ck .ck-widget.ck-widget_with-selection-handle:hover>.ck-widget__selection-handle{opacity:1;background-color:var(--ck-color-widget-hover-border)}.ck .ck-widget.ck-widget_with-selection-handle.ck-widget_selected:hover>.ck-widget__selection-handle,.ck .ck-widget.ck-widget_with-selection-handle.ck-widget_selected>.ck-widget__selection-handle{opacity:1;background-color:var(--ck-color-focus-border)}.ck .ck-widget.ck-widget_with-selection-handle.ck-widget_selected:hover>.ck-widget__selection-handle .ck-icon .ck-icon__selected-indicator,.ck .ck-widget.ck-widget_with-selection-handle.ck-widget_selected>.ck-widget__selection-handle .ck-icon .ck-icon__selected-indicator{opacity:1}.ck[dir=rtl] .ck-widget.ck-widget_with-selection-handle .ck-widget__selection-handle{left:auto;right:calc(0px - var(--ck-widget-outline-thickness))}.ck.ck-editor__editable.ck-read-only .ck-widget{transition:none}.ck.ck-editor__editable.ck-read-only .ck-widget:not(.ck-widget_selected){--ck-widget-outline-thickness:0px}.ck.ck-editor__editable.ck-read-only .ck-widget.ck-widget_with-selection-handle .ck-widget__selection-handle,.ck.ck-editor__editable.ck-read-only .ck-widget.ck-widget_with-selection-handle .ck-widget__selection-handle:hover{background:var(--ck-color-widget-blurred-border)}.ck.ck-editor__editable.ck-blurred .ck-widget.ck-widget_selected,.ck.ck-editor__editable.ck-blurred .ck-widget.ck-widget_selected:hover{outline-color:var(--ck-color-widget-blurred-border)}.ck.ck-editor__editable.ck-blurred .ck-widget.ck-widget_selected.ck-widget_with-selection-handle>.ck-widget__selection-handle,.ck.ck-editor__editable.ck-blurred .ck-widget.ck-widget_selected.ck-widget_with-selection-handle>.ck-widget__selection-handle:hover,.ck.ck-editor__editable.ck-blurred .ck-widget.ck-widget_selected:hover.ck-widget_with-selection-handle>.ck-widget__selection-handle,.ck.ck-editor__editable.ck-blurred .ck-widget.ck-widget_selected:hover.ck-widget_with-selection-handle>.ck-widget__selection-handle:hover{background:var(--ck-color-widget-blurred-border)}.ck.ck-editor__editable>.ck-widget.ck-widget_with-selection-handle:first-child,.ck.ck-editor__editable blockquote>.ck-widget.ck-widget_with-selection-handle:first-child{margin-top:calc(1em + var(--ck-widget-handler-icon-size))}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-widget/theme/widget.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/ckeditor5-widget/widget.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/mixins/_focus.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/mixins/_shadow.css"],"names":[],"mappings":"AAKA,MACC,+CAAgD,CAChD,6CAAsD,CACtD,uCAAgD,CAEhD,kDAAmD,CACnD,gCAAiC,CACjC,kEACD,CAOA,8DAEC,iBAqBD,CAnBC,4EACC,iBAOD,CALC,qFAGC,aACD,CASD,iLACC,kBACD,CAGD,kBACC,qDAAsD,CACtD,0CAA2C,CAC3C,qDAAsD,CACtD,6CAA8C,CAC9C,kCAAmC,CACnC,aAAc,CACd,iCAAkC,CAClC,uCAAwC,CACxC,4CAoCD,CAlCC,8NAKC,iBACD,CAEA,0CACC,oCAAqC,CACrC,qCACD,CAEA,2CACC,oCAAqC,CACrC,sCACD,CAEA,8CACC,uCAAwC,CACxC,sCACD,CAEA,6CACC,uCAAwC,CACxC,qCACD,CAGA,8CACC,6CAAgD,CAChD,QAAS,CACT,yBACD,CCjFD,MACC,iCAAkC,CAClC,kCAAmC,CACnC,4CAA6C,CAC7C,wCAAyC,CAEzC,wCAAiD,CACjD,sCAAkD,CAClD,2EAA4E,CAC5E,yEACD,CAEA,eACC,gDAAiD,CACjD,mBAAoB,CACpB,yBAA0B,CAC1B,6GAUD,CARC,0EAEC,6EACD,CAEA,qBACC,iDACD,CAGD,gCACC,4BAWD,CAPC,yGC/BA,YAAa,CACb,2BAA2B,CCF3B,qCAA8B,CFqC7B,iEACD,CAIA,4EACC,WAAY,CACZ,qBAAsB,CAGtB,4BAA6B,CAC7B,SAAU,CAMV,6SAG6F,CAG7F,iEAAkE,CAGlE,2BAA4B,CAC5B,mDAAoD,CACpD,KAqBD,CAnBC,qFAEC,wCAAyC,CACzC,yCAA0C,CAC1C,oDASD,CANC,kHACC,SAAU,CAGV,+DACD,CAID,wHACC,SACD,CAID,kFACC,SAAU,CACV,oDACD,CAKC,oMACC,SAAU,CACV,6CAMD,CAHC,gRACC,SACD,CAOH,qFACC,SAAU,CACV,oDACD,CAGA,gDAEC,eAkBD,CAhBC,yEAOC,iCACD,CAGC,gOAEC,gDACD,CAOD,wIAEC,mDAQD,CALE,ghBAEC,gDACD,CAKH,yKAOC,yDACD","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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-resizer: var(--ck-color-focus-border);\n\t--ck-color-resizer-tooltip-background: hsl(0, 0%, 15%);\n\t--ck-color-resizer-tooltip-text: hsl(0, 0%, 95%);\n\n\t--ck-resizer-border-radius: var(--ck-border-radius);\n\t--ck-resizer-tooltip-offset: 10px;\n\t--ck-resizer-tooltip-height: calc(var(--ck-spacing-small) * 2 + 10px);\n}\n\n.ck .ck-widget {\n\t/* This is neccessary for type around UI to be positioned properly. */\n\tposition: relative;\n}\n\n.ck .ck-widget.ck-widget_with-selection-handle {\n\t/* Make the widget wrapper a relative positioning container for the drag handle. */\n\tposition: relative;\n\n\t& .ck-widget__selection-handle {\n\t\tposition: absolute;\n\n\t\t& .ck-icon {\n\t\t\t/* Make sure the icon in not a subject to font-size or line-height to avoid\n\t\t\tunnecessary spacing around it. */\n\t\t\tdisplay: block;\n\t\t}\n\t}\n\n\t/* Show the selection handle on mouse hover over the widget, but not for nested widgets. */\n\t&:hover > .ck-widget__selection-handle {\n\t\tvisibility: visible;\n\t}\n\n\t/* Show the selection handle when the widget is selected, but not for nested widgets. */\n\t&.ck-widget_selected > .ck-widget__selection-handle {\n\t\tvisibility: visible;\n\t}\n}\n\n.ck .ck-size-view {\n\tbackground: var(--ck-color-resizer-tooltip-background);\n\tcolor: var(--ck-color-resizer-tooltip-text);\n\tborder: 1px solid var(--ck-color-resizer-tooltip-text);\n\tborder-radius: var(--ck-resizer-border-radius);\n\tfont-size: var(--ck-font-size-tiny);\n\tdisplay: block;\n\tpadding: 0 var(--ck-spacing-small);\n\theight: var(--ck-resizer-tooltip-height);\n\tline-height: var(--ck-resizer-tooltip-height);\n\n\t&.ck-orientation-top-left,\n\t&.ck-orientation-top-right,\n\t&.ck-orientation-bottom-right,\n\t&.ck-orientation-bottom-left,\n\t&.ck-orientation-above-center {\n\t\tposition: absolute;\n\t}\n\n\t&.ck-orientation-top-left {\n\t\ttop: var(--ck-resizer-tooltip-offset);\n\t\tleft: var(--ck-resizer-tooltip-offset);\n\t}\n\n\t&.ck-orientation-top-right {\n\t\ttop: var(--ck-resizer-tooltip-offset);\n\t\tright: var(--ck-resizer-tooltip-offset);\n\t}\n\n\t&.ck-orientation-bottom-right {\n\t\tbottom: var(--ck-resizer-tooltip-offset);\n\t\tright: var(--ck-resizer-tooltip-offset);\n\t}\n\n\t&.ck-orientation-bottom-left {\n\t\tbottom: var(--ck-resizer-tooltip-offset);\n\t\tleft: var(--ck-resizer-tooltip-offset);\n\t}\n\n\t/* Class applied if the widget is too small to contain the size label */\n\t&.ck-orientation-above-center {\n\t\ttop: calc(var(--ck-resizer-tooltip-height) * -1);\n\t\tleft: 50%;\n\t\ttransform: translate(-50%);\n\t}\n}\n","/*\n * Copyright (c) 2003-2022, 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 \"../mixins/_focus.css\";\n@import \"../mixins/_shadow.css\";\n\n:root {\n\t--ck-widget-outline-thickness: 3px;\n\t--ck-widget-handler-icon-size: 16px;\n\t--ck-widget-handler-animation-duration: 200ms;\n\t--ck-widget-handler-animation-curve: ease;\n\n\t--ck-color-widget-blurred-border: hsl(0, 0%, 87%);\n\t--ck-color-widget-hover-border: hsl(43, 100%, 62%);\n\t--ck-color-widget-editable-focus-background: var(--ck-color-base-background);\n\t--ck-color-widget-drag-handler-icon-color: var(--ck-color-base-background);\n}\n\n.ck .ck-widget {\n\toutline-width: var(--ck-widget-outline-thickness);\n\toutline-style: solid;\n\toutline-color: transparent;\n\ttransition: outline-color var(--ck-widget-handler-animation-duration) var(--ck-widget-handler-animation-curve);\n\n\t&.ck-widget_selected,\n\t&.ck-widget_selected:hover {\n\t\toutline: var(--ck-widget-outline-thickness) solid var(--ck-color-focus-border);\n\t}\n\n\t&:hover {\n\t\toutline-color: var(--ck-color-widget-hover-border);\n\t}\n}\n\n.ck .ck-editor__nested-editable {\n\tborder: 1px solid transparent;\n\n\t/* The :focus style is applied before .ck-editor__nested-editable_focused class is rendered in the view.\n\tThese styles show a different border for a blink of an eye, so `:focus` need to have same styles applied. */\n\t&.ck-editor__nested-editable_focused,\n\t&:focus {\n\t\t@mixin ck-focus-ring;\n\t\t@mixin ck-box-shadow var(--ck-inner-shadow);\n\n\t\tbackground-color: var(--ck-color-widget-editable-focus-background);\n\t}\n}\n\n.ck .ck-widget.ck-widget_with-selection-handle {\n\t& .ck-widget__selection-handle {\n\t\tpadding: 4px;\n\t\tbox-sizing: border-box;\n\n\t\t/* Background and opacity will be animated as the handler shows up or the widget gets selected. */\n\t\tbackground-color: transparent;\n\t\topacity: 0;\n\n\t\t/* Transition:\n\t\t * background-color for the .ck-widget_selected state change,\n\t\t * visibility for hiding the handler,\n\t\t * opacity for the proper look of the icon when the handler disappears. */\n\t\ttransition:\n\t\t\tbackground-color var(--ck-widget-handler-animation-duration) var(--ck-widget-handler-animation-curve),\n\t\t\tvisibility var(--ck-widget-handler-animation-duration) var(--ck-widget-handler-animation-curve),\n\t\t\topacity var(--ck-widget-handler-animation-duration) var(--ck-widget-handler-animation-curve);\n\n\t\t/* Make only top corners round. */\n\t\tborder-radius: var(--ck-border-radius) var(--ck-border-radius) 0 0;\n\n\t\t/* Place the drag handler outside the widget wrapper. */\n\t\ttransform: translateY(-100%);\n\t\tleft: calc(0px - var(--ck-widget-outline-thickness));\n\t\ttop: 0;\n\n\t\t& .ck-icon {\n\t\t\t/* Make sure the dimensions of the icon are independent of the fon-size of the content. */\n\t\t\twidth: var(--ck-widget-handler-icon-size);\n\t\t\theight: var(--ck-widget-handler-icon-size);\n\t\t\tcolor: var(--ck-color-widget-drag-handler-icon-color);\n\n\t\t\t/* The \"selected\" part of the icon is invisible by default */\n\t\t\t& .ck-icon__selected-indicator {\n\t\t\t\topacity: 0;\n\n\t\t\t\t/* Note: The animation is longer on purpose. Simply feels better. */\n\t\t\t\ttransition: opacity 300ms var(--ck-widget-handler-animation-curve);\n\t\t\t}\n\t\t}\n\n\t\t/* Advertise using the look of the icon that once clicked the handler, the widget will be selected. */\n\t\t&:hover .ck-icon .ck-icon__selected-indicator {\n\t\t\topacity: 1;\n\t\t}\n\t}\n\n\t/* Show the selection handler on mouse hover over the widget, but not for nested widgets. */\n\t&:hover > .ck-widget__selection-handle {\n\t\topacity: 1;\n\t\tbackground-color: var(--ck-color-widget-hover-border);\n\t}\n\n\t/* Show the selection handler when the widget is selected, but not for nested widgets. */\n\t&.ck-widget_selected,\n\t&.ck-widget_selected:hover {\n\t\t& > .ck-widget__selection-handle {\n\t\t\topacity: 1;\n\t\t\tbackground-color: var(--ck-color-focus-border);\n\n\t\t\t/* When the widget is selected, notify the user using the proper look of the icon. */\n\t\t\t& .ck-icon .ck-icon__selected-indicator {\n\t\t\t\topacity: 1;\n\t\t\t}\n\t\t}\n\t}\n}\n\n/* In a RTL environment, align the selection handler to the right side of the widget */\n/* stylelint-disable-next-line no-descending-specificity */\n.ck[dir=\"rtl\"] .ck-widget.ck-widget_with-selection-handle .ck-widget__selection-handle {\n\tleft: auto;\n\tright: calc(0px - var(--ck-widget-outline-thickness));\n}\n\n/* https://github.com/ckeditor/ckeditor5/issues/6415 */\n.ck.ck-editor__editable.ck-read-only .ck-widget {\n\t/* Prevent the :hover outline from showing up because of the used outline-color transition. */\n\ttransition: none;\n\n\t&:not(.ck-widget_selected) {\n\t\t/* Disable visual effects of hover/active widget when CKEditor is in readOnly mode.\n\t\t * See: https://github.com/ckeditor/ckeditor5/issues/1261\n\t\t *\n\t\t * Leave the unit because this custom property is used in calc() by other features.\n\t\t * See: https://github.com/ckeditor/ckeditor5/issues/6775\n\t\t */\n\t\t--ck-widget-outline-thickness: 0px;\n\t}\n\n\t&.ck-widget_with-selection-handle {\n\t\t& .ck-widget__selection-handle,\n\t\t& .ck-widget__selection-handle:hover {\n\t\t\tbackground: var(--ck-color-widget-blurred-border);\n\t\t}\n\t}\n}\n\n/* Style the widget when it's selected but the editable it belongs to lost focus. */\n/* stylelint-disable-next-line no-descending-specificity */\n.ck.ck-editor__editable.ck-blurred .ck-widget {\n\t&.ck-widget_selected,\n\t&.ck-widget_selected:hover {\n\t\toutline-color: var(--ck-color-widget-blurred-border);\n\n\t\t&.ck-widget_with-selection-handle {\n\t\t\t& > .ck-widget__selection-handle,\n\t\t\t& > .ck-widget__selection-handle:hover {\n\t\t\t\tbackground: var(--ck-color-widget-blurred-border);\n\t\t\t}\n\t\t}\n\t}\n}\n\n.ck.ck-editor__editable > .ck-widget.ck-widget_with-selection-handle:first-child,\n.ck.ck-editor__editable blockquote > .ck-widget.ck-widget_with-selection-handle:first-child {\n\t/* Do not crop selection handler if a widget is a first-child in the blockquote or in the root editable.\n\tIn fact, anything with overflow: hidden.\n\thttps://github.com/ckeditor/ckeditor5-block-quote/issues/28\n\thttps://github.com/ckeditor/ckeditor5-widget/issues/44\n\thttps://github.com/ckeditor/ckeditor5-widget/issues/66 */\n\tmargin-top: calc(1em + var(--ck-widget-handler-icon-size));\n}\n","/*\n * Copyright (c) 2003-2022, 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/**\n * A visual style of focused element's border.\n */\n@define-mixin ck-focus-ring {\n\t/* Disable native outline. */\n\toutline: none;\n\tborder: var(--ck-focus-ring)\n}\n","/*\n * Copyright (c) 2003-2022, 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/**\n * A helper to combine multiple shadows.\n */\n@define-mixin ck-box-shadow $shadowA, $shadowB: 0 0 {\n\tbox-shadow: $shadowA, $shadowB;\n}\n\n/**\n * Gives an element a drop shadow so it looks like a floating panel.\n */\n@define-mixin ck-drop-shadow {\n\t@mixin ck-box-shadow var(--ck-drop-shadow);\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-widget/theme/widgetresize.css":
/*!************************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-widget/theme/widgetresize.css ***!
\************************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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-widget_with-resizer{position:relative}.ck .ck-widget__resizer{display:none;position:absolute;pointer-events:none;left:0;top:0}.ck-focused .ck-widget_with-resizer.ck-widget_selected>.ck-widget__resizer{display:block}.ck .ck-widget__resizer__handle{position:absolute;pointer-events:all}.ck .ck-widget__resizer__handle.ck-widget__resizer__handle-bottom-right,.ck .ck-widget__resizer__handle.ck-widget__resizer__handle-top-left{cursor:nwse-resize}.ck .ck-widget__resizer__handle.ck-widget__resizer__handle-bottom-left,.ck .ck-widget__resizer__handle.ck-widget__resizer__handle-top-right{cursor:nesw-resize}:root{--ck-resizer-size:10px;--ck-resizer-offset:calc(var(--ck-resizer-size)/-2 - 2px);--ck-resizer-border-width:1px}.ck .ck-widget__resizer{outline:1px solid var(--ck-color-resizer)}.ck .ck-widget__resizer__handle{width:var(--ck-resizer-size);height:var(--ck-resizer-size);background:var(--ck-color-focus-border);border:var(--ck-resizer-border-width) solid #fff;border-radius:var(--ck-resizer-border-radius)}.ck .ck-widget__resizer__handle.ck-widget__resizer__handle-top-left{top:var(--ck-resizer-offset);left:var(--ck-resizer-offset)}.ck .ck-widget__resizer__handle.ck-widget__resizer__handle-top-right{top:var(--ck-resizer-offset);right:var(--ck-resizer-offset)}.ck .ck-widget__resizer__handle.ck-widget__resizer__handle-bottom-right{bottom:var(--ck-resizer-offset);right:var(--ck-resizer-offset)}.ck .ck-widget__resizer__handle.ck-widget__resizer__handle-bottom-left{bottom:var(--ck-resizer-offset);left:var(--ck-resizer-offset)}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-widget/theme/widgetresize.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/ckeditor5-widget/widgetresize.css"],"names":[],"mappings":"AAKA,4BAEC,iBACD,CAEA,wBACC,YAAa,CACb,iBAAkB,CAGlB,mBAAoB,CAEpB,MAAO,CACP,KACD,CAGC,2EACC,aACD,CAGD,gCACC,iBAAkB,CAGlB,kBAWD,CATC,4IAEC,kBACD,CAEA,4IAEC,kBACD,CCpCD,MACC,sBAAuB,CAGvB,yDAAiE,CACjE,6BACD,CAEA,wBACC,yCACD,CAEA,gCACC,4BAA6B,CAC7B,6BAA8B,CAC9B,uCAAwC,CACxC,gDAA6D,CAC7D,6CAqBD,CAnBC,oEACC,4BAA6B,CAC7B,6BACD,CAEA,qEACC,4BAA6B,CAC7B,8BACD,CAEA,wEACC,+BAAgC,CAChC,8BACD,CAEA,uEACC,+BAAgC,CAChC,6BACD","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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-widget_with-resizer {\n\t/* Make the widget wrapper a relative positioning container for the drag handle. */\n\tposition: relative;\n}\n\n.ck .ck-widget__resizer {\n\tdisplay: none;\n\tposition: absolute;\n\n\t/* The wrapper itself should not interfere with the pointer device, only the handles should. */\n\tpointer-events: none;\n\n\tleft: 0;\n\ttop: 0;\n}\n\n.ck-focused .ck-widget_with-resizer.ck-widget_selected {\n\t& > .ck-widget__resizer {\n\t\tdisplay: block;\n\t}\n}\n\n.ck .ck-widget__resizer__handle {\n\tposition: absolute;\n\n\t/* Resizers are the only UI elements that should interfere with a pointer device. */\n\tpointer-events: all;\n\n\t&.ck-widget__resizer__handle-top-left,\n\t&.ck-widget__resizer__handle-bottom-right {\n\t\tcursor: nwse-resize;\n\t}\n\n\t&.ck-widget__resizer__handle-top-right,\n\t&.ck-widget__resizer__handle-bottom-left {\n\t\tcursor: nesw-resize;\n\t}\n}\n","/*\n * Copyright (c) 2003-2022, 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-resizer-size: 10px;\n\n\t/* Set the resizer with a 50% offset. */\n\t--ck-resizer-offset: calc( ( var(--ck-resizer-size) / -2 ) - 2px);\n\t--ck-resizer-border-width: 1px;\n}\n\n.ck .ck-widget__resizer {\n\toutline: 1px solid var(--ck-color-resizer);\n}\n\n.ck .ck-widget__resizer__handle {\n\twidth: var(--ck-resizer-size);\n\theight: var(--ck-resizer-size);\n\tbackground: var(--ck-color-focus-border);\n\tborder: var(--ck-resizer-border-width) solid hsl(0, 0%, 100%);\n\tborder-radius: var(--ck-resizer-border-radius);\n\n\t&.ck-widget__resizer__handle-top-left {\n\t\ttop: var(--ck-resizer-offset);\n\t\tleft: var(--ck-resizer-offset);\n\t}\n\n\t&.ck-widget__resizer__handle-top-right {\n\t\ttop: var(--ck-resizer-offset);\n\t\tright: var(--ck-resizer-offset);\n\t}\n\n\t&.ck-widget__resizer__handle-bottom-right {\n\t\tbottom: var(--ck-resizer-offset);\n\t\tright: var(--ck-resizer-offset);\n\t}\n\n\t&.ck-widget__resizer__handle-bottom-left {\n\t\tbottom: var(--ck-resizer-offset);\n\t\tleft: var(--ck-resizer-offset);\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[1].use[2]!./node_modules/@ckeditor/ckeditor5-widget/theme/widgettypearound.css":
/*!****************************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-widget/theme/widgettypearound.css ***!
\****************************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
__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-widget .ck-widget__type-around__button{display:block;position:absolute;overflow:hidden;z-index:var(--ck-z-default)}.ck .ck-widget .ck-widget__type-around__button svg{position:absolute;top:50%;left:50%;z-index:calc(var(--ck-z-default) + 2)}.ck .ck-widget .ck-widget__type-around__button.ck-widget__type-around__button_before{top:calc(var(--ck-widget-outline-thickness)*-0.5);left:min(10%,30px);transform:translateY(-50%)}.ck .ck-widget .ck-widget__type-around__button.ck-widget__type-around__button_after{bottom:calc(var(--ck-widget-outline-thickness)*-0.5);right:min(10%,30px);transform:translateY(50%)}.ck .ck-widget.ck-widget_selected>.ck-widget__type-around>.ck-widget__type-around__button:after,.ck .ck-widget>.ck-widget__type-around>.ck-widget__type-around__button:hover:after{content:\"\";display:block;position:absolute;top:1px;left:1px;z-index:calc(var(--ck-z-default) + 1)}.ck .ck-widget>.ck-widget__type-around>.ck-widget__type-around__fake-caret{display:none;position:absolute;left:0;right:0}.ck .ck-widget:hover>.ck-widget__type-around>.ck-widget__type-around__fake-caret{left:calc(var(--ck-widget-outline-thickness)*-1);right:calc(var(--ck-widget-outline-thickness)*-1)}.ck .ck-widget.ck-widget_type-around_show-fake-caret_before>.ck-widget__type-around>.ck-widget__type-around__fake-caret{top:calc(var(--ck-widget-outline-thickness)*-1 - 1px);display:block}.ck .ck-widget.ck-widget_type-around_show-fake-caret_after>.ck-widget__type-around>.ck-widget__type-around__fake-caret{bottom:calc(var(--ck-widget-outline-thickness)*-1 - 1px);display:block}.ck.ck-editor__editable.ck-read-only .ck-widget__type-around,.ck.ck-editor__editable.ck-restricted-editing_mode_restricted .ck-widget__type-around,.ck.ck-editor__editable.ck-widget__type-around_disabled .ck-widget__type-around{display:none}:root{--ck-widget-type-around-button-size:20px;--ck-color-widget-type-around-button-active:var(--ck-color-focus-border);--ck-color-widget-type-around-button-hover:var(--ck-color-widget-hover-border);--ck-color-widget-type-around-button-blurred-editable:var(--ck-color-widget-blurred-border);--ck-color-widget-type-around-button-radar-start-alpha:0;--ck-color-widget-type-around-button-radar-end-alpha:.3;--ck-color-widget-type-around-button-icon:var(--ck-color-base-background)}.ck .ck-widget .ck-widget__type-around__button{width:var(--ck-widget-type-around-button-size);height:var(--ck-widget-type-around-button-size);background:var(--ck-color-widget-type-around-button);border-radius:100px;transition:opacity var(--ck-widget-handler-animation-duration) var(--ck-widget-handler-animation-curve),background var(--ck-widget-handler-animation-duration) var(--ck-widget-handler-animation-curve);opacity:0;pointer-events:none}.ck .ck-widget .ck-widget__type-around__button svg{width:10px;height:8px;transform:translate(-50%,-50%);transition:transform .5s ease;margin-top:1px}.ck .ck-widget .ck-widget__type-around__button svg *{stroke-dasharray:10;stroke-dashoffset:0;fill:none;stroke:var(--ck-color-widget-type-around-button-icon);stroke-width:1.5px;stroke-linecap:round;stroke-linejoin:round}.ck .ck-widget .ck-widget__type-around__button svg line{stroke-dasharray:7}.ck .ck-widget .ck-widget__type-around__button:hover{animation:ck-widget-type-around-button-sonar 1s ease infinite}.ck .ck-widget .ck-widget__type-around__button:hover svg polyline{animation:ck-widget-type-around-arrow-dash 2s linear}.ck .ck-widget .ck-widget__type-around__button:hover svg line{animation:ck-widget-type-around-arrow-tip-dash 2s linear}.ck .ck-widget.ck-widget_selected>.ck-widget__type-around>.ck-widget__type-around__button,.ck .ck-widget:hover>.ck-widget__type-around>.ck-widget__type-around__button{opacity:1;pointer-events:auto}.ck .ck-widget:not(.ck-widget_selected)>.ck-widget__type-around>.ck-widget__type-around__button{background:var(--ck-color-widget-type-around-button-hover)}.ck .ck-widget.ck-widget_selected>.ck-widget__type-around>.ck-widget__type-around__button,.ck .ck-widget>.ck-widget__type-around>.ck-widget__type-around__button:hover{background:var(--ck-color-widget-type-around-button-active)}.ck .ck-widget.ck-widget_selected>.ck-widget__type-around>.ck-widget__type-around__button:after,.ck .ck-widget>.ck-widget__type-around>.ck-widget__type-around__button:hover:after{width:calc(var(--ck-widget-type-around-button-size) - 2px);height:calc(var(--ck-widget-type-around-button-size) - 2px);border-radius:100px;background:linear-gradient(135deg,hsla(0,0%,100%,0),hsla(0,0%,100%,.3))}.ck .ck-widget.ck-widget_with-selection-handle>.ck-widget__type-around>.ck-widget__type-around__button_before{margin-left:20px}.ck .ck-widget .ck-widget__type-around__fake-caret{pointer-events:none;height:1px;animation:ck-widget-type-around-fake-caret-pulse 1s linear infinite normal forwards;outline:1px solid hsla(0,0%,100%,.5);background:var(--ck-color-base-text)}.ck .ck-widget.ck-widget_selected.ck-widget_type-around_show-fake-caret_after,.ck .ck-widget.ck-widget_selected.ck-widget_type-around_show-fake-caret_before{outline-color:transparent}.ck .ck-widget.ck-widget_type-around_show-fake-caret_after.ck-widget_selected:hover,.ck .ck-widget.ck-widget_type-around_show-fake-caret_before.ck-widget_selected:hover{outline-color:var(--ck-color-widget-hover-border)}.ck .ck-widget.ck-widget_type-around_show-fake-caret_after>.ck-widget__type-around>.ck-widget__type-around__button,.ck .ck-widget.ck-widget_type-around_show-fake-caret_before>.ck-widget__type-around>.ck-widget__type-around__button{opacity:0;pointer-events:none}.ck .ck-widget.ck-widget_type-around_show-fake-caret_after.ck-widget_with-selection-handle.ck-widget_selected:hover>.ck-widget__selection-handle,.ck .ck-widget.ck-widget_type-around_show-fake-caret_after.ck-widget_with-selection-handle.ck-widget_selected>.ck-widget__selection-handle,.ck .ck-widget.ck-widget_type-around_show-fake-caret_before.ck-widget_with-selection-handle.ck-widget_selected:hover>.ck-widget__selection-handle,.ck .ck-widget.ck-widget_type-around_show-fake-caret_before.ck-widget_with-selection-handle.ck-widget_selected>.ck-widget__selection-handle{opacity:0}.ck .ck-widget.ck-widget_type-around_show-fake-caret_after.ck-widget_selected.ck-widget_with-resizer>.ck-widget__resizer,.ck .ck-widget.ck-widget_type-around_show-fake-caret_before.ck-widget_selected.ck-widget_with-resizer>.ck-widget__resizer{opacity:0}.ck[dir=rtl] .ck-widget.ck-widget_with-selection-handle .ck-widget__type-around>.ck-widget__type-around__button_before{margin-left:0;margin-right:20px}.ck-editor__nested-editable.ck-editor__editable_selected .ck-widget.ck-widget_selected>.ck-widget__type-around>.ck-widget__type-around__button,.ck-editor__nested-editable.ck-editor__editable_selected .ck-widget:hover>.ck-widget__type-around>.ck-widget__type-around__button{opacity:0;pointer-events:none}.ck-editor__editable.ck-blurred .ck-widget.ck-widget_selected>.ck-widget__type-around>.ck-widget__type-around__button:not(:hover){background:var(--ck-color-widget-type-around-button-blurred-editable)}.ck-editor__editable.ck-blurred .ck-widget.ck-widget_selected>.ck-widget__type-around>.ck-widget__type-around__button:not(:hover) svg *{stroke:#999}@keyframes ck-widget-type-around-arrow-dash{0%{stroke-dashoffset:10}20%,to{stroke-dashoffset:0}}@keyframes ck-widget-type-around-arrow-tip-dash{0%,20%{stroke-dashoffset:7}40%,to{stroke-dashoffset:0}}@keyframes ck-widget-type-around-button-sonar{0%{box-shadow:0 0 0 0 hsla(var(--ck-color-focus-border-coordinates),var(--ck-color-widget-type-around-button-radar-start-alpha))}50%{box-shadow:0 0 0 5px hsla(var(--ck-color-focus-border-coordinates),var(--ck-color-widget-type-around-button-radar-end-alpha))}to{box-shadow:0 0 0 5px hsla(var(--ck-color-focus-border-coordinates),var(--ck-color-widget-type-around-button-radar-start-alpha))}}@keyframes ck-widget-type-around-fake-caret-pulse{0%{opacity:1}49%{opacity:1}50%{opacity:0}99%{opacity:0}to{opacity:1}}", "",{"version":3,"sources":["webpack://./node_modules/@ckeditor/ckeditor5-widget/theme/widgettypearound.css","webpack://./node_modules/@ckeditor/ckeditor5-theme-lark/theme/ckeditor5-widget/widgettypearound.css"],"names":[],"mappings":"AASC,+CACC,aAAc,CACd,iBAAkB,CAClB,eAAgB,CAChB,2BAwBD,CAtBC,mDACC,iBAAkB,CAClB,OAAQ,CACR,QAAS,CACT,qCACD,CAEA,qFAEC,iDAAoD,CACpD,kBAAoB,CAEpB,0BACD,CAEA,oFAEC,oDAAuD,CACvD,mBAAqB,CAErB,yBACD,CAUA,mLACC,UAAW,CACX,aAAc,CACd,iBAAkB,CAClB,OAAQ,CACR,QAAS,CACT,qCACD,CAMD,2EACC,YAAa,CACb,iBAAkB,CAClB,MAAO,CACP,OACD,CAOA,iFACC,gDAAqD,CACrD,iDACD,CAKA,wHACC,qDAA0D,CAC1D,aACD,CAKA,uHACC,wDAA6D,CAC7D,aACD,CAoBD,mOACC,YACD,CC3GA,MACC,wCAAyC,CACzC,wEAAyE,CACzE,8EAA+E,CAC/E,2FAA4F,CAC5F,wDAAyD,CACzD,uDAAwD,CACxD,yEACD,CAgBC,+CACC,8CAA+C,CAC/C,+CAAgD,CAChD,oDAAqD,CACrD,mBAAoB,CACpB,uMAAyM,CAb1M,SAAU,CACV,mBA0DA,CA1CC,mDACC,UAAW,CACX,UAAW,CACX,8BAA+B,CAC/B,6BAA8B,CAC9B,cAgBD,CAdC,qDACC,mBAAoB,CACpB,mBAAoB,CAEpB,SAAU,CACV,qDAAsD,CACtD,kBAAmB,CACnB,oBAAqB,CACrB,qBACD,CAEA,wDACC,kBACD,CAGD,qDAIC,6DAcD,CARE,kEACC,oDACD,CAEA,8DACC,wDACD,CAUF,uKAvED,SAAU,CACV,mBAwEC,CAOD,gGACC,0DACD,CAOA,uKAEC,2DAQD,CANC,mLACC,0DAA2D,CAC3D,2DAA4D,CAC5D,mBAAoB,CACpB,uEACD,CAOD,8GACC,gBACD,CAKA,mDACC,mBAAoB,CACpB,UAAW,CACX,mFAAoF,CAMpF,oCAAwC,CACxC,oCACD,CAOC,6JAEC,yBACD,CAUA,yKACC,iDACD,CAMA,uOAlJD,SAAU,CACV,mBAmJC,CASE,0jBACC,SACD,CASF,mPACC,SACD,CASF,uHACC,aAAc,CACd,iBACD,CAYG,iRAlMF,SAAU,CACV,mBAmME,CAQH,kIACC,qEAKD,CAHC,wIACC,WACD,CAGD,4CACC,GACC,oBACD,CACA,OACC,mBACD,CACD,CAEA,gDACC,OACC,mBACD,CACA,OACC,mBACD,CACD,CAEA,8CACC,GACC,6HACD,CACA,IACC,6HACD,CACA,GACC,+HACD,CACD,CAEA,kDACC,GACC,SACD,CACA,IACC,SACD,CACA,IACC,SACD,CACA,IACC,SACD,CACA,GACC,SACD,CACD","sourcesContent":["/*\n * Copyright (c) 2003-2022, 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-widget {\n\t/*\n\t * Styles of the type around buttons\n\t */\n\t& .ck-widget__type-around__button {\n\t\tdisplay: block;\n\t\tposition: absolute;\n\t\toverflow: hidden;\n\t\tz-index: var(--ck-z-default);\n\n\t\t& svg {\n\t\t\tposition: absolute;\n\t\t\ttop: 50%;\n\t\t\tleft: 50%;\n\t\t\tz-index: calc(var(--ck-z-default) + 2);\n\t\t}\n\n\t\t&.ck-widget__type-around__button_before {\n\t\t\t/* Place it in the middle of the outline */\n\t\t\ttop: calc(-0.5 * var(--ck-widget-outline-thickness));\n\t\t\tleft: min(10%, 30px);\n\n\t\t\ttransform: translateY(-50%);\n\t\t}\n\n\t\t&.ck-widget__type-around__button_after {\n\t\t\t/* Place it in the middle of the outline */\n\t\t\tbottom: calc(-0.5 * var(--ck-widget-outline-thickness));\n\t\t\tright: min(10%, 30px);\n\n\t\t\ttransform: translateY(50%);\n\t\t}\n\t}\n\n\t/*\n\t * Styles for the buttons when:\n\t * - the widget is selected,\n\t * - or the button is being hovered (regardless of the widget state).\n\t */\n\t&.ck-widget_selected > .ck-widget__type-around > .ck-widget__type-around__button,\n\t& > .ck-widget__type-around > .ck-widget__type-around__button:hover {\n\t\t&::after {\n\t\t\tcontent: \"\";\n\t\t\tdisplay: block;\n\t\t\tposition: absolute;\n\t\t\ttop: 1px;\n\t\t\tleft: 1px;\n\t\t\tz-index: calc(var(--ck-z-default) + 1);\n\t\t}\n\t}\n\n\t/*\n\t * Styles for the horizontal \"fake caret\" which is displayed when the user navigates using the keyboard.\n\t */\n\t& > .ck-widget__type-around > .ck-widget__type-around__fake-caret {\n\t\tdisplay: none;\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\tright: 0;\n\t}\n\n\t/*\n\t * When the widget is hovered the \"fake caret\" would normally be narrower than the\n\t * extra outline displayed around the widget. Let's extend the \"fake caret\" to match\n\t * the full width of the widget.\n\t */\n\t&:hover > .ck-widget__type-around > .ck-widget__type-around__fake-caret {\n\t\tleft: calc( -1 * var(--ck-widget-outline-thickness) );\n\t\tright: calc( -1 * var(--ck-widget-outline-thickness) );\n\t}\n\n\t/*\n\t * Styles for the horizontal \"fake caret\" when it should be displayed before the widget (backward keyboard navigation).\n\t */\n\t&.ck-widget_type-around_show-fake-caret_before > .ck-widget__type-around > .ck-widget__type-around__fake-caret {\n\t\ttop: calc( -1 * var(--ck-widget-outline-thickness) - 1px );\n\t\tdisplay: block;\n\t}\n\n\t/*\n\t * Styles for the horizontal \"fake caret\" when it should be displayed after the widget (forward keyboard navigation).\n\t */\n\t&.ck-widget_type-around_show-fake-caret_after > .ck-widget__type-around > .ck-widget__type-around__fake-caret {\n\t\tbottom: calc( -1 * var(--ck-widget-outline-thickness) - 1px );\n\t\tdisplay: block;\n\t}\n}\n\n/*\n * Integration with the read-only mode of the editor.\n */\n.ck.ck-editor__editable.ck-read-only .ck-widget__type-around {\n\tdisplay: none;\n}\n\n/*\n * Integration with the restricted editing mode (feature) of the editor.\n */\n.ck.ck-editor__editable.ck-restricted-editing_mode_restricted .ck-widget__type-around {\n\tdisplay: none;\n}\n\n/*\n * Integration with the #isEnabled property of the WidgetTypeAround plugin.\n */\n.ck.ck-editor__editable.ck-widget__type-around_disabled .ck-widget__type-around {\n\tdisplay: none;\n}\n","/*\n * Copyright (c) 2003-2022, 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-widget-type-around-button-size: 20px;\n\t--ck-color-widget-type-around-button-active: var(--ck-color-focus-border);\n\t--ck-color-widget-type-around-button-hover: var(--ck-color-widget-hover-border);\n\t--ck-color-widget-type-around-button-blurred-editable: var(--ck-color-widget-blurred-border);\n\t--ck-color-widget-type-around-button-radar-start-alpha: 0;\n\t--ck-color-widget-type-around-button-radar-end-alpha: .3;\n\t--ck-color-widget-type-around-button-icon: var(--ck-color-base-background);\n}\n\n@define-mixin ck-widget-type-around-button-visible {\n\topacity: 1;\n\tpointer-events: auto;\n}\n\n@define-mixin ck-widget-type-around-button-hidden {\n\topacity: 0;\n\tpointer-events: none;\n}\n\n.ck .ck-widget {\n\t/*\n\t * Styles of the type around buttons\n\t */\n\t& .ck-widget__type-around__button {\n\t\twidth: var(--ck-widget-type-around-button-size);\n\t\theight: var(--ck-widget-type-around-button-size);\n\t\tbackground: var(--ck-color-widget-type-around-button);\n\t\tborder-radius: 100px;\n\t\ttransition: opacity var(--ck-widget-handler-animation-duration) var(--ck-widget-handler-animation-curve), background var(--ck-widget-handler-animation-duration) var(--ck-widget-handler-animation-curve);\n\n\t\t@mixin ck-widget-type-around-button-hidden;\n\n\t\t& svg {\n\t\t\twidth: 10px;\n\t\t\theight: 8px;\n\t\t\ttransform: translate(-50%,-50%);\n\t\t\ttransition: transform .5s ease;\n\t\t\tmargin-top: 1px;\n\n\t\t\t& * {\n\t\t\t\tstroke-dasharray: 10;\n\t\t\t\tstroke-dashoffset: 0;\n\n\t\t\t\tfill: none;\n\t\t\t\tstroke: var(--ck-color-widget-type-around-button-icon);\n\t\t\t\tstroke-width: 1.5px;\n\t\t\t\tstroke-linecap: round;\n\t\t\t\tstroke-linejoin: round;\n\t\t\t}\n\n\t\t\t& line {\n\t\t\t\tstroke-dasharray: 7;\n\t\t\t}\n\t\t}\n\n\t\t&:hover {\n\t\t\t/*\n\t\t\t * Display the \"sonar\" around the button when hovered.\n\t\t\t */\n\t\t\tanimation: ck-widget-type-around-button-sonar 1s ease infinite;\n\n\t\t\t/*\n\t\t\t * Animate active button's icon.\n\t\t\t */\n\t\t\t& svg {\n\t\t\t\t& polyline {\n\t\t\t\t\tanimation: ck-widget-type-around-arrow-dash 2s linear;\n\t\t\t\t}\n\n\t\t\t\t& line {\n\t\t\t\t\tanimation: ck-widget-type-around-arrow-tip-dash 2s linear;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/*\n\t * Show type around buttons when the widget gets selected or being hovered.\n\t */\n\t&.ck-widget_selected,\n\t&:hover {\n\t\t& > .ck-widget__type-around > .ck-widget__type-around__button {\n\t\t\t@mixin ck-widget-type-around-button-visible;\n\t\t}\n\t}\n\n\t/*\n\t * Styles for the buttons when the widget is NOT selected (but the buttons are visible\n\t * and still can be hovered).\n\t */\n\t&:not(.ck-widget_selected) > .ck-widget__type-around > .ck-widget__type-around__button {\n\t\tbackground: var(--ck-color-widget-type-around-button-hover);\n\t}\n\n\t/*\n\t * Styles for the buttons when:\n\t * - the widget is selected,\n\t * - or the button is being hovered (regardless of the widget state).\n\t */\n\t&.ck-widget_selected > .ck-widget__type-around > .ck-widget__type-around__button,\n\t& > .ck-widget__type-around > .ck-widget__type-around__button:hover {\n\t\tbackground: var(--ck-color-widget-type-around-button-active);\n\n\t\t&::after {\n\t\t\twidth: calc(var(--ck-widget-type-around-button-size) - 2px);\n\t\t\theight: calc(var(--ck-widget-type-around-button-size) - 2px);\n\t\t\tborder-radius: 100px;\n\t\t\tbackground: linear-gradient(135deg, hsla(0,0%,100%,0) 0%, hsla(0,0%,100%,.3) 100%);\n\t\t}\n\t}\n\n\t/*\n\t * Styles for the \"before\" button when the widget has a selection handle. Because some space\n\t * is consumed by the handle, the button must be moved slightly to the right to let it breathe.\n\t */\n\t&.ck-widget_with-selection-handle > .ck-widget__type-around > .ck-widget__type-around__button_before {\n\t\tmargin-left: 20px;\n\t}\n\n\t/*\n\t * Styles for the horizontal \"fake caret\" which is displayed when the user navigates using the keyboard.\n\t */\n\t& .ck-widget__type-around__fake-caret {\n\t\tpointer-events: none;\n\t\theight: 1px;\n\t\tanimation: ck-widget-type-around-fake-caret-pulse linear 1s infinite normal forwards;\n\n\t\t/*\n\t\t * The semi-transparent-outline+background combo improves the contrast\n\t\t * when the background underneath the fake caret is dark.\n\t\t */\n\t\toutline: solid 1px hsla(0, 0%, 100%, .5);\n\t\tbackground: var(--ck-color-base-text);\n\t}\n\n\t/*\n\t * Styles of the widget when the \"fake caret\" is blinking (e.g. upon keyboard navigation).\n\t * Despite the widget being physically selected in the model, its outline should disappear.\n\t */\n\t&.ck-widget_selected {\n\t\t&.ck-widget_type-around_show-fake-caret_before,\n\t\t&.ck-widget_type-around_show-fake-caret_after {\n\t\t\toutline-color: transparent;\n\t\t}\n\t}\n\n\t&.ck-widget_type-around_show-fake-caret_before,\n\t&.ck-widget_type-around_show-fake-caret_after {\n\t\t/*\n\t\t * When the \"fake caret\" is visible we simulate that the widget is not selected\n\t\t * (despite being physically selected), so the outline color should be for the\n\t\t * unselected widget.\n\t\t */\n\t\t&.ck-widget_selected:hover {\n\t\t\toutline-color: var(--ck-color-widget-hover-border);\n\t\t}\n\n\t\t/*\n\t\t * Styles of the type around buttons when the \"fake caret\" is blinking (e.g. upon keyboard navigation).\n\t\t * In this state, the type around buttons would collide with the fake carets so they should disappear.\n\t\t */\n\t\t& > .ck-widget__type-around > .ck-widget__type-around__button {\n\t\t\t@mixin ck-widget-type-around-button-hidden;\n\t\t}\n\n\t\t/*\n\t\t * Fake horizontal caret integration with the selection handle. When the caret is visible, simply\n\t\t * hide the handle because it intersects with the caret (and does not make much sense anyway).\n\t\t */\n\t\t&.ck-widget_with-selection-handle {\n\t\t\t&.ck-widget_selected,\n\t\t\t&.ck-widget_selected:hover {\n\t\t\t\t& > .ck-widget__selection-handle {\n\t\t\t\t\topacity: 0\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t/*\n\t\t * Fake horizontal caret integration with the resize UI. When the caret is visible, simply\n\t\t * hide the resize UI because it creates too much noise. It can be visible when the user\n\t\t * hovers the widget, though.\n\t\t */\n\t\t&.ck-widget_selected.ck-widget_with-resizer > .ck-widget__resizer {\n\t\t\topacity: 0\n\t\t}\n\t}\n}\n\n/*\n * Styles for the \"before\" button when the widget has a selection handle in an RTL environment.\n * The selection handler is aligned to the right side of the widget so there is no need to create\n * additional space for it next to the \"before\" button.\n */\n.ck[dir=\"rtl\"] .ck-widget.ck-widget_with-selection-handle .ck-widget__type-around > .ck-widget__type-around__button_before {\n\tmargin-left: 0;\n\tmargin-right: 20px;\n}\n\n/*\n * Hide type around buttons when the widget is selected as a child of a selected\n * nested editable (e.g. mulit-cell table selection).\n *\n * See https://github.com/ckeditor/ckeditor5/issues/7263.\n */\n.ck-editor__nested-editable.ck-editor__editable_selected {\n\t& .ck-widget {\n\t\t&.ck-widget_selected,\n\t\t&:hover {\n\t\t\t& > .ck-widget__type-around > .ck-widget__type-around__button {\n\t\t\t\t@mixin ck-widget-type-around-button-hidden;\n\t\t\t}\n\t\t}\n\t}\n}\n\n/*\n * Styles for the buttons when the widget is selected but the user clicked outside of the editor (blurred the editor).\n */\n.ck-editor__editable.ck-blurred .ck-widget.ck-widget_selected > .ck-widget__type-around > .ck-widget__type-around__button:not(:hover) {\n\tbackground: var(--ck-color-widget-type-around-button-blurred-editable);\n\n\t& svg * {\n\t\tstroke: hsl(0,0%,60%);\n\t}\n}\n\n@keyframes ck-widget-type-around-arrow-dash {\n\t0% {\n\t\tstroke-dashoffset: 10;\n\t}\n\t20%, 100% {\n\t\tstroke-dashoffset: 0;\n\t}\n}\n\n@keyframes ck-widget-type-around-arrow-tip-dash {\n\t0%, 20% {\n\t\tstroke-dashoffset: 7;\n\t}\n\t40%, 100% {\n\t\tstroke-dashoffset: 0;\n\t}\n}\n\n@keyframes ck-widget-type-around-button-sonar {\n\t0% {\n\t\tbox-shadow: 0 0 0 0 hsla(var(--ck-color-focus-border-coordinates), var(--ck-color-widget-type-around-button-radar-start-alpha));\n\t}\n\t50% {\n\t\tbox-shadow: 0 0 0 5px hsla(var(--ck-color-focus-border-coordinates), var(--ck-color-widget-type-around-button-radar-end-alpha));\n\t}\n\t100% {\n\t\tbox-shadow: 0 0 0 5px hsla(var(--ck-color-focus-border-coordinates), var(--ck-color-widget-type-around-button-radar-start-alpha));\n\t}\n}\n\n@keyframes ck-widget-type-around-fake-caret-pulse {\n\t0% {\n\t\topacity: 1;\n\t}\n\t49% {\n\t\topacity: 1;\n\t}\n\t50% {\n\t\topacity: 0;\n\t}\n\t99% {\n\t\topacity: 0;\n\t}\n\t100% {\n\t\topacity: 1;\n\t}\n}\n"],"sourceRoot":""}]);
// Exports
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (___CSS_LOADER_EXPORT___);
/***/ }),
/***/ "./node_modules/css-loader/dist/runtime/api.js":
/*!*****************************************************!*\
!*** ./node_modules/css-loader/dist/runtime/api.js ***!
\*****************************************************/
/***/ ((module) => {
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
// css base code, injected by the css-loader
// eslint-disable-next-line func-names
module.exports = function (cssWithMappingToString) {
var list = []; // return the list of modules as css string
list.toString = function toString() {
return this.map(function (item) {
var content = cssWithMappingToString(item);
if (item[2]) {
return "@media ".concat(item[2], " {").concat(content, "}");
}
return content;
}).join("");
}; // import a list of modules into the list
// eslint-disable-next-line func-names
list.i = function (modules, mediaQuery, dedupe) {
if (typeof modules === "string") {
// eslint-disable-next-line no-param-reassign
modules = [[null, modules, ""]];
}
var alreadyImportedModules = {};
if (dedupe) {
for (var i = 0; i < this.length; i++) {
// eslint-disable-next-line prefer-destructuring
var id = this[i][0];
if (id != null) {
alreadyImportedModules[id] = true;
}
}
}
for (var _i = 0; _i < modules.length; _i++) {
var item = [].concat(modules[_i]);
if (dedupe && alreadyImportedModules[item[0]]) {
// eslint-disable-next-line no-continue
continue;
}
if (mediaQuery) {
if (!item[2]) {
item[2] = mediaQuery;
} else {
item[2] = "".concat(mediaQuery, " and ").concat(item[2]);
}
}
list.push(item);
}
};
return list;
};
/***/ }),
/***/ "./node_modules/css-loader/dist/runtime/cssWithMappingToString.js":
/*!************************************************************************!*\
!*** ./node_modules/css-loader/dist/runtime/cssWithMappingToString.js ***!
\************************************************************************/
/***/ ((module) => {
function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
function _iterableToArrayLimit(arr, i) { var _i = arr && (typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"]); if (_i == null) return; var _arr = []; var _n = true; var _d = false; var _s, _e; try { for (_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; }
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
module.exports = function cssWithMappingToString(item) {
var _item = _slicedToArray(item, 4),
content = _item[1],
cssMapping = _item[3];
if (!cssMapping) {
return content;
}
if (typeof btoa === "function") {
// eslint-disable-next-line no-undef
var base64 = btoa(unescape(encodeURIComponent(JSON.stringify(cssMapping))));
var data = "sourceMappingURL=data:application/json;charset=utf-8;base64,".concat(base64);
var sourceMapping = "/*# ".concat(data, " */");
var sourceURLs = cssMapping.sources.map(function (source) {
return "/*# sourceURL=".concat(cssMapping.sourceRoot || "").concat(source, " */");
});
return [content].concat(sourceURLs).concat([sourceMapping]).join("\n");
}
return [content].join("\n");
};
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-basic-styles/theme/icons/bold.svg":
/*!****************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-basic-styles/theme/icons/bold.svg ***!
\****************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M10.187 17H5.773c-.637 0-1.092-.138-1.364-.415-.273-.277-.409-.718-.409-1.323V4.738c0-.617.14-1.062.419-1.332.279-.27.73-.406 1.354-.406h4.68c.69 0 1.288.041 1.793.124.506.083.96.242 1.36.478.341.197.644.447.906.75a3.262 3.262 0 0 1 .808 2.162c0 1.401-.722 2.426-2.167 3.075C15.05 10.175 16 11.315 16 13.01a3.756 3.756 0 0 1-2.296 3.504 6.1 6.1 0 0 1-1.517.377c-.571.073-1.238.11-2 .11zm-.217-6.217H7v4.087h3.069c1.977 0 2.965-.69 2.965-2.072 0-.707-.256-1.22-.768-1.537-.512-.319-1.277-.478-2.296-.478zM7 5.13v3.619h2.606c.729 0 1.292-.067 1.69-.2a1.6 1.6 0 0 0 .91-.765c.165-.267.247-.566.247-.897 0-.707-.26-1.176-.778-1.409-.519-.232-1.31-.348-2.375-.348H7z\"/></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-basic-styles/theme/icons/code.svg":
/*!****************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-basic-styles/theme/icons/code.svg ***!
\****************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"m12.5 5.7 5.2 3.9v1.3l-5.6 4c-.1.2-.3.2-.5.2-.3-.1-.6-.7-.6-1l.3-.4 4.7-3.5L11.5 7l-.2-.2c-.1-.3-.1-.6 0-.8.2-.2.5-.4.8-.4a.8.8 0 0 1 .4.1zm-5.2 0L2 9.6v1.3l5.6 4c.1.2.3.2.5.2.3-.1.7-.7.6-1 0-.1 0-.3-.2-.4l-5-3.5L8.2 7l.2-.2c.1-.3.1-.6 0-.8-.2-.2-.5-.4-.8-.4a.8.8 0 0 0-.3.1z\"/></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-basic-styles/theme/icons/italic.svg":
/*!******************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-basic-styles/theme/icons/italic.svg ***!
\******************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"m9.586 14.633.021.004c-.036.335.095.655.393.962.082.083.173.15.274.201h1.474a.6.6 0 1 1 0 1.2H5.304a.6.6 0 0 1 0-1.2h1.15c.474-.07.809-.182 1.005-.334.157-.122.291-.32.404-.597l2.416-9.55a1.053 1.053 0 0 0-.281-.823 1.12 1.12 0 0 0-.442-.296H8.15a.6.6 0 0 1 0-1.2h6.443a.6.6 0 1 1 0 1.2h-1.195c-.376.056-.65.155-.823.296-.215.175-.423.439-.623.79l-2.366 9.347z\"/></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-code-block/theme/icons/codeblock.svg":
/*!*******************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-code-block/theme/icons/codeblock.svg ***!
\*******************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M12.87 12.61a.75.75 0 0 1-.089.976l-.085.07-3.154 2.254 3.412 2.414a.75.75 0 0 1 .237.95l-.057.095a.75.75 0 0 1-.95.237l-.096-.058-4.272-3.022-.003-1.223 4.01-2.867a.75.75 0 0 1 1.047.174zm2.795-.231.095.057 4.011 2.867-.003 1.223-4.272 3.022-.095.058a.75.75 0 0 1-.88-.151l-.07-.086-.058-.095a.75.75 0 0 1 .15-.88l.087-.07 3.412-2.414-3.154-2.253-.085-.071a.75.75 0 0 1 .862-1.207zM16 0a2 2 0 0 1 2 2v9.354l-.663-.492-.837-.001V2a.5.5 0 0 0-.5-.5H2a.5.5 0 0 0-.5.5v15a.5.5 0 0 0 .5.5h3.118L7.156 19H2a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2h14zM5.009 15l.003 1H3v-1h2.009zm2.188-2-1.471 1H5v-1h2.197zM10 11v.095L8.668 12H7v-1h3zm4-2v1H7V9h7zm0-2v1H7V7h7zm-4-2v1H5V5h5zM6 3v1H3V3h3z\"/></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-core/theme/icons/align-bottom.svg":
/*!****************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-core/theme/icons/align-bottom.svg ***!
\****************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"m9.239 13.938-2.88-1.663a.75.75 0 0 1 .75-1.3L9 12.067V4.75a.75.75 0 1 1 1.5 0v7.318l1.89-1.093a.75.75 0 0 1 .75 1.3l-2.879 1.663a.752.752 0 0 1-.511.187.752.752 0 0 1-.511-.187zM4.25 17a.75.75 0 1 1 0-1.5h10.5a.75.75 0 0 1 0 1.5H4.25z\"/></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-core/theme/icons/align-center.svg":
/*!****************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-core/theme/icons/align-center.svg ***!
\****************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M2 3.75c0 .414.336.75.75.75h14.5a.75.75 0 1 0 0-1.5H2.75a.75.75 0 0 0-.75.75zm0 8c0 .414.336.75.75.75h14.5a.75.75 0 1 0 0-1.5H2.75a.75.75 0 0 0-.75.75zm2.286 4c0 .414.336.75.75.75h9.928a.75.75 0 1 0 0-1.5H5.036a.75.75 0 0 0-.75.75zm0-8c0 .414.336.75.75.75h9.928a.75.75 0 1 0 0-1.5H5.036a.75.75 0 0 0-.75.75z\"/></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-core/theme/icons/align-justify.svg":
/*!*****************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-core/theme/icons/align-justify.svg ***!
\*****************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M2 3.75c0 .414.336.75.75.75h14.5a.75.75 0 1 0 0-1.5H2.75a.75.75 0 0 0-.75.75zm0 8c0 .414.336.75.75.75h14.5a.75.75 0 1 0 0-1.5H2.75a.75.75 0 0 0-.75.75zm0 4c0 .414.336.75.75.75h9.929a.75.75 0 1 0 0-1.5H2.75a.75.75 0 0 0-.75.75zm0-8c0 .414.336.75.75.75h14.5a.75.75 0 1 0 0-1.5H2.75a.75.75 0 0 0-.75.75z\"/></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-core/theme/icons/align-left.svg":
/*!**************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-core/theme/icons/align-left.svg ***!
\**************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M2 3.75c0 .414.336.75.75.75h14.5a.75.75 0 1 0 0-1.5H2.75a.75.75 0 0 0-.75.75zm0 8c0 .414.336.75.75.75h14.5a.75.75 0 1 0 0-1.5H2.75a.75.75 0 0 0-.75.75zm0 4c0 .414.336.75.75.75h9.929a.75.75 0 1 0 0-1.5H2.75a.75.75 0 0 0-.75.75zm0-8c0 .414.336.75.75.75h9.929a.75.75 0 1 0 0-1.5H2.75a.75.75 0 0 0-.75.75z\"/></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-core/theme/icons/align-middle.svg":
/*!****************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-core/theme/icons/align-middle.svg ***!
\****************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M9.75 11.875a.752.752 0 0 1 .508.184l2.883 1.666a.75.75 0 0 1-.659 1.344l-.091-.044-1.892-1.093.001 4.318a.75.75 0 1 1-1.5 0v-4.317l-1.89 1.092a.75.75 0 0 1-.75-1.3l2.879-1.663a.752.752 0 0 1 .51-.187zM15.25 9a.75.75 0 1 1 0 1.5H4.75a.75.75 0 1 1 0-1.5h10.5zM9.75.375a.75.75 0 0 1 .75.75v4.318l1.89-1.093.092-.045a.75.75 0 0 1 .659 1.344l-2.883 1.667a.752.752 0 0 1-.508.184.752.752 0 0 1-.511-.187L6.359 5.65a.75.75 0 0 1 .75-1.299L9 5.442V1.125a.75.75 0 0 1 .75-.75z\"/></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-core/theme/icons/align-right.svg":
/*!***************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-core/theme/icons/align-right.svg ***!
\***************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M18 3.75a.75.75 0 0 1-.75.75H2.75a.75.75 0 1 1 0-1.5h14.5a.75.75 0 0 1 .75.75zm0 8a.75.75 0 0 1-.75.75H2.75a.75.75 0 1 1 0-1.5h14.5a.75.75 0 0 1 .75.75zm0 4a.75.75 0 0 1-.75.75H7.321a.75.75 0 1 1 0-1.5h9.929a.75.75 0 0 1 .75.75zm0-8a.75.75 0 0 1-.75.75H7.321a.75.75 0 1 1 0-1.5h9.929a.75.75 0 0 1 .75.75z\"/></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-core/theme/icons/align-top.svg":
/*!*************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-core/theme/icons/align-top.svg ***!
\*************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"m10.261 7.062 2.88 1.663a.75.75 0 0 1-.75 1.3L10.5 8.933v7.317a.75.75 0 1 1-1.5 0V8.932l-1.89 1.093a.75.75 0 0 1-.75-1.3l2.879-1.663a.752.752 0 0 1 .511-.187.752.752 0 0 1 .511.187zM15.25 4a.75.75 0 1 1 0 1.5H4.75a.75.75 0 0 1 0-1.5h10.5z\"/></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-core/theme/icons/cancel.svg":
/*!**********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-core/theme/icons/cancel.svg ***!
\**********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"m11.591 10.177 4.243 4.242a1 1 0 0 1-1.415 1.415l-4.242-4.243-4.243 4.243a1 1 0 0 1-1.414-1.415l4.243-4.242L4.52 5.934A1 1 0 0 1 5.934 4.52l4.243 4.243 4.242-4.243a1 1 0 1 1 1.415 1.414l-4.243 4.243z\"/></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-core/theme/icons/caption.svg":
/*!***********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-core/theme/icons/caption.svg ***!
\***********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M2 16h9a1 1 0 0 1 0 2H2a1 1 0 0 1 0-2z\"/><path d=\"M17 1a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V3a2 2 0 0 1 2-2h14zm0 1.5H3a.5.5 0 0 0-.492.41L2.5 3v9a.5.5 0 0 0 .41.492L3 12.5h14a.5.5 0 0 0 .492-.41L17.5 12V3a.5.5 0 0 0-.41-.492L17 2.5z\" fill-opacity=\".6\"/></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-core/theme/icons/check.svg":
/*!*********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-core/theme/icons/check.svg ***!
\*********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M6.972 16.615a.997.997 0 0 1-.744-.292l-4.596-4.596a1 1 0 1 1 1.414-1.414l3.926 3.926 9.937-9.937a1 1 0 0 1 1.414 1.415L7.717 16.323a.997.997 0 0 1-.745.292z\"/></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-core/theme/icons/cog.svg":
/*!*******************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-core/theme/icons/cog.svg ***!
\*******************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"m11.333 2 .19 2.263a5.899 5.899 0 0 1 1.458.604L14.714 3.4 16.6 5.286l-1.467 1.733c.263.452.468.942.605 1.46L18 8.666v2.666l-2.263.19a5.899 5.899 0 0 1-.604 1.458l1.467 1.733-1.886 1.886-1.733-1.467a5.899 5.899 0 0 1-1.46.605L11.334 18H8.667l-.19-2.263a5.899 5.899 0 0 1-1.458-.604L5.286 16.6 3.4 14.714l1.467-1.733a5.899 5.899 0 0 1-.604-1.458L2 11.333V8.667l2.262-.189a5.899 5.899 0 0 1 .605-1.459L3.4 5.286 5.286 3.4l1.733 1.467a5.899 5.899 0 0 1 1.46-.605L8.666 2h2.666zM10 6.267a3.733 3.733 0 1 0 0 7.466 3.733 3.733 0 0 0 0-7.466z\"/></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-core/theme/icons/eraser.svg":
/*!**********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-core/theme/icons/eraser.svg ***!
\**********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"m8.636 9.531-2.758 3.94a.5.5 0 0 0 .122.696l3.224 2.284h1.314l2.636-3.736L8.636 9.53zm.288 8.451L5.14 15.396a2 2 0 0 1-.491-2.786l6.673-9.53a2 2 0 0 1 2.785-.49l3.742 2.62a2 2 0 0 1 .491 2.785l-7.269 10.053-2.147-.066z\"/><path d=\"M4 18h5.523v-1H4zm-2 0h1v-1H2z\"/></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-core/theme/icons/image.svg":
/*!*********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-core/theme/icons/image.svg ***!
\*********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M6.91 10.54c.26-.23.64-.21.88.03l3.36 3.14 2.23-2.06a.64.64 0 0 1 .87 0l2.52 2.97V4.5H3.2v10.12l3.71-4.08zm10.27-7.51c.6 0 1.09.47 1.09 1.05v11.84c0 .59-.49 1.06-1.09 1.06H2.79c-.6 0-1.09-.47-1.09-1.06V4.08c0-.58.49-1.05 1.1-1.05h14.38zm-5.22 5.56a1.96 1.96 0 1 1 3.4-1.96 1.96 1.96 0 0 1-3.4 1.96z\"/></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-core/theme/icons/low-vision.svg":
/*!**************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-core/theme/icons/low-vision.svg ***!
\**************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M5.085 6.22 2.943 4.078a.75.75 0 1 1 1.06-1.06l2.592 2.59A11.094 11.094 0 0 1 10 5.068c4.738 0 8.578 3.101 8.578 5.083 0 1.197-1.401 2.803-3.555 3.887l1.714 1.713a.75.75 0 0 1-.09 1.138.488.488 0 0 1-.15.084.75.75 0 0 1-.821-.16L6.17 7.304c-.258.11-.51.233-.757.365l6.239 6.24-.006.005.78.78c-.388.094-.78.166-1.174.215l-1.11-1.11h.011L4.55 8.197a7.2 7.2 0 0 0-.665.514l-.112.098 4.897 4.897-.005.006 1.276 1.276a10.164 10.164 0 0 1-1.477-.117l-.479-.479-.009.009-4.863-4.863-.022.031a2.563 2.563 0 0 0-.124.2c-.043.077-.08.158-.108.241a.534.534 0 0 0-.028.133.29.29 0 0 0 .008.072.927.927 0 0 0 .082.226c.067.133.145.26.234.379l3.242 3.365.025.01.59.623c-3.265-.918-5.59-3.155-5.59-4.668 0-1.194 1.448-2.838 3.663-3.93zm7.07.531a4.632 4.632 0 0 1 1.108 5.992l.345.344.046-.018a9.313 9.313 0 0 0 2-1.112c.256-.187.5-.392.727-.613.137-.134.27-.277.392-.431.072-.091.141-.185.203-.286.057-.093.107-.19.148-.292a.72.72 0 0 0 .036-.12.29.29 0 0 0 .008-.072.492.492 0 0 0-.028-.133.999.999 0 0 0-.036-.096 2.165 2.165 0 0 0-.071-.145 2.917 2.917 0 0 0-.125-.2 3.592 3.592 0 0 0-.263-.335 5.444 5.444 0 0 0-.53-.523 7.955 7.955 0 0 0-1.054-.768 9.766 9.766 0 0 0-1.879-.891c-.337-.118-.68-.219-1.027-.301zm-2.85.21-.069.002a.508.508 0 0 0-.254.097.496.496 0 0 0-.104.679.498.498 0 0 0 .326.199l.045.005c.091.003.181.003.272.012a2.45 2.45 0 0 1 2.017 1.513c.024.061.043.125.069.185a.494.494 0 0 0 .45.287h.008a.496.496 0 0 0 .35-.158.482.482 0 0 0 .13-.335.638.638 0 0 0-.048-.219 3.379 3.379 0 0 0-.36-.723 3.438 3.438 0 0 0-2.791-1.543l-.028-.001h-.013z\"/></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-core/theme/icons/object-center.svg":
/*!*****************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-core/theme/icons/object-center.svg ***!
\*****************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path opacity=\".5\" d=\"M2 3h16v1.5H2zm0 12h16v1.5H2z\"/><path d=\"M15.003 7v5.5a1 1 0 0 1-1 1H5.996a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h8.007a1 1 0 0 1 1 1zm-1.506.5H6.5V12h6.997V7.5z\"/></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-core/theme/icons/object-full-width.svg":
/*!*********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-core/theme/icons/object-full-width.svg ***!
\*********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path opacity=\".5\" d=\"M2 3h16v1.5H2zm0 12h16v1.5H2z\"/><path d=\"M18 7v5.5a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1zm-1.505.5H3.504V12h12.991V7.5z\"/></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-core/theme/icons/object-inline-left.svg":
/*!**********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-core/theme/icons/object-inline-left.svg ***!
\**********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path opacity=\".5\" d=\"M2 3h16v1.5H2zm11.5 9H18v1.5h-4.5zm0-3H18v1.5h-4.5zm0-3H18v1.5h-4.5zM2 15h16v1.5H2z\"/><path d=\"M12.003 7v5.5a1 1 0 0 1-1 1H2.996a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h8.007a1 1 0 0 1 1 1zm-1.506.5H3.5V12h6.997V7.5z\"/></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-core/theme/icons/object-inline-right.svg":
/*!***********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-core/theme/icons/object-inline-right.svg ***!
\***********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path opacity=\".5\" d=\"M2 3h16v1.5H2zm0 12h16v1.5H2zm0-9h5v1.5H2zm0 3h5v1.5H2zm0 3h5v1.5H2z\"/><path d=\"M18.003 7v5.5a1 1 0 0 1-1 1H8.996a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h8.007a1 1 0 0 1 1 1zm-1.506.5H9.5V12h6.997V7.5z\"/></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-core/theme/icons/object-inline.svg":
/*!*****************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-core/theme/icons/object-inline.svg ***!
\*****************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path opacity=\".5\" d=\"M2 3h16v1.5H2zm11.5 9H18v1.5h-4.5zM2 15h16v1.5H2z\"/><path d=\"M12.003 7v5.5a1 1 0 0 1-1 1H2.996a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h8.007a1 1 0 0 1 1 1zm-1.506.5H3.5V12h6.997V7.5z\"/></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-core/theme/icons/object-left.svg":
/*!***************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-core/theme/icons/object-left.svg ***!
\***************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path opacity=\".5\" d=\"M2 3h16v1.5H2zm0 12h16v1.5H2z\"/><path d=\"M12.003 7v5.5a1 1 0 0 1-1 1H2.996a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h8.007a1 1 0 0 1 1 1zm-1.506.5H3.5V12h6.997V7.5z\"/></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-core/theme/icons/object-right.svg":
/*!****************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-core/theme/icons/object-right.svg ***!
\****************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path opacity=\".5\" d=\"M2 3h16v1.5H2zm0 12h16v1.5H2z\"/><path d=\"M18.003 7v5.5a1 1 0 0 1-1 1H8.996a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h8.007a1 1 0 0 1 1 1zm-1.506.5H9.5V12h6.997V7.5z\"/></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-core/theme/icons/object-size-full.svg":
/*!********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-core/theme/icons/object-size-full.svg ***!
\********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 20 20\"><path d=\"M2.5 17v1h-1v-1h1zm2 0v1h-1v-1h1zm2 0v1h-1v-1h1zm2 0v1h-1v-1h1zm2 0v1h-1v-1h1zm2 0v1h-1v-1h1zm2 0v1h-1v-1h1zm2 0v1h-1v-1h1zm2 0v1h-1v-1h1zM1 15.5v1H0v-1h1zm19 0v1h-1v-1h1zm-19-2v1H0v-1h1zm19 0v1h-1v-1h1zm-19-2v1H0v-1h1zm19 0v1h-1v-1h1zm-19-2v1H0v-1h1zm19 0v1h-1v-1h1zm-19-2v1H0v-1h1zm19 0v1h-1v-1h1zm-19-2v1H0v-1h1zm19 0v1h-1v-1h1zm0-2v1h-1v-1h1zm-19 0v1H0v-1h1zM14.5 2v1h-1V2h1zm2 0v1h-1V2h1zm2 0v1h-1V2h1zm-8 0v1h-1V2h1zm-2 0v1h-1V2h1zm-2 0v1h-1V2h1zm-2 0v1h-1V2h1zm8 0v1h-1V2h1zm-10 0v1h-1V2h1z\"/><path d=\"M18.095 2H1.905C.853 2 0 2.895 0 4v12c0 1.105.853 2 1.905 2h16.19C19.147 18 20 17.105 20 16V4c0-1.105-.853-2-1.905-2zm0 1.5c.263 0 .476.224.476.5v12c0 .276-.213.5-.476.5H1.905a.489.489 0 0 1-.476-.5V4c0-.276.213-.5.476-.5h16.19z\"/></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-core/theme/icons/object-size-large.svg":
/*!*********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-core/theme/icons/object-size-large.svg ***!
\*********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 20 20\"><path d=\"M2.5 17v1h-1v-1h1zm2 0v1h-1v-1h1zm2 0v1h-1v-1h1zm2 0v1h-1v-1h1zm2 0v1h-1v-1h1zm2 0v1h-1v-1h1zm2 0v1h-1v-1h1zm2 0v1h-1v-1h1zm2 0v1h-1v-1h1zM1 15.5v1H0v-1h1zm19 0v1h-1v-1h1zm-19-2v1H0v-1h1zm19 0v1h-1v-1h1zm-19-2v1H0v-1h1zm19 0v1h-1v-1h1zm-19-2v1H0v-1h1zm19 0v1h-1v-1h1zm-19-2v1H0v-1h1zm19 0v1h-1v-1h1zm-19-2v1H0v-1h1zm19 0v1h-1v-1h1zm0-2v1h-1v-1h1zm-19 0v1H0v-1h1zM14.5 2v1h-1V2h1zm2 0v1h-1V2h1zm2 0v1h-1V2h1zm-8 0v1h-1V2h1zm-2 0v1h-1V2h1zm-2 0v1h-1V2h1zm-2 0v1h-1V2h1zm8 0v1h-1V2h1zm-10 0v1h-1V2h1z\"/><path d=\"M13 6H2a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h11a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2zm0 1.5a.5.5 0 0 1 .5.5v8a.5.5 0 0 1-.5.5H2a.5.5 0 0 1-.5-.5V8a.5.5 0 0 1 .5-.5h11z\"/></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-core/theme/icons/object-size-medium.svg":
/*!**********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-core/theme/icons/object-size-medium.svg ***!
\**********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 20 20\"><path d=\"M2.5 17v1h-1v-1h1zm2 0v1h-1v-1h1zm2 0v1h-1v-1h1zm2 0v1h-1v-1h1zm2 0v1h-1v-1h1zm2 0v1h-1v-1h1zm2 0v1h-1v-1h1zm2 0v1h-1v-1h1zm2 0v1h-1v-1h1zM1 15.5v1H0v-1h1zm19 0v1h-1v-1h1zm-19-2v1H0v-1h1zm19 0v1h-1v-1h1zm-19-2v1H0v-1h1zm19 0v1h-1v-1h1zm-19-2v1H0v-1h1zm19 0v1h-1v-1h1zm-19-2v1H0v-1h1zm19 0v1h-1v-1h1zm-19-2v1H0v-1h1zm19 0v1h-1v-1h1zm0-2v1h-1v-1h1zm-19 0v1H0v-1h1zM14.5 2v1h-1V2h1zm2 0v1h-1V2h1zm2 0v1h-1V2h1zm-8 0v1h-1V2h1zm-2 0v1h-1V2h1zm-2 0v1h-1V2h1zm-2 0v1h-1V2h1zm8 0v1h-1V2h1zm-10 0v1h-1V2h1z\"/><path d=\"M10 8H2a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2v-6a2 2 0 0 0-2-2zm0 1.5a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-.5.5H2a.5.5 0 0 1-.5-.5v-6a.5.5 0 0 1 .5-.5h8z\"/></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-core/theme/icons/object-size-small.svg":
/*!*********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-core/theme/icons/object-size-small.svg ***!
\*********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 20 20\"><path d=\"M2.5 17v1h-1v-1h1zm2 0v1h-1v-1h1zm2 0v1h-1v-1h1zm2 0v1h-1v-1h1zm2 0v1h-1v-1h1zm2 0v1h-1v-1h1zm2 0v1h-1v-1h1zm2 0v1h-1v-1h1zm2 0v1h-1v-1h1zM1 15.5v1H0v-1h1zm19 0v1h-1v-1h1zm-19-2v1H0v-1h1zm19 0v1h-1v-1h1zm-19-2v1H0v-1h1zm19 0v1h-1v-1h1zm-19-2v1H0v-1h1zm19 0v1h-1v-1h1zm-19-2v1H0v-1h1zm19 0v1h-1v-1h1zm-19-2v1H0v-1h1zm19 0v1h-1v-1h1zm0-2v1h-1v-1h1zm-19 0v1H0v-1h1zM14.5 2v1h-1V2h1zm2 0v1h-1V2h1zm2 0v1h-1V2h1zm-8 0v1h-1V2h1zm-2 0v1h-1V2h1zm-2 0v1h-1V2h1zm-2 0v1h-1V2h1zm8 0v1h-1V2h1zm-10 0v1h-1V2h1z\"/><path d=\"M7 10H2a2 2 0 0 0-2 2v4a2 2 0 0 0 2 2h5a2 2 0 0 0 2-2v-4a2 2 0 0 0-2-2zm0 1.5a.5.5 0 0 1 .5.5v4a.5.5 0 0 1-.5.5H2a.5.5 0 0 1-.5-.5v-4a.5.5 0 0 1 .5-.5h5z\"/></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-core/theme/icons/pencil.svg":
/*!**********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-core/theme/icons/pencil.svg ***!
\**********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"m7.3 17.37-.061.088a1.518 1.518 0 0 1-.934.535l-4.178.663-.806-4.153a1.495 1.495 0 0 1 .187-1.058l.056-.086L8.77 2.639c.958-1.351 2.803-1.076 4.296-.03 1.497 1.047 2.387 2.693 1.433 4.055L7.3 17.37zM9.14 4.728l-5.545 8.346 3.277 2.294 5.544-8.346L9.14 4.728zM6.07 16.512l-3.276-2.295.53 2.73 2.746-.435zM9.994 3.506 13.271 5.8c.316-.452-.16-1.333-1.065-1.966-.905-.634-1.895-.78-2.212-.328zM8 18.5 9.375 17H19v1.5H8z\"/></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-core/theme/icons/pilcrow.svg":
/*!***********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-core/theme/icons/pilcrow.svg ***!
\***********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M6.999 2H15a1 1 0 0 1 0 2h-1.004v13a1 1 0 1 1-2 0V4H8.999v13a1 1 0 1 1-2 0v-7A4 4 0 0 1 3 6a4 4 0 0 1 3.999-4z\"/></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-core/theme/icons/quote.svg":
/*!*********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-core/theme/icons/quote.svg ***!
\*********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M3 10.423a6.5 6.5 0 0 1 6.056-6.408l.038.67C6.448 5.423 5.354 7.663 5.22 10H9c.552 0 .5.432.5.986v4.511c0 .554-.448.503-1 .503h-5c-.552 0-.5-.449-.5-1.003v-4.574zm8 0a6.5 6.5 0 0 1 6.056-6.408l.038.67c-2.646.739-3.74 2.979-3.873 5.315H17c.552 0 .5.432.5.986v4.511c0 .554-.448.503-1 .503h-5c-.552 0-.5-.449-.5-1.003v-4.574z\"/></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-core/theme/icons/three-vertical-dots.svg":
/*!***********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-core/theme/icons/three-vertical-dots.svg ***!
\***********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><circle cx=\"9.5\" cy=\"4.5\" r=\"1.5\"/><circle cx=\"9.5\" cy=\"10.5\" r=\"1.5\"/><circle cx=\"9.5\" cy=\"16.5\" r=\"1.5\"/></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-find-and-replace/theme/icons/find-replace.svg":
/*!****************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-find-and-replace/theme/icons/find-replace.svg ***!
\****************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"m12.87 13.786 1.532-1.286 3.857 4.596a1 1 0 1 1-1.532 1.286l-3.857-4.596z\"/><path d=\"M16.004 8.5a6.5 6.5 0 0 1-9.216 5.905c-1.154-.53-.863-1.415-.663-1.615.194-.194.564-.592 1.635-.141a4.5 4.5 0 0 0 5.89-5.904l-.104-.227 1.332-1.331c.045-.046.196-.041.224.007a6.47 6.47 0 0 1 .902 3.306zm-3.4-5.715c.562.305.742 1.106.354 1.494-.388.388-.995.414-1.476.178a4.5 4.5 0 0 0-6.086 5.882l.114.236-1.348 1.349c-.038.037-.17.022-.198-.023a6.5 6.5 0 0 1 5.54-9.9 6.469 6.469 0 0 1 3.1.784z\"/><path d=\"M4.001 11.93.948 8.877a.2.2 0 0 1 .141-.341h6.106a.2.2 0 0 1 .141.341L4.283 11.93a.2.2 0 0 1-.282 0zm11.083-6.789 3.053 3.053a.2.2 0 0 1-.14.342H11.89a.2.2 0 0 1-.14-.342l3.052-3.053a.2.2 0 0 1 .282 0z\"/></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-font/theme/icons/font-background.svg":
/*!*******************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-font/theme/icons/font-background.svg ***!
\*******************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M4 2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2zm8.38 9.262H7.62L10 5.506l2.38 5.756zm.532 1.285L14.34 16h1.426L10.804 4H9.196L4.234 16H5.66l1.428-3.453h5.824z\"/></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-font/theme/icons/font-color.svg":
/*!**************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-font/theme/icons/font-color.svg ***!
\**************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M12.4 10.3 10 4.5l-2.4 5.8h4.8zm.5 1.2H7.1L5.7 15H4.2l5-12h1.6l5 12h-1.5L13 11.5zm3.1 7H4a1 1 0 0 1 0-2h12a1 1 0 0 1 0 2z\"/></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-font/theme/icons/font-family.svg":
/*!***************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-font/theme/icons/font-family.svg ***!
\***************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M11.03 3h6.149a.75.75 0 1 1 0 1.5h-5.514L11.03 3zm1.27 3h4.879a.75.75 0 1 1 0 1.5h-4.244L12.3 6zm1.27 3h3.609a.75.75 0 1 1 0 1.5h-2.973L13.57 9zm-2.754 2.5L8.038 4.785 5.261 11.5h5.555zm.62 1.5H4.641l-1.666 4.028H1.312l5.789-14h1.875l5.789 14h-1.663L11.436 13z\"/></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-font/theme/icons/font-size.svg":
/*!*************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-font/theme/icons/font-size.svg ***!
\*************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M9.816 11.5 7.038 4.785 4.261 11.5h5.555zm.62 1.5H3.641l-1.666 4.028H.312l5.789-14h1.875l5.789 14h-1.663L10.436 13zm7.55 2.279.779-.779.707.707-2.265 2.265-2.193-2.265.707-.707.765.765V4.825c0-.042 0-.083.002-.123l-.77.77-.707-.707L17.207 2.5l2.265 2.265-.707.707-.782-.782c.002.043.003.089.003.135v10.454z\"/></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-highlight/theme/icons/marker.svg":
/*!***************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-highlight/theme/icons/marker.svg ***!
\***************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path class=\"ck-icon__fill\" d=\"M10.798 1.59 3.002 12.875l1.895 1.852 2.521 1.402 6.997-12.194z\"/><path d=\"m2.556 16.727.234-.348c-.297-.151-.462-.293-.498-.426-.036-.137.002-.416.115-.837.094-.25.15-.449.169-.595a4.495 4.495 0 0 0 0-.725c-.209-.621-.303-1.041-.284-1.26.02-.218.178-.506.475-.862l6.77-9.414c.539-.91 1.605-.85 3.199.18 1.594 1.032 2.188 1.928 1.784 2.686l-5.877 10.36c-.158.412-.333.673-.526.782-.193.108-.604.179-1.232.21-.362.131-.608.237-.738.318-.13.081-.305.238-.526.47-.293.265-.504.397-.632.397-.096 0-.27-.075-.524-.226l-.31.41-1.6-1.12zm-.279.415 1.575 1.103-.392.515H1.19l1.087-1.618zm8.1-13.656-4.953 6.9L8.75 12.57l4.247-7.574c.175-.25-.188-.647-1.092-1.192-.903-.546-1.412-.652-1.528-.32zM8.244 18.5 9.59 17h9.406v1.5H8.245z\"/></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-highlight/theme/icons/pen.svg":
/*!************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-highlight/theme/icons/pen.svg ***!
\************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path class=\"ck-icon__fill\" d=\"M10.126 2.268 2.002 13.874l1.895 1.852 2.521 1.402L14.47 5.481l-1.543-2.568-2.801-.645z\"/><path d=\"m4.5 18.088-2.645-1.852-.04-2.95-.006-.005.006-.008v-.025l.011.008L8.73 2.97c.165-.233.356-.417.567-.557l-1.212.308L4.604 7.9l-.83-.558 3.694-5.495 2.708-.69 1.65 1.145.046.018.85-1.216 2.16 1.512-.856 1.222c.828.967 1.144 2.141.432 3.158L7.55 17.286l.006.005-3.055.797H4.5zm-.634.166-1.976.516-.026-1.918 2.002 1.402zM9.968 3.817l-.006-.004-6.123 9.184 3.277 2.294 6.108-9.162.005.003c.317-.452-.16-1.332-1.064-1.966-.891-.624-1.865-.776-2.197-.349zM8.245 18.5 9.59 17h9.406v1.5H8.245z\"/></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-horizontal-line/theme/icons/horizontalline.svg":
/*!*****************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-horizontal-line/theme/icons/horizontalline.svg ***!
\*****************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M2 9h16v2H2z\"/></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-html-embed/theme/icons/html.svg":
/*!**************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-html-embed/theme/icons/html.svg ***!
\**************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M17 0a2 2 0 0 1 2 2v7a1 1 0 0 1 1 1v5a1 1 0 0 1-.883.993l-.118.006L19 17a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2l-.001-1.001-.116-.006A1 1 0 0 1 0 15v-5a1 1 0 0 1 .999-1L1 2a2 2 0 0 1 2-2h14zm.499 15.999h-15L2.5 17a.5.5 0 0 0 .5.5h14a.5.5 0 0 0 .5-.5l-.001-1.001zm-3.478-6.013-.014.014H14v.007l-1.525 1.525-1.46-1.46-.015.013V10h-1v5h1v-3.53l1.428 1.43.048.043.131-.129L14 11.421V15h1v-5h-.965l-.014-.014zM2 10H1v5h1v-2h2v2h1v-5H4v2H2v-2zm7 0H6v1h1v4h1v-4h1v-1zm8 0h-1v5h3v-1h-2v-4zm0-8.5H3a.5.5 0 0 0-.5.5l-.001 6.999h15L17.5 2a.5.5 0 0 0-.5-.5zM10 7v1H4V7h6zm3-2v1H4V5h9zm-3-2v1H4V3h6z\"/></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-indent/theme/icons/indent.svg":
/*!************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-indent/theme/icons/indent.svg ***!
\************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M2 3.75c0 .414.336.75.75.75h14.5a.75.75 0 1 0 0-1.5H2.75a.75.75 0 0 0-.75.75zm5 6c0 .414.336.75.75.75h9.5a.75.75 0 1 0 0-1.5h-9.5a.75.75 0 0 0-.75.75zM2.75 16.5h14.5a.75.75 0 1 0 0-1.5H2.75a.75.75 0 1 0 0 1.5zM1.632 6.95 5.02 9.358a.4.4 0 0 1-.013.661l-3.39 2.207A.4.4 0 0 1 1 11.892V7.275a.4.4 0 0 1 .632-.326z\"/></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-indent/theme/icons/outdent.svg":
/*!*************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-indent/theme/icons/outdent.svg ***!
\*************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M2 3.75c0 .414.336.75.75.75h14.5a.75.75 0 1 0 0-1.5H2.75a.75.75 0 0 0-.75.75zm5 6c0 .414.336.75.75.75h9.5a.75.75 0 1 0 0-1.5h-9.5a.75.75 0 0 0-.75.75zM2.75 16.5h14.5a.75.75 0 1 0 0-1.5H2.75a.75.75 0 1 0 0 1.5zm1.618-9.55L.98 9.358a.4.4 0 0 0 .013.661l3.39 2.207A.4.4 0 0 0 5 11.892V7.275a.4.4 0 0 0-.632-.326z\"/></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-link/theme/icons/link.svg":
/*!********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-link/theme/icons/link.svg ***!
\********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"m11.077 15 .991-1.416a.75.75 0 1 1 1.229.86l-1.148 1.64a.748.748 0 0 1-.217.206 5.251 5.251 0 0 1-8.503-5.955.741.741 0 0 1 .12-.274l1.147-1.639a.75.75 0 1 1 1.228.86L4.933 10.7l.006.003a3.75 3.75 0 0 0 6.132 4.294l.006.004zm5.494-5.335a.748.748 0 0 1-.12.274l-1.147 1.639a.75.75 0 1 1-1.228-.86l.86-1.23a3.75 3.75 0 0 0-6.144-4.301l-.86 1.229a.75.75 0 0 1-1.229-.86l1.148-1.64a.748.748 0 0 1 .217-.206 5.251 5.251 0 0 1 8.503 5.955zm-4.563-2.532a.75.75 0 0 1 .184 1.045l-3.155 4.505a.75.75 0 1 1-1.229-.86l3.155-4.506a.75.75 0 0 1 1.045-.184z\"/></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-link/theme/icons/unlink.svg":
/*!**********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-link/theme/icons/unlink.svg ***!
\**********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"m11.077 15 .991-1.416a.75.75 0 1 1 1.229.86l-1.148 1.64a.748.748 0 0 1-.217.206 5.251 5.251 0 0 1-8.503-5.955.741.741 0 0 1 .12-.274l1.147-1.639a.75.75 0 1 1 1.228.86L4.933 10.7l.006.003a3.75 3.75 0 0 0 6.132 4.294l.006.004zm5.494-5.335a.748.748 0 0 1-.12.274l-1.147 1.639a.75.75 0 1 1-1.228-.86l.86-1.23a3.75 3.75 0 0 0-6.144-4.301l-.86 1.229a.75.75 0 0 1-1.229-.86l1.148-1.64a.748.748 0 0 1 .217-.206 5.251 5.251 0 0 1 8.503 5.955zm-4.563-2.532a.75.75 0 0 1 .184 1.045l-3.155 4.505a.75.75 0 1 1-1.229-.86l3.155-4.506a.75.75 0 0 1 1.045-.184zm4.919 10.562-1.414 1.414a.75.75 0 1 1-1.06-1.06l1.414-1.415-1.415-1.414a.75.75 0 0 1 1.061-1.06l1.414 1.414 1.414-1.415a.75.75 0 0 1 1.061 1.061l-1.414 1.414 1.414 1.415a.75.75 0 0 1-1.06 1.06l-1.415-1.414z\"/></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-list/theme/icons/bulletedlist.svg":
/*!****************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-list/theme/icons/bulletedlist.svg ***!
\****************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M7 5.75c0 .414.336.75.75.75h9.5a.75.75 0 1 0 0-1.5h-9.5a.75.75 0 0 0-.75.75zm-6 0C1 4.784 1.777 4 2.75 4c.966 0 1.75.777 1.75 1.75 0 .966-.777 1.75-1.75 1.75C1.784 7.5 1 6.723 1 5.75zm6 9c0 .414.336.75.75.75h9.5a.75.75 0 1 0 0-1.5h-9.5a.75.75 0 0 0-.75.75zm-6 0c0-.966.777-1.75 1.75-1.75.966 0 1.75.777 1.75 1.75 0 .966-.777 1.75-1.75 1.75-.966 0-1.75-.777-1.75-1.75z\"/></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-list/theme/icons/numberedlist.svg":
/*!****************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-list/theme/icons/numberedlist.svg ***!
\****************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M7 5.75c0 .414.336.75.75.75h9.5a.75.75 0 1 0 0-1.5h-9.5a.75.75 0 0 0-.75.75zM3.5 3v5H2V3.7H1v-1h2.5V3zM.343 17.857l2.59-3.257H2.92a.6.6 0 1 0-1.04 0H.302a2 2 0 1 1 3.995 0h-.001c-.048.405-.16.734-.333.988-.175.254-.59.692-1.244 1.312H4.3v1h-4l.043-.043zM7 14.75a.75.75 0 0 1 .75-.75h9.5a.75.75 0 1 1 0 1.5h-9.5a.75.75 0 0 1-.75-.75z\"/></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-media-embed/theme/icons/media-placeholder.svg":
/*!****************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-media-embed/theme/icons/media-placeholder.svg ***!
\****************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg viewBox=\"0 0 64 42\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M47.426 17V3.713L63.102 0v19.389h-.001l.001.272c0 1.595-2.032 3.43-4.538 4.098-2.506.668-4.538-.083-4.538-1.678 0-1.594 2.032-3.43 4.538-4.098.914-.244 2.032-.565 2.888-.603V4.516L49.076 7.447v9.556A1.014 1.014 0 0 0 49 17h-1.574zM29.5 17h-8.343a7.073 7.073 0 1 0-4.657 4.06v3.781H3.3a2.803 2.803 0 0 1-2.8-2.804V8.63a2.803 2.803 0 0 1 2.8-2.805h4.082L8.58 2.768A1.994 1.994 0 0 1 10.435 1.5h8.985c.773 0 1.477.448 1.805 1.149l1.488 3.177H26.7c1.546 0 2.8 1.256 2.8 2.805V17zm-11.637 0H17.5a1 1 0 0 0-1 1v.05A4.244 4.244 0 1 1 17.863 17zm29.684 2c.97 0 .953-.048.953.889v20.743c0 .953.016.905-.953.905H19.453c-.97 0-.953.048-.953-.905V19.89c0-.937-.016-.889.97-.889h28.077zm-4.701 19.338V22.183H24.154v16.155h18.692zM20.6 21.375v1.616h1.616v-1.616H20.6zm0 3.231v1.616h1.616v-1.616H20.6zm0 3.231v1.616h1.616v-1.616H20.6zm0 3.231v1.616h1.616v-1.616H20.6zm0 3.231v1.616h1.616v-1.616H20.6zm0 3.231v1.616h1.616V37.53H20.6zm24.233-16.155v1.616h1.615v-1.616h-1.615zm0 3.231v1.616h1.615v-1.616h-1.615zm0 3.231v1.616h1.615v-1.616h-1.615zm0 3.231v1.616h1.615v-1.616h-1.615zm0 3.231v1.616h1.615v-1.616h-1.615zm0 3.231v1.616h1.615V37.53h-1.615zM29.485 25.283a.4.4 0 0 1 .593-.35l9.05 4.977a.4.4 0 0 1 0 .701l-9.05 4.978a.4.4 0 0 1-.593-.35v-9.956z\"/></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-media-embed/theme/icons/media.svg":
/*!****************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-media-embed/theme/icons/media.svg ***!
\****************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 20 20\"><path d=\"M18.68 3.03c.6 0 .59-.03.59.55v12.84c0 .59.01.56-.59.56H1.29c-.6 0-.59.03-.59-.56V3.58c0-.58-.01-.55.6-.55h17.38zM15.77 15V5H4.2v10h11.57zM2 4v1h1V4H2zm0 2v1h1V6H2zm0 2v1h1V8H2zm0 2v1h1v-1H2zm0 2v1h1v-1H2zm0 2v1h1v-1H2zM17 4v1h1V4h-1zm0 2v1h1V6h-1zm0 2v1h1V8h-1zm0 2v1h1v-1h-1zm0 2v1h1v-1h-1zm0 2v1h1v-1h-1zM7.5 7.177a.4.4 0 0 1 .593-.351l5.133 2.824a.4.4 0 0 1 0 .7l-5.133 2.824a.4.4 0 0 1-.593-.35V7.176v.001z\"/></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-paragraph/theme/icons/paragraph.svg":
/*!******************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-paragraph/theme/icons/paragraph.svg ***!
\******************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M10.5 5.5H7v5h3.5a2.5 2.5 0 1 0 0-5zM5 3h6.5v.025a5 5 0 0 1 0 9.95V13H7v4a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1z\"/></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-select-all/theme/icons/select-all.svg":
/*!********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-select-all/theme/icons/select-all.svg ***!
\********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 20 20\"><path d=\"M.75 15.5a.75.75 0 0 1 .75.75V18l.008.09A.5.5 0 0 0 2 18.5h1.75a.75.75 0 1 1 0 1.5H1.5l-.144-.007a1.5 1.5 0 0 1-1.35-1.349L0 18.5v-2.25a.75.75 0 0 1 .75-.75zm18.5 0a.75.75 0 0 1 .75.75v2.25l-.007.144a1.5 1.5 0 0 1-1.349 1.35L18.5 20h-2.25a.75.75 0 1 1 0-1.5H18a.5.5 0 0 0 .492-.41L18.5 18v-1.75a.75.75 0 0 1 .75-.75zm-10.45 3c.11 0 .2.09.2.2v1.1a.2.2 0 0 1-.2.2H7.2a.2.2 0 0 1-.2-.2v-1.1c0-.11.09-.2.2-.2h1.6zm4 0c.11 0 .2.09.2.2v1.1a.2.2 0 0 1-.2.2h-1.6a.2.2 0 0 1-.2-.2v-1.1c0-.11.09-.2.2-.2h1.6zm.45-5.5a.75.75 0 1 1 0 1.5h-8.5a.75.75 0 1 1 0-1.5h8.5zM1.3 11c.11 0 .2.09.2.2v1.6a.2.2 0 0 1-.2.2H.2a.2.2 0 0 1-.2-.2v-1.6c0-.11.09-.2.2-.2h1.1zm18.5 0c.11 0 .2.09.2.2v1.6a.2.2 0 0 1-.2.2h-1.1a.2.2 0 0 1-.2-.2v-1.6c0-.11.09-.2.2-.2h1.1zm-4.55-2a.75.75 0 1 1 0 1.5H4.75a.75.75 0 1 1 0-1.5h10.5zM1.3 7c.11 0 .2.09.2.2v1.6a.2.2 0 0 1-.2.2H.2a.2.2 0 0 1-.2-.2V7.2c0-.11.09-.2.2-.2h1.1zm18.5 0c.11 0 .2.09.2.2v1.6a.2.2 0 0 1-.2.2h-1.1a.2.2 0 0 1-.2-.2V7.2c0-.11.09-.2.2-.2h1.1zm-4.55-2a.75.75 0 1 1 0 1.5h-2.5a.75.75 0 1 1 0-1.5h2.5zm-5 0a.75.75 0 1 1 0 1.5h-5.5a.75.75 0 0 1 0-1.5h5.5zm-6.5-5a.75.75 0 0 1 0 1.5H2a.5.5 0 0 0-.492.41L1.5 2v1.75a.75.75 0 0 1-1.5 0V1.5l.007-.144A1.5 1.5 0 0 1 1.356.006L1.5 0h2.25zM18.5 0l.144.007a1.5 1.5 0 0 1 1.35 1.349L20 1.5v2.25a.75.75 0 1 1-1.5 0V2l-.008-.09A.5.5 0 0 0 18 1.5h-1.75a.75.75 0 1 1 0-1.5h2.25zM8.8 0c.11 0 .2.09.2.2v1.1a.2.2 0 0 1-.2.2H7.2a.2.2 0 0 1-.2-.2V.2c0-.11.09-.2.2-.2h1.6zm4 0c.11 0 .2.09.2.2v1.1a.2.2 0 0 1-.2.2h-1.6a.2.2 0 0 1-.2-.2V.2c0-.11.09-.2.2-.2h1.6z\"/></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-special-characters/theme/icons/specialcharacters.svg":
/*!***********************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-special-characters/theme/icons/specialcharacters.svg ***!
\***********************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M10 2.5a7.47 7.47 0 0 1 4.231 1.31 7.268 7.268 0 0 1 2.703 3.454 7.128 7.128 0 0 1 .199 4.353c-.39 1.436-1.475 2.72-2.633 3.677h2.013c0-.226.092-.443.254-.603a.876.876 0 0 1 1.229 0c.163.16.254.377.254.603v.853c0 .209-.078.41-.22.567a.873.873 0 0 1-.547.28l-.101.006h-4.695a.517.517 0 0 1-.516-.518v-1.265c0-.21.128-.398.317-.489a5.601 5.601 0 0 0 2.492-2.371 5.459 5.459 0 0 0 .552-3.693 5.53 5.53 0 0 0-1.955-3.2A5.71 5.71 0 0 0 10 4.206 5.708 5.708 0 0 0 6.419 5.46 5.527 5.527 0 0 0 4.46 8.663a5.457 5.457 0 0 0 .554 3.695 5.6 5.6 0 0 0 2.497 2.37.55.55 0 0 1 .317.49v1.264c0 .286-.23.518-.516.518H2.618a.877.877 0 0 1-.614-.25.845.845 0 0 1-.254-.603v-.853c0-.226.091-.443.254-.603a.876.876 0 0 1 1.228 0c.163.16.255.377.255.603h1.925c-1.158-.958-2.155-2.241-2.545-3.678a7.128 7.128 0 0 1 .199-4.352 7.268 7.268 0 0 1 2.703-3.455A7.475 7.475 0 0 1 10 2.5z\"/></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/theme/icons/table-cell-properties.svg":
/*!**************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/theme/icons/table-cell-properties.svg ***!
\**************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"m11.105 18-.17 1H2.5A1.5 1.5 0 0 1 1 17.5v-15A1.5 1.5 0 0 1 2.5 1h15A1.5 1.5 0 0 1 19 2.5v9.975l-.85-.124-.15-.302V8h-5v4h.021l-.172.351-1.916.28-.151.027c-.287.063-.54.182-.755.341L8 13v5h3.105zM2 12h5V8H2v4zm10-4H8v4h4V8zM2 2v5h5V2H2zm0 16h5v-5H2v5zM13 7h5V2h-5v5zM8 2v5h4V2H8z\" opacity=\".6\"/><path d=\"m15.5 11.5 1.323 2.68 2.957.43-2.14 2.085.505 2.946L15.5 18.25l-2.645 1.39.505-2.945-2.14-2.086 2.957-.43L15.5 11.5zM13 6a1 1 0 0 1 1 1v3.172a2.047 2.047 0 0 0-.293.443l-.858 1.736-1.916.28-.151.027A1.976 1.976 0 0 0 9.315 14H7a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h6zm-1 2H8v4h4V8z\"/></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/theme/icons/table-column.svg":
/*!*****************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/theme/icons/table-column.svg ***!
\*****************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M2.5 1h15A1.5 1.5 0 0 1 19 2.5v15a1.5 1.5 0 0 1-1.5 1.5h-15A1.5 1.5 0 0 1 1 17.5v-15A1.5 1.5 0 0 1 2.5 1zM2 2v16h16V2H2z\" opacity=\".6\"/><path d=\"M18 7v1H2V7h16zm0 5v1H2v-1h16z\" opacity=\".6\"/><path d=\"M14 1v18a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V1a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1zm-2 1H8v4h4V2zm0 6H8v4h4V8zm0 6H8v4h4v-4z\"/></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/theme/icons/table-merge-cell.svg":
/*!*********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/theme/icons/table-merge-cell.svg ***!
\*********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M2.5 1h15A1.5 1.5 0 0 1 19 2.5v15a1.5 1.5 0 0 1-1.5 1.5h-15A1.5 1.5 0 0 1 1 17.5v-15A1.5 1.5 0 0 1 2.5 1zM2 2v16h16V2H2z\" opacity=\".6\"/><path d=\"M7 2h1v16H7V2zm5 0h1v7h-1V2zm6 5v1H2V7h16zM8 12v1H2v-1h6z\" opacity=\".6\"/><path d=\"M7 7h12a1 1 0 0 1 1 1v11a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V8a1 1 0 0 1 1-1zm1 2v9h10V9H8z\"/></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/theme/icons/table-properties.svg":
/*!*********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/theme/icons/table-properties.svg ***!
\*********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M8 2v5h4V2h1v5h5v1h-5v4h.021l-.172.351-1.916.28-.151.027c-.287.063-.54.182-.755.341L8 13v5H7v-5H2v-1h5V8H2V7h5V2h1zm4 6H8v4h4V8z\" opacity=\".6\"/><path d=\"m15.5 11.5 1.323 2.68 2.957.43-2.14 2.085.505 2.946L15.5 18.25l-2.645 1.39.505-2.945-2.14-2.086 2.957-.43L15.5 11.5zM17 1a2 2 0 0 1 2 2v9.475l-.85-.124-.857-1.736a2.048 2.048 0 0 0-.292-.44L17 3H3v14h7.808l.402.392L10.935 19H3a2 2 0 0 1-2-2V3a2 2 0 0 1 2-2h14z\"/></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/theme/icons/table-row.svg":
/*!**************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/theme/icons/table-row.svg ***!
\**************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M2.5 1h15A1.5 1.5 0 0 1 19 2.5v15a1.5 1.5 0 0 1-1.5 1.5h-15A1.5 1.5 0 0 1 1 17.5v-15A1.5 1.5 0 0 1 2.5 1zM2 2v16h16V2H2z\" opacity=\".6\"/><path d=\"M7 2h1v16H7V2zm5 0h1v16h-1V2z\" opacity=\".6\"/><path d=\"M1 6h18a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1H1a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1zm1 2v4h4V8H2zm6 0v4h4V8H8zm6 0v4h4V8h-4z\"/></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/theme/icons/table.svg":
/*!**********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/theme/icons/table.svg ***!
\**********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M3 6v3h4V6H3zm0 4v3h4v-3H3zm0 4v3h4v-3H3zm5 3h4v-3H8v3zm5 0h4v-3h-4v3zm4-4v-3h-4v3h4zm0-4V6h-4v3h4zm1.5 8a1.5 1.5 0 0 1-1.5 1.5H3A1.5 1.5 0 0 1 1.5 17V4c.222-.863 1.068-1.5 2-1.5h13c.932 0 1.778.637 2 1.5v13zM12 13v-3H8v3h4zm0-4V6H8v3h4z\"/></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/theme/icons/color-tile-check.svg":
/*!******************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/theme/icons/color-tile-check.svg ***!
\******************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path class=\"ck-icon__fill\" d=\"M16.935 5.328a2 2 0 0 1 0 2.829l-7.778 7.778a2 2 0 0 1-2.829 0L3.5 13.107a1.999 1.999 0 1 1 2.828-2.829l.707.707a1 1 0 0 0 1.414 0l5.658-5.657a2 2 0 0 1 2.828 0z\"/><path d=\"M14.814 6.035 8.448 12.4a1 1 0 0 1-1.414 0l-1.413-1.415A1 1 0 1 0 4.207 12.4l2.829 2.829a1 1 0 0 0 1.414 0l7.778-7.778a1 1 0 1 0-1.414-1.415z\"/></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/theme/icons/dropdown-arrow.svg":
/*!****************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/theme/icons/dropdown-arrow.svg ***!
\****************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg viewBox=\"0 0 10 10\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M.941 4.523a.75.75 0 1 1 1.06-1.06l3.006 3.005 3.005-3.005a.75.75 0 1 1 1.06 1.06l-3.549 3.55a.75.75 0 0 1-1.168-.136L.941 4.523z\"/></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/theme/icons/next-arrow.svg":
/*!************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/theme/icons/next-arrow.svg ***!
\************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M8.537 14.813a.888.888 0 1 1-1.254-1.255L10.84 10 7.283 6.442a.888.888 0 1 1 1.254-1.255L12.74 9.39a.888.888 0 0 1-.16 1.382l-4.043 4.042z\"/></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/theme/icons/previous-arrow.svg":
/*!****************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/theme/icons/previous-arrow.svg ***!
\****************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M11.463 5.187a.888.888 0 1 1 1.254 1.255L9.16 10l3.557 3.557a.888.888 0 1 1-1.254 1.255L7.26 10.61a.888.888 0 0 1 .16-1.382l4.043-4.042z\"/></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-undo/theme/icons/redo.svg":
/*!********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-undo/theme/icons/redo.svg ***!
\********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"m14.958 9.367-2.189 1.837a.75.75 0 0 0 .965 1.149l3.788-3.18a.747.747 0 0 0 .21-.284.75.75 0 0 0-.17-.945L13.77 4.762a.75.75 0 1 0-.964 1.15l2.331 1.955H6.22A.75.75 0 0 0 6 7.9a4 4 0 1 0 1.477 7.718l-.344-1.489A2.5 2.5 0 1 1 6.039 9.4l-.008-.032h8.927z\"/></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-undo/theme/icons/undo.svg":
/*!********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-undo/theme/icons/undo.svg ***!
\********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"m5.042 9.367 2.189 1.837a.75.75 0 0 1-.965 1.149l-3.788-3.18a.747.747 0 0 1-.21-.284.75.75 0 0 1 .17-.945L6.23 4.762a.75.75 0 1 1 .964 1.15L4.863 7.866h8.917A.75.75 0 0 1 14 7.9a4 4 0 1 1-1.477 7.718l.344-1.489a2.5 2.5 0 1 0 1.094-4.73l.008-.032H5.042z\"/></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-widget/theme/icons/drag-handle.svg":
/*!*****************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-widget/theme/icons/drag-handle.svg ***!
\*****************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg viewBox=\"0 0 16 16\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M4 0v1H1v3H0V.5A.5.5 0 0 1 .5 0H4zm8 0h3.5a.5.5 0 0 1 .5.5V4h-1V1h-3V0zM4 16H.5a.5.5 0 0 1-.5-.5V12h1v3h3v1zm8 0v-1h3v-3h1v3.5a.5.5 0 0 1-.5.5H12z\"/><path fill-opacity=\".256\" d=\"M1 1h14v14H1z\"/><g class=\"ck-icon__selected-indicator\"><path d=\"M7 0h2v1H7V0zM0 7h1v2H0V7zm15 0h1v2h-1V7zm-8 8h2v1H7v-1z\"/><path fill-opacity=\".254\" d=\"M1 1h14v14H1z\"/></g></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-widget/theme/icons/return-arrow.svg":
/*!******************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-widget/theme/icons/return-arrow.svg ***!
\******************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("<svg viewBox=\"0 0 10 8\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M9.055.263v3.972h-6.77M1 4.216l2-2.038m-2 2 2 2.038\"/></svg>");
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-basic-styles/theme/code.css":
/*!**********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-basic-styles/theme/code.css ***!
\**********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_code_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../css-loader/dist/cjs.js!../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./code.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-basic-styles/theme/code.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_code_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_code_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-block-quote/theme/blockquote.css":
/*!***************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-block-quote/theme/blockquote.css ***!
\***************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_blockquote_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../css-loader/dist/cjs.js!../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./blockquote.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-block-quote/theme/blockquote.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_blockquote_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_blockquote_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-clipboard/theme/clipboard.css":
/*!************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-clipboard/theme/clipboard.css ***!
\************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_clipboard_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../css-loader/dist/cjs.js!../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./clipboard.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-clipboard/theme/clipboard.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_clipboard_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_clipboard_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-code-block/theme/codeblock.css":
/*!*************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-code-block/theme/codeblock.css ***!
\*************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_codeblock_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../css-loader/dist/cjs.js!../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./codeblock.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-code-block/theme/codeblock.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_codeblock_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_codeblock_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-editor-classic/theme/classiceditor.css":
/*!*********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-editor-classic/theme/classiceditor.css ***!
\*********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_classiceditor_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../css-loader/dist/cjs.js!../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./classiceditor.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-editor-classic/theme/classiceditor.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_classiceditor_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_classiceditor_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/theme/placeholder.css":
/*!***********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/theme/placeholder.css ***!
\***********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_placeholder_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../css-loader/dist/cjs.js!../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./placeholder.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-engine/theme/placeholder.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_placeholder_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_placeholder_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-engine/theme/renderer.css":
/*!********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-engine/theme/renderer.css ***!
\********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_renderer_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../css-loader/dist/cjs.js!../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./renderer.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-engine/theme/renderer.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_renderer_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_renderer_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-find-and-replace/theme/findandreplace.css":
/*!************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-find-and-replace/theme/findandreplace.css ***!
\************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_findandreplace_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../css-loader/dist/cjs.js!../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./findandreplace.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-find-and-replace/theme/findandreplace.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_findandreplace_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_findandreplace_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-find-and-replace/theme/findandreplaceform.css":
/*!****************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-find-and-replace/theme/findandreplaceform.css ***!
\****************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_findandreplaceform_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../css-loader/dist/cjs.js!../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./findandreplaceform.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-find-and-replace/theme/findandreplaceform.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_findandreplaceform_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_findandreplaceform_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-font/theme/fontcolor.css":
/*!*******************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-font/theme/fontcolor.css ***!
\*******************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_fontcolor_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../css-loader/dist/cjs.js!../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./fontcolor.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-font/theme/fontcolor.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_fontcolor_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_fontcolor_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-font/theme/fontsize.css":
/*!******************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-font/theme/fontsize.css ***!
\******************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_fontsize_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../css-loader/dist/cjs.js!../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./fontsize.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-font/theme/fontsize.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_fontsize_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_fontsize_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-heading/theme/heading.css":
/*!********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-heading/theme/heading.css ***!
\********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_heading_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../css-loader/dist/cjs.js!../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./heading.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-heading/theme/heading.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_heading_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_heading_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-highlight/theme/highlight.css":
/*!************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-highlight/theme/highlight.css ***!
\************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_highlight_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../css-loader/dist/cjs.js!../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./highlight.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-highlight/theme/highlight.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_highlight_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_highlight_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-horizontal-line/theme/horizontalline.css":
/*!***********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-horizontal-line/theme/horizontalline.css ***!
\***********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_horizontalline_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../css-loader/dist/cjs.js!../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./horizontalline.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-horizontal-line/theme/horizontalline.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_horizontalline_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_horizontalline_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-html-embed/theme/htmlembed.css":
/*!*************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-html-embed/theme/htmlembed.css ***!
\*************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_htmlembed_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../css-loader/dist/cjs.js!../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./htmlembed.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-html-embed/theme/htmlembed.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_htmlembed_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_htmlembed_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-html-support/theme/datafilter.css":
/*!****************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-html-support/theme/datafilter.css ***!
\****************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_datafilter_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../css-loader/dist/cjs.js!../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./datafilter.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-html-support/theme/datafilter.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_datafilter_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_datafilter_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-image/theme/image.css":
/*!****************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-image/theme/image.css ***!
\****************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_image_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../css-loader/dist/cjs.js!../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./image.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-image/theme/image.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_image_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_image_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-image/theme/imagecaption.css":
/*!***********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-image/theme/imagecaption.css ***!
\***********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_imagecaption_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../css-loader/dist/cjs.js!../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./imagecaption.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-image/theme/imagecaption.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_imagecaption_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_imagecaption_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-image/theme/imageinsert.css":
/*!**********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-image/theme/imageinsert.css ***!
\**********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_imageinsert_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../css-loader/dist/cjs.js!../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./imageinsert.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-image/theme/imageinsert.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_imageinsert_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_imageinsert_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-image/theme/imageinsertformrowview.css":
/*!*********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-image/theme/imageinsertformrowview.css ***!
\*********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_imageinsertformrowview_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../css-loader/dist/cjs.js!../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./imageinsertformrowview.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-image/theme/imageinsertformrowview.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_imageinsertformrowview_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_imageinsertformrowview_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-image/theme/imageresize.css":
/*!**********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-image/theme/imageresize.css ***!
\**********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_imageresize_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../css-loader/dist/cjs.js!../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./imageresize.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-image/theme/imageresize.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_imageresize_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_imageresize_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-image/theme/imagestyle.css":
/*!*********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-image/theme/imagestyle.css ***!
\*********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_imagestyle_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../css-loader/dist/cjs.js!../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./imagestyle.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-image/theme/imagestyle.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_imagestyle_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_imagestyle_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-image/theme/imageuploadicon.css":
/*!**************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-image/theme/imageuploadicon.css ***!
\**************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_imageuploadicon_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../css-loader/dist/cjs.js!../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./imageuploadicon.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-image/theme/imageuploadicon.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_imageuploadicon_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_imageuploadicon_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-image/theme/imageuploadloader.css":
/*!****************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-image/theme/imageuploadloader.css ***!
\****************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_imageuploadloader_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../css-loader/dist/cjs.js!../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./imageuploadloader.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-image/theme/imageuploadloader.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_imageuploadloader_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_imageuploadloader_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-image/theme/imageuploadprogress.css":
/*!******************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-image/theme/imageuploadprogress.css ***!
\******************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_imageuploadprogress_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../css-loader/dist/cjs.js!../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./imageuploadprogress.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-image/theme/imageuploadprogress.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_imageuploadprogress_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_imageuploadprogress_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-image/theme/textalternativeform.css":
/*!******************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-image/theme/textalternativeform.css ***!
\******************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_textalternativeform_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../css-loader/dist/cjs.js!../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./textalternativeform.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-image/theme/textalternativeform.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_textalternativeform_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_textalternativeform_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-link/theme/link.css":
/*!**************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-link/theme/link.css ***!
\**************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_link_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../css-loader/dist/cjs.js!../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./link.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-link/theme/link.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_link_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_link_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-link/theme/linkactions.css":
/*!*********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-link/theme/linkactions.css ***!
\*********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_linkactions_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../css-loader/dist/cjs.js!../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./linkactions.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-link/theme/linkactions.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_linkactions_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_linkactions_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-link/theme/linkform.css":
/*!******************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-link/theme/linkform.css ***!
\******************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_linkform_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../css-loader/dist/cjs.js!../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./linkform.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-link/theme/linkform.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_linkform_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_linkform_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-link/theme/linkimage.css":
/*!*******************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-link/theme/linkimage.css ***!
\*******************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_linkimage_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../css-loader/dist/cjs.js!../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./linkimage.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-link/theme/linkimage.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_linkimage_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_linkimage_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-media-embed/theme/mediaembed.css":
/*!***************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-media-embed/theme/mediaembed.css ***!
\***************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_mediaembed_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../css-loader/dist/cjs.js!../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./mediaembed.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-media-embed/theme/mediaembed.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_mediaembed_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_mediaembed_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-media-embed/theme/mediaembedediting.css":
/*!**********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-media-embed/theme/mediaembedediting.css ***!
\**********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_mediaembedediting_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../css-loader/dist/cjs.js!../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./mediaembedediting.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-media-embed/theme/mediaembedediting.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_mediaembedediting_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_mediaembedediting_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-media-embed/theme/mediaform.css":
/*!**************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-media-embed/theme/mediaform.css ***!
\**************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_mediaform_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../css-loader/dist/cjs.js!../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./mediaform.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-media-embed/theme/mediaform.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_mediaform_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_mediaform_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-special-characters/theme/charactergrid.css":
/*!*************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-special-characters/theme/charactergrid.css ***!
\*************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_charactergrid_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../css-loader/dist/cjs.js!../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./charactergrid.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-special-characters/theme/charactergrid.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_charactergrid_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_charactergrid_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-special-characters/theme/characterinfo.css":
/*!*************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-special-characters/theme/characterinfo.css ***!
\*************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_characterinfo_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../css-loader/dist/cjs.js!../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./characterinfo.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-special-characters/theme/characterinfo.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_characterinfo_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_characterinfo_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-special-characters/theme/specialcharacters.css":
/*!*****************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-special-characters/theme/specialcharacters.css ***!
\*****************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_specialcharacters_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../css-loader/dist/cjs.js!../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./specialcharacters.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-special-characters/theme/specialcharacters.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_specialcharacters_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_specialcharacters_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/theme/colorinput.css":
/*!*********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/theme/colorinput.css ***!
\*********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_colorinput_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../css-loader/dist/cjs.js!../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./colorinput.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-table/theme/colorinput.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_colorinput_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_colorinput_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/theme/form.css":
/*!***************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/theme/form.css ***!
\***************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_form_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../css-loader/dist/cjs.js!../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./form.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-table/theme/form.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_form_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_form_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/theme/formrow.css":
/*!******************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/theme/formrow.css ***!
\******************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_formrow_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../css-loader/dist/cjs.js!../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./formrow.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-table/theme/formrow.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_formrow_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_formrow_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/theme/inserttable.css":
/*!**********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/theme/inserttable.css ***!
\**********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_inserttable_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../css-loader/dist/cjs.js!../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./inserttable.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-table/theme/inserttable.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_inserttable_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_inserttable_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/theme/table.css":
/*!****************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/theme/table.css ***!
\****************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_table_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../css-loader/dist/cjs.js!../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./table.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-table/theme/table.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_table_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_table_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/theme/tablecaption.css":
/*!***********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/theme/tablecaption.css ***!
\***********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_tablecaption_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../css-loader/dist/cjs.js!../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./tablecaption.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-table/theme/tablecaption.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_tablecaption_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_tablecaption_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/theme/tablecellproperties.css":
/*!******************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/theme/tablecellproperties.css ***!
\******************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_tablecellproperties_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../css-loader/dist/cjs.js!../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./tablecellproperties.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-table/theme/tablecellproperties.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_tablecellproperties_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_tablecellproperties_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/theme/tableediting.css":
/*!***********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/theme/tableediting.css ***!
\***********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_tableediting_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../css-loader/dist/cjs.js!../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./tableediting.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-table/theme/tableediting.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_tableediting_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_tableediting_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/theme/tableform.css":
/*!********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/theme/tableform.css ***!
\********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_tableform_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../css-loader/dist/cjs.js!../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./tableform.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-table/theme/tableform.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_tableform_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_tableform_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/theme/tableproperties.css":
/*!**************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/theme/tableproperties.css ***!
\**************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_tableproperties_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../css-loader/dist/cjs.js!../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./tableproperties.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-table/theme/tableproperties.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_tableproperties_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_tableproperties_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-table/theme/tableselection.css":
/*!*************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-table/theme/tableselection.css ***!
\*************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_tableselection_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../css-loader/dist/cjs.js!../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./tableselection.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-table/theme/tableselection.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_tableselection_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_tableselection_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/theme/components/button/button.css":
/*!********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/theme/components/button/button.css ***!
\********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_button_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../../../css-loader/dist/cjs.js!../../../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./button.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/components/button/button.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_button_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_button_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/theme/components/button/switchbutton.css":
/*!**************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/theme/components/button/switchbutton.css ***!
\**************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_switchbutton_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../../../css-loader/dist/cjs.js!../../../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./switchbutton.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/components/button/switchbutton.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_switchbutton_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_switchbutton_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/theme/components/colorgrid/colorgrid.css":
/*!**************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/theme/components/colorgrid/colorgrid.css ***!
\**************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_colorgrid_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../../../css-loader/dist/cjs.js!../../../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./colorgrid.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/components/colorgrid/colorgrid.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_colorgrid_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_colorgrid_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/theme/components/dropdown/dropdown.css":
/*!************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/theme/components/dropdown/dropdown.css ***!
\************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_dropdown_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../../../css-loader/dist/cjs.js!../../../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./dropdown.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/components/dropdown/dropdown.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_dropdown_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_dropdown_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/theme/components/dropdown/listdropdown.css":
/*!****************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/theme/components/dropdown/listdropdown.css ***!
\****************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_listdropdown_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../../../css-loader/dist/cjs.js!../../../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./listdropdown.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/components/dropdown/listdropdown.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_listdropdown_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_listdropdown_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/theme/components/dropdown/splitbutton.css":
/*!***************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/theme/components/dropdown/splitbutton.css ***!
\***************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_splitbutton_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../../../css-loader/dist/cjs.js!../../../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./splitbutton.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/components/dropdown/splitbutton.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_splitbutton_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_splitbutton_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/theme/components/dropdown/toolbardropdown.css":
/*!*******************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/theme/components/dropdown/toolbardropdown.css ***!
\*******************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_toolbardropdown_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../../../css-loader/dist/cjs.js!../../../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./toolbardropdown.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/components/dropdown/toolbardropdown.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_toolbardropdown_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_toolbardropdown_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/theme/components/editorui/editorui.css":
/*!************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/theme/components/editorui/editorui.css ***!
\************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_editorui_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../../../css-loader/dist/cjs.js!../../../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./editorui.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/components/editorui/editorui.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_editorui_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_editorui_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/theme/components/formheader/formheader.css":
/*!****************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/theme/components/formheader/formheader.css ***!
\****************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_formheader_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../../../css-loader/dist/cjs.js!../../../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./formheader.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/components/formheader/formheader.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_formheader_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_formheader_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/theme/components/icon/icon.css":
/*!****************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/theme/components/icon/icon.css ***!
\****************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_icon_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../../../css-loader/dist/cjs.js!../../../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./icon.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/components/icon/icon.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_icon_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_icon_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/theme/components/input/input.css":
/*!******************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/theme/components/input/input.css ***!
\******************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_input_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../../../css-loader/dist/cjs.js!../../../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./input.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/components/input/input.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_input_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_input_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/theme/components/label/label.css":
/*!******************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/theme/components/label/label.css ***!
\******************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_label_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../../../css-loader/dist/cjs.js!../../../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./label.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/components/label/label.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_label_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_label_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/theme/components/labeledfield/labeledfieldview.css":
/*!************************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/theme/components/labeledfield/labeledfieldview.css ***!
\************************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_labeledfieldview_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../../../css-loader/dist/cjs.js!../../../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./labeledfieldview.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/components/labeledfield/labeledfieldview.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_labeledfieldview_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_labeledfieldview_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/theme/components/list/list.css":
/*!****************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/theme/components/list/list.css ***!
\****************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_list_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../../../css-loader/dist/cjs.js!../../../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./list.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/components/list/list.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_list_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_list_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/theme/components/panel/balloonpanel.css":
/*!*************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/theme/components/panel/balloonpanel.css ***!
\*************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_balloonpanel_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../../../css-loader/dist/cjs.js!../../../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./balloonpanel.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/components/panel/balloonpanel.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_balloonpanel_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_balloonpanel_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/theme/components/panel/balloonrotator.css":
/*!***************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/theme/components/panel/balloonrotator.css ***!
\***************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_balloonrotator_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../../../css-loader/dist/cjs.js!../../../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./balloonrotator.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/components/panel/balloonrotator.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_balloonrotator_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_balloonrotator_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/theme/components/panel/fakepanel.css":
/*!**********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/theme/components/panel/fakepanel.css ***!
\**********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_fakepanel_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../../../css-loader/dist/cjs.js!../../../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./fakepanel.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/components/panel/fakepanel.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_fakepanel_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_fakepanel_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/theme/components/panel/stickypanel.css":
/*!************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/theme/components/panel/stickypanel.css ***!
\************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_stickypanel_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../../../css-loader/dist/cjs.js!../../../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./stickypanel.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/components/panel/stickypanel.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_stickypanel_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_stickypanel_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/theme/components/responsive-form/responsiveform.css":
/*!*************************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/theme/components/responsive-form/responsiveform.css ***!
\*************************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_responsiveform_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../../../css-loader/dist/cjs.js!../../../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./responsiveform.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/components/responsive-form/responsiveform.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_responsiveform_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_responsiveform_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/theme/components/toolbar/blocktoolbar.css":
/*!***************************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/theme/components/toolbar/blocktoolbar.css ***!
\***************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_blocktoolbar_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../../../css-loader/dist/cjs.js!../../../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./blocktoolbar.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/components/toolbar/blocktoolbar.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_blocktoolbar_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_blocktoolbar_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/theme/components/toolbar/toolbar.css":
/*!**********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/theme/components/toolbar/toolbar.css ***!
\**********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_toolbar_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../../../css-loader/dist/cjs.js!../../../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./toolbar.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/components/toolbar/toolbar.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_toolbar_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_toolbar_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/theme/components/tooltip/tooltip.css":
/*!**********************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/theme/components/tooltip/tooltip.css ***!
\**********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_tooltip_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../../../css-loader/dist/cjs.js!../../../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./tooltip.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/components/tooltip/tooltip.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_tooltip_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_tooltip_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-ui/theme/globals/globals.css":
/*!***********************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-ui/theme/globals/globals.css ***!
\***********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_globals_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../../css-loader/dist/cjs.js!../../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./globals.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-ui/theme/globals/globals.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_globals_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_globals_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-widget/theme/widget.css":
/*!******************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-widget/theme/widget.css ***!
\******************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_widget_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../css-loader/dist/cjs.js!../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./widget.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-widget/theme/widget.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_widget_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_widget_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-widget/theme/widgetresize.css":
/*!************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-widget/theme/widgetresize.css ***!
\************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_widgetresize_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../css-loader/dist/cjs.js!../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./widgetresize.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-widget/theme/widgetresize.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_widgetresize_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_widgetresize_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/@ckeditor/ckeditor5-widget/theme/widgettypearound.css":
/*!****************************************************************************!*\
!*** ./node_modules/@ckeditor/ckeditor5-widget/theme/widgettypearound.css ***!
\****************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_widgettypearound_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../../css-loader/dist/cjs.js!../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./widgettypearound.css */ "./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/@ckeditor/ckeditor5-widget/theme/widgettypearound.css");
var options = {"injectType":"singletonStyleTag","attributes":{"data-cke":true}};
options.insert = "head";
options.singleton = true;
var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_widgettypearound_css__WEBPACK_IMPORTED_MODULE_1__["default"], options);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_postcss_loader_dist_cjs_js_ruleSet_1_rules_1_use_2_widgettypearound_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
/***/ }),
/***/ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js":
/*!****************************************************************************!*\
!*** ./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js ***!
\****************************************************************************/
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
var isOldIE = function isOldIE() {
var memo;
return function memorize() {
if (typeof memo === 'undefined') {
// Test for IE <= 9 as proposed by Browserhacks
// @see http://browserhacks.com/#hack-e71d8692f65334173fee715c222cb805
// Tests for existence of standard globals is to allow style-loader
// to operate correctly into non-standard environments
// @see https://github.com/webpack-contrib/style-loader/issues/177
memo = Boolean(window && document && document.all && !window.atob);
}
return memo;
};
}();
var getTarget = function getTarget() {
var memo = {};
return function memorize(target) {
if (typeof memo[target] === 'undefined') {
var styleTarget = document.querySelector(target); // Special case to return head of iframe instead of iframe itself
if (window.HTMLIFrameElement && styleTarget instanceof window.HTMLIFrameElement) {
try {
// This will throw an exception if access to iframe is blocked
// due to cross-origin restrictions
styleTarget = styleTarget.contentDocument.head;
} catch (e) {
// istanbul ignore next
styleTarget = null;
}
}
memo[target] = styleTarget;
}
return memo[target];
};
}();
var stylesInDom = [];
function getIndexByIdentifier(identifier) {
var result = -1;
for (var i = 0; i < stylesInDom.length; i++) {
if (stylesInDom[i].identifier === identifier) {
result = i;
break;
}
}
return result;
}
function modulesToDom(list, options) {
var idCountMap = {};
var identifiers = [];
for (var i = 0; i < list.length; i++) {
var item = list[i];
var id = options.base ? item[0] + options.base : item[0];
var count = idCountMap[id] || 0;
var identifier = "".concat(id, " ").concat(count);
idCountMap[id] = count + 1;
var index = getIndexByIdentifier(identifier);
var obj = {
css: item[1],
media: item[2],
sourceMap: item[3]
};
if (index !== -1) {
stylesInDom[index].references++;
stylesInDom[index].updater(obj);
} else {
stylesInDom.push({
identifier: identifier,
updater: addStyle(obj, options),
references: 1
});
}
identifiers.push(identifier);
}
return identifiers;
}
function insertStyleElement(options) {
var style = document.createElement('style');
var attributes = options.attributes || {};
if (typeof attributes.nonce === 'undefined') {
var nonce = true ? __webpack_require__.nc : 0;
if (nonce) {
attributes.nonce = nonce;
}
}
Object.keys(attributes).forEach(function (key) {
style.setAttribute(key, attributes[key]);
});
if (typeof options.insert === 'function') {
options.insert(style);
} else {
var target = getTarget(options.insert || 'head');
if (!target) {
throw new Error("Couldn't find a style target. This probably means that the value for the 'insert' parameter is invalid.");
}
target.appendChild(style);
}
return style;
}
function removeStyleElement(style) {
// istanbul ignore if
if (style.parentNode === null) {
return false;
}
style.parentNode.removeChild(style);
}
/* istanbul ignore next */
var replaceText = function replaceText() {
var textStore = [];
return function replace(index, replacement) {
textStore[index] = replacement;
return textStore.filter(Boolean).join('\n');
};
}();
function applyToSingletonTag(style, index, remove, obj) {
var css = remove ? '' : obj.media ? "@media ".concat(obj.media, " {").concat(obj.css, "}") : obj.css; // For old IE
/* istanbul ignore if */
if (style.styleSheet) {
style.styleSheet.cssText = replaceText(index, css);
} else {
var cssNode = document.createTextNode(css);
var childNodes = style.childNodes;
if (childNodes[index]) {
style.removeChild(childNodes[index]);
}
if (childNodes.length) {
style.insertBefore(cssNode, childNodes[index]);
} else {
style.appendChild(cssNode);
}
}
}
function applyToTag(style, options, obj) {
var css = obj.css;
var media = obj.media;
var sourceMap = obj.sourceMap;
if (media) {
style.setAttribute('media', media);
} else {
style.removeAttribute('media');
}
if (sourceMap && typeof btoa !== 'undefined') {
css += "\n/*# sourceMappingURL=data:application/json;base64,".concat(btoa(unescape(encodeURIComponent(JSON.stringify(sourceMap)))), " */");
} // For old IE
/* istanbul ignore if */
if (style.styleSheet) {
style.styleSheet.cssText = css;
} else {
while (style.firstChild) {
style.removeChild(style.firstChild);
}
style.appendChild(document.createTextNode(css));
}
}
var singleton = null;
var singletonCounter = 0;
function addStyle(obj, options) {
var style;
var update;
var remove;
if (options.singleton) {
var styleIndex = singletonCounter++;
style = singleton || (singleton = insertStyleElement(options));
update = applyToSingletonTag.bind(null, style, styleIndex, false);
remove = applyToSingletonTag.bind(null, style, styleIndex, true);
} else {
style = insertStyleElement(options);
update = applyToTag.bind(null, style, options);
remove = function remove() {
removeStyleElement(style);
};
}
update(obj);
return function updateStyle(newObj) {
if (newObj) {
if (newObj.css === obj.css && newObj.media === obj.media && newObj.sourceMap === obj.sourceMap) {
return;
}
update(obj = newObj);
} else {
remove();
}
};
}
module.exports = function (list, options) {
options = options || {}; // Force single-tag solution on IE6-9, which has a hard limit on the # of <style>
// tags it will allow on a page
if (!options.singleton && typeof options.singleton !== 'boolean') {
options.singleton = isOldIE();
}
list = list || [];
var lastIdentifiers = modulesToDom(list, options);
return function update(newList) {
newList = newList || [];
if (Object.prototype.toString.call(newList) !== '[object Array]') {
return;
}
for (var i = 0; i < lastIdentifiers.length; i++) {
var identifier = lastIdentifiers[i];
var index = getIndexByIdentifier(identifier);
stylesInDom[index].references--;
}
var newLastIdentifiers = modulesToDom(newList, options);
for (var _i = 0; _i < lastIdentifiers.length; _i++) {
var _identifier = lastIdentifiers[_i];
var _index = getIndexByIdentifier(_identifier);
if (stylesInDom[_index].references === 0) {
stylesInDom[_index].updater();
stylesInDom.splice(_index, 1);
}
}
lastIdentifiers = newLastIdentifiers;
};
};
/***/ }),
/***/ "./node_modules/lodash-es/_DataView.js":
/*!*********************************************!*\
!*** ./node_modules/lodash-es/_DataView.js ***!
\*********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _getNative_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_getNative.js */ "./node_modules/lodash-es/_getNative.js");
/* harmony import */ var _root_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./_root.js */ "./node_modules/lodash-es/_root.js");
/* Built-in method references that are verified to be native. */
var DataView = (0,_getNative_js__WEBPACK_IMPORTED_MODULE_0__["default"])(_root_js__WEBPACK_IMPORTED_MODULE_1__["default"], 'DataView');
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (DataView);
/***/ }),
/***/ "./node_modules/lodash-es/_Hash.js":
/*!*****************************************!*\
!*** ./node_modules/lodash-es/_Hash.js ***!
\*****************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _hashClear_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_hashClear.js */ "./node_modules/lodash-es/_hashClear.js");
/* harmony import */ var _hashDelete_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./_hashDelete.js */ "./node_modules/lodash-es/_hashDelete.js");
/* harmony import */ var _hashGet_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./_hashGet.js */ "./node_modules/lodash-es/_hashGet.js");
/* harmony import */ var _hashHas_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./_hashHas.js */ "./node_modules/lodash-es/_hashHas.js");
/* harmony import */ var _hashSet_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./_hashSet.js */ "./node_modules/lodash-es/_hashSet.js");
/**
* Creates a hash object.
*
* @private
* @constructor
* @param {Array} [entries] The key-value pairs to cache.
*/
function Hash(entries) {
var index = -1,
length = entries == null ? 0 : entries.length;
this.clear();
while (++index < length) {
var entry = entries[index];
this.set(entry[0], entry[1]);
}
}
// Add methods to `Hash`.
Hash.prototype.clear = _hashClear_js__WEBPACK_IMPORTED_MODULE_0__["default"];
Hash.prototype['delete'] = _hashDelete_js__WEBPACK_IMPORTED_MODULE_1__["default"];
Hash.prototype.get = _hashGet_js__WEBPACK_IMPORTED_MODULE_2__["default"];
Hash.prototype.has = _hashHas_js__WEBPACK_IMPORTED_MODULE_3__["default"];
Hash.prototype.set = _hashSet_js__WEBPACK_IMPORTED_MODULE_4__["default"];
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (Hash);
/***/ }),
/***/ "./node_modules/lodash-es/_ListCache.js":
/*!**********************************************!*\
!*** ./node_modules/lodash-es/_ListCache.js ***!
\**********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _listCacheClear_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_listCacheClear.js */ "./node_modules/lodash-es/_listCacheClear.js");
/* harmony import */ var _listCacheDelete_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./_listCacheDelete.js */ "./node_modules/lodash-es/_listCacheDelete.js");
/* harmony import */ var _listCacheGet_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./_listCacheGet.js */ "./node_modules/lodash-es/_listCacheGet.js");
/* harmony import */ var _listCacheHas_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./_listCacheHas.js */ "./node_modules/lodash-es/_listCacheHas.js");
/* harmony import */ var _listCacheSet_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./_listCacheSet.js */ "./node_modules/lodash-es/_listCacheSet.js");
/**
* Creates an list cache object.
*
* @private
* @constructor
* @param {Array} [entries] The key-value pairs to cache.
*/
function ListCache(entries) {
var index = -1,
length = entries == null ? 0 : entries.length;
this.clear();
while (++index < length) {
var entry = entries[index];
this.set(entry[0], entry[1]);
}
}
// Add methods to `ListCache`.
ListCache.prototype.clear = _listCacheClear_js__WEBPACK_IMPORTED_MODULE_0__["default"];
ListCache.prototype['delete'] = _listCacheDelete_js__WEBPACK_IMPORTED_MODULE_1__["default"];
ListCache.prototype.get = _listCacheGet_js__WEBPACK_IMPORTED_MODULE_2__["default"];
ListCache.prototype.has = _listCacheHas_js__WEBPACK_IMPORTED_MODULE_3__["default"];
ListCache.prototype.set = _listCacheSet_js__WEBPACK_IMPORTED_MODULE_4__["default"];
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (ListCache);
/***/ }),
/***/ "./node_modules/lodash-es/_Map.js":
/*!****************************************!*\
!*** ./node_modules/lodash-es/_Map.js ***!
\****************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _getNative_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_getNative.js */ "./node_modules/lodash-es/_getNative.js");
/* harmony import */ var _root_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./_root.js */ "./node_modules/lodash-es/_root.js");
/* Built-in method references that are verified to be native. */
var Map = (0,_getNative_js__WEBPACK_IMPORTED_MODULE_0__["default"])(_root_js__WEBPACK_IMPORTED_MODULE_1__["default"], 'Map');
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (Map);
/***/ }),
/***/ "./node_modules/lodash-es/_MapCache.js":
/*!*********************************************!*\
!*** ./node_modules/lodash-es/_MapCache.js ***!
\*********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _mapCacheClear_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_mapCacheClear.js */ "./node_modules/lodash-es/_mapCacheClear.js");
/* harmony import */ var _mapCacheDelete_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./_mapCacheDelete.js */ "./node_modules/lodash-es/_mapCacheDelete.js");
/* harmony import */ var _mapCacheGet_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./_mapCacheGet.js */ "./node_modules/lodash-es/_mapCacheGet.js");
/* harmony import */ var _mapCacheHas_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./_mapCacheHas.js */ "./node_modules/lodash-es/_mapCacheHas.js");
/* harmony import */ var _mapCacheSet_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./_mapCacheSet.js */ "./node_modules/lodash-es/_mapCacheSet.js");
/**
* Creates a map cache object to store key-value pairs.
*
* @private
* @constructor
* @param {Array} [entries] The key-value pairs to cache.
*/
function MapCache(entries) {
var index = -1,
length = entries == null ? 0 : entries.length;
this.clear();
while (++index < length) {
var entry = entries[index];
this.set(entry[0], entry[1]);
}
}
// Add methods to `MapCache`.
MapCache.prototype.clear = _mapCacheClear_js__WEBPACK_IMPORTED_MODULE_0__["default"];
MapCache.prototype['delete'] = _mapCacheDelete_js__WEBPACK_IMPORTED_MODULE_1__["default"];
MapCache.prototype.get = _mapCacheGet_js__WEBPACK_IMPORTED_MODULE_2__["default"];
MapCache.prototype.has = _mapCacheHas_js__WEBPACK_IMPORTED_MODULE_3__["default"];
MapCache.prototype.set = _mapCacheSet_js__WEBPACK_IMPORTED_MODULE_4__["default"];
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (MapCache);
/***/ }),
/***/ "./node_modules/lodash-es/_Promise.js":
/*!********************************************!*\
!*** ./node_modules/lodash-es/_Promise.js ***!
\********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _getNative_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_getNative.js */ "./node_modules/lodash-es/_getNative.js");
/* harmony import */ var _root_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./_root.js */ "./node_modules/lodash-es/_root.js");
/* Built-in method references that are verified to be native. */
var Promise = (0,_getNative_js__WEBPACK_IMPORTED_MODULE_0__["default"])(_root_js__WEBPACK_IMPORTED_MODULE_1__["default"], 'Promise');
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (Promise);
/***/ }),
/***/ "./node_modules/lodash-es/_Set.js":
/*!****************************************!*\
!*** ./node_modules/lodash-es/_Set.js ***!
\****************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _getNative_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_getNative.js */ "./node_modules/lodash-es/_getNative.js");
/* harmony import */ var _root_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./_root.js */ "./node_modules/lodash-es/_root.js");
/* Built-in method references that are verified to be native. */
var Set = (0,_getNative_js__WEBPACK_IMPORTED_MODULE_0__["default"])(_root_js__WEBPACK_IMPORTED_MODULE_1__["default"], 'Set');
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (Set);
/***/ }),
/***/ "./node_modules/lodash-es/_SetCache.js":
/*!*********************************************!*\
!*** ./node_modules/lodash-es/_SetCache.js ***!
\*********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _MapCache_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_MapCache.js */ "./node_modules/lodash-es/_MapCache.js");
/* harmony import */ var _setCacheAdd_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./_setCacheAdd.js */ "./node_modules/lodash-es/_setCacheAdd.js");
/* harmony import */ var _setCacheHas_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./_setCacheHas.js */ "./node_modules/lodash-es/_setCacheHas.js");
/**
*
* Creates an array cache object to store unique values.
*
* @private
* @constructor
* @param {Array} [values] The values to cache.
*/
function SetCache(values) {
var index = -1,
length = values == null ? 0 : values.length;
this.__data__ = new _MapCache_js__WEBPACK_IMPORTED_MODULE_0__["default"];
while (++index < length) {
this.add(values[index]);
}
}
// Add methods to `SetCache`.
SetCache.prototype.add = SetCache.prototype.push = _setCacheAdd_js__WEBPACK_IMPORTED_MODULE_1__["default"];
SetCache.prototype.has = _setCacheHas_js__WEBPACK_IMPORTED_MODULE_2__["default"];
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (SetCache);
/***/ }),
/***/ "./node_modules/lodash-es/_Stack.js":
/*!******************************************!*\
!*** ./node_modules/lodash-es/_Stack.js ***!
\******************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _ListCache_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_ListCache.js */ "./node_modules/lodash-es/_ListCache.js");
/* harmony import */ var _stackClear_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./_stackClear.js */ "./node_modules/lodash-es/_stackClear.js");
/* harmony import */ var _stackDelete_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./_stackDelete.js */ "./node_modules/lodash-es/_stackDelete.js");
/* harmony import */ var _stackGet_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./_stackGet.js */ "./node_modules/lodash-es/_stackGet.js");
/* harmony import */ var _stackHas_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./_stackHas.js */ "./node_modules/lodash-es/_stackHas.js");
/* harmony import */ var _stackSet_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./_stackSet.js */ "./node_modules/lodash-es/_stackSet.js");
/**
* Creates a stack cache object to store key-value pairs.
*
* @private
* @constructor
* @param {Array} [entries] The key-value pairs to cache.
*/
function Stack(entries) {
var data = this.__data__ = new _ListCache_js__WEBPACK_IMPORTED_MODULE_0__["default"](entries);
this.size = data.size;
}
// Add methods to `Stack`.
Stack.prototype.clear = _stackClear_js__WEBPACK_IMPORTED_MODULE_1__["default"];
Stack.prototype['delete'] = _stackDelete_js__WEBPACK_IMPORTED_MODULE_2__["default"];
Stack.prototype.get = _stackGet_js__WEBPACK_IMPORTED_MODULE_3__["default"];
Stack.prototype.has = _stackHas_js__WEBPACK_IMPORTED_MODULE_4__["default"];
Stack.prototype.set = _stackSet_js__WEBPACK_IMPORTED_MODULE_5__["default"];
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (Stack);
/***/ }),
/***/ "./node_modules/lodash-es/_Symbol.js":
/*!*******************************************!*\
!*** ./node_modules/lodash-es/_Symbol.js ***!
\*******************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _root_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_root.js */ "./node_modules/lodash-es/_root.js");
/** Built-in value references. */
var Symbol = _root_js__WEBPACK_IMPORTED_MODULE_0__["default"].Symbol;
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (Symbol);
/***/ }),
/***/ "./node_modules/lodash-es/_Uint8Array.js":
/*!***********************************************!*\
!*** ./node_modules/lodash-es/_Uint8Array.js ***!
\***********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _root_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_root.js */ "./node_modules/lodash-es/_root.js");
/** Built-in value references. */
var Uint8Array = _root_js__WEBPACK_IMPORTED_MODULE_0__["default"].Uint8Array;
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (Uint8Array);
/***/ }),
/***/ "./node_modules/lodash-es/_WeakMap.js":
/*!********************************************!*\
!*** ./node_modules/lodash-es/_WeakMap.js ***!
\********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _getNative_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_getNative.js */ "./node_modules/lodash-es/_getNative.js");
/* harmony import */ var _root_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./_root.js */ "./node_modules/lodash-es/_root.js");
/* Built-in method references that are verified to be native. */
var WeakMap = (0,_getNative_js__WEBPACK_IMPORTED_MODULE_0__["default"])(_root_js__WEBPACK_IMPORTED_MODULE_1__["default"], 'WeakMap');
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (WeakMap);
/***/ }),
/***/ "./node_modules/lodash-es/_apply.js":
/*!******************************************!*\
!*** ./node_modules/lodash-es/_apply.js ***!
\******************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/**
* A faster alternative to `Function#apply`, this function invokes `func`
* with the `this` binding of `thisArg` and the arguments of `args`.
*
* @private
* @param {Function} func The function to invoke.
* @param {*} thisArg The `this` binding of `func`.
* @param {Array} args The arguments to invoke `func` with.
* @returns {*} Returns the result of `func`.
*/
function apply(func, thisArg, args) {
switch (args.length) {
case 0: return func.call(thisArg);
case 1: return func.call(thisArg, args[0]);
case 2: return func.call(thisArg, args[0], args[1]);
case 3: return func.call(thisArg, args[0], args[1], args[2]);
}
return func.apply(thisArg, args);
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (apply);
/***/ }),
/***/ "./node_modules/lodash-es/_arrayEach.js":
/*!**********************************************!*\
!*** ./node_modules/lodash-es/_arrayEach.js ***!
\**********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/**
* A specialized version of `_.forEach` for arrays without support for
* iteratee shorthands.
*
* @private
* @param {Array} [array] The array to iterate over.
* @param {Function} iteratee The function invoked per iteration.
* @returns {Array} Returns `array`.
*/
function arrayEach(array, iteratee) {
var index = -1,
length = array == null ? 0 : array.length;
while (++index < length) {
if (iteratee(array[index], index, array) === false) {
break;
}
}
return array;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (arrayEach);
/***/ }),
/***/ "./node_modules/lodash-es/_arrayFilter.js":
/*!************************************************!*\
!*** ./node_modules/lodash-es/_arrayFilter.js ***!
\************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/**
* A specialized version of `_.filter` for arrays without support for
* iteratee shorthands.
*
* @private
* @param {Array} [array] The array to iterate over.
* @param {Function} predicate The function invoked per iteration.
* @returns {Array} Returns the new filtered array.
*/
function arrayFilter(array, predicate) {
var index = -1,
length = array == null ? 0 : array.length,
resIndex = 0,
result = [];
while (++index < length) {
var value = array[index];
if (predicate(value, index, array)) {
result[resIndex++] = value;
}
}
return result;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (arrayFilter);
/***/ }),
/***/ "./node_modules/lodash-es/_arrayLikeKeys.js":
/*!**************************************************!*\
!*** ./node_modules/lodash-es/_arrayLikeKeys.js ***!
\**************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _baseTimes_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./_baseTimes.js */ "./node_modules/lodash-es/_baseTimes.js");
/* harmony import */ var _isArguments_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./isArguments.js */ "./node_modules/lodash-es/isArguments.js");
/* harmony import */ var _isArray_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./isArray.js */ "./node_modules/lodash-es/isArray.js");
/* harmony import */ var _isBuffer_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./isBuffer.js */ "./node_modules/lodash-es/isBuffer.js");
/* harmony import */ var _isIndex_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./_isIndex.js */ "./node_modules/lodash-es/_isIndex.js");
/* harmony import */ var _isTypedArray_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./isTypedArray.js */ "./node_modules/lodash-es/isTypedArray.js");
/** Used for built-in method references. */
var objectProto = Object.prototype;
/** Used to check objects for own properties. */
var hasOwnProperty = objectProto.hasOwnProperty;
/**
* Creates an array of the enumerable property names of the array-like `value`.
*
* @private
* @param {*} value The value to query.
* @param {boolean} inherited Specify returning inherited property names.
* @returns {Array} Returns the array of property names.
*/
function arrayLikeKeys(value, inherited) {
var isArr = (0,_isArray_js__WEBPACK_IMPORTED_MODULE_0__["default"])(value),
isArg = !isArr && (0,_isArguments_js__WEBPACK_IMPORTED_MODULE_1__["default"])(value),
isBuff = !isArr && !isArg && (0,_isBuffer_js__WEBPACK_IMPORTED_MODULE_2__["default"])(value),
isType = !isArr && !isArg && !isBuff && (0,_isTypedArray_js__WEBPACK_IMPORTED_MODULE_3__["default"])(value),
skipIndexes = isArr || isArg || isBuff || isType,
result = skipIndexes ? (0,_baseTimes_js__WEBPACK_IMPORTED_MODULE_4__["default"])(value.length, String) : [],
length = result.length;
for (var key in value) {
if ((inherited || hasOwnProperty.call(value, key)) &&
!(skipIndexes && (
// Safari 9 has enumerable `arguments.length` in strict mode.
key == 'length' ||
// Node.js 0.10 has enumerable non-index properties on buffers.
(isBuff && (key == 'offset' || key == 'parent')) ||
// PhantomJS 2 has enumerable non-index properties on typed arrays.
(isType && (key == 'buffer' || key == 'byteLength' || key == 'byteOffset')) ||
// Skip index properties.
(0,_isIndex_js__WEBPACK_IMPORTED_MODULE_5__["default"])(key, length)
))) {
result.push(key);
}
}
return result;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (arrayLikeKeys);
/***/ }),
/***/ "./node_modules/lodash-es/_arrayMap.js":
/*!*********************************************!*\
!*** ./node_modules/lodash-es/_arrayMap.js ***!
\*********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/**
* A specialized version of `_.map` for arrays without support for iteratee
* shorthands.
*
* @private
* @param {Array} [array] The array to iterate over.
* @param {Function} iteratee The function invoked per iteration.
* @returns {Array} Returns the new mapped array.
*/
function arrayMap(array, iteratee) {
var index = -1,
length = array == null ? 0 : array.length,
result = Array(length);
while (++index < length) {
result[index] = iteratee(array[index], index, array);
}
return result;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (arrayMap);
/***/ }),
/***/ "./node_modules/lodash-es/_arrayPush.js":
/*!**********************************************!*\
!*** ./node_modules/lodash-es/_arrayPush.js ***!
\**********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/**
* Appends the elements of `values` to `array`.
*
* @private
* @param {Array} array The array to modify.
* @param {Array} values The values to append.
* @returns {Array} Returns `array`.
*/
function arrayPush(array, values) {
var index = -1,
length = values.length,
offset = array.length;
while (++index < length) {
array[offset + index] = values[index];
}
return array;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (arrayPush);
/***/ }),
/***/ "./node_modules/lodash-es/_arraySome.js":
/*!**********************************************!*\
!*** ./node_modules/lodash-es/_arraySome.js ***!
\**********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/**
* A specialized version of `_.some` for arrays without support for iteratee
* shorthands.
*
* @private
* @param {Array} [array] The array to iterate over.
* @param {Function} predicate The function invoked per iteration.
* @returns {boolean} Returns `true` if any element passes the predicate check,
* else `false`.
*/
function arraySome(array, predicate) {
var index = -1,
length = array == null ? 0 : array.length;
while (++index < length) {
if (predicate(array[index], index, array)) {
return true;
}
}
return false;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (arraySome);
/***/ }),
/***/ "./node_modules/lodash-es/_asciiToArray.js":
/*!*************************************************!*\
!*** ./node_modules/lodash-es/_asciiToArray.js ***!
\*************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/**
* Converts an ASCII `string` to an array.
*
* @private
* @param {string} string The string to convert.
* @returns {Array} Returns the converted array.
*/
function asciiToArray(string) {
return string.split('');
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (asciiToArray);
/***/ }),
/***/ "./node_modules/lodash-es/_assignMergeValue.js":
/*!*****************************************************!*\
!*** ./node_modules/lodash-es/_assignMergeValue.js ***!
\*****************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _baseAssignValue_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./_baseAssignValue.js */ "./node_modules/lodash-es/_baseAssignValue.js");
/* harmony import */ var _eq_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./eq.js */ "./node_modules/lodash-es/eq.js");
/**
* This function is like `assignValue` except that it doesn't assign
* `undefined` values.
*
* @private
* @param {Object} object The object to modify.
* @param {string} key The key of the property to assign.
* @param {*} value The value to assign.
*/
function assignMergeValue(object, key, value) {
if ((value !== undefined && !(0,_eq_js__WEBPACK_IMPORTED_MODULE_0__["default"])(object[key], value)) ||
(value === undefined && !(key in object))) {
(0,_baseAssignValue_js__WEBPACK_IMPORTED_MODULE_1__["default"])(object, key, value);
}
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (assignMergeValue);
/***/ }),
/***/ "./node_modules/lodash-es/_assignValue.js":
/*!************************************************!*\
!*** ./node_modules/lodash-es/_assignValue.js ***!
\************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _baseAssignValue_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./_baseAssignValue.js */ "./node_modules/lodash-es/_baseAssignValue.js");
/* harmony import */ var _eq_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./eq.js */ "./node_modules/lodash-es/eq.js");
/** Used for built-in method references. */
var objectProto = Object.prototype;
/** Used to check objects for own properties. */
var hasOwnProperty = objectProto.hasOwnProperty;
/**
* Assigns `value` to `key` of `object` if the existing value is not equivalent
* using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
* for equality comparisons.
*
* @private
* @param {Object} object The object to modify.
* @param {string} key The key of the property to assign.
* @param {*} value The value to assign.
*/
function assignValue(object, key, value) {
var objValue = object[key];
if (!(hasOwnProperty.call(object, key) && (0,_eq_js__WEBPACK_IMPORTED_MODULE_0__["default"])(objValue, value)) ||
(value === undefined && !(key in object))) {
(0,_baseAssignValue_js__WEBPACK_IMPORTED_MODULE_1__["default"])(object, key, value);
}
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (assignValue);
/***/ }),
/***/ "./node_modules/lodash-es/_assocIndexOf.js":
/*!*************************************************!*\
!*** ./node_modules/lodash-es/_assocIndexOf.js ***!
\*************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _eq_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./eq.js */ "./node_modules/lodash-es/eq.js");
/**
* Gets the index at which the `key` is found in `array` of key-value pairs.
*
* @private
* @param {Array} array The array to inspect.
* @param {*} key The key to search for.
* @returns {number} Returns the index of the matched value, else `-1`.
*/
function assocIndexOf(array, key) {
var length = array.length;
while (length--) {
if ((0,_eq_js__WEBPACK_IMPORTED_MODULE_0__["default"])(array[length][0], key)) {
return length;
}
}
return -1;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (assocIndexOf);
/***/ }),
/***/ "./node_modules/lodash-es/_baseAssign.js":
/*!***********************************************!*\
!*** ./node_modules/lodash-es/_baseAssign.js ***!
\***********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _copyObject_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_copyObject.js */ "./node_modules/lodash-es/_copyObject.js");
/* harmony import */ var _keys_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./keys.js */ "./node_modules/lodash-es/keys.js");
/**
* The base implementation of `_.assign` without support for multiple sources
* or `customizer` functions.
*
* @private
* @param {Object} object The destination object.
* @param {Object} source The source object.
* @returns {Object} Returns `object`.
*/
function baseAssign(object, source) {
return object && (0,_copyObject_js__WEBPACK_IMPORTED_MODULE_0__["default"])(source, (0,_keys_js__WEBPACK_IMPORTED_MODULE_1__["default"])(source), object);
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (baseAssign);
/***/ }),
/***/ "./node_modules/lodash-es/_baseAssignIn.js":
/*!*************************************************!*\
!*** ./node_modules/lodash-es/_baseAssignIn.js ***!
\*************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _copyObject_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_copyObject.js */ "./node_modules/lodash-es/_copyObject.js");
/* harmony import */ var _keysIn_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./keysIn.js */ "./node_modules/lodash-es/keysIn.js");
/**
* The base implementation of `_.assignIn` without support for multiple sources
* or `customizer` functions.
*
* @private
* @param {Object} object The destination object.
* @param {Object} source The source object.
* @returns {Object} Returns `object`.
*/
function baseAssignIn(object, source) {
return object && (0,_copyObject_js__WEBPACK_IMPORTED_MODULE_0__["default"])(source, (0,_keysIn_js__WEBPACK_IMPORTED_MODULE_1__["default"])(source), object);
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (baseAssignIn);
/***/ }),
/***/ "./node_modules/lodash-es/_baseAssignValue.js":
/*!****************************************************!*\
!*** ./node_modules/lodash-es/_baseAssignValue.js ***!
\****************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _defineProperty_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_defineProperty.js */ "./node_modules/lodash-es/_defineProperty.js");
/**
* The base implementation of `assignValue` and `assignMergeValue` without
* value checks.
*
* @private
* @param {Object} object The object to modify.
* @param {string} key The key of the property to assign.
* @param {*} value The value to assign.
*/
function baseAssignValue(object, key, value) {
if (key == '__proto__' && _defineProperty_js__WEBPACK_IMPORTED_MODULE_0__["default"]) {
(0,_defineProperty_js__WEBPACK_IMPORTED_MODULE_0__["default"])(object, key, {
'configurable': true,
'enumerable': true,
'value': value,
'writable': true
});
} else {
object[key] = value;
}
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (baseAssignValue);
/***/ }),
/***/ "./node_modules/lodash-es/_baseClone.js":
/*!**********************************************!*\
!*** ./node_modules/lodash-es/_baseClone.js ***!
\**********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _Stack_js__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(/*! ./_Stack.js */ "./node_modules/lodash-es/_Stack.js");
/* harmony import */ var _arrayEach_js__WEBPACK_IMPORTED_MODULE_20__ = __webpack_require__(/*! ./_arrayEach.js */ "./node_modules/lodash-es/_arrayEach.js");
/* harmony import */ var _assignValue_js__WEBPACK_IMPORTED_MODULE_21__ = __webpack_require__(/*! ./_assignValue.js */ "./node_modules/lodash-es/_assignValue.js");
/* harmony import */ var _baseAssign_js__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ./_baseAssign.js */ "./node_modules/lodash-es/_baseAssign.js");
/* harmony import */ var _baseAssignIn_js__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./_baseAssignIn.js */ "./node_modules/lodash-es/_baseAssignIn.js");
/* harmony import */ var _cloneBuffer_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./_cloneBuffer.js */ "./node_modules/lodash-es/_cloneBuffer.js");
/* harmony import */ var _copyArray_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./_copyArray.js */ "./node_modules/lodash-es/_copyArray.js");
/* harmony import */ var _copySymbols_js__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./_copySymbols.js */ "./node_modules/lodash-es/_copySymbols.js");
/* harmony import */ var _copySymbolsIn_js__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./_copySymbolsIn.js */ "./node_modules/lodash-es/_copySymbolsIn.js");
/* harmony import */ var _getAllKeys_js__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(/*! ./_getAllKeys.js */ "./node_modules/lodash-es/_getAllKeys.js");
/* harmony import */ var _getAllKeysIn_js__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(/*! ./_getAllKeysIn.js */ "./node_modules/lodash-es/_getAllKeysIn.js");
/* harmony import */ var _getTag_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./_getTag.js */ "./node_modules/lodash-es/_getTag.js");
/* harmony import */ var _initCloneArray_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./_initCloneArray.js */ "./node_modules/lodash-es/_initCloneArray.js");
/* harmony import */ var _initCloneByTag_js__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! ./_initCloneByTag.js */ "./node_modules/lodash-es/_initCloneByTag.js");
/* harmony import */ var _initCloneObject_js__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./_initCloneObject.js */ "./node_modules/lodash-es/_initCloneObject.js");
/* harmony import */ var _isArray_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./isArray.js */ "./node_modules/lodash-es/isArray.js");
/* harmony import */ var _isBuffer_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./isBuffer.js */ "./node_modules/lodash-es/isBuffer.js");
/* harmony import */ var _isMap_js__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(/*! ./isMap.js */ "./node_modules/lodash-es/isMap.js");
/* harmony import */ var _isObject_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./isObject.js */ "./node_modules/lodash-es/isObject.js");
/* harmony import */ var _isSet_js__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(/*! ./isSet.js */ "./node_modules/lodash-es/isSet.js");
/* harmony import */ var _keys_js__WEBPACK_IMPORTED_MODULE_19__ = __webpack_require__(/*! ./keys.js */ "./node_modules/lodash-es/keys.js");
/* harmony import */ var _keysIn_js__WEBPACK_IMPORTED_MODULE_18__ = __webpack_require__(/*! ./keysIn.js */ "./node_modules/lodash-es/keysIn.js");
/** Used to compose bitmasks for cloning. */
var CLONE_DEEP_FLAG = 1,
CLONE_FLAT_FLAG = 2,
CLONE_SYMBOLS_FLAG = 4;
/** `Object#toString` result references. */
var argsTag = '[object Arguments]',
arrayTag = '[object Array]',
boolTag = '[object Boolean]',
dateTag = '[object Date]',
errorTag = '[object Error]',
funcTag = '[object Function]',
genTag = '[object GeneratorFunction]',
mapTag = '[object Map]',
numberTag = '[object Number]',
objectTag = '[object Object]',
regexpTag = '[object RegExp]',
setTag = '[object Set]',
stringTag = '[object String]',
symbolTag = '[object Symbol]',
weakMapTag = '[object WeakMap]';
var arrayBufferTag = '[object ArrayBuffer]',
dataViewTag = '[object DataView]',
float32Tag = '[object Float32Array]',
float64Tag = '[object Float64Array]',
int8Tag = '[object Int8Array]',
int16Tag = '[object Int16Array]',
int32Tag = '[object Int32Array]',
uint8Tag = '[object Uint8Array]',
uint8ClampedTag = '[object Uint8ClampedArray]',
uint16Tag = '[object Uint16Array]',
uint32Tag = '[object Uint32Array]';
/** Used to identify `toStringTag` values supported by `_.clone`. */
var cloneableTags = {};
cloneableTags[argsTag] = cloneableTags[arrayTag] =
cloneableTags[arrayBufferTag] = cloneableTags[dataViewTag] =
cloneableTags[boolTag] = cloneableTags[dateTag] =
cloneableTags[float32Tag] = cloneableTags[float64Tag] =
cloneableTags[int8Tag] = cloneableTags[int16Tag] =
cloneableTags[int32Tag] = cloneableTags[mapTag] =
cloneableTags[numberTag] = cloneableTags[objectTag] =
cloneableTags[regexpTag] = cloneableTags[setTag] =
cloneableTags[stringTag] = cloneableTags[symbolTag] =
cloneableTags[uint8Tag] = cloneableTags[uint8ClampedTag] =
cloneableTags[uint16Tag] = cloneableTags[uint32Tag] = true;
cloneableTags[errorTag] = cloneableTags[funcTag] =
cloneableTags[weakMapTag] = false;
/**
* The base implementation of `_.clone` and `_.cloneDeep` which tracks
* traversed objects.
*
* @private
* @param {*} value The value to clone.
* @param {boolean} bitmask The bitmask flags.
* 1 - Deep clone
* 2 - Flatten inherited properties
* 4 - Clone symbols
* @param {Function} [customizer] The function to customize cloning.
* @param {string} [key] The key of `value`.
* @param {Object} [object] The parent object of `value`.
* @param {Object} [stack] Tracks traversed objects and their clone counterparts.
* @returns {*} Returns the cloned value.
*/
function baseClone(value, bitmask, customizer, key, object, stack) {
var result,
isDeep = bitmask & CLONE_DEEP_FLAG,
isFlat = bitmask & CLONE_FLAT_FLAG,
isFull = bitmask & CLONE_SYMBOLS_FLAG;
if (customizer) {
result = object ? customizer(value, key, object, stack) : customizer(value);
}
if (result !== undefined) {
return result;
}
if (!(0,_isObject_js__WEBPACK_IMPORTED_MODULE_0__["default"])(value)) {
return value;
}
var isArr = (0,_isArray_js__WEBPACK_IMPORTED_MODULE_1__["default"])(value);
if (isArr) {
result = (0,_initCloneArray_js__WEBPACK_IMPORTED_MODULE_2__["default"])(value);
if (!isDeep) {
return (0,_copyArray_js__WEBPACK_IMPORTED_MODULE_3__["default"])(value, result);
}
} else {
var tag = (0,_getTag_js__WEBPACK_IMPORTED_MODULE_4__["default"])(value),
isFunc = tag == funcTag || tag == genTag;
if ((0,_isBuffer_js__WEBPACK_IMPORTED_MODULE_5__["default"])(value)) {
return (0,_cloneBuffer_js__WEBPACK_IMPORTED_MODULE_6__["default"])(value, isDeep);
}
if (tag == objectTag || tag == argsTag || (isFunc && !object)) {
result = (isFlat || isFunc) ? {} : (0,_initCloneObject_js__WEBPACK_IMPORTED_MODULE_7__["default"])(value);
if (!isDeep) {
return isFlat
? (0,_copySymbolsIn_js__WEBPACK_IMPORTED_MODULE_8__["default"])(value, (0,_baseAssignIn_js__WEBPACK_IMPORTED_MODULE_9__["default"])(result, value))
: (0,_copySymbols_js__WEBPACK_IMPORTED_MODULE_10__["default"])(value, (0,_baseAssign_js__WEBPACK_IMPORTED_MODULE_11__["default"])(result, value));
}
} else {
if (!cloneableTags[tag]) {
return object ? value : {};
}
result = (0,_initCloneByTag_js__WEBPACK_IMPORTED_MODULE_12__["default"])(value, tag, isDeep);
}
}
// Check for circular references and return its corresponding clone.
stack || (stack = new _Stack_js__WEBPACK_IMPORTED_MODULE_13__["default"]);
var stacked = stack.get(value);
if (stacked) {
return stacked;
}
stack.set(value, result);
if ((0,_isSet_js__WEBPACK_IMPORTED_MODULE_14__["default"])(value)) {
value.forEach(function(subValue) {
result.add(baseClone(subValue, bitmask, customizer, subValue, value, stack));
});
} else if ((0,_isMap_js__WEBPACK_IMPORTED_MODULE_15__["default"])(value)) {
value.forEach(function(subValue, key) {
result.set(key, baseClone(subValue, bitmask, customizer, key, value, stack));
});
}
var keysFunc = isFull
? (isFlat ? _getAllKeysIn_js__WEBPACK_IMPORTED_MODULE_16__["default"] : _getAllKeys_js__WEBPACK_IMPORTED_MODULE_17__["default"])
: (isFlat ? _keysIn_js__WEBPACK_IMPORTED_MODULE_18__["default"] : _keys_js__WEBPACK_IMPORTED_MODULE_19__["default"]);
var props = isArr ? undefined : keysFunc(value);
(0,_arrayEach_js__WEBPACK_IMPORTED_MODULE_20__["default"])(props || value, function(subValue, key) {
if (props) {
key = subValue;
subValue = value[key];
}
// Recursively populate clone (susceptible to call stack limits).
(0,_assignValue_js__WEBPACK_IMPORTED_MODULE_21__["default"])(result, key, baseClone(subValue, bitmask, customizer, key, value, stack));
});
return result;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (baseClone);
/***/ }),
/***/ "./node_modules/lodash-es/_baseCreate.js":
/*!***********************************************!*\
!*** ./node_modules/lodash-es/_baseCreate.js ***!
\***********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _isObject_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./isObject.js */ "./node_modules/lodash-es/isObject.js");
/** Built-in value references. */
var objectCreate = Object.create;
/**
* The base implementation of `_.create` without support for assigning
* properties to the created object.
*
* @private
* @param {Object} proto The object to inherit from.
* @returns {Object} Returns the new object.
*/
var baseCreate = (function() {
function object() {}
return function(proto) {
if (!(0,_isObject_js__WEBPACK_IMPORTED_MODULE_0__["default"])(proto)) {
return {};
}
if (objectCreate) {
return objectCreate(proto);
}
object.prototype = proto;
var result = new object;
object.prototype = undefined;
return result;
};
}());
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (baseCreate);
/***/ }),
/***/ "./node_modules/lodash-es/_baseFindIndex.js":
/*!**************************************************!*\
!*** ./node_modules/lodash-es/_baseFindIndex.js ***!
\**************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/**
* The base implementation of `_.findIndex` and `_.findLastIndex` without
* support for iteratee shorthands.
*
* @private
* @param {Array} array The array to inspect.
* @param {Function} predicate The function invoked per iteration.
* @param {number} fromIndex The index to search from.
* @param {boolean} [fromRight] Specify iterating from right to left.
* @returns {number} Returns the index of the matched value, else `-1`.
*/
function baseFindIndex(array, predicate, fromIndex, fromRight) {
var length = array.length,
index = fromIndex + (fromRight ? 1 : -1);
while ((fromRight ? index-- : ++index < length)) {
if (predicate(array[index], index, array)) {
return index;
}
}
return -1;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (baseFindIndex);
/***/ }),
/***/ "./node_modules/lodash-es/_baseFor.js":
/*!********************************************!*\
!*** ./node_modules/lodash-es/_baseFor.js ***!
\********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _createBaseFor_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_createBaseFor.js */ "./node_modules/lodash-es/_createBaseFor.js");
/**
* The base implementation of `baseForOwn` which iterates over `object`
* properties returned by `keysFunc` and invokes `iteratee` for each property.
* Iteratee functions may exit iteration early by explicitly returning `false`.
*
* @private
* @param {Object} object The object to iterate over.
* @param {Function} iteratee The function invoked per iteration.
* @param {Function} keysFunc The function to get the keys of `object`.
* @returns {Object} Returns `object`.
*/
var baseFor = (0,_createBaseFor_js__WEBPACK_IMPORTED_MODULE_0__["default"])();
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (baseFor);
/***/ }),
/***/ "./node_modules/lodash-es/_baseGet.js":
/*!********************************************!*\
!*** ./node_modules/lodash-es/_baseGet.js ***!
\********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _castPath_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_castPath.js */ "./node_modules/lodash-es/_castPath.js");
/* harmony import */ var _toKey_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./_toKey.js */ "./node_modules/lodash-es/_toKey.js");
/**
* The base implementation of `_.get` without support for default values.
*
* @private
* @param {Object} object The object to query.
* @param {Array|string} path The path of the property to get.
* @returns {*} Returns the resolved value.
*/
function baseGet(object, path) {
path = (0,_castPath_js__WEBPACK_IMPORTED_MODULE_0__["default"])(path, object);
var index = 0,
length = path.length;
while (object != null && index < length) {
object = object[(0,_toKey_js__WEBPACK_IMPORTED_MODULE_1__["default"])(path[index++])];
}
return (index && index == length) ? object : undefined;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (baseGet);
/***/ }),
/***/ "./node_modules/lodash-es/_baseGetAllKeys.js":
/*!***************************************************!*\
!*** ./node_modules/lodash-es/_baseGetAllKeys.js ***!
\***************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _arrayPush_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./_arrayPush.js */ "./node_modules/lodash-es/_arrayPush.js");
/* harmony import */ var _isArray_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./isArray.js */ "./node_modules/lodash-es/isArray.js");
/**
* The base implementation of `getAllKeys` and `getAllKeysIn` which uses
* `keysFunc` and `symbolsFunc` to get the enumerable property names and
* symbols of `object`.
*
* @private
* @param {Object} object The object to query.
* @param {Function} keysFunc The function to get the keys of `object`.
* @param {Function} symbolsFunc The function to get the symbols of `object`.
* @returns {Array} Returns the array of property names and symbols.
*/
function baseGetAllKeys(object, keysFunc, symbolsFunc) {
var result = keysFunc(object);
return (0,_isArray_js__WEBPACK_IMPORTED_MODULE_0__["default"])(object) ? result : (0,_arrayPush_js__WEBPACK_IMPORTED_MODULE_1__["default"])(result, symbolsFunc(object));
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (baseGetAllKeys);
/***/ }),
/***/ "./node_modules/lodash-es/_baseGetTag.js":
/*!***********************************************!*\
!*** ./node_modules/lodash-es/_baseGetTag.js ***!
\***********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _Symbol_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_Symbol.js */ "./node_modules/lodash-es/_Symbol.js");
/* harmony import */ var _getRawTag_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./_getRawTag.js */ "./node_modules/lodash-es/_getRawTag.js");
/* harmony import */ var _objectToString_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./_objectToString.js */ "./node_modules/lodash-es/_objectToString.js");
/** `Object#toString` result references. */
var nullTag = '[object Null]',
undefinedTag = '[object Undefined]';
/** Built-in value references. */
var symToStringTag = _Symbol_js__WEBPACK_IMPORTED_MODULE_0__["default"] ? _Symbol_js__WEBPACK_IMPORTED_MODULE_0__["default"].toStringTag : undefined;
/**
* The base implementation of `getTag` without fallbacks for buggy environments.
*
* @private
* @param {*} value The value to query.
* @returns {string} Returns the `toStringTag`.
*/
function baseGetTag(value) {
if (value == null) {
return value === undefined ? undefinedTag : nullTag;
}
return (symToStringTag && symToStringTag in Object(value))
? (0,_getRawTag_js__WEBPACK_IMPORTED_MODULE_1__["default"])(value)
: (0,_objectToString_js__WEBPACK_IMPORTED_MODULE_2__["default"])(value);
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (baseGetTag);
/***/ }),
/***/ "./node_modules/lodash-es/_baseIndexOf.js":
/*!************************************************!*\
!*** ./node_modules/lodash-es/_baseIndexOf.js ***!
\************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _baseFindIndex_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./_baseFindIndex.js */ "./node_modules/lodash-es/_baseFindIndex.js");
/* harmony import */ var _baseIsNaN_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./_baseIsNaN.js */ "./node_modules/lodash-es/_baseIsNaN.js");
/* harmony import */ var _strictIndexOf_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_strictIndexOf.js */ "./node_modules/lodash-es/_strictIndexOf.js");
/**
* The base implementation of `_.indexOf` without `fromIndex` bounds checks.
*
* @private
* @param {Array} array The array to inspect.
* @param {*} value The value to search for.
* @param {number} fromIndex The index to search from.
* @returns {number} Returns the index of the matched value, else `-1`.
*/
function baseIndexOf(array, value, fromIndex) {
return value === value
? (0,_strictIndexOf_js__WEBPACK_IMPORTED_MODULE_0__["default"])(array, value, fromIndex)
: (0,_baseFindIndex_js__WEBPACK_IMPORTED_MODULE_1__["default"])(array, _baseIsNaN_js__WEBPACK_IMPORTED_MODULE_2__["default"], fromIndex);
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (baseIndexOf);
/***/ }),
/***/ "./node_modules/lodash-es/_baseIndexOfWith.js":
/*!****************************************************!*\
!*** ./node_modules/lodash-es/_baseIndexOfWith.js ***!
\****************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/**
* This function is like `baseIndexOf` except that it accepts a comparator.
*
* @private
* @param {Array} array The array to inspect.
* @param {*} value The value to search for.
* @param {number} fromIndex The index to search from.
* @param {Function} comparator The comparator invoked per element.
* @returns {number} Returns the index of the matched value, else `-1`.
*/
function baseIndexOfWith(array, value, fromIndex, comparator) {
var index = fromIndex - 1,
length = array.length;
while (++index < length) {
if (comparator(array[index], value)) {
return index;
}
}
return -1;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (baseIndexOfWith);
/***/ }),
/***/ "./node_modules/lodash-es/_baseIsArguments.js":
/*!****************************************************!*\
!*** ./node_modules/lodash-es/_baseIsArguments.js ***!
\****************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _baseGetTag_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./_baseGetTag.js */ "./node_modules/lodash-es/_baseGetTag.js");
/* harmony import */ var _isObjectLike_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./isObjectLike.js */ "./node_modules/lodash-es/isObjectLike.js");
/** `Object#toString` result references. */
var argsTag = '[object Arguments]';
/**
* The base implementation of `_.isArguments`.
*
* @private
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is an `arguments` object,
*/
function baseIsArguments(value) {
return (0,_isObjectLike_js__WEBPACK_IMPORTED_MODULE_0__["default"])(value) && (0,_baseGetTag_js__WEBPACK_IMPORTED_MODULE_1__["default"])(value) == argsTag;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (baseIsArguments);
/***/ }),
/***/ "./node_modules/lodash-es/_baseIsEqual.js":
/*!************************************************!*\
!*** ./node_modules/lodash-es/_baseIsEqual.js ***!
\************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _baseIsEqualDeep_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./_baseIsEqualDeep.js */ "./node_modules/lodash-es/_baseIsEqualDeep.js");
/* harmony import */ var _isObjectLike_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./isObjectLike.js */ "./node_modules/lodash-es/isObjectLike.js");
/**
* The base implementation of `_.isEqual` which supports partial comparisons
* and tracks traversed objects.
*
* @private
* @param {*} value The value to compare.
* @param {*} other The other value to compare.
* @param {boolean} bitmask The bitmask flags.
* 1 - Unordered comparison
* 2 - Partial comparison
* @param {Function} [customizer] The function to customize comparisons.
* @param {Object} [stack] Tracks traversed `value` and `other` objects.
* @returns {boolean} Returns `true` if the values are equivalent, else `false`.
*/
function baseIsEqual(value, other, bitmask, customizer, stack) {
if (value === other) {
return true;
}
if (value == null || other == null || (!(0,_isObjectLike_js__WEBPACK_IMPORTED_MODULE_0__["default"])(value) && !(0,_isObjectLike_js__WEBPACK_IMPORTED_MODULE_0__["default"])(other))) {
return value !== value && other !== other;
}
return (0,_baseIsEqualDeep_js__WEBPACK_IMPORTED_MODULE_1__["default"])(value, other, bitmask, customizer, baseIsEqual, stack);
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (baseIsEqual);
/***/ }),
/***/ "./node_modules/lodash-es/_baseIsEqualDeep.js":
/*!****************************************************!*\
!*** ./node_modules/lodash-es/_baseIsEqualDeep.js ***!
\****************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _Stack_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./_Stack.js */ "./node_modules/lodash-es/_Stack.js");
/* harmony import */ var _equalArrays_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./_equalArrays.js */ "./node_modules/lodash-es/_equalArrays.js");
/* harmony import */ var _equalByTag_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./_equalByTag.js */ "./node_modules/lodash-es/_equalByTag.js");
/* harmony import */ var _equalObjects_js__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./_equalObjects.js */ "./node_modules/lodash-es/_equalObjects.js");
/* harmony import */ var _getTag_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./_getTag.js */ "./node_modules/lodash-es/_getTag.js");
/* harmony import */ var _isArray_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./isArray.js */ "./node_modules/lodash-es/isArray.js");
/* harmony import */ var _isBuffer_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./isBuffer.js */ "./node_modules/lodash-es/isBuffer.js");
/* harmony import */ var _isTypedArray_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./isTypedArray.js */ "./node_modules/lodash-es/isTypedArray.js");
/** Used to compose bitmasks for value comparisons. */
var COMPARE_PARTIAL_FLAG = 1;
/** `Object#toString` result references. */
var argsTag = '[object Arguments]',
arrayTag = '[object Array]',
objectTag = '[object Object]';
/** Used for built-in method references. */
var objectProto = Object.prototype;
/** Used to check objects for own properties. */
var hasOwnProperty = objectProto.hasOwnProperty;
/**
* A specialized version of `baseIsEqual` for arrays and objects which performs
* deep comparisons and tracks traversed objects enabling objects with circular
* references to be compared.
*
* @private
* @param {Object} object The object to compare.
* @param {Object} other The other object to compare.
* @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details.
* @param {Function} customizer The function to customize comparisons.
* @param {Function} equalFunc The function to determine equivalents of values.
* @param {Object} [stack] Tracks traversed `object` and `other` objects.
* @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
*/
function baseIsEqualDeep(object, other, bitmask, customizer, equalFunc, stack) {
var objIsArr = (0,_isArray_js__WEBPACK_IMPORTED_MODULE_0__["default"])(object),
othIsArr = (0,_isArray_js__WEBPACK_IMPORTED_MODULE_0__["default"])(other),
objTag = objIsArr ? arrayTag : (0,_getTag_js__WEBPACK_IMPORTED_MODULE_1__["default"])(object),
othTag = othIsArr ? arrayTag : (0,_getTag_js__WEBPACK_IMPORTED_MODULE_1__["default"])(other);
objTag = objTag == argsTag ? objectTag : objTag;
othTag = othTag == argsTag ? objectTag : othTag;
var objIsObj = objTag == objectTag,
othIsObj = othTag == objectTag,
isSameTag = objTag == othTag;
if (isSameTag && (0,_isBuffer_js__WEBPACK_IMPORTED_MODULE_2__["default"])(object)) {
if (!(0,_isBuffer_js__WEBPACK_IMPORTED_MODULE_2__["default"])(other)) {
return false;
}
objIsArr = true;
objIsObj = false;
}
if (isSameTag && !objIsObj) {
stack || (stack = new _Stack_js__WEBPACK_IMPORTED_MODULE_3__["default"]);
return (objIsArr || (0,_isTypedArray_js__WEBPACK_IMPORTED_MODULE_4__["default"])(object))
? (0,_equalArrays_js__WEBPACK_IMPORTED_MODULE_5__["default"])(object, other, bitmask, customizer, equalFunc, stack)
: (0,_equalByTag_js__WEBPACK_IMPORTED_MODULE_6__["default"])(object, other, objTag, bitmask, customizer, equalFunc, stack);
}
if (!(bitmask & COMPARE_PARTIAL_FLAG)) {
var objIsWrapped = objIsObj && hasOwnProperty.call(object, '__wrapped__'),
othIsWrapped = othIsObj && hasOwnProperty.call(other, '__wrapped__');
if (objIsWrapped || othIsWrapped) {
var objUnwrapped = objIsWrapped ? object.value() : object,
othUnwrapped = othIsWrapped ? other.value() : other;
stack || (stack = new _Stack_js__WEBPACK_IMPORTED_MODULE_3__["default"]);
return equalFunc(objUnwrapped, othUnwrapped, bitmask, customizer, stack);
}
}
if (!isSameTag) {
return false;
}
stack || (stack = new _Stack_js__WEBPACK_IMPORTED_MODULE_3__["default"]);
return (0,_equalObjects_js__WEBPACK_IMPORTED_MODULE_7__["default"])(object, other, bitmask, customizer, equalFunc, stack);
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (baseIsEqualDeep);
/***/ }),
/***/ "./node_modules/lodash-es/_baseIsMap.js":
/*!**********************************************!*\
!*** ./node_modules/lodash-es/_baseIsMap.js ***!
\**********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _getTag_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./_getTag.js */ "./node_modules/lodash-es/_getTag.js");
/* harmony import */ var _isObjectLike_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./isObjectLike.js */ "./node_modules/lodash-es/isObjectLike.js");
/** `Object#toString` result references. */
var mapTag = '[object Map]';
/**
* The base implementation of `_.isMap` without Node.js optimizations.
*
* @private
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a map, else `false`.
*/
function baseIsMap(value) {
return (0,_isObjectLike_js__WEBPACK_IMPORTED_MODULE_0__["default"])(value) && (0,_getTag_js__WEBPACK_IMPORTED_MODULE_1__["default"])(value) == mapTag;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (baseIsMap);
/***/ }),
/***/ "./node_modules/lodash-es/_baseIsNaN.js":
/*!**********************************************!*\
!*** ./node_modules/lodash-es/_baseIsNaN.js ***!
\**********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/**
* The base implementation of `_.isNaN` without support for number objects.
*
* @private
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is `NaN`, else `false`.
*/
function baseIsNaN(value) {
return value !== value;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (baseIsNaN);
/***/ }),
/***/ "./node_modules/lodash-es/_baseIsNative.js":
/*!*************************************************!*\
!*** ./node_modules/lodash-es/_baseIsNative.js ***!
\*************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _isFunction_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./isFunction.js */ "./node_modules/lodash-es/isFunction.js");
/* harmony import */ var _isMasked_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./_isMasked.js */ "./node_modules/lodash-es/_isMasked.js");
/* harmony import */ var _isObject_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./isObject.js */ "./node_modules/lodash-es/isObject.js");
/* harmony import */ var _toSource_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./_toSource.js */ "./node_modules/lodash-es/_toSource.js");
/**
* Used to match `RegExp`
* [syntax characters](http://ecma-international.org/ecma-262/7.0/#sec-patterns).
*/
var reRegExpChar = /[\\^$.*+?()[\]{}|]/g;
/** Used to detect host constructors (Safari). */
var reIsHostCtor = /^\[object .+?Constructor\]$/;
/** Used for built-in method references. */
var funcProto = Function.prototype,
objectProto = Object.prototype;
/** Used to resolve the decompiled source of functions. */
var funcToString = funcProto.toString;
/** Used to check objects for own properties. */
var hasOwnProperty = objectProto.hasOwnProperty;
/** Used to detect if a method is native. */
var reIsNative = RegExp('^' +
funcToString.call(hasOwnProperty).replace(reRegExpChar, '\\$&')
.replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$'
);
/**
* The base implementation of `_.isNative` without bad shim checks.
*
* @private
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a native function,
* else `false`.
*/
function baseIsNative(value) {
if (!(0,_isObject_js__WEBPACK_IMPORTED_MODULE_0__["default"])(value) || (0,_isMasked_js__WEBPACK_IMPORTED_MODULE_1__["default"])(value)) {
return false;
}
var pattern = (0,_isFunction_js__WEBPACK_IMPORTED_MODULE_2__["default"])(value) ? reIsNative : reIsHostCtor;
return pattern.test((0,_toSource_js__WEBPACK_IMPORTED_MODULE_3__["default"])(value));
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (baseIsNative);
/***/ }),
/***/ "./node_modules/lodash-es/_baseIsSet.js":
/*!**********************************************!*\
!*** ./node_modules/lodash-es/_baseIsSet.js ***!
\**********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _getTag_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./_getTag.js */ "./node_modules/lodash-es/_getTag.js");
/* harmony import */ var _isObjectLike_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./isObjectLike.js */ "./node_modules/lodash-es/isObjectLike.js");
/** `Object#toString` result references. */
var setTag = '[object Set]';
/**
* The base implementation of `_.isSet` without Node.js optimizations.
*
* @private
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a set, else `false`.
*/
function baseIsSet(value) {
return (0,_isObjectLike_js__WEBPACK_IMPORTED_MODULE_0__["default"])(value) && (0,_getTag_js__WEBPACK_IMPORTED_MODULE_1__["default"])(value) == setTag;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (baseIsSet);
/***/ }),
/***/ "./node_modules/lodash-es/_baseIsTypedArray.js":
/*!*****************************************************!*\
!*** ./node_modules/lodash-es/_baseIsTypedArray.js ***!
\*****************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _baseGetTag_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./_baseGetTag.js */ "./node_modules/lodash-es/_baseGetTag.js");
/* harmony import */ var _isLength_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./isLength.js */ "./node_modules/lodash-es/isLength.js");
/* harmony import */ var _isObjectLike_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./isObjectLike.js */ "./node_modules/lodash-es/isObjectLike.js");
/** `Object#toString` result references. */
var argsTag = '[object Arguments]',
arrayTag = '[object Array]',
boolTag = '[object Boolean]',
dateTag = '[object Date]',
errorTag = '[object Error]',
funcTag = '[object Function]',
mapTag = '[object Map]',
numberTag = '[object Number]',
objectTag = '[object Object]',
regexpTag = '[object RegExp]',
setTag = '[object Set]',
stringTag = '[object String]',
weakMapTag = '[object WeakMap]';
var arrayBufferTag = '[object ArrayBuffer]',
dataViewTag = '[object DataView]',
float32Tag = '[object Float32Array]',
float64Tag = '[object Float64Array]',
int8Tag = '[object Int8Array]',
int16Tag = '[object Int16Array]',
int32Tag = '[object Int32Array]',
uint8Tag = '[object Uint8Array]',
uint8ClampedTag = '[object Uint8ClampedArray]',
uint16Tag = '[object Uint16Array]',
uint32Tag = '[object Uint32Array]';
/** Used to identify `toStringTag` values of typed arrays. */
var typedArrayTags = {};
typedArrayTags[float32Tag] = typedArrayTags[float64Tag] =
typedArrayTags[int8Tag] = typedArrayTags[int16Tag] =
typedArrayTags[int32Tag] = typedArrayTags[uint8Tag] =
typedArrayTags[uint8ClampedTag] = typedArrayTags[uint16Tag] =
typedArrayTags[uint32Tag] = true;
typedArrayTags[argsTag] = typedArrayTags[arrayTag] =
typedArrayTags[arrayBufferTag] = typedArrayTags[boolTag] =
typedArrayTags[dataViewTag] = typedArrayTags[dateTag] =
typedArrayTags[errorTag] = typedArrayTags[funcTag] =
typedArrayTags[mapTag] = typedArrayTags[numberTag] =
typedArrayTags[objectTag] = typedArrayTags[regexpTag] =
typedArrayTags[setTag] = typedArrayTags[stringTag] =
typedArrayTags[weakMapTag] = false;
/**
* The base implementation of `_.isTypedArray` without Node.js optimizations.
*
* @private
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a typed array, else `false`.
*/
function baseIsTypedArray(value) {
return (0,_isObjectLike_js__WEBPACK_IMPORTED_MODULE_0__["default"])(value) &&
(0,_isLength_js__WEBPACK_IMPORTED_MODULE_1__["default"])(value.length) && !!typedArrayTags[(0,_baseGetTag_js__WEBPACK_IMPORTED_MODULE_2__["default"])(value)];
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (baseIsTypedArray);
/***/ }),
/***/ "./node_modules/lodash-es/_baseKeys.js":
/*!*********************************************!*\
!*** ./node_modules/lodash-es/_baseKeys.js ***!
\*********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _isPrototype_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_isPrototype.js */ "./node_modules/lodash-es/_isPrototype.js");
/* harmony import */ var _nativeKeys_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./_nativeKeys.js */ "./node_modules/lodash-es/_nativeKeys.js");
/** Used for built-in method references. */
var objectProto = Object.prototype;
/** Used to check objects for own properties. */
var hasOwnProperty = objectProto.hasOwnProperty;
/**
* The base implementation of `_.keys` which doesn't treat sparse arrays as dense.
*
* @private
* @param {Object} object The object to query.
* @returns {Array} Returns the array of property names.
*/
function baseKeys(object) {
if (!(0,_isPrototype_js__WEBPACK_IMPORTED_MODULE_0__["default"])(object)) {
return (0,_nativeKeys_js__WEBPACK_IMPORTED_MODULE_1__["default"])(object);
}
var result = [];
for (var key in Object(object)) {
if (hasOwnProperty.call(object, key) && key != 'constructor') {
result.push(key);
}
}
return result;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (baseKeys);
/***/ }),
/***/ "./node_modules/lodash-es/_baseKeysIn.js":
/*!***********************************************!*\
!*** ./node_modules/lodash-es/_baseKeysIn.js ***!
\***********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _isObject_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./isObject.js */ "./node_modules/lodash-es/isObject.js");
/* harmony import */ var _isPrototype_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./_isPrototype.js */ "./node_modules/lodash-es/_isPrototype.js");
/* harmony import */ var _nativeKeysIn_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./_nativeKeysIn.js */ "./node_modules/lodash-es/_nativeKeysIn.js");
/** Used for built-in method references. */
var objectProto = Object.prototype;
/** Used to check objects for own properties. */
var hasOwnProperty = objectProto.hasOwnProperty;
/**
* The base implementation of `_.keysIn` which doesn't treat sparse arrays as dense.
*
* @private
* @param {Object} object The object to query.
* @returns {Array} Returns the array of property names.
*/
function baseKeysIn(object) {
if (!(0,_isObject_js__WEBPACK_IMPORTED_MODULE_0__["default"])(object)) {
return (0,_nativeKeysIn_js__WEBPACK_IMPORTED_MODULE_1__["default"])(object);
}
var isProto = (0,_isPrototype_js__WEBPACK_IMPORTED_MODULE_2__["default"])(object),
result = [];
for (var key in object) {
if (!(key == 'constructor' && (isProto || !hasOwnProperty.call(object, key)))) {
result.push(key);
}
}
return result;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (baseKeysIn);
/***/ }),
/***/ "./node_modules/lodash-es/_baseMerge.js":
/*!**********************************************!*\
!*** ./node_modules/lodash-es/_baseMerge.js ***!
\**********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _Stack_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./_Stack.js */ "./node_modules/lodash-es/_Stack.js");
/* harmony import */ var _assignMergeValue_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./_assignMergeValue.js */ "./node_modules/lodash-es/_assignMergeValue.js");
/* harmony import */ var _baseFor_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_baseFor.js */ "./node_modules/lodash-es/_baseFor.js");
/* harmony import */ var _baseMergeDeep_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./_baseMergeDeep.js */ "./node_modules/lodash-es/_baseMergeDeep.js");
/* harmony import */ var _isObject_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./isObject.js */ "./node_modules/lodash-es/isObject.js");
/* harmony import */ var _keysIn_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./keysIn.js */ "./node_modules/lodash-es/keysIn.js");
/* harmony import */ var _safeGet_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./_safeGet.js */ "./node_modules/lodash-es/_safeGet.js");
/**
* The base implementation of `_.merge` without support for multiple sources.
*
* @private
* @param {Object} object The destination object.
* @param {Object} source The source object.
* @param {number} srcIndex The index of `source`.
* @param {Function} [customizer] The function to customize merged values.
* @param {Object} [stack] Tracks traversed source values and their merged
* counterparts.
*/
function baseMerge(object, source, srcIndex, customizer, stack) {
if (object === source) {
return;
}
(0,_baseFor_js__WEBPACK_IMPORTED_MODULE_0__["default"])(source, function(srcValue, key) {
stack || (stack = new _Stack_js__WEBPACK_IMPORTED_MODULE_1__["default"]);
if ((0,_isObject_js__WEBPACK_IMPORTED_MODULE_2__["default"])(srcValue)) {
(0,_baseMergeDeep_js__WEBPACK_IMPORTED_MODULE_3__["default"])(object, source, key, srcIndex, baseMerge, customizer, stack);
}
else {
var newValue = customizer
? customizer((0,_safeGet_js__WEBPACK_IMPORTED_MODULE_4__["default"])(object, key), srcValue, (key + ''), object, source, stack)
: undefined;
if (newValue === undefined) {
newValue = srcValue;
}
(0,_assignMergeValue_js__WEBPACK_IMPORTED_MODULE_5__["default"])(object, key, newValue);
}
}, _keysIn_js__WEBPACK_IMPORTED_MODULE_6__["default"]);
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (baseMerge);
/***/ }),
/***/ "./node_modules/lodash-es/_baseMergeDeep.js":
/*!**************************************************!*\
!*** ./node_modules/lodash-es/_baseMergeDeep.js ***!
\**************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _assignMergeValue_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./_assignMergeValue.js */ "./node_modules/lodash-es/_assignMergeValue.js");
/* harmony import */ var _cloneBuffer_js__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./_cloneBuffer.js */ "./node_modules/lodash-es/_cloneBuffer.js");
/* harmony import */ var _cloneTypedArray_js__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./_cloneTypedArray.js */ "./node_modules/lodash-es/_cloneTypedArray.js");
/* harmony import */ var _copyArray_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./_copyArray.js */ "./node_modules/lodash-es/_copyArray.js");
/* harmony import */ var _initCloneObject_js__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(/*! ./_initCloneObject.js */ "./node_modules/lodash-es/_initCloneObject.js");
/* harmony import */ var _isArguments_js__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./isArguments.js */ "./node_modules/lodash-es/isArguments.js");
/* harmony import */ var _isArray_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./isArray.js */ "./node_modules/lodash-es/isArray.js");
/* harmony import */ var _isArrayLikeObject_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./isArrayLikeObject.js */ "./node_modules/lodash-es/isArrayLikeObject.js");
/* harmony import */ var _isBuffer_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./isBuffer.js */ "./node_modules/lodash-es/isBuffer.js");
/* harmony import */ var _isFunction_js__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(/*! ./isFunction.js */ "./node_modules/lodash-es/isFunction.js");
/* harmony import */ var _isObject_js__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! ./isObject.js */ "./node_modules/lodash-es/isObject.js");
/* harmony import */ var _isPlainObject_js__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./isPlainObject.js */ "./node_modules/lodash-es/isPlainObject.js");
/* harmony import */ var _isTypedArray_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./isTypedArray.js */ "./node_modules/lodash-es/isTypedArray.js");
/* harmony import */ var _safeGet_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_safeGet.js */ "./node_modules/lodash-es/_safeGet.js");
/* harmony import */ var _toPlainObject_js__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ./toPlainObject.js */ "./node_modules/lodash-es/toPlainObject.js");
/**
* A specialized version of `baseMerge` for arrays and objects which performs
* deep merges and tracks traversed objects enabling objects with circular
* references to be merged.
*
* @private
* @param {Object} object The destination object.
* @param {Object} source The source object.
* @param {string} key The key of the value to merge.
* @param {number} srcIndex The index of `source`.
* @param {Function} mergeFunc The function to merge values.
* @param {Function} [customizer] The function to customize assigned values.
* @param {Object} [stack] Tracks traversed source values and their merged
* counterparts.
*/
function baseMergeDeep(object, source, key, srcIndex, mergeFunc, customizer, stack) {
var objValue = (0,_safeGet_js__WEBPACK_IMPORTED_MODULE_0__["default"])(object, key),
srcValue = (0,_safeGet_js__WEBPACK_IMPORTED_MODULE_0__["default"])(source, key),
stacked = stack.get(srcValue);
if (stacked) {
(0,_assignMergeValue_js__WEBPACK_IMPORTED_MODULE_1__["default"])(object, key, stacked);
return;
}
var newValue = customizer
? customizer(objValue, srcValue, (key + ''), object, source, stack)
: undefined;
var isCommon = newValue === undefined;
if (isCommon) {
var isArr = (0,_isArray_js__WEBPACK_IMPORTED_MODULE_2__["default"])(srcValue),
isBuff = !isArr && (0,_isBuffer_js__WEBPACK_IMPORTED_MODULE_3__["default"])(srcValue),
isTyped = !isArr && !isBuff && (0,_isTypedArray_js__WEBPACK_IMPORTED_MODULE_4__["default"])(srcValue);
newValue = srcValue;
if (isArr || isBuff || isTyped) {
if ((0,_isArray_js__WEBPACK_IMPORTED_MODULE_2__["default"])(objValue)) {
newValue = objValue;
}
else if ((0,_isArrayLikeObject_js__WEBPACK_IMPORTED_MODULE_5__["default"])(objValue)) {
newValue = (0,_copyArray_js__WEBPACK_IMPORTED_MODULE_6__["default"])(objValue);
}
else if (isBuff) {
isCommon = false;
newValue = (0,_cloneBuffer_js__WEBPACK_IMPORTED_MODULE_7__["default"])(srcValue, true);
}
else if (isTyped) {
isCommon = false;
newValue = (0,_cloneTypedArray_js__WEBPACK_IMPORTED_MODULE_8__["default"])(srcValue, true);
}
else {
newValue = [];
}
}
else if ((0,_isPlainObject_js__WEBPACK_IMPORTED_MODULE_9__["default"])(srcValue) || (0,_isArguments_js__WEBPACK_IMPORTED_MODULE_10__["default"])(srcValue)) {
newValue = objValue;
if ((0,_isArguments_js__WEBPACK_IMPORTED_MODULE_10__["default"])(objValue)) {
newValue = (0,_toPlainObject_js__WEBPACK_IMPORTED_MODULE_11__["default"])(objValue);
}
else if (!(0,_isObject_js__WEBPACK_IMPORTED_MODULE_12__["default"])(objValue) || (0,_isFunction_js__WEBPACK_IMPORTED_MODULE_13__["default"])(objValue)) {
newValue = (0,_initCloneObject_js__WEBPACK_IMPORTED_MODULE_14__["default"])(srcValue);
}
}
else {
isCommon = false;
}
}
if (isCommon) {
// Recursively merge objects and arrays (susceptible to call stack limits).
stack.set(srcValue, newValue);
mergeFunc(newValue, srcValue, srcIndex, customizer, stack);
stack['delete'](srcValue);
}
(0,_assignMergeValue_js__WEBPACK_IMPORTED_MODULE_1__["default"])(object, key, newValue);
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (baseMergeDeep);
/***/ }),
/***/ "./node_modules/lodash-es/_basePullAll.js":
/*!************************************************!*\
!*** ./node_modules/lodash-es/_basePullAll.js ***!
\************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _arrayMap_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./_arrayMap.js */ "./node_modules/lodash-es/_arrayMap.js");
/* harmony import */ var _baseIndexOf_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./_baseIndexOf.js */ "./node_modules/lodash-es/_baseIndexOf.js");
/* harmony import */ var _baseIndexOfWith_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_baseIndexOfWith.js */ "./node_modules/lodash-es/_baseIndexOfWith.js");
/* harmony import */ var _baseUnary_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./_baseUnary.js */ "./node_modules/lodash-es/_baseUnary.js");
/* harmony import */ var _copyArray_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./_copyArray.js */ "./node_modules/lodash-es/_copyArray.js");
/** Used for built-in method references. */
var arrayProto = Array.prototype;
/** Built-in value references. */
var splice = arrayProto.splice;
/**
* The base implementation of `_.pullAllBy` without support for iteratee
* shorthands.
*
* @private
* @param {Array} array The array to modify.
* @param {Array} values The values to remove.
* @param {Function} [iteratee] The iteratee invoked per element.
* @param {Function} [comparator] The comparator invoked per element.
* @returns {Array} Returns `array`.
*/
function basePullAll(array, values, iteratee, comparator) {
var indexOf = comparator ? _baseIndexOfWith_js__WEBPACK_IMPORTED_MODULE_0__["default"] : _baseIndexOf_js__WEBPACK_IMPORTED_MODULE_1__["default"],
index = -1,
length = values.length,
seen = array;
if (array === values) {
values = (0,_copyArray_js__WEBPACK_IMPORTED_MODULE_2__["default"])(values);
}
if (iteratee) {
seen = (0,_arrayMap_js__WEBPACK_IMPORTED_MODULE_3__["default"])(array, (0,_baseUnary_js__WEBPACK_IMPORTED_MODULE_4__["default"])(iteratee));
}
while (++index < length) {
var fromIndex = 0,
value = values[index],
computed = iteratee ? iteratee(value) : value;
while ((fromIndex = indexOf(seen, computed, fromIndex, comparator)) > -1) {
if (seen !== array) {
splice.call(seen, fromIndex, 1);
}
splice.call(array, fromIndex, 1);
}
}
return array;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (basePullAll);
/***/ }),
/***/ "./node_modules/lodash-es/_baseRest.js":
/*!*********************************************!*\
!*** ./node_modules/lodash-es/_baseRest.js ***!
\*********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _identity_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./identity.js */ "./node_modules/lodash-es/identity.js");
/* harmony import */ var _overRest_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./_overRest.js */ "./node_modules/lodash-es/_overRest.js");
/* harmony import */ var _setToString_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_setToString.js */ "./node_modules/lodash-es/_setToString.js");
/**
* The base implementation of `_.rest` which doesn't validate or coerce arguments.
*
* @private
* @param {Function} func The function to apply a rest parameter to.
* @param {number} [start=func.length-1] The start position of the rest parameter.
* @returns {Function} Returns the new function.
*/
function baseRest(func, start) {
return (0,_setToString_js__WEBPACK_IMPORTED_MODULE_0__["default"])((0,_overRest_js__WEBPACK_IMPORTED_MODULE_1__["default"])(func, start, _identity_js__WEBPACK_IMPORTED_MODULE_2__["default"]), func + '');
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (baseRest);
/***/ }),
/***/ "./node_modules/lodash-es/_baseSet.js":
/*!********************************************!*\
!*** ./node_modules/lodash-es/_baseSet.js ***!
\********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _assignValue_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./_assignValue.js */ "./node_modules/lodash-es/_assignValue.js");
/* harmony import */ var _castPath_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./_castPath.js */ "./node_modules/lodash-es/_castPath.js");
/* harmony import */ var _isIndex_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./_isIndex.js */ "./node_modules/lodash-es/_isIndex.js");
/* harmony import */ var _isObject_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./isObject.js */ "./node_modules/lodash-es/isObject.js");
/* harmony import */ var _toKey_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./_toKey.js */ "./node_modules/lodash-es/_toKey.js");
/**
* The base implementation of `_.set`.
*
* @private
* @param {Object} object The object to modify.
* @param {Array|string} path The path of the property to set.
* @param {*} value The value to set.
* @param {Function} [customizer] The function to customize path creation.
* @returns {Object} Returns `object`.
*/
function baseSet(object, path, value, customizer) {
if (!(0,_isObject_js__WEBPACK_IMPORTED_MODULE_0__["default"])(object)) {
return object;
}
path = (0,_castPath_js__WEBPACK_IMPORTED_MODULE_1__["default"])(path, object);
var index = -1,
length = path.length,
lastIndex = length - 1,
nested = object;
while (nested != null && ++index < length) {
var key = (0,_toKey_js__WEBPACK_IMPORTED_MODULE_2__["default"])(path[index]),
newValue = value;
if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
return object;
}
if (index != lastIndex) {
var objValue = nested[key];
newValue = customizer ? customizer(objValue, key, nested) : undefined;
if (newValue === undefined) {
newValue = (0,_isObject_js__WEBPACK_IMPORTED_MODULE_0__["default"])(objValue)
? objValue
: ((0,_isIndex_js__WEBPACK_IMPORTED_MODULE_3__["default"])(path[index + 1]) ? [] : {});
}
}
(0,_assignValue_js__WEBPACK_IMPORTED_MODULE_4__["default"])(nested, key, newValue);
nested = nested[key];
}
return object;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (baseSet);
/***/ }),
/***/ "./node_modules/lodash-es/_baseSetToString.js":
/*!****************************************************!*\
!*** ./node_modules/lodash-es/_baseSetToString.js ***!
\****************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _constant_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./constant.js */ "./node_modules/lodash-es/constant.js");
/* harmony import */ var _defineProperty_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_defineProperty.js */ "./node_modules/lodash-es/_defineProperty.js");
/* harmony import */ var _identity_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./identity.js */ "./node_modules/lodash-es/identity.js");
/**
* The base implementation of `setToString` without support for hot loop shorting.
*
* @private
* @param {Function} func The function to modify.
* @param {Function} string The `toString` result.
* @returns {Function} Returns `func`.
*/
var baseSetToString = !_defineProperty_js__WEBPACK_IMPORTED_MODULE_0__["default"] ? _identity_js__WEBPACK_IMPORTED_MODULE_1__["default"] : function(func, string) {
return (0,_defineProperty_js__WEBPACK_IMPORTED_MODULE_0__["default"])(func, 'toString', {
'configurable': true,
'enumerable': false,
'value': (0,_constant_js__WEBPACK_IMPORTED_MODULE_2__["default"])(string),
'writable': true
});
};
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (baseSetToString);
/***/ }),
/***/ "./node_modules/lodash-es/_baseSlice.js":
/*!**********************************************!*\
!*** ./node_modules/lodash-es/_baseSlice.js ***!
\**********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/**
* The base implementation of `_.slice` without an iteratee call guard.
*
* @private
* @param {Array} array The array to slice.
* @param {number} [start=0] The start position.
* @param {number} [end=array.length] The end position.
* @returns {Array} Returns the slice of `array`.
*/
function baseSlice(array, start, end) {
var index = -1,
length = array.length;
if (start < 0) {
start = -start > length ? 0 : (length + start);
}
end = end > length ? length : end;
if (end < 0) {
end += length;
}
length = start > end ? 0 : ((end - start) >>> 0);
start >>>= 0;
var result = Array(length);
while (++index < length) {
result[index] = array[index + start];
}
return result;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (baseSlice);
/***/ }),
/***/ "./node_modules/lodash-es/_baseTimes.js":
/*!**********************************************!*\
!*** ./node_modules/lodash-es/_baseTimes.js ***!
\**********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/**
* The base implementation of `_.times` without support for iteratee shorthands
* or max array length checks.
*
* @private
* @param {number} n The number of times to invoke `iteratee`.
* @param {Function} iteratee The function invoked per iteration.
* @returns {Array} Returns the array of results.
*/
function baseTimes(n, iteratee) {
var index = -1,
result = Array(n);
while (++index < n) {
result[index] = iteratee(index);
}
return result;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (baseTimes);
/***/ }),
/***/ "./node_modules/lodash-es/_baseToString.js":
/*!*************************************************!*\
!*** ./node_modules/lodash-es/_baseToString.js ***!
\*************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _Symbol_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_Symbol.js */ "./node_modules/lodash-es/_Symbol.js");
/* harmony import */ var _arrayMap_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./_arrayMap.js */ "./node_modules/lodash-es/_arrayMap.js");
/* harmony import */ var _isArray_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./isArray.js */ "./node_modules/lodash-es/isArray.js");
/* harmony import */ var _isSymbol_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./isSymbol.js */ "./node_modules/lodash-es/isSymbol.js");
/** Used as references for various `Number` constants. */
var INFINITY = 1 / 0;
/** Used to convert symbols to primitives and strings. */
var symbolProto = _Symbol_js__WEBPACK_IMPORTED_MODULE_0__["default"] ? _Symbol_js__WEBPACK_IMPORTED_MODULE_0__["default"].prototype : undefined,
symbolToString = symbolProto ? symbolProto.toString : undefined;
/**
* The base implementation of `_.toString` which doesn't convert nullish
* values to empty strings.
*
* @private
* @param {*} value The value to process.
* @returns {string} Returns the string.
*/
function baseToString(value) {
// Exit early for strings to avoid a performance hit in some environments.
if (typeof value == 'string') {
return value;
}
if ((0,_isArray_js__WEBPACK_IMPORTED_MODULE_1__["default"])(value)) {
// Recursively convert values (susceptible to call stack limits).
return (0,_arrayMap_js__WEBPACK_IMPORTED_MODULE_2__["default"])(value, baseToString) + '';
}
if ((0,_isSymbol_js__WEBPACK_IMPORTED_MODULE_3__["default"])(value)) {
return symbolToString ? symbolToString.call(value) : '';
}
var result = (value + '');
return (result == '0' && (1 / value) == -INFINITY) ? '-0' : result;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (baseToString);
/***/ }),
/***/ "./node_modules/lodash-es/_baseTrim.js":
/*!*********************************************!*\
!*** ./node_modules/lodash-es/_baseTrim.js ***!
\*********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _trimmedEndIndex_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_trimmedEndIndex.js */ "./node_modules/lodash-es/_trimmedEndIndex.js");
/** Used to match leading whitespace. */
var reTrimStart = /^\s+/;
/**
* The base implementation of `_.trim`.
*
* @private
* @param {string} string The string to trim.
* @returns {string} Returns the trimmed string.
*/
function baseTrim(string) {
return string
? string.slice(0, (0,_trimmedEndIndex_js__WEBPACK_IMPORTED_MODULE_0__["default"])(string) + 1).replace(reTrimStart, '')
: string;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (baseTrim);
/***/ }),
/***/ "./node_modules/lodash-es/_baseUnary.js":
/*!**********************************************!*\
!*** ./node_modules/lodash-es/_baseUnary.js ***!
\**********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/**
* The base implementation of `_.unary` without support for storing metadata.
*
* @private
* @param {Function} func The function to cap arguments for.
* @returns {Function} Returns the new capped function.
*/
function baseUnary(func) {
return function(value) {
return func(value);
};
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (baseUnary);
/***/ }),
/***/ "./node_modules/lodash-es/_baseUnset.js":
/*!**********************************************!*\
!*** ./node_modules/lodash-es/_baseUnset.js ***!
\**********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _castPath_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_castPath.js */ "./node_modules/lodash-es/_castPath.js");
/* harmony import */ var _last_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./last.js */ "./node_modules/lodash-es/last.js");
/* harmony import */ var _parent_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./_parent.js */ "./node_modules/lodash-es/_parent.js");
/* harmony import */ var _toKey_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./_toKey.js */ "./node_modules/lodash-es/_toKey.js");
/**
* The base implementation of `_.unset`.
*
* @private
* @param {Object} object The object to modify.
* @param {Array|string} path The property path to unset.
* @returns {boolean} Returns `true` if the property is deleted, else `false`.
*/
function baseUnset(object, path) {
path = (0,_castPath_js__WEBPACK_IMPORTED_MODULE_0__["default"])(path, object);
object = (0,_parent_js__WEBPACK_IMPORTED_MODULE_1__["default"])(object, path);
return object == null || delete object[(0,_toKey_js__WEBPACK_IMPORTED_MODULE_2__["default"])((0,_last_js__WEBPACK_IMPORTED_MODULE_3__["default"])(path))];
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (baseUnset);
/***/ }),
/***/ "./node_modules/lodash-es/_cacheHas.js":
/*!*********************************************!*\
!*** ./node_modules/lodash-es/_cacheHas.js ***!
\*********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/**
* Checks if a `cache` value for `key` exists.
*
* @private
* @param {Object} cache The cache to query.
* @param {string} key The key of the entry to check.
* @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
*/
function cacheHas(cache, key) {
return cache.has(key);
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (cacheHas);
/***/ }),
/***/ "./node_modules/lodash-es/_castPath.js":
/*!*********************************************!*\
!*** ./node_modules/lodash-es/_castPath.js ***!
\*********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _isArray_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./isArray.js */ "./node_modules/lodash-es/isArray.js");
/* harmony import */ var _isKey_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./_isKey.js */ "./node_modules/lodash-es/_isKey.js");
/* harmony import */ var _stringToPath_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./_stringToPath.js */ "./node_modules/lodash-es/_stringToPath.js");
/* harmony import */ var _toString_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./toString.js */ "./node_modules/lodash-es/toString.js");
/**
* Casts `value` to a path array if it's not one.
*
* @private
* @param {*} value The value to inspect.
* @param {Object} [object] The object to query keys on.
* @returns {Array} Returns the cast property path array.
*/
function castPath(value, object) {
if ((0,_isArray_js__WEBPACK_IMPORTED_MODULE_0__["default"])(value)) {
return value;
}
return (0,_isKey_js__WEBPACK_IMPORTED_MODULE_1__["default"])(value, object) ? [value] : (0,_stringToPath_js__WEBPACK_IMPORTED_MODULE_2__["default"])((0,_toString_js__WEBPACK_IMPORTED_MODULE_3__["default"])(value));
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (castPath);
/***/ }),
/***/ "./node_modules/lodash-es/_castSlice.js":
/*!**********************************************!*\
!*** ./node_modules/lodash-es/_castSlice.js ***!
\**********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _baseSlice_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_baseSlice.js */ "./node_modules/lodash-es/_baseSlice.js");
/**
* Casts `array` to a slice if it's needed.
*
* @private
* @param {Array} array The array to inspect.
* @param {number} start The start position.
* @param {number} [end=array.length] The end position.
* @returns {Array} Returns the cast slice.
*/
function castSlice(array, start, end) {
var length = array.length;
end = end === undefined ? length : end;
return (!start && end >= length) ? array : (0,_baseSlice_js__WEBPACK_IMPORTED_MODULE_0__["default"])(array, start, end);
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (castSlice);
/***/ }),
/***/ "./node_modules/lodash-es/_cloneArrayBuffer.js":
/*!*****************************************************!*\
!*** ./node_modules/lodash-es/_cloneArrayBuffer.js ***!
\*****************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _Uint8Array_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_Uint8Array.js */ "./node_modules/lodash-es/_Uint8Array.js");
/**
* Creates a clone of `arrayBuffer`.
*
* @private
* @param {ArrayBuffer} arrayBuffer The array buffer to clone.
* @returns {ArrayBuffer} Returns the cloned array buffer.
*/
function cloneArrayBuffer(arrayBuffer) {
var result = new arrayBuffer.constructor(arrayBuffer.byteLength);
new _Uint8Array_js__WEBPACK_IMPORTED_MODULE_0__["default"](result).set(new _Uint8Array_js__WEBPACK_IMPORTED_MODULE_0__["default"](arrayBuffer));
return result;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (cloneArrayBuffer);
/***/ }),
/***/ "./node_modules/lodash-es/_cloneBuffer.js":
/*!************************************************!*\
!*** ./node_modules/lodash-es/_cloneBuffer.js ***!
\************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _root_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_root.js */ "./node_modules/lodash-es/_root.js");
/** Detect free variable `exports`. */
var freeExports = typeof exports == 'object' && exports && !exports.nodeType && exports;
/** Detect free variable `module`. */
var freeModule = freeExports && typeof module == 'object' && module && !module.nodeType && module;
/** Detect the popular CommonJS extension `module.exports`. */
var moduleExports = freeModule && freeModule.exports === freeExports;
/** Built-in value references. */
var Buffer = moduleExports ? _root_js__WEBPACK_IMPORTED_MODULE_0__["default"].Buffer : undefined,
allocUnsafe = Buffer ? Buffer.allocUnsafe : undefined;
/**
* Creates a clone of `buffer`.
*
* @private
* @param {Buffer} buffer The buffer to clone.
* @param {boolean} [isDeep] Specify a deep clone.
* @returns {Buffer} Returns the cloned buffer.
*/
function cloneBuffer(buffer, isDeep) {
if (isDeep) {
return buffer.slice();
}
var length = buffer.length,
result = allocUnsafe ? allocUnsafe(length) : new buffer.constructor(length);
buffer.copy(result);
return result;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (cloneBuffer);
/***/ }),
/***/ "./node_modules/lodash-es/_cloneDataView.js":
/*!**************************************************!*\
!*** ./node_modules/lodash-es/_cloneDataView.js ***!
\**************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _cloneArrayBuffer_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_cloneArrayBuffer.js */ "./node_modules/lodash-es/_cloneArrayBuffer.js");
/**
* Creates a clone of `dataView`.
*
* @private
* @param {Object} dataView The data view to clone.
* @param {boolean} [isDeep] Specify a deep clone.
* @returns {Object} Returns the cloned data view.
*/
function cloneDataView(dataView, isDeep) {
var buffer = isDeep ? (0,_cloneArrayBuffer_js__WEBPACK_IMPORTED_MODULE_0__["default"])(dataView.buffer) : dataView.buffer;
return new dataView.constructor(buffer, dataView.byteOffset, dataView.byteLength);
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (cloneDataView);
/***/ }),
/***/ "./node_modules/lodash-es/_cloneRegExp.js":
/*!************************************************!*\
!*** ./node_modules/lodash-es/_cloneRegExp.js ***!
\************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/** Used to match `RegExp` flags from their coerced string values. */
var reFlags = /\w*$/;
/**
* Creates a clone of `regexp`.
*
* @private
* @param {Object} regexp The regexp to clone.
* @returns {Object} Returns the cloned regexp.
*/
function cloneRegExp(regexp) {
var result = new regexp.constructor(regexp.source, reFlags.exec(regexp));
result.lastIndex = regexp.lastIndex;
return result;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (cloneRegExp);
/***/ }),
/***/ "./node_modules/lodash-es/_cloneSymbol.js":
/*!************************************************!*\
!*** ./node_modules/lodash-es/_cloneSymbol.js ***!
\************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _Symbol_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_Symbol.js */ "./node_modules/lodash-es/_Symbol.js");
/** Used to convert symbols to primitives and strings. */
var symbolProto = _Symbol_js__WEBPACK_IMPORTED_MODULE_0__["default"] ? _Symbol_js__WEBPACK_IMPORTED_MODULE_0__["default"].prototype : undefined,
symbolValueOf = symbolProto ? symbolProto.valueOf : undefined;
/**
* Creates a clone of the `symbol` object.
*
* @private
* @param {Object} symbol The symbol object to clone.
* @returns {Object} Returns the cloned symbol object.
*/
function cloneSymbol(symbol) {
return symbolValueOf ? Object(symbolValueOf.call(symbol)) : {};
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (cloneSymbol);
/***/ }),
/***/ "./node_modules/lodash-es/_cloneTypedArray.js":
/*!****************************************************!*\
!*** ./node_modules/lodash-es/_cloneTypedArray.js ***!
\****************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _cloneArrayBuffer_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_cloneArrayBuffer.js */ "./node_modules/lodash-es/_cloneArrayBuffer.js");
/**
* Creates a clone of `typedArray`.
*
* @private
* @param {Object} typedArray The typed array to clone.
* @param {boolean} [isDeep] Specify a deep clone.
* @returns {Object} Returns the cloned typed array.
*/
function cloneTypedArray(typedArray, isDeep) {
var buffer = isDeep ? (0,_cloneArrayBuffer_js__WEBPACK_IMPORTED_MODULE_0__["default"])(typedArray.buffer) : typedArray.buffer;
return new typedArray.constructor(buffer, typedArray.byteOffset, typedArray.length);
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (cloneTypedArray);
/***/ }),
/***/ "./node_modules/lodash-es/_copyArray.js":
/*!**********************************************!*\
!*** ./node_modules/lodash-es/_copyArray.js ***!
\**********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/**
* Copies the values of `source` to `array`.
*
* @private
* @param {Array} source The array to copy values from.
* @param {Array} [array=[]] The array to copy values to.
* @returns {Array} Returns `array`.
*/
function copyArray(source, array) {
var index = -1,
length = source.length;
array || (array = Array(length));
while (++index < length) {
array[index] = source[index];
}
return array;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (copyArray);
/***/ }),
/***/ "./node_modules/lodash-es/_copyObject.js":
/*!***********************************************!*\
!*** ./node_modules/lodash-es/_copyObject.js ***!
\***********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _assignValue_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./_assignValue.js */ "./node_modules/lodash-es/_assignValue.js");
/* harmony import */ var _baseAssignValue_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_baseAssignValue.js */ "./node_modules/lodash-es/_baseAssignValue.js");
/**
* Copies properties of `source` to `object`.
*
* @private
* @param {Object} source The object to copy properties from.
* @param {Array} props The property identifiers to copy.
* @param {Object} [object={}] The object to copy properties to.
* @param {Function} [customizer] The function to customize copied values.
* @returns {Object} Returns `object`.
*/
function copyObject(source, props, object, customizer) {
var isNew = !object;
object || (object = {});
var index = -1,
length = props.length;
while (++index < length) {
var key = props[index];
var newValue = customizer
? customizer(object[key], source[key], key, object, source)
: undefined;
if (newValue === undefined) {
newValue = source[key];
}
if (isNew) {
(0,_baseAssignValue_js__WEBPACK_IMPORTED_MODULE_0__["default"])(object, key, newValue);
} else {
(0,_assignValue_js__WEBPACK_IMPORTED_MODULE_1__["default"])(object, key, newValue);
}
}
return object;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (copyObject);
/***/ }),
/***/ "./node_modules/lodash-es/_copySymbols.js":
/*!************************************************!*\
!*** ./node_modules/lodash-es/_copySymbols.js ***!
\************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _copyObject_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_copyObject.js */ "./node_modules/lodash-es/_copyObject.js");
/* harmony import */ var _getSymbols_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./_getSymbols.js */ "./node_modules/lodash-es/_getSymbols.js");
/**
* Copies own symbols of `source` to `object`.
*
* @private
* @param {Object} source The object to copy symbols from.
* @param {Object} [object={}] The object to copy symbols to.
* @returns {Object} Returns `object`.
*/
function copySymbols(source, object) {
return (0,_copyObject_js__WEBPACK_IMPORTED_MODULE_0__["default"])(source, (0,_getSymbols_js__WEBPACK_IMPORTED_MODULE_1__["default"])(source), object);
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (copySymbols);
/***/ }),
/***/ "./node_modules/lodash-es/_copySymbolsIn.js":
/*!**************************************************!*\
!*** ./node_modules/lodash-es/_copySymbolsIn.js ***!
\**************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _copyObject_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_copyObject.js */ "./node_modules/lodash-es/_copyObject.js");
/* harmony import */ var _getSymbolsIn_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./_getSymbolsIn.js */ "./node_modules/lodash-es/_getSymbolsIn.js");
/**
* Copies own and inherited symbols of `source` to `object`.
*
* @private
* @param {Object} source The object to copy symbols from.
* @param {Object} [object={}] The object to copy symbols to.
* @returns {Object} Returns `object`.
*/
function copySymbolsIn(source, object) {
return (0,_copyObject_js__WEBPACK_IMPORTED_MODULE_0__["default"])(source, (0,_getSymbolsIn_js__WEBPACK_IMPORTED_MODULE_1__["default"])(source), object);
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (copySymbolsIn);
/***/ }),
/***/ "./node_modules/lodash-es/_coreJsData.js":
/*!***********************************************!*\
!*** ./node_modules/lodash-es/_coreJsData.js ***!
\***********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _root_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_root.js */ "./node_modules/lodash-es/_root.js");
/** Used to detect overreaching core-js shims. */
var coreJsData = _root_js__WEBPACK_IMPORTED_MODULE_0__["default"]["__core-js_shared__"];
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (coreJsData);
/***/ }),
/***/ "./node_modules/lodash-es/_createAssigner.js":
/*!***************************************************!*\
!*** ./node_modules/lodash-es/_createAssigner.js ***!
\***************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _baseRest_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_baseRest.js */ "./node_modules/lodash-es/_baseRest.js");
/* harmony import */ var _isIterateeCall_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./_isIterateeCall.js */ "./node_modules/lodash-es/_isIterateeCall.js");
/**
* Creates a function like `_.assign`.
*
* @private
* @param {Function} assigner The function to assign values.
* @returns {Function} Returns the new assigner function.
*/
function createAssigner(assigner) {
return (0,_baseRest_js__WEBPACK_IMPORTED_MODULE_0__["default"])(function(object, sources) {
var index = -1,
length = sources.length,
customizer = length > 1 ? sources[length - 1] : undefined,
guard = length > 2 ? sources[2] : undefined;
customizer = (assigner.length > 3 && typeof customizer == 'function')
? (length--, customizer)
: undefined;
if (guard && (0,_isIterateeCall_js__WEBPACK_IMPORTED_MODULE_1__["default"])(sources[0], sources[1], guard)) {
customizer = length < 3 ? undefined : customizer;
length = 1;
}
object = Object(object);
while (++index < length) {
var source = sources[index];
if (source) {
assigner(object, source, index, customizer);
}
}
return object;
});
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (createAssigner);
/***/ }),
/***/ "./node_modules/lodash-es/_createBaseFor.js":
/*!**************************************************!*\
!*** ./node_modules/lodash-es/_createBaseFor.js ***!
\**************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/**
* Creates a base function for methods like `_.forIn` and `_.forOwn`.
*
* @private
* @param {boolean} [fromRight] Specify iterating from right to left.
* @returns {Function} Returns the new base function.
*/
function createBaseFor(fromRight) {
return function(object, iteratee, keysFunc) {
var index = -1,
iterable = Object(object),
props = keysFunc(object),
length = props.length;
while (length--) {
var key = props[fromRight ? length : ++index];
if (iteratee(iterable[key], key, iterable) === false) {
break;
}
}
return object;
};
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (createBaseFor);
/***/ }),
/***/ "./node_modules/lodash-es/_createCaseFirst.js":
/*!****************************************************!*\
!*** ./node_modules/lodash-es/_createCaseFirst.js ***!
\****************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _castSlice_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./_castSlice.js */ "./node_modules/lodash-es/_castSlice.js");
/* harmony import */ var _hasUnicode_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./_hasUnicode.js */ "./node_modules/lodash-es/_hasUnicode.js");
/* harmony import */ var _stringToArray_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./_stringToArray.js */ "./node_modules/lodash-es/_stringToArray.js");
/* harmony import */ var _toString_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./toString.js */ "./node_modules/lodash-es/toString.js");
/**
* Creates a function like `_.lowerFirst`.
*
* @private
* @param {string} methodName The name of the `String` case method to use.
* @returns {Function} Returns the new case function.
*/
function createCaseFirst(methodName) {
return function(string) {
string = (0,_toString_js__WEBPACK_IMPORTED_MODULE_0__["default"])(string);
var strSymbols = (0,_hasUnicode_js__WEBPACK_IMPORTED_MODULE_1__["default"])(string)
? (0,_stringToArray_js__WEBPACK_IMPORTED_MODULE_2__["default"])(string)
: undefined;
var chr = strSymbols
? strSymbols[0]
: string.charAt(0);
var trailing = strSymbols
? (0,_castSlice_js__WEBPACK_IMPORTED_MODULE_3__["default"])(strSymbols, 1).join('')
: string.slice(1);
return chr[methodName]() + trailing;
};
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (createCaseFirst);
/***/ }),
/***/ "./node_modules/lodash-es/_defineProperty.js":
/*!***************************************************!*\
!*** ./node_modules/lodash-es/_defineProperty.js ***!
\***************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _getNative_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_getNative.js */ "./node_modules/lodash-es/_getNative.js");
var defineProperty = (function() {
try {
var func = (0,_getNative_js__WEBPACK_IMPORTED_MODULE_0__["default"])(Object, 'defineProperty');
func({}, '', {});
return func;
} catch (e) {}
}());
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (defineProperty);
/***/ }),
/***/ "./node_modules/lodash-es/_equalArrays.js":
/*!************************************************!*\
!*** ./node_modules/lodash-es/_equalArrays.js ***!
\************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _SetCache_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_SetCache.js */ "./node_modules/lodash-es/_SetCache.js");
/* harmony import */ var _arraySome_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./_arraySome.js */ "./node_modules/lodash-es/_arraySome.js");
/* harmony import */ var _cacheHas_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./_cacheHas.js */ "./node_modules/lodash-es/_cacheHas.js");
/** Used to compose bitmasks for value comparisons. */
var COMPARE_PARTIAL_FLAG = 1,
COMPARE_UNORDERED_FLAG = 2;
/**
* A specialized version of `baseIsEqualDeep` for arrays with support for
* partial deep comparisons.
*
* @private
* @param {Array} array The array to compare.
* @param {Array} other The other array to compare.
* @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details.
* @param {Function} customizer The function to customize comparisons.
* @param {Function} equalFunc The function to determine equivalents of values.
* @param {Object} stack Tracks traversed `array` and `other` objects.
* @returns {boolean} Returns `true` if the arrays are equivalent, else `false`.
*/
function equalArrays(array, other, bitmask, customizer, equalFunc, stack) {
var isPartial = bitmask & COMPARE_PARTIAL_FLAG,
arrLength = array.length,
othLength = other.length;
if (arrLength != othLength && !(isPartial && othLength > arrLength)) {
return false;
}
// Check that cyclic values are equal.
var arrStacked = stack.get(array);
var othStacked = stack.get(other);
if (arrStacked && othStacked) {
return arrStacked == other && othStacked == array;
}
var index = -1,
result = true,
seen = (bitmask & COMPARE_UNORDERED_FLAG) ? new _SetCache_js__WEBPACK_IMPORTED_MODULE_0__["default"] : undefined;
stack.set(array, other);
stack.set(other, array);
// Ignore non-index properties.
while (++index < arrLength) {
var arrValue = array[index],
othValue = other[index];
if (customizer) {
var compared = isPartial
? customizer(othValue, arrValue, index, other, array, stack)
: customizer(arrValue, othValue, index, array, other, stack);
}
if (compared !== undefined) {
if (compared) {
continue;
}
result = false;
break;
}
// Recursively compare arrays (susceptible to call stack limits).
if (seen) {
if (!(0,_arraySome_js__WEBPACK_IMPORTED_MODULE_1__["default"])(other, function(othValue, othIndex) {
if (!(0,_cacheHas_js__WEBPACK_IMPORTED_MODULE_2__["default"])(seen, othIndex) &&
(arrValue === othValue || equalFunc(arrValue, othValue, bitmask, customizer, stack))) {
return seen.push(othIndex);
}
})) {
result = false;
break;
}
} else if (!(
arrValue === othValue ||
equalFunc(arrValue, othValue, bitmask, customizer, stack)
)) {
result = false;
break;
}
}
stack['delete'](array);
stack['delete'](other);
return result;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (equalArrays);
/***/ }),
/***/ "./node_modules/lodash-es/_equalByTag.js":
/*!***********************************************!*\
!*** ./node_modules/lodash-es/_equalByTag.js ***!
\***********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _Symbol_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_Symbol.js */ "./node_modules/lodash-es/_Symbol.js");
/* harmony import */ var _Uint8Array_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./_Uint8Array.js */ "./node_modules/lodash-es/_Uint8Array.js");
/* harmony import */ var _eq_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./eq.js */ "./node_modules/lodash-es/eq.js");
/* harmony import */ var _equalArrays_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./_equalArrays.js */ "./node_modules/lodash-es/_equalArrays.js");
/* harmony import */ var _mapToArray_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./_mapToArray.js */ "./node_modules/lodash-es/_mapToArray.js");
/* harmony import */ var _setToArray_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./_setToArray.js */ "./node_modules/lodash-es/_setToArray.js");
/** Used to compose bitmasks for value comparisons. */
var COMPARE_PARTIAL_FLAG = 1,
COMPARE_UNORDERED_FLAG = 2;
/** `Object#toString` result references. */
var boolTag = '[object Boolean]',
dateTag = '[object Date]',
errorTag = '[object Error]',
mapTag = '[object Map]',
numberTag = '[object Number]',
regexpTag = '[object RegExp]',
setTag = '[object Set]',
stringTag = '[object String]',
symbolTag = '[object Symbol]';
var arrayBufferTag = '[object ArrayBuffer]',
dataViewTag = '[object DataView]';
/** Used to convert symbols to primitives and strings. */
var symbolProto = _Symbol_js__WEBPACK_IMPORTED_MODULE_0__["default"] ? _Symbol_js__WEBPACK_IMPORTED_MODULE_0__["default"].prototype : undefined,
symbolValueOf = symbolProto ? symbolProto.valueOf : undefined;
/**
* A specialized version of `baseIsEqualDeep` for comparing objects of
* the same `toStringTag`.
*
* **Note:** This function only supports comparing values with tags of
* `Boolean`, `Date`, `Error`, `Number`, `RegExp`, or `String`.
*
* @private
* @param {Object} object The object to compare.
* @param {Object} other The other object to compare.
* @param {string} tag The `toStringTag` of the objects to compare.
* @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details.
* @param {Function} customizer The function to customize comparisons.
* @param {Function} equalFunc The function to determine equivalents of values.
* @param {Object} stack Tracks traversed `object` and `other` objects.
* @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
*/
function equalByTag(object, other, tag, bitmask, customizer, equalFunc, stack) {
switch (tag) {
case dataViewTag:
if ((object.byteLength != other.byteLength) ||
(object.byteOffset != other.byteOffset)) {
return false;
}
object = object.buffer;
other = other.buffer;
case arrayBufferTag:
if ((object.byteLength != other.byteLength) ||
!equalFunc(new _Uint8Array_js__WEBPACK_IMPORTED_MODULE_1__["default"](object), new _Uint8Array_js__WEBPACK_IMPORTED_MODULE_1__["default"](other))) {
return false;
}
return true;
case boolTag:
case dateTag:
case numberTag:
// Coerce booleans to `1` or `0` and dates to milliseconds.
// Invalid dates are coerced to `NaN`.
return (0,_eq_js__WEBPACK_IMPORTED_MODULE_2__["default"])(+object, +other);
case errorTag:
return object.name == other.name && object.message == other.message;
case regexpTag:
case stringTag:
// Coerce regexes to strings and treat strings, primitives and objects,
// as equal. See http://www.ecma-international.org/ecma-262/7.0/#sec-regexp.prototype.tostring
// for more details.
return object == (other + '');
case mapTag:
var convert = _mapToArray_js__WEBPACK_IMPORTED_MODULE_3__["default"];
case setTag:
var isPartial = bitmask & COMPARE_PARTIAL_FLAG;
convert || (convert = _setToArray_js__WEBPACK_IMPORTED_MODULE_4__["default"]);
if (object.size != other.size && !isPartial) {
return false;
}
// Assume cyclic values are equal.
var stacked = stack.get(object);
if (stacked) {
return stacked == other;
}
bitmask |= COMPARE_UNORDERED_FLAG;
// Recursively compare objects (susceptible to call stack limits).
stack.set(object, other);
var result = (0,_equalArrays_js__WEBPACK_IMPORTED_MODULE_5__["default"])(convert(object), convert(other), bitmask, customizer, equalFunc, stack);
stack['delete'](object);
return result;
case symbolTag:
if (symbolValueOf) {
return symbolValueOf.call(object) == symbolValueOf.call(other);
}
}
return false;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (equalByTag);
/***/ }),
/***/ "./node_modules/lodash-es/_equalObjects.js":
/*!*************************************************!*\
!*** ./node_modules/lodash-es/_equalObjects.js ***!
\*************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _getAllKeys_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_getAllKeys.js */ "./node_modules/lodash-es/_getAllKeys.js");
/** Used to compose bitmasks for value comparisons. */
var COMPARE_PARTIAL_FLAG = 1;
/** Used for built-in method references. */
var objectProto = Object.prototype;
/** Used to check objects for own properties. */
var hasOwnProperty = objectProto.hasOwnProperty;
/**
* A specialized version of `baseIsEqualDeep` for objects with support for
* partial deep comparisons.
*
* @private
* @param {Object} object The object to compare.
* @param {Object} other The other object to compare.
* @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details.
* @param {Function} customizer The function to customize comparisons.
* @param {Function} equalFunc The function to determine equivalents of values.
* @param {Object} stack Tracks traversed `object` and `other` objects.
* @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
*/
function equalObjects(object, other, bitmask, customizer, equalFunc, stack) {
var isPartial = bitmask & COMPARE_PARTIAL_FLAG,
objProps = (0,_getAllKeys_js__WEBPACK_IMPORTED_MODULE_0__["default"])(object),
objLength = objProps.length,
othProps = (0,_getAllKeys_js__WEBPACK_IMPORTED_MODULE_0__["default"])(other),
othLength = othProps.length;
if (objLength != othLength && !isPartial) {
return false;
}
var index = objLength;
while (index--) {
var key = objProps[index];
if (!(isPartial ? key in other : hasOwnProperty.call(other, key))) {
return false;
}
}
// Check that cyclic values are equal.
var objStacked = stack.get(object);
var othStacked = stack.get(other);
if (objStacked && othStacked) {
return objStacked == other && othStacked == object;
}
var result = true;
stack.set(object, other);
stack.set(other, object);
var skipCtor = isPartial;
while (++index < objLength) {
key = objProps[index];
var objValue = object[key],
othValue = other[key];
if (customizer) {
var compared = isPartial
? customizer(othValue, objValue, key, other, object, stack)
: customizer(objValue, othValue, key, object, other, stack);
}
// Recursively compare objects (susceptible to call stack limits).
if (!(compared === undefined
? (objValue === othValue || equalFunc(objValue, othValue, bitmask, customizer, stack))
: compared
)) {
result = false;
break;
}
skipCtor || (skipCtor = key == 'constructor');
}
if (result && !skipCtor) {
var objCtor = object.constructor,
othCtor = other.constructor;
// Non `Object` object instances with different constructors are not equal.
if (objCtor != othCtor &&
('constructor' in object && 'constructor' in other) &&
!(typeof objCtor == 'function' && objCtor instanceof objCtor &&
typeof othCtor == 'function' && othCtor instanceof othCtor)) {
result = false;
}
}
stack['delete'](object);
stack['delete'](other);
return result;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (equalObjects);
/***/ }),
/***/ "./node_modules/lodash-es/_freeGlobal.js":
/*!***********************************************!*\
!*** ./node_modules/lodash-es/_freeGlobal.js ***!
\***********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/** Detect free variable `global` from Node.js. */
var freeGlobal = typeof global == 'object' && global && global.Object === Object && global;
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (freeGlobal);
/***/ }),
/***/ "./node_modules/lodash-es/_getAllKeys.js":
/*!***********************************************!*\
!*** ./node_modules/lodash-es/_getAllKeys.js ***!
\***********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _baseGetAllKeys_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_baseGetAllKeys.js */ "./node_modules/lodash-es/_baseGetAllKeys.js");
/* harmony import */ var _getSymbols_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./_getSymbols.js */ "./node_modules/lodash-es/_getSymbols.js");
/* harmony import */ var _keys_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./keys.js */ "./node_modules/lodash-es/keys.js");
/**
* Creates an array of own enumerable property names and symbols of `object`.
*
* @private
* @param {Object} object The object to query.
* @returns {Array} Returns the array of property names and symbols.
*/
function getAllKeys(object) {
return (0,_baseGetAllKeys_js__WEBPACK_IMPORTED_MODULE_0__["default"])(object, _keys_js__WEBPACK_IMPORTED_MODULE_1__["default"], _getSymbols_js__WEBPACK_IMPORTED_MODULE_2__["default"]);
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (getAllKeys);
/***/ }),
/***/ "./node_modules/lodash-es/_getAllKeysIn.js":
/*!*************************************************!*\
!*** ./node_modules/lodash-es/_getAllKeysIn.js ***!
\*************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _baseGetAllKeys_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_baseGetAllKeys.js */ "./node_modules/lodash-es/_baseGetAllKeys.js");
/* harmony import */ var _getSymbolsIn_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./_getSymbolsIn.js */ "./node_modules/lodash-es/_getSymbolsIn.js");
/* harmony import */ var _keysIn_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./keysIn.js */ "./node_modules/lodash-es/keysIn.js");
/**
* Creates an array of own and inherited enumerable property names and
* symbols of `object`.
*
* @private
* @param {Object} object The object to query.
* @returns {Array} Returns the array of property names and symbols.
*/
function getAllKeysIn(object) {
return (0,_baseGetAllKeys_js__WEBPACK_IMPORTED_MODULE_0__["default"])(object, _keysIn_js__WEBPACK_IMPORTED_MODULE_1__["default"], _getSymbolsIn_js__WEBPACK_IMPORTED_MODULE_2__["default"]);
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (getAllKeysIn);
/***/ }),
/***/ "./node_modules/lodash-es/_getMapData.js":
/*!***********************************************!*\
!*** ./node_modules/lodash-es/_getMapData.js ***!
\***********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _isKeyable_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_isKeyable.js */ "./node_modules/lodash-es/_isKeyable.js");
/**
* Gets the data for `map`.
*
* @private
* @param {Object} map The map to query.
* @param {string} key The reference key.
* @returns {*} Returns the map data.
*/
function getMapData(map, key) {
var data = map.__data__;
return (0,_isKeyable_js__WEBPACK_IMPORTED_MODULE_0__["default"])(key)
? data[typeof key == 'string' ? 'string' : 'hash']
: data.map;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (getMapData);
/***/ }),
/***/ "./node_modules/lodash-es/_getNative.js":
/*!**********************************************!*\
!*** ./node_modules/lodash-es/_getNative.js ***!
\**********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _baseIsNative_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./_baseIsNative.js */ "./node_modules/lodash-es/_baseIsNative.js");
/* harmony import */ var _getValue_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_getValue.js */ "./node_modules/lodash-es/_getValue.js");
/**
* Gets the native function at `key` of `object`.
*
* @private
* @param {Object} object The object to query.
* @param {string} key The key of the method to get.
* @returns {*} Returns the function if it's native, else `undefined`.
*/
function getNative(object, key) {
var value = (0,_getValue_js__WEBPACK_IMPORTED_MODULE_0__["default"])(object, key);
return (0,_baseIsNative_js__WEBPACK_IMPORTED_MODULE_1__["default"])(value) ? value : undefined;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (getNative);
/***/ }),
/***/ "./node_modules/lodash-es/_getPrototype.js":
/*!*************************************************!*\
!*** ./node_modules/lodash-es/_getPrototype.js ***!
\*************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _overArg_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_overArg.js */ "./node_modules/lodash-es/_overArg.js");
/** Built-in value references. */
var getPrototype = (0,_overArg_js__WEBPACK_IMPORTED_MODULE_0__["default"])(Object.getPrototypeOf, Object);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (getPrototype);
/***/ }),
/***/ "./node_modules/lodash-es/_getRawTag.js":
/*!**********************************************!*\
!*** ./node_modules/lodash-es/_getRawTag.js ***!
\**********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _Symbol_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_Symbol.js */ "./node_modules/lodash-es/_Symbol.js");
/** Used for built-in method references. */
var objectProto = Object.prototype;
/** Used to check objects for own properties. */
var hasOwnProperty = objectProto.hasOwnProperty;
/**
* Used to resolve the
* [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
* of values.
*/
var nativeObjectToString = objectProto.toString;
/** Built-in value references. */
var symToStringTag = _Symbol_js__WEBPACK_IMPORTED_MODULE_0__["default"] ? _Symbol_js__WEBPACK_IMPORTED_MODULE_0__["default"].toStringTag : undefined;
/**
* A specialized version of `baseGetTag` which ignores `Symbol.toStringTag` values.
*
* @private
* @param {*} value The value to query.
* @returns {string} Returns the raw `toStringTag`.
*/
function getRawTag(value) {
var isOwn = hasOwnProperty.call(value, symToStringTag),
tag = value[symToStringTag];
try {
value[symToStringTag] = undefined;
var unmasked = true;
} catch (e) {}
var result = nativeObjectToString.call(value);
if (unmasked) {
if (isOwn) {
value[symToStringTag] = tag;
} else {
delete value[symToStringTag];
}
}
return result;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (getRawTag);
/***/ }),
/***/ "./node_modules/lodash-es/_getSymbols.js":
/*!***********************************************!*\
!*** ./node_modules/lodash-es/_getSymbols.js ***!
\***********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _arrayFilter_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./_arrayFilter.js */ "./node_modules/lodash-es/_arrayFilter.js");
/* harmony import */ var _stubArray_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./stubArray.js */ "./node_modules/lodash-es/stubArray.js");
/** Used for built-in method references. */
var objectProto = Object.prototype;
/** Built-in value references. */
var propertyIsEnumerable = objectProto.propertyIsEnumerable;
/* Built-in method references for those with the same name as other `lodash` methods. */
var nativeGetSymbols = Object.getOwnPropertySymbols;
/**
* Creates an array of the own enumerable symbols of `object`.
*
* @private
* @param {Object} object The object to query.
* @returns {Array} Returns the array of symbols.
*/
var getSymbols = !nativeGetSymbols ? _stubArray_js__WEBPACK_IMPORTED_MODULE_0__["default"] : function(object) {
if (object == null) {
return [];
}
object = Object(object);
return (0,_arrayFilter_js__WEBPACK_IMPORTED_MODULE_1__["default"])(nativeGetSymbols(object), function(symbol) {
return propertyIsEnumerable.call(object, symbol);
});
};
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (getSymbols);
/***/ }),
/***/ "./node_modules/lodash-es/_getSymbolsIn.js":
/*!*************************************************!*\
!*** ./node_modules/lodash-es/_getSymbolsIn.js ***!
\*************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _arrayPush_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./_arrayPush.js */ "./node_modules/lodash-es/_arrayPush.js");
/* harmony import */ var _getPrototype_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./_getPrototype.js */ "./node_modules/lodash-es/_getPrototype.js");
/* harmony import */ var _getSymbols_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./_getSymbols.js */ "./node_modules/lodash-es/_getSymbols.js");
/* harmony import */ var _stubArray_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./stubArray.js */ "./node_modules/lodash-es/stubArray.js");
/* Built-in method references for those with the same name as other `lodash` methods. */
var nativeGetSymbols = Object.getOwnPropertySymbols;
/**
* Creates an array of the own and inherited enumerable symbols of `object`.
*
* @private
* @param {Object} object The object to query.
* @returns {Array} Returns the array of symbols.
*/
var getSymbolsIn = !nativeGetSymbols ? _stubArray_js__WEBPACK_IMPORTED_MODULE_0__["default"] : function(object) {
var result = [];
while (object) {
(0,_arrayPush_js__WEBPACK_IMPORTED_MODULE_1__["default"])(result, (0,_getSymbols_js__WEBPACK_IMPORTED_MODULE_2__["default"])(object));
object = (0,_getPrototype_js__WEBPACK_IMPORTED_MODULE_3__["default"])(object);
}
return result;
};
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (getSymbolsIn);
/***/ }),
/***/ "./node_modules/lodash-es/_getTag.js":
/*!*******************************************!*\
!*** ./node_modules/lodash-es/_getTag.js ***!
\*******************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _DataView_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./_DataView.js */ "./node_modules/lodash-es/_DataView.js");
/* harmony import */ var _Map_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./_Map.js */ "./node_modules/lodash-es/_Map.js");
/* harmony import */ var _Promise_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./_Promise.js */ "./node_modules/lodash-es/_Promise.js");
/* harmony import */ var _Set_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./_Set.js */ "./node_modules/lodash-es/_Set.js");
/* harmony import */ var _WeakMap_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./_WeakMap.js */ "./node_modules/lodash-es/_WeakMap.js");
/* harmony import */ var _baseGetTag_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./_baseGetTag.js */ "./node_modules/lodash-es/_baseGetTag.js");
/* harmony import */ var _toSource_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_toSource.js */ "./node_modules/lodash-es/_toSource.js");
/** `Object#toString` result references. */
var mapTag = '[object Map]',
objectTag = '[object Object]',
promiseTag = '[object Promise]',
setTag = '[object Set]',
weakMapTag = '[object WeakMap]';
var dataViewTag = '[object DataView]';
/** Used to detect maps, sets, and weakmaps. */
var dataViewCtorString = (0,_toSource_js__WEBPACK_IMPORTED_MODULE_0__["default"])(_DataView_js__WEBPACK_IMPORTED_MODULE_1__["default"]),
mapCtorString = (0,_toSource_js__WEBPACK_IMPORTED_MODULE_0__["default"])(_Map_js__WEBPACK_IMPORTED_MODULE_2__["default"]),
promiseCtorString = (0,_toSource_js__WEBPACK_IMPORTED_MODULE_0__["default"])(_Promise_js__WEBPACK_IMPORTED_MODULE_3__["default"]),
setCtorString = (0,_toSource_js__WEBPACK_IMPORTED_MODULE_0__["default"])(_Set_js__WEBPACK_IMPORTED_MODULE_4__["default"]),
weakMapCtorString = (0,_toSource_js__WEBPACK_IMPORTED_MODULE_0__["default"])(_WeakMap_js__WEBPACK_IMPORTED_MODULE_5__["default"]);
/**
* Gets the `toStringTag` of `value`.
*
* @private
* @param {*} value The value to query.
* @returns {string} Returns the `toStringTag`.
*/
var getTag = _baseGetTag_js__WEBPACK_IMPORTED_MODULE_6__["default"];
// Fallback for data views, maps, sets, and weak maps in IE 11 and promises in Node.js < 6.
if ((_DataView_js__WEBPACK_IMPORTED_MODULE_1__["default"] && getTag(new _DataView_js__WEBPACK_IMPORTED_MODULE_1__["default"](new ArrayBuffer(1))) != dataViewTag) ||
(_Map_js__WEBPACK_IMPORTED_MODULE_2__["default"] && getTag(new _Map_js__WEBPACK_IMPORTED_MODULE_2__["default"]) != mapTag) ||
(_Promise_js__WEBPACK_IMPORTED_MODULE_3__["default"] && getTag(_Promise_js__WEBPACK_IMPORTED_MODULE_3__["default"].resolve()) != promiseTag) ||
(_Set_js__WEBPACK_IMPORTED_MODULE_4__["default"] && getTag(new _Set_js__WEBPACK_IMPORTED_MODULE_4__["default"]) != setTag) ||
(_WeakMap_js__WEBPACK_IMPORTED_MODULE_5__["default"] && getTag(new _WeakMap_js__WEBPACK_IMPORTED_MODULE_5__["default"]) != weakMapTag)) {
getTag = function(value) {
var result = (0,_baseGetTag_js__WEBPACK_IMPORTED_MODULE_6__["default"])(value),
Ctor = result == objectTag ? value.constructor : undefined,
ctorString = Ctor ? (0,_toSource_js__WEBPACK_IMPORTED_MODULE_0__["default"])(Ctor) : '';
if (ctorString) {
switch (ctorString) {
case dataViewCtorString: return dataViewTag;
case mapCtorString: return mapTag;
case promiseCtorString: return promiseTag;
case setCtorString: return setTag;
case weakMapCtorString: return weakMapTag;
}
}
return result;
};
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (getTag);
/***/ }),
/***/ "./node_modules/lodash-es/_getValue.js":
/*!*********************************************!*\
!*** ./node_modules/lodash-es/_getValue.js ***!
\*********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/**
* Gets the value at `key` of `object`.
*
* @private
* @param {Object} [object] The object to query.
* @param {string} key The key of the property to get.
* @returns {*} Returns the property value.
*/
function getValue(object, key) {
return object == null ? undefined : object[key];
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (getValue);
/***/ }),
/***/ "./node_modules/lodash-es/_hasUnicode.js":
/*!***********************************************!*\
!*** ./node_modules/lodash-es/_hasUnicode.js ***!
\***********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/** Used to compose unicode character classes. */
var rsAstralRange = '\\ud800-\\udfff',
rsComboMarksRange = '\\u0300-\\u036f',
reComboHalfMarksRange = '\\ufe20-\\ufe2f',
rsComboSymbolsRange = '\\u20d0-\\u20ff',
rsComboRange = rsComboMarksRange + reComboHalfMarksRange + rsComboSymbolsRange,
rsVarRange = '\\ufe0e\\ufe0f';
/** Used to compose unicode capture groups. */
var rsZWJ = '\\u200d';
/** Used to detect strings with [zero-width joiners or code points from the astral planes](http://eev.ee/blog/2015/09/12/dark-corners-of-unicode/). */
var reHasUnicode = RegExp('[' + rsZWJ + rsAstralRange + rsComboRange + rsVarRange + ']');
/**
* Checks if `string` contains Unicode symbols.
*
* @private
* @param {string} string The string to inspect.
* @returns {boolean} Returns `true` if a symbol is found, else `false`.
*/
function hasUnicode(string) {
return reHasUnicode.test(string);
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (hasUnicode);
/***/ }),
/***/ "./node_modules/lodash-es/_hashClear.js":
/*!**********************************************!*\
!*** ./node_modules/lodash-es/_hashClear.js ***!
\**********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _nativeCreate_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_nativeCreate.js */ "./node_modules/lodash-es/_nativeCreate.js");
/**
* Removes all key-value entries from the hash.
*
* @private
* @name clear
* @memberOf Hash
*/
function hashClear() {
this.__data__ = _nativeCreate_js__WEBPACK_IMPORTED_MODULE_0__["default"] ? (0,_nativeCreate_js__WEBPACK_IMPORTED_MODULE_0__["default"])(null) : {};
this.size = 0;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (hashClear);
/***/ }),
/***/ "./node_modules/lodash-es/_hashDelete.js":
/*!***********************************************!*\
!*** ./node_modules/lodash-es/_hashDelete.js ***!
\***********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/**
* Removes `key` and its value from the hash.
*
* @private
* @name delete
* @memberOf Hash
* @param {Object} hash The hash to modify.
* @param {string} key The key of the value to remove.
* @returns {boolean} Returns `true` if the entry was removed, else `false`.
*/
function hashDelete(key) {
var result = this.has(key) && delete this.__data__[key];
this.size -= result ? 1 : 0;
return result;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (hashDelete);
/***/ }),
/***/ "./node_modules/lodash-es/_hashGet.js":
/*!********************************************!*\
!*** ./node_modules/lodash-es/_hashGet.js ***!
\********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _nativeCreate_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_nativeCreate.js */ "./node_modules/lodash-es/_nativeCreate.js");
/** Used to stand-in for `undefined` hash values. */
var HASH_UNDEFINED = '__lodash_hash_undefined__';
/** Used for built-in method references. */
var objectProto = Object.prototype;
/** Used to check objects for own properties. */
var hasOwnProperty = objectProto.hasOwnProperty;
/**
* Gets the hash value for `key`.
*
* @private
* @name get
* @memberOf Hash
* @param {string} key The key of the value to get.
* @returns {*} Returns the entry value.
*/
function hashGet(key) {
var data = this.__data__;
if (_nativeCreate_js__WEBPACK_IMPORTED_MODULE_0__["default"]) {
var result = data[key];
return result === HASH_UNDEFINED ? undefined : result;
}
return hasOwnProperty.call(data, key) ? data[key] : undefined;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (hashGet);
/***/ }),
/***/ "./node_modules/lodash-es/_hashHas.js":
/*!********************************************!*\
!*** ./node_modules/lodash-es/_hashHas.js ***!
\********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _nativeCreate_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_nativeCreate.js */ "./node_modules/lodash-es/_nativeCreate.js");
/** Used for built-in method references. */
var objectProto = Object.prototype;
/** Used to check objects for own properties. */
var hasOwnProperty = objectProto.hasOwnProperty;
/**
* Checks if a hash value for `key` exists.
*
* @private
* @name has
* @memberOf Hash
* @param {string} key The key of the entry to check.
* @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
*/
function hashHas(key) {
var data = this.__data__;
return _nativeCreate_js__WEBPACK_IMPORTED_MODULE_0__["default"] ? (data[key] !== undefined) : hasOwnProperty.call(data, key);
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (hashHas);
/***/ }),
/***/ "./node_modules/lodash-es/_hashSet.js":
/*!********************************************!*\
!*** ./node_modules/lodash-es/_hashSet.js ***!
\********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _nativeCreate_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_nativeCreate.js */ "./node_modules/lodash-es/_nativeCreate.js");
/** Used to stand-in for `undefined` hash values. */
var HASH_UNDEFINED = '__lodash_hash_undefined__';
/**
* Sets the hash `key` to `value`.
*
* @private
* @name set
* @memberOf Hash
* @param {string} key The key of the value to set.
* @param {*} value The value to set.
* @returns {Object} Returns the hash instance.
*/
function hashSet(key, value) {
var data = this.__data__;
this.size += this.has(key) ? 0 : 1;
data[key] = (_nativeCreate_js__WEBPACK_IMPORTED_MODULE_0__["default"] && value === undefined) ? HASH_UNDEFINED : value;
return this;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (hashSet);
/***/ }),
/***/ "./node_modules/lodash-es/_initCloneArray.js":
/*!***************************************************!*\
!*** ./node_modules/lodash-es/_initCloneArray.js ***!
\***************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/** Used for built-in method references. */
var objectProto = Object.prototype;
/** Used to check objects for own properties. */
var hasOwnProperty = objectProto.hasOwnProperty;
/**
* Initializes an array clone.
*
* @private
* @param {Array} array The array to clone.
* @returns {Array} Returns the initialized clone.
*/
function initCloneArray(array) {
var length = array.length,
result = new array.constructor(length);
// Add properties assigned by `RegExp#exec`.
if (length && typeof array[0] == 'string' && hasOwnProperty.call(array, 'index')) {
result.index = array.index;
result.input = array.input;
}
return result;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (initCloneArray);
/***/ }),
/***/ "./node_modules/lodash-es/_initCloneByTag.js":
/*!***************************************************!*\
!*** ./node_modules/lodash-es/_initCloneByTag.js ***!
\***************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _cloneArrayBuffer_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_cloneArrayBuffer.js */ "./node_modules/lodash-es/_cloneArrayBuffer.js");
/* harmony import */ var _cloneDataView_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./_cloneDataView.js */ "./node_modules/lodash-es/_cloneDataView.js");
/* harmony import */ var _cloneRegExp_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./_cloneRegExp.js */ "./node_modules/lodash-es/_cloneRegExp.js");
/* harmony import */ var _cloneSymbol_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./_cloneSymbol.js */ "./node_modules/lodash-es/_cloneSymbol.js");
/* harmony import */ var _cloneTypedArray_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./_cloneTypedArray.js */ "./node_modules/lodash-es/_cloneTypedArray.js");
/** `Object#toString` result references. */
var boolTag = '[object Boolean]',
dateTag = '[object Date]',
mapTag = '[object Map]',
numberTag = '[object Number]',
regexpTag = '[object RegExp]',
setTag = '[object Set]',
stringTag = '[object String]',
symbolTag = '[object Symbol]';
var arrayBufferTag = '[object ArrayBuffer]',
dataViewTag = '[object DataView]',
float32Tag = '[object Float32Array]',
float64Tag = '[object Float64Array]',
int8Tag = '[object Int8Array]',
int16Tag = '[object Int16Array]',
int32Tag = '[object Int32Array]',
uint8Tag = '[object Uint8Array]',
uint8ClampedTag = '[object Uint8ClampedArray]',
uint16Tag = '[object Uint16Array]',
uint32Tag = '[object Uint32Array]';
/**
* Initializes an object clone based on its `toStringTag`.
*
* **Note:** This function only supports cloning values with tags of
* `Boolean`, `Date`, `Error`, `Map`, `Number`, `RegExp`, `Set`, or `String`.
*
* @private
* @param {Object} object The object to clone.
* @param {string} tag The `toStringTag` of the object to clone.
* @param {boolean} [isDeep] Specify a deep clone.
* @returns {Object} Returns the initialized clone.
*/
function initCloneByTag(object, tag, isDeep) {
var Ctor = object.constructor;
switch (tag) {
case arrayBufferTag:
return (0,_cloneArrayBuffer_js__WEBPACK_IMPORTED_MODULE_0__["default"])(object);
case boolTag:
case dateTag:
return new Ctor(+object);
case dataViewTag:
return (0,_cloneDataView_js__WEBPACK_IMPORTED_MODULE_1__["default"])(object, isDeep);
case float32Tag: case float64Tag:
case int8Tag: case int16Tag: case int32Tag:
case uint8Tag: case uint8ClampedTag: case uint16Tag: case uint32Tag:
return (0,_cloneTypedArray_js__WEBPACK_IMPORTED_MODULE_2__["default"])(object, isDeep);
case mapTag:
return new Ctor;
case numberTag:
case stringTag:
return new Ctor(object);
case regexpTag:
return (0,_cloneRegExp_js__WEBPACK_IMPORTED_MODULE_3__["default"])(object);
case setTag:
return new Ctor;
case symbolTag:
return (0,_cloneSymbol_js__WEBPACK_IMPORTED_MODULE_4__["default"])(object);
}
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (initCloneByTag);
/***/ }),
/***/ "./node_modules/lodash-es/_initCloneObject.js":
/*!****************************************************!*\
!*** ./node_modules/lodash-es/_initCloneObject.js ***!
\****************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _baseCreate_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./_baseCreate.js */ "./node_modules/lodash-es/_baseCreate.js");
/* harmony import */ var _getPrototype_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./_getPrototype.js */ "./node_modules/lodash-es/_getPrototype.js");
/* harmony import */ var _isPrototype_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_isPrototype.js */ "./node_modules/lodash-es/_isPrototype.js");
/**
* Initializes an object clone.
*
* @private
* @param {Object} object The object to clone.
* @returns {Object} Returns the initialized clone.
*/
function initCloneObject(object) {
return (typeof object.constructor == 'function' && !(0,_isPrototype_js__WEBPACK_IMPORTED_MODULE_0__["default"])(object))
? (0,_baseCreate_js__WEBPACK_IMPORTED_MODULE_1__["default"])((0,_getPrototype_js__WEBPACK_IMPORTED_MODULE_2__["default"])(object))
: {};
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (initCloneObject);
/***/ }),
/***/ "./node_modules/lodash-es/_isIndex.js":
/*!********************************************!*\
!*** ./node_modules/lodash-es/_isIndex.js ***!
\********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/** Used as references for various `Number` constants. */
var MAX_SAFE_INTEGER = 9007199254740991;
/** Used to detect unsigned integer values. */
var reIsUint = /^(?:0|[1-9]\d*)$/;
/**
* Checks if `value` is a valid array-like index.
*
* @private
* @param {*} value The value to check.
* @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index.
* @returns {boolean} Returns `true` if `value` is a valid index, else `false`.
*/
function isIndex(value, length) {
var type = typeof value;
length = length == null ? MAX_SAFE_INTEGER : length;
return !!length &&
(type == 'number' ||
(type != 'symbol' && reIsUint.test(value))) &&
(value > -1 && value % 1 == 0 && value < length);
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (isIndex);
/***/ }),
/***/ "./node_modules/lodash-es/_isIterateeCall.js":
/*!***************************************************!*\
!*** ./node_modules/lodash-es/_isIterateeCall.js ***!
\***************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _eq_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./eq.js */ "./node_modules/lodash-es/eq.js");
/* harmony import */ var _isArrayLike_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./isArrayLike.js */ "./node_modules/lodash-es/isArrayLike.js");
/* harmony import */ var _isIndex_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./_isIndex.js */ "./node_modules/lodash-es/_isIndex.js");
/* harmony import */ var _isObject_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./isObject.js */ "./node_modules/lodash-es/isObject.js");
/**
* Checks if the given arguments are from an iteratee call.
*
* @private
* @param {*} value The potential iteratee value argument.
* @param {*} index The potential iteratee index or key argument.
* @param {*} object The potential iteratee object argument.
* @returns {boolean} Returns `true` if the arguments are from an iteratee call,
* else `false`.
*/
function isIterateeCall(value, index, object) {
if (!(0,_isObject_js__WEBPACK_IMPORTED_MODULE_0__["default"])(object)) {
return false;
}
var type = typeof index;
if (type == 'number'
? ((0,_isArrayLike_js__WEBPACK_IMPORTED_MODULE_1__["default"])(object) && (0,_isIndex_js__WEBPACK_IMPORTED_MODULE_2__["default"])(index, object.length))
: (type == 'string' && index in object)
) {
return (0,_eq_js__WEBPACK_IMPORTED_MODULE_3__["default"])(object[index], value);
}
return false;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (isIterateeCall);
/***/ }),
/***/ "./node_modules/lodash-es/_isKey.js":
/*!******************************************!*\
!*** ./node_modules/lodash-es/_isKey.js ***!
\******************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _isArray_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./isArray.js */ "./node_modules/lodash-es/isArray.js");
/* harmony import */ var _isSymbol_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./isSymbol.js */ "./node_modules/lodash-es/isSymbol.js");
/** Used to match property names within property paths. */
var reIsDeepProp = /\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,
reIsPlainProp = /^\w*$/;
/**
* Checks if `value` is a property name and not a property path.
*
* @private
* @param {*} value The value to check.
* @param {Object} [object] The object to query keys on.
* @returns {boolean} Returns `true` if `value` is a property name, else `false`.
*/
function isKey(value, object) {
if ((0,_isArray_js__WEBPACK_IMPORTED_MODULE_0__["default"])(value)) {
return false;
}
var type = typeof value;
if (type == 'number' || type == 'symbol' || type == 'boolean' ||
value == null || (0,_isSymbol_js__WEBPACK_IMPORTED_MODULE_1__["default"])(value)) {
return true;
}
return reIsPlainProp.test(value) || !reIsDeepProp.test(value) ||
(object != null && value in Object(object));
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (isKey);
/***/ }),
/***/ "./node_modules/lodash-es/_isKeyable.js":
/*!**********************************************!*\
!*** ./node_modules/lodash-es/_isKeyable.js ***!
\**********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/**
* Checks if `value` is suitable for use as unique object key.
*
* @private
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is suitable, else `false`.
*/
function isKeyable(value) {
var type = typeof value;
return (type == 'string' || type == 'number' || type == 'symbol' || type == 'boolean')
? (value !== '__proto__')
: (value === null);
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (isKeyable);
/***/ }),
/***/ "./node_modules/lodash-es/_isMasked.js":
/*!*********************************************!*\
!*** ./node_modules/lodash-es/_isMasked.js ***!
\*********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _coreJsData_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_coreJsData.js */ "./node_modules/lodash-es/_coreJsData.js");
/** Used to detect methods masquerading as native. */
var maskSrcKey = (function() {
var uid = /[^.]+$/.exec(_coreJsData_js__WEBPACK_IMPORTED_MODULE_0__["default"] && _coreJsData_js__WEBPACK_IMPORTED_MODULE_0__["default"].keys && _coreJsData_js__WEBPACK_IMPORTED_MODULE_0__["default"].keys.IE_PROTO || '');
return uid ? ('Symbol(src)_1.' + uid) : '';
}());
/**
* Checks if `func` has its source masked.
*
* @private
* @param {Function} func The function to check.
* @returns {boolean} Returns `true` if `func` is masked, else `false`.
*/
function isMasked(func) {
return !!maskSrcKey && (maskSrcKey in func);
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (isMasked);
/***/ }),
/***/ "./node_modules/lodash-es/_isPrototype.js":
/*!************************************************!*\
!*** ./node_modules/lodash-es/_isPrototype.js ***!
\************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/** Used for built-in method references. */
var objectProto = Object.prototype;
/**
* Checks if `value` is likely a prototype object.
*
* @private
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a prototype, else `false`.
*/
function isPrototype(value) {
var Ctor = value && value.constructor,
proto = (typeof Ctor == 'function' && Ctor.prototype) || objectProto;
return value === proto;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (isPrototype);
/***/ }),
/***/ "./node_modules/lodash-es/_listCacheClear.js":
/*!***************************************************!*\
!*** ./node_modules/lodash-es/_listCacheClear.js ***!
\***************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/**
* Removes all key-value entries from the list cache.
*
* @private
* @name clear
* @memberOf ListCache
*/
function listCacheClear() {
this.__data__ = [];
this.size = 0;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (listCacheClear);
/***/ }),
/***/ "./node_modules/lodash-es/_listCacheDelete.js":
/*!****************************************************!*\
!*** ./node_modules/lodash-es/_listCacheDelete.js ***!
\****************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _assocIndexOf_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_assocIndexOf.js */ "./node_modules/lodash-es/_assocIndexOf.js");
/** Used for built-in method references. */
var arrayProto = Array.prototype;
/** Built-in value references. */
var splice = arrayProto.splice;
/**
* Removes `key` and its value from the list cache.
*
* @private
* @name delete
* @memberOf ListCache
* @param {string} key The key of the value to remove.
* @returns {boolean} Returns `true` if the entry was removed, else `false`.
*/
function listCacheDelete(key) {
var data = this.__data__,
index = (0,_assocIndexOf_js__WEBPACK_IMPORTED_MODULE_0__["default"])(data, key);
if (index < 0) {
return false;
}
var lastIndex = data.length - 1;
if (index == lastIndex) {
data.pop();
} else {
splice.call(data, index, 1);
}
--this.size;
return true;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (listCacheDelete);
/***/ }),
/***/ "./node_modules/lodash-es/_listCacheGet.js":
/*!*************************************************!*\
!*** ./node_modules/lodash-es/_listCacheGet.js ***!
\*************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _assocIndexOf_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_assocIndexOf.js */ "./node_modules/lodash-es/_assocIndexOf.js");
/**
* Gets the list cache value for `key`.
*
* @private
* @name get
* @memberOf ListCache
* @param {string} key The key of the value to get.
* @returns {*} Returns the entry value.
*/
function listCacheGet(key) {
var data = this.__data__,
index = (0,_assocIndexOf_js__WEBPACK_IMPORTED_MODULE_0__["default"])(data, key);
return index < 0 ? undefined : data[index][1];
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (listCacheGet);
/***/ }),
/***/ "./node_modules/lodash-es/_listCacheHas.js":
/*!*************************************************!*\
!*** ./node_modules/lodash-es/_listCacheHas.js ***!
\*************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _assocIndexOf_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_assocIndexOf.js */ "./node_modules/lodash-es/_assocIndexOf.js");
/**
* Checks if a list cache value for `key` exists.
*
* @private
* @name has
* @memberOf ListCache
* @param {string} key The key of the entry to check.
* @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
*/
function listCacheHas(key) {
return (0,_assocIndexOf_js__WEBPACK_IMPORTED_MODULE_0__["default"])(this.__data__, key) > -1;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (listCacheHas);
/***/ }),
/***/ "./node_modules/lodash-es/_listCacheSet.js":
/*!*************************************************!*\
!*** ./node_modules/lodash-es/_listCacheSet.js ***!
\*************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _assocIndexOf_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_assocIndexOf.js */ "./node_modules/lodash-es/_assocIndexOf.js");
/**
* Sets the list cache `key` to `value`.
*
* @private
* @name set
* @memberOf ListCache
* @param {string} key The key of the value to set.
* @param {*} value The value to set.
* @returns {Object} Returns the list cache instance.
*/
function listCacheSet(key, value) {
var data = this.__data__,
index = (0,_assocIndexOf_js__WEBPACK_IMPORTED_MODULE_0__["default"])(data, key);
if (index < 0) {
++this.size;
data.push([key, value]);
} else {
data[index][1] = value;
}
return this;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (listCacheSet);
/***/ }),
/***/ "./node_modules/lodash-es/_mapCacheClear.js":
/*!**************************************************!*\
!*** ./node_modules/lodash-es/_mapCacheClear.js ***!
\**************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _Hash_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_Hash.js */ "./node_modules/lodash-es/_Hash.js");
/* harmony import */ var _ListCache_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./_ListCache.js */ "./node_modules/lodash-es/_ListCache.js");
/* harmony import */ var _Map_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./_Map.js */ "./node_modules/lodash-es/_Map.js");
/**
* Removes all key-value entries from the map.
*
* @private
* @name clear
* @memberOf MapCache
*/
function mapCacheClear() {
this.size = 0;
this.__data__ = {
'hash': new _Hash_js__WEBPACK_IMPORTED_MODULE_0__["default"],
'map': new (_Map_js__WEBPACK_IMPORTED_MODULE_1__["default"] || _ListCache_js__WEBPACK_IMPORTED_MODULE_2__["default"]),
'string': new _Hash_js__WEBPACK_IMPORTED_MODULE_0__["default"]
};
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (mapCacheClear);
/***/ }),
/***/ "./node_modules/lodash-es/_mapCacheDelete.js":
/*!***************************************************!*\
!*** ./node_modules/lodash-es/_mapCacheDelete.js ***!
\***************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _getMapData_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_getMapData.js */ "./node_modules/lodash-es/_getMapData.js");
/**
* Removes `key` and its value from the map.
*
* @private
* @name delete
* @memberOf MapCache
* @param {string} key The key of the value to remove.
* @returns {boolean} Returns `true` if the entry was removed, else `false`.
*/
function mapCacheDelete(key) {
var result = (0,_getMapData_js__WEBPACK_IMPORTED_MODULE_0__["default"])(this, key)['delete'](key);
this.size -= result ? 1 : 0;
return result;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (mapCacheDelete);
/***/ }),
/***/ "./node_modules/lodash-es/_mapCacheGet.js":
/*!************************************************!*\
!*** ./node_modules/lodash-es/_mapCacheGet.js ***!
\************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _getMapData_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_getMapData.js */ "./node_modules/lodash-es/_getMapData.js");
/**
* Gets the map value for `key`.
*
* @private
* @name get
* @memberOf MapCache
* @param {string} key The key of the value to get.
* @returns {*} Returns the entry value.
*/
function mapCacheGet(key) {
return (0,_getMapData_js__WEBPACK_IMPORTED_MODULE_0__["default"])(this, key).get(key);
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (mapCacheGet);
/***/ }),
/***/ "./node_modules/lodash-es/_mapCacheHas.js":
/*!************************************************!*\
!*** ./node_modules/lodash-es/_mapCacheHas.js ***!
\************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _getMapData_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_getMapData.js */ "./node_modules/lodash-es/_getMapData.js");
/**
* Checks if a map value for `key` exists.
*
* @private
* @name has
* @memberOf MapCache
* @param {string} key The key of the entry to check.
* @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
*/
function mapCacheHas(key) {
return (0,_getMapData_js__WEBPACK_IMPORTED_MODULE_0__["default"])(this, key).has(key);
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (mapCacheHas);
/***/ }),
/***/ "./node_modules/lodash-es/_mapCacheSet.js":
/*!************************************************!*\
!*** ./node_modules/lodash-es/_mapCacheSet.js ***!
\************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _getMapData_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_getMapData.js */ "./node_modules/lodash-es/_getMapData.js");
/**
* Sets the map `key` to `value`.
*
* @private
* @name set
* @memberOf MapCache
* @param {string} key The key of the value to set.
* @param {*} value The value to set.
* @returns {Object} Returns the map cache instance.
*/
function mapCacheSet(key, value) {
var data = (0,_getMapData_js__WEBPACK_IMPORTED_MODULE_0__["default"])(this, key),
size = data.size;
data.set(key, value);
this.size += data.size == size ? 0 : 1;
return this;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (mapCacheSet);
/***/ }),
/***/ "./node_modules/lodash-es/_mapToArray.js":
/*!***********************************************!*\
!*** ./node_modules/lodash-es/_mapToArray.js ***!
\***********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/**
* Converts `map` to its key-value pairs.
*
* @private
* @param {Object} map The map to convert.
* @returns {Array} Returns the key-value pairs.
*/
function mapToArray(map) {
var index = -1,
result = Array(map.size);
map.forEach(function(value, key) {
result[++index] = [key, value];
});
return result;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (mapToArray);
/***/ }),
/***/ "./node_modules/lodash-es/_memoizeCapped.js":
/*!**************************************************!*\
!*** ./node_modules/lodash-es/_memoizeCapped.js ***!
\**************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _memoize_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./memoize.js */ "./node_modules/lodash-es/memoize.js");
/** Used as the maximum memoize cache size. */
var MAX_MEMOIZE_SIZE = 500;
/**
* A specialized version of `_.memoize` which clears the memoized function's
* cache when it exceeds `MAX_MEMOIZE_SIZE`.
*
* @private
* @param {Function} func The function to have its output memoized.
* @returns {Function} Returns the new memoized function.
*/
function memoizeCapped(func) {
var result = (0,_memoize_js__WEBPACK_IMPORTED_MODULE_0__["default"])(func, function(key) {
if (cache.size === MAX_MEMOIZE_SIZE) {
cache.clear();
}
return key;
});
var cache = result.cache;
return result;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (memoizeCapped);
/***/ }),
/***/ "./node_modules/lodash-es/_nativeCreate.js":
/*!*************************************************!*\
!*** ./node_modules/lodash-es/_nativeCreate.js ***!
\*************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _getNative_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_getNative.js */ "./node_modules/lodash-es/_getNative.js");
/* Built-in method references that are verified to be native. */
var nativeCreate = (0,_getNative_js__WEBPACK_IMPORTED_MODULE_0__["default"])(Object, 'create');
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (nativeCreate);
/***/ }),
/***/ "./node_modules/lodash-es/_nativeKeys.js":
/*!***********************************************!*\
!*** ./node_modules/lodash-es/_nativeKeys.js ***!
\***********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _overArg_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_overArg.js */ "./node_modules/lodash-es/_overArg.js");
/* Built-in method references for those with the same name as other `lodash` methods. */
var nativeKeys = (0,_overArg_js__WEBPACK_IMPORTED_MODULE_0__["default"])(Object.keys, Object);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (nativeKeys);
/***/ }),
/***/ "./node_modules/lodash-es/_nativeKeysIn.js":
/*!*************************************************!*\
!*** ./node_modules/lodash-es/_nativeKeysIn.js ***!
\*************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/**
* This function is like
* [`Object.keys`](http://ecma-international.org/ecma-262/7.0/#sec-object.keys)
* except that it includes inherited enumerable properties.
*
* @private
* @param {Object} object The object to query.
* @returns {Array} Returns the array of property names.
*/
function nativeKeysIn(object) {
var result = [];
if (object != null) {
for (var key in Object(object)) {
result.push(key);
}
}
return result;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (nativeKeysIn);
/***/ }),
/***/ "./node_modules/lodash-es/_nodeUtil.js":
/*!*********************************************!*\
!*** ./node_modules/lodash-es/_nodeUtil.js ***!
\*********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _freeGlobal_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_freeGlobal.js */ "./node_modules/lodash-es/_freeGlobal.js");
/** Detect free variable `exports`. */
var freeExports = typeof exports == 'object' && exports && !exports.nodeType && exports;
/** Detect free variable `module`. */
var freeModule = freeExports && typeof module == 'object' && module && !module.nodeType && module;
/** Detect the popular CommonJS extension `module.exports`. */
var moduleExports = freeModule && freeModule.exports === freeExports;
/** Detect free variable `process` from Node.js. */
var freeProcess = moduleExports && _freeGlobal_js__WEBPACK_IMPORTED_MODULE_0__["default"].process;
/** Used to access faster Node.js helpers. */
var nodeUtil = (function() {
try {
// Use `util.types` for Node.js 10+.
var types = freeModule && freeModule.require && freeModule.require('util').types;
if (types) {
return types;
}
// Legacy `process.binding('util')` for Node.js < 10.
return freeProcess && freeProcess.binding && freeProcess.binding('util');
} catch (e) {}
}());
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (nodeUtil);
/***/ }),
/***/ "./node_modules/lodash-es/_objectToString.js":
/*!***************************************************!*\
!*** ./node_modules/lodash-es/_objectToString.js ***!
\***************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/** Used for built-in method references. */
var objectProto = Object.prototype;
/**
* Used to resolve the
* [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
* of values.
*/
var nativeObjectToString = objectProto.toString;
/**
* Converts `value` to a string using `Object.prototype.toString`.
*
* @private
* @param {*} value The value to convert.
* @returns {string} Returns the converted string.
*/
function objectToString(value) {
return nativeObjectToString.call(value);
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (objectToString);
/***/ }),
/***/ "./node_modules/lodash-es/_overArg.js":
/*!********************************************!*\
!*** ./node_modules/lodash-es/_overArg.js ***!
\********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/**
* Creates a unary function that invokes `func` with its argument transformed.
*
* @private
* @param {Function} func The function to wrap.
* @param {Function} transform The argument transform.
* @returns {Function} Returns the new function.
*/
function overArg(func, transform) {
return function(arg) {
return func(transform(arg));
};
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (overArg);
/***/ }),
/***/ "./node_modules/lodash-es/_overRest.js":
/*!*********************************************!*\
!*** ./node_modules/lodash-es/_overRest.js ***!
\*********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _apply_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_apply.js */ "./node_modules/lodash-es/_apply.js");
/* Built-in method references for those with the same name as other `lodash` methods. */
var nativeMax = Math.max;
/**
* A specialized version of `baseRest` which transforms the rest array.
*
* @private
* @param {Function} func The function to apply a rest parameter to.
* @param {number} [start=func.length-1] The start position of the rest parameter.
* @param {Function} transform The rest array transform.
* @returns {Function} Returns the new function.
*/
function overRest(func, start, transform) {
start = nativeMax(start === undefined ? (func.length - 1) : start, 0);
return function() {
var args = arguments,
index = -1,
length = nativeMax(args.length - start, 0),
array = Array(length);
while (++index < length) {
array[index] = args[start + index];
}
index = -1;
var otherArgs = Array(start + 1);
while (++index < start) {
otherArgs[index] = args[index];
}
otherArgs[start] = transform(array);
return (0,_apply_js__WEBPACK_IMPORTED_MODULE_0__["default"])(func, this, otherArgs);
};
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (overRest);
/***/ }),
/***/ "./node_modules/lodash-es/_parent.js":
/*!*******************************************!*\
!*** ./node_modules/lodash-es/_parent.js ***!
\*******************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _baseGet_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_baseGet.js */ "./node_modules/lodash-es/_baseGet.js");
/* harmony import */ var _baseSlice_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./_baseSlice.js */ "./node_modules/lodash-es/_baseSlice.js");
/**
* Gets the parent value at `path` of `object`.
*
* @private
* @param {Object} object The object to query.
* @param {Array} path The path to get the parent value of.
* @returns {*} Returns the parent value.
*/
function parent(object, path) {
return path.length < 2 ? object : (0,_baseGet_js__WEBPACK_IMPORTED_MODULE_0__["default"])(object, (0,_baseSlice_js__WEBPACK_IMPORTED_MODULE_1__["default"])(path, 0, -1));
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (parent);
/***/ }),
/***/ "./node_modules/lodash-es/_root.js":
/*!*****************************************!*\
!*** ./node_modules/lodash-es/_root.js ***!
\*****************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _freeGlobal_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_freeGlobal.js */ "./node_modules/lodash-es/_freeGlobal.js");
/** Detect free variable `self`. */
var freeSelf = typeof self == 'object' && self && self.Object === Object && self;
/** Used as a reference to the global object. */
var root = _freeGlobal_js__WEBPACK_IMPORTED_MODULE_0__["default"] || freeSelf || Function('return this')();
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (root);
/***/ }),
/***/ "./node_modules/lodash-es/_safeGet.js":
/*!********************************************!*\
!*** ./node_modules/lodash-es/_safeGet.js ***!
\********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/**
* Gets the value at `key`, unless `key` is "__proto__" or "constructor".
*
* @private
* @param {Object} object The object to query.
* @param {string} key The key of the property to get.
* @returns {*} Returns the property value.
*/
function safeGet(object, key) {
if (key === 'constructor' && typeof object[key] === 'function') {
return;
}
if (key == '__proto__') {
return;
}
return object[key];
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (safeGet);
/***/ }),
/***/ "./node_modules/lodash-es/_setCacheAdd.js":
/*!************************************************!*\
!*** ./node_modules/lodash-es/_setCacheAdd.js ***!
\************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/** Used to stand-in for `undefined` hash values. */
var HASH_UNDEFINED = '__lodash_hash_undefined__';
/**
* Adds `value` to the array cache.
*
* @private
* @name add
* @memberOf SetCache
* @alias push
* @param {*} value The value to cache.
* @returns {Object} Returns the cache instance.
*/
function setCacheAdd(value) {
this.__data__.set(value, HASH_UNDEFINED);
return this;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (setCacheAdd);
/***/ }),
/***/ "./node_modules/lodash-es/_setCacheHas.js":
/*!************************************************!*\
!*** ./node_modules/lodash-es/_setCacheHas.js ***!
\************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/**
* Checks if `value` is in the array cache.
*
* @private
* @name has
* @memberOf SetCache
* @param {*} value The value to search for.
* @returns {number} Returns `true` if `value` is found, else `false`.
*/
function setCacheHas(value) {
return this.__data__.has(value);
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (setCacheHas);
/***/ }),
/***/ "./node_modules/lodash-es/_setToArray.js":
/*!***********************************************!*\
!*** ./node_modules/lodash-es/_setToArray.js ***!
\***********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/**
* Converts `set` to an array of its values.
*
* @private
* @param {Object} set The set to convert.
* @returns {Array} Returns the values.
*/
function setToArray(set) {
var index = -1,
result = Array(set.size);
set.forEach(function(value) {
result[++index] = value;
});
return result;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (setToArray);
/***/ }),
/***/ "./node_modules/lodash-es/_setToString.js":
/*!************************************************!*\
!*** ./node_modules/lodash-es/_setToString.js ***!
\************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _baseSetToString_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./_baseSetToString.js */ "./node_modules/lodash-es/_baseSetToString.js");
/* harmony import */ var _shortOut_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_shortOut.js */ "./node_modules/lodash-es/_shortOut.js");
/**
* Sets the `toString` method of `func` to return `string`.
*
* @private
* @param {Function} func The function to modify.
* @param {Function} string The `toString` result.
* @returns {Function} Returns `func`.
*/
var setToString = (0,_shortOut_js__WEBPACK_IMPORTED_MODULE_0__["default"])(_baseSetToString_js__WEBPACK_IMPORTED_MODULE_1__["default"]);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (setToString);
/***/ }),
/***/ "./node_modules/lodash-es/_shortOut.js":
/*!*********************************************!*\
!*** ./node_modules/lodash-es/_shortOut.js ***!
\*********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/** Used to detect hot functions by number of calls within a span of milliseconds. */
var HOT_COUNT = 800,
HOT_SPAN = 16;
/* Built-in method references for those with the same name as other `lodash` methods. */
var nativeNow = Date.now;
/**
* Creates a function that'll short out and invoke `identity` instead
* of `func` when it's called `HOT_COUNT` or more times in `HOT_SPAN`
* milliseconds.
*
* @private
* @param {Function} func The function to restrict.
* @returns {Function} Returns the new shortable function.
*/
function shortOut(func) {
var count = 0,
lastCalled = 0;
return function() {
var stamp = nativeNow(),
remaining = HOT_SPAN - (stamp - lastCalled);
lastCalled = stamp;
if (remaining > 0) {
if (++count >= HOT_COUNT) {
return arguments[0];
}
} else {
count = 0;
}
return func.apply(undefined, arguments);
};
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (shortOut);
/***/ }),
/***/ "./node_modules/lodash-es/_stackClear.js":
/*!***********************************************!*\
!*** ./node_modules/lodash-es/_stackClear.js ***!
\***********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _ListCache_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_ListCache.js */ "./node_modules/lodash-es/_ListCache.js");
/**
* Removes all key-value entries from the stack.
*
* @private
* @name clear
* @memberOf Stack
*/
function stackClear() {
this.__data__ = new _ListCache_js__WEBPACK_IMPORTED_MODULE_0__["default"];
this.size = 0;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (stackClear);
/***/ }),
/***/ "./node_modules/lodash-es/_stackDelete.js":
/*!************************************************!*\
!*** ./node_modules/lodash-es/_stackDelete.js ***!
\************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/**
* Removes `key` and its value from the stack.
*
* @private
* @name delete
* @memberOf Stack
* @param {string} key The key of the value to remove.
* @returns {boolean} Returns `true` if the entry was removed, else `false`.
*/
function stackDelete(key) {
var data = this.__data__,
result = data['delete'](key);
this.size = data.size;
return result;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (stackDelete);
/***/ }),
/***/ "./node_modules/lodash-es/_stackGet.js":
/*!*********************************************!*\
!*** ./node_modules/lodash-es/_stackGet.js ***!
\*********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/**
* Gets the stack value for `key`.
*
* @private
* @name get
* @memberOf Stack
* @param {string} key The key of the value to get.
* @returns {*} Returns the entry value.
*/
function stackGet(key) {
return this.__data__.get(key);
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (stackGet);
/***/ }),
/***/ "./node_modules/lodash-es/_stackHas.js":
/*!*********************************************!*\
!*** ./node_modules/lodash-es/_stackHas.js ***!
\*********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/**
* Checks if a stack value for `key` exists.
*
* @private
* @name has
* @memberOf Stack
* @param {string} key The key of the entry to check.
* @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
*/
function stackHas(key) {
return this.__data__.has(key);
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (stackHas);
/***/ }),
/***/ "./node_modules/lodash-es/_stackSet.js":
/*!*********************************************!*\
!*** ./node_modules/lodash-es/_stackSet.js ***!
\*********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _ListCache_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_ListCache.js */ "./node_modules/lodash-es/_ListCache.js");
/* harmony import */ var _Map_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./_Map.js */ "./node_modules/lodash-es/_Map.js");
/* harmony import */ var _MapCache_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./_MapCache.js */ "./node_modules/lodash-es/_MapCache.js");
/** Used as the size to enable large array optimizations. */
var LARGE_ARRAY_SIZE = 200;
/**
* Sets the stack `key` to `value`.
*
* @private
* @name set
* @memberOf Stack
* @param {string} key The key of the value to set.
* @param {*} value The value to set.
* @returns {Object} Returns the stack cache instance.
*/
function stackSet(key, value) {
var data = this.__data__;
if (data instanceof _ListCache_js__WEBPACK_IMPORTED_MODULE_0__["default"]) {
var pairs = data.__data__;
if (!_Map_js__WEBPACK_IMPORTED_MODULE_1__["default"] || (pairs.length < LARGE_ARRAY_SIZE - 1)) {
pairs.push([key, value]);
this.size = ++data.size;
return this;
}
data = this.__data__ = new _MapCache_js__WEBPACK_IMPORTED_MODULE_2__["default"](pairs);
}
data.set(key, value);
this.size = data.size;
return this;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (stackSet);
/***/ }),
/***/ "./node_modules/lodash-es/_strictIndexOf.js":
/*!**************************************************!*\
!*** ./node_modules/lodash-es/_strictIndexOf.js ***!
\**************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/**
* A specialized version of `_.indexOf` which performs strict equality
* comparisons of values, i.e. `===`.
*
* @private
* @param {Array} array The array to inspect.
* @param {*} value The value to search for.
* @param {number} fromIndex The index to search from.
* @returns {number} Returns the index of the matched value, else `-1`.
*/
function strictIndexOf(array, value, fromIndex) {
var index = fromIndex - 1,
length = array.length;
while (++index < length) {
if (array[index] === value) {
return index;
}
}
return -1;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (strictIndexOf);
/***/ }),
/***/ "./node_modules/lodash-es/_stringToArray.js":
/*!**************************************************!*\
!*** ./node_modules/lodash-es/_stringToArray.js ***!
\**************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _asciiToArray_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./_asciiToArray.js */ "./node_modules/lodash-es/_asciiToArray.js");
/* harmony import */ var _hasUnicode_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_hasUnicode.js */ "./node_modules/lodash-es/_hasUnicode.js");
/* harmony import */ var _unicodeToArray_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./_unicodeToArray.js */ "./node_modules/lodash-es/_unicodeToArray.js");
/**
* Converts `string` to an array.
*
* @private
* @param {string} string The string to convert.
* @returns {Array} Returns the converted array.
*/
function stringToArray(string) {
return (0,_hasUnicode_js__WEBPACK_IMPORTED_MODULE_0__["default"])(string)
? (0,_unicodeToArray_js__WEBPACK_IMPORTED_MODULE_1__["default"])(string)
: (0,_asciiToArray_js__WEBPACK_IMPORTED_MODULE_2__["default"])(string);
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (stringToArray);
/***/ }),
/***/ "./node_modules/lodash-es/_stringToPath.js":
/*!*************************************************!*\
!*** ./node_modules/lodash-es/_stringToPath.js ***!
\*************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _memoizeCapped_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_memoizeCapped.js */ "./node_modules/lodash-es/_memoizeCapped.js");
/** Used to match property names within property paths. */
var rePropName = /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g;
/** Used to match backslashes in property paths. */
var reEscapeChar = /\\(\\)?/g;
/**
* Converts `string` to a property path array.
*
* @private
* @param {string} string The string to convert.
* @returns {Array} Returns the property path array.
*/
var stringToPath = (0,_memoizeCapped_js__WEBPACK_IMPORTED_MODULE_0__["default"])(function(string) {
var result = [];
if (string.charCodeAt(0) === 46 /* . */) {
result.push('');
}
string.replace(rePropName, function(match, number, quote, subString) {
result.push(quote ? subString.replace(reEscapeChar, '$1') : (number || match));
});
return result;
});
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (stringToPath);
/***/ }),
/***/ "./node_modules/lodash-es/_toKey.js":
/*!******************************************!*\
!*** ./node_modules/lodash-es/_toKey.js ***!
\******************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _isSymbol_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./isSymbol.js */ "./node_modules/lodash-es/isSymbol.js");
/** Used as references for various `Number` constants. */
var INFINITY = 1 / 0;
/**
* Converts `value` to a string key if it's not a string or symbol.
*
* @private
* @param {*} value The value to inspect.
* @returns {string|symbol} Returns the key.
*/
function toKey(value) {
if (typeof value == 'string' || (0,_isSymbol_js__WEBPACK_IMPORTED_MODULE_0__["default"])(value)) {
return value;
}
var result = (value + '');
return (result == '0' && (1 / value) == -INFINITY) ? '-0' : result;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (toKey);
/***/ }),
/***/ "./node_modules/lodash-es/_toSource.js":
/*!*********************************************!*\
!*** ./node_modules/lodash-es/_toSource.js ***!
\*********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/** Used for built-in method references. */
var funcProto = Function.prototype;
/** Used to resolve the decompiled source of functions. */
var funcToString = funcProto.toString;
/**
* Converts `func` to its source code.
*
* @private
* @param {Function} func The function to convert.
* @returns {string} Returns the source code.
*/
function toSource(func) {
if (func != null) {
try {
return funcToString.call(func);
} catch (e) {}
try {
return (func + '');
} catch (e) {}
}
return '';
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (toSource);
/***/ }),
/***/ "./node_modules/lodash-es/_trimmedEndIndex.js":
/*!****************************************************!*\
!*** ./node_modules/lodash-es/_trimmedEndIndex.js ***!
\****************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/** Used to match a single whitespace character. */
var reWhitespace = /\s/;
/**
* Used by `_.trim` and `_.trimEnd` to get the index of the last non-whitespace
* character of `string`.
*
* @private
* @param {string} string The string to inspect.
* @returns {number} Returns the index of the last non-whitespace character.
*/
function trimmedEndIndex(string) {
var index = string.length;
while (index-- && reWhitespace.test(string.charAt(index))) {}
return index;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (trimmedEndIndex);
/***/ }),
/***/ "./node_modules/lodash-es/_unicodeToArray.js":
/*!***************************************************!*\
!*** ./node_modules/lodash-es/_unicodeToArray.js ***!
\***************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/** Used to compose unicode character classes. */
var rsAstralRange = '\\ud800-\\udfff',
rsComboMarksRange = '\\u0300-\\u036f',
reComboHalfMarksRange = '\\ufe20-\\ufe2f',
rsComboSymbolsRange = '\\u20d0-\\u20ff',
rsComboRange = rsComboMarksRange + reComboHalfMarksRange + rsComboSymbolsRange,
rsVarRange = '\\ufe0e\\ufe0f';
/** Used to compose unicode capture groups. */
var rsAstral = '[' + rsAstralRange + ']',
rsCombo = '[' + rsComboRange + ']',
rsFitz = '\\ud83c[\\udffb-\\udfff]',
rsModifier = '(?:' + rsCombo + '|' + rsFitz + ')',
rsNonAstral = '[^' + rsAstralRange + ']',
rsRegional = '(?:\\ud83c[\\udde6-\\uddff]){2}',
rsSurrPair = '[\\ud800-\\udbff][\\udc00-\\udfff]',
rsZWJ = '\\u200d';
/** Used to compose unicode regexes. */
var reOptMod = rsModifier + '?',
rsOptVar = '[' + rsVarRange + ']?',
rsOptJoin = '(?:' + rsZWJ + '(?:' + [rsNonAstral, rsRegional, rsSurrPair].join('|') + ')' + rsOptVar + reOptMod + ')*',
rsSeq = rsOptVar + reOptMod + rsOptJoin,
rsSymbol = '(?:' + [rsNonAstral + rsCombo + '?', rsCombo, rsRegional, rsSurrPair, rsAstral].join('|') + ')';
/** Used to match [string symbols](https://mathiasbynens.be/notes/javascript-unicode). */
var reUnicode = RegExp(rsFitz + '(?=' + rsFitz + ')|' + rsSymbol + rsSeq, 'g');
/**
* Converts a Unicode `string` to an array.
*
* @private
* @param {string} string The string to convert.
* @returns {Array} Returns the converted array.
*/
function unicodeToArray(string) {
return string.match(reUnicode) || [];
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (unicodeToArray);
/***/ }),
/***/ "./node_modules/lodash-es/assignIn.js":
/*!********************************************!*\
!*** ./node_modules/lodash-es/assignIn.js ***!
\********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _copyObject_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./_copyObject.js */ "./node_modules/lodash-es/_copyObject.js");
/* harmony import */ var _createAssigner_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_createAssigner.js */ "./node_modules/lodash-es/_createAssigner.js");
/* harmony import */ var _keysIn_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./keysIn.js */ "./node_modules/lodash-es/keysIn.js");
/**
* This method is like `_.assign` except that it iterates over own and
* inherited source properties.
*
* **Note:** This method mutates `object`.
*
* @static
* @memberOf _
* @since 4.0.0
* @alias extend
* @category Object
* @param {Object} object The destination object.
* @param {...Object} [sources] The source objects.
* @returns {Object} Returns `object`.
* @see _.assign
* @example
*
* function Foo() {
* this.a = 1;
* }
*
* function Bar() {
* this.c = 3;
* }
*
* Foo.prototype.b = 2;
* Bar.prototype.d = 4;
*
* _.assignIn({ 'a': 0 }, new Foo, new Bar);
* // => { 'a': 1, 'b': 2, 'c': 3, 'd': 4 }
*/
var assignIn = (0,_createAssigner_js__WEBPACK_IMPORTED_MODULE_0__["default"])(function(object, source) {
(0,_copyObject_js__WEBPACK_IMPORTED_MODULE_1__["default"])(source, (0,_keysIn_js__WEBPACK_IMPORTED_MODULE_2__["default"])(source), object);
});
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (assignIn);
/***/ }),
/***/ "./node_modules/lodash-es/clone.js":
/*!*****************************************!*\
!*** ./node_modules/lodash-es/clone.js ***!
\*****************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _baseClone_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_baseClone.js */ "./node_modules/lodash-es/_baseClone.js");
/** Used to compose bitmasks for cloning. */
var CLONE_SYMBOLS_FLAG = 4;
/**
* Creates a shallow clone of `value`.
*
* **Note:** This method is loosely based on the
* [structured clone algorithm](https://mdn.io/Structured_clone_algorithm)
* and supports cloning arrays, array buffers, booleans, date objects, maps,
* numbers, `Object` objects, regexes, sets, strings, symbols, and typed
* arrays. The own enumerable properties of `arguments` objects are cloned
* as plain objects. An empty object is returned for uncloneable values such
* as error objects, functions, DOM nodes, and WeakMaps.
*
* @static
* @memberOf _
* @since 0.1.0
* @category Lang
* @param {*} value The value to clone.
* @returns {*} Returns the cloned value.
* @see _.cloneDeep
* @example
*
* var objects = [{ 'a': 1 }, { 'b': 2 }];
*
* var shallow = _.clone(objects);
* console.log(shallow[0] === objects[0]);
* // => true
*/
function clone(value) {
return (0,_baseClone_js__WEBPACK_IMPORTED_MODULE_0__["default"])(value, CLONE_SYMBOLS_FLAG);
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (clone);
/***/ }),
/***/ "./node_modules/lodash-es/cloneDeep.js":
/*!*********************************************!*\
!*** ./node_modules/lodash-es/cloneDeep.js ***!
\*********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _baseClone_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_baseClone.js */ "./node_modules/lodash-es/_baseClone.js");
/** Used to compose bitmasks for cloning. */
var CLONE_DEEP_FLAG = 1,
CLONE_SYMBOLS_FLAG = 4;
/**
* This method is like `_.clone` except that it recursively clones `value`.
*
* @static
* @memberOf _
* @since 1.0.0
* @category Lang
* @param {*} value The value to recursively clone.
* @returns {*} Returns the deep cloned value.
* @see _.clone
* @example
*
* var objects = [{ 'a': 1 }, { 'b': 2 }];
*
* var deep = _.cloneDeep(objects);
* console.log(deep[0] === objects[0]);
* // => false
*/
function cloneDeep(value) {
return (0,_baseClone_js__WEBPACK_IMPORTED_MODULE_0__["default"])(value, CLONE_DEEP_FLAG | CLONE_SYMBOLS_FLAG);
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (cloneDeep);
/***/ }),
/***/ "./node_modules/lodash-es/cloneDeepWith.js":
/*!*************************************************!*\
!*** ./node_modules/lodash-es/cloneDeepWith.js ***!
\*************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _baseClone_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_baseClone.js */ "./node_modules/lodash-es/_baseClone.js");
/** Used to compose bitmasks for cloning. */
var CLONE_DEEP_FLAG = 1,
CLONE_SYMBOLS_FLAG = 4;
/**
* This method is like `_.cloneWith` except that it recursively clones `value`.
*
* @static
* @memberOf _
* @since 4.0.0
* @category Lang
* @param {*} value The value to recursively clone.
* @param {Function} [customizer] The function to customize cloning.
* @returns {*} Returns the deep cloned value.
* @see _.cloneWith
* @example
*
* function customizer(value) {
* if (_.isElement(value)) {
* return value.cloneNode(true);
* }
* }
*
* var el = _.cloneDeepWith(document.body, customizer);
*
* console.log(el === document.body);
* // => false
* console.log(el.nodeName);
* // => 'BODY'
* console.log(el.childNodes.length);
* // => 20
*/
function cloneDeepWith(value, customizer) {
customizer = typeof customizer == 'function' ? customizer : undefined;
return (0,_baseClone_js__WEBPACK_IMPORTED_MODULE_0__["default"])(value, CLONE_DEEP_FLAG | CLONE_SYMBOLS_FLAG, customizer);
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (cloneDeepWith);
/***/ }),
/***/ "./node_modules/lodash-es/constant.js":
/*!********************************************!*\
!*** ./node_modules/lodash-es/constant.js ***!
\********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/**
* Creates a function that returns `value`.
*
* @static
* @memberOf _
* @since 2.4.0
* @category Util
* @param {*} value The value to return from the new function.
* @returns {Function} Returns the new constant function.
* @example
*
* var objects = _.times(2, _.constant({ 'a': 1 }));
*
* console.log(objects);
* // => [{ 'a': 1 }, { 'a': 1 }]
*
* console.log(objects[0] === objects[1]);
* // => true
*/
function constant(value) {
return function() {
return value;
};
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (constant);
/***/ }),
/***/ "./node_modules/lodash-es/debounce.js":
/*!********************************************!*\
!*** ./node_modules/lodash-es/debounce.js ***!
\********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _isObject_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./isObject.js */ "./node_modules/lodash-es/isObject.js");
/* harmony import */ var _now_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./now.js */ "./node_modules/lodash-es/now.js");
/* harmony import */ var _toNumber_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./toNumber.js */ "./node_modules/lodash-es/toNumber.js");
/** Error message constants. */
var FUNC_ERROR_TEXT = 'Expected a function';
/* Built-in method references for those with the same name as other `lodash` methods. */
var nativeMax = Math.max,
nativeMin = Math.min;
/**
* Creates a debounced function that delays invoking `func` until after `wait`
* milliseconds have elapsed since the last time the debounced function was
* invoked. The debounced function comes with a `cancel` method to cancel
* delayed `func` invocations and a `flush` method to immediately invoke them.
* Provide `options` to indicate whether `func` should be invoked on the
* leading and/or trailing edge of the `wait` timeout. The `func` is invoked
* with the last arguments provided to the debounced function. Subsequent
* calls to the debounced function return the result of the last `func`
* invocation.
*
* **Note:** If `leading` and `trailing` options are `true`, `func` is
* invoked on the trailing edge of the timeout only if the debounced function
* is invoked more than once during the `wait` timeout.
*
* If `wait` is `0` and `leading` is `false`, `func` invocation is deferred
* until to the next tick, similar to `setTimeout` with a timeout of `0`.
*
* See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/)
* for details over the differences between `_.debounce` and `_.throttle`.
*
* @static
* @memberOf _
* @since 0.1.0
* @category Function
* @param {Function} func The function to debounce.
* @param {number} [wait=0] The number of milliseconds to delay.
* @param {Object} [options={}] The options object.
* @param {boolean} [options.leading=false]
* Specify invoking on the leading edge of the timeout.
* @param {number} [options.maxWait]
* The maximum time `func` is allowed to be delayed before it's invoked.
* @param {boolean} [options.trailing=true]
* Specify invoking on the trailing edge of the timeout.
* @returns {Function} Returns the new debounced function.
* @example
*
* // Avoid costly calculations while the window size is in flux.
* jQuery(window).on('resize', _.debounce(calculateLayout, 150));
*
* // Invoke `sendMail` when clicked, debouncing subsequent calls.
* jQuery(element).on('click', _.debounce(sendMail, 300, {
* 'leading': true,
* 'trailing': false
* }));
*
* // Ensure `batchLog` is invoked once after 1 second of debounced calls.
* var debounced = _.debounce(batchLog, 250, { 'maxWait': 1000 });
* var source = new EventSource('/stream');
* jQuery(source).on('message', debounced);
*
* // Cancel the trailing debounced invocation.
* jQuery(window).on('popstate', debounced.cancel);
*/
function debounce(func, wait, options) {
var lastArgs,
lastThis,
maxWait,
result,
timerId,
lastCallTime,
lastInvokeTime = 0,
leading = false,
maxing = false,
trailing = true;
if (typeof func != 'function') {
throw new TypeError(FUNC_ERROR_TEXT);
}
wait = (0,_toNumber_js__WEBPACK_IMPORTED_MODULE_0__["default"])(wait) || 0;
if ((0,_isObject_js__WEBPACK_IMPORTED_MODULE_1__["default"])(options)) {
leading = !!options.leading;
maxing = 'maxWait' in options;
maxWait = maxing ? nativeMax((0,_toNumber_js__WEBPACK_IMPORTED_MODULE_0__["default"])(options.maxWait) || 0, wait) : maxWait;
trailing = 'trailing' in options ? !!options.trailing : trailing;
}
function invokeFunc(time) {
var args = lastArgs,
thisArg = lastThis;
lastArgs = lastThis = undefined;
lastInvokeTime = time;
result = func.apply(thisArg, args);
return result;
}
function leadingEdge(time) {
// Reset any `maxWait` timer.
lastInvokeTime = time;
// Start the timer for the trailing edge.
timerId = setTimeout(timerExpired, wait);
// Invoke the leading edge.
return leading ? invokeFunc(time) : result;
}
function remainingWait(time) {
var timeSinceLastCall = time - lastCallTime,
timeSinceLastInvoke = time - lastInvokeTime,
timeWaiting = wait - timeSinceLastCall;
return maxing
? nativeMin(timeWaiting, maxWait - timeSinceLastInvoke)
: timeWaiting;
}
function shouldInvoke(time) {
var timeSinceLastCall = time - lastCallTime,
timeSinceLastInvoke = time - lastInvokeTime;
// Either this is the first call, activity has stopped and we're at the
// trailing edge, the system time has gone backwards and we're treating
// it as the trailing edge, or we've hit the `maxWait` limit.
return (lastCallTime === undefined || (timeSinceLastCall >= wait) ||
(timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait));
}
function timerExpired() {
var time = (0,_now_js__WEBPACK_IMPORTED_MODULE_2__["default"])();
if (shouldInvoke(time)) {
return trailingEdge(time);
}
// Restart the timer.
timerId = setTimeout(timerExpired, remainingWait(time));
}
function trailingEdge(time) {
timerId = undefined;
// Only invoke if we have `lastArgs` which means `func` has been
// debounced at least once.
if (trailing && lastArgs) {
return invokeFunc(time);
}
lastArgs = lastThis = undefined;
return result;
}
function cancel() {
if (timerId !== undefined) {
clearTimeout(timerId);
}
lastInvokeTime = 0;
lastArgs = lastCallTime = lastThis = timerId = undefined;
}
function flush() {
return timerId === undefined ? result : trailingEdge((0,_now_js__WEBPACK_IMPORTED_MODULE_2__["default"])());
}
function debounced() {
var time = (0,_now_js__WEBPACK_IMPORTED_MODULE_2__["default"])(),
isInvoking = shouldInvoke(time);
lastArgs = arguments;
lastThis = this;
lastCallTime = time;
if (isInvoking) {
if (timerId === undefined) {
return leadingEdge(lastCallTime);
}
if (maxing) {
// Handle invocations in a tight loop.
clearTimeout(timerId);
timerId = setTimeout(timerExpired, wait);
return invokeFunc(lastCallTime);
}
}
if (timerId === undefined) {
timerId = setTimeout(timerExpired, wait);
}
return result;
}
debounced.cancel = cancel;
debounced.flush = flush;
return debounced;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (debounce);
/***/ }),
/***/ "./node_modules/lodash-es/eq.js":
/*!**************************************!*\
!*** ./node_modules/lodash-es/eq.js ***!
\**************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/**
* Performs a
* [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
* comparison between two values to determine if they are equivalent.
*
* @static
* @memberOf _
* @since 4.0.0
* @category Lang
* @param {*} value The value to compare.
* @param {*} other The other value to compare.
* @returns {boolean} Returns `true` if the values are equivalent, else `false`.
* @example
*
* var object = { 'a': 1 };
* var other = { 'a': 1 };
*
* _.eq(object, object);
* // => true
*
* _.eq(object, other);
* // => false
*
* _.eq('a', 'a');
* // => true
*
* _.eq('a', Object('a'));
* // => false
*
* _.eq(NaN, NaN);
* // => true
*/
function eq(value, other) {
return value === other || (value !== value && other !== other);
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (eq);
/***/ }),
/***/ "./node_modules/lodash-es/escapeRegExp.js":
/*!************************************************!*\
!*** ./node_modules/lodash-es/escapeRegExp.js ***!
\************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _toString_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./toString.js */ "./node_modules/lodash-es/toString.js");
/**
* Used to match `RegExp`
* [syntax characters](http://ecma-international.org/ecma-262/7.0/#sec-patterns).
*/
var reRegExpChar = /[\\^$.*+?()[\]{}|]/g,
reHasRegExpChar = RegExp(reRegExpChar.source);
/**
* Escapes the `RegExp` special characters "^", "$", "\", ".", "*", "+",
* "?", "(", ")", "[", "]", "{", "}", and "|" in `string`.
*
* @static
* @memberOf _
* @since 3.0.0
* @category String
* @param {string} [string=''] The string to escape.
* @returns {string} Returns the escaped string.
* @example
*
* _.escapeRegExp('[lodash](https://lodash.com/)');
* // => '\[lodash\]\(https://lodash\.com/\)'
*/
function escapeRegExp(string) {
string = (0,_toString_js__WEBPACK_IMPORTED_MODULE_0__["default"])(string);
return (string && reHasRegExpChar.test(string))
? string.replace(reRegExpChar, '\\$&')
: string;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (escapeRegExp);
/***/ }),
/***/ "./node_modules/lodash-es/get.js":
/*!***************************************!*\
!*** ./node_modules/lodash-es/get.js ***!
\***************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _baseGet_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_baseGet.js */ "./node_modules/lodash-es/_baseGet.js");
/**
* Gets the value at `path` of `object`. If the resolved value is
* `undefined`, the `defaultValue` is returned in its place.
*
* @static
* @memberOf _
* @since 3.7.0
* @category Object
* @param {Object} object The object to query.
* @param {Array|string} path The path of the property to get.
* @param {*} [defaultValue] The value returned for `undefined` resolved values.
* @returns {*} Returns the resolved value.
* @example
*
* var object = { 'a': [{ 'b': { 'c': 3 } }] };
*
* _.get(object, 'a[0].b.c');
* // => 3
*
* _.get(object, ['a', '0', 'b', 'c']);
* // => 3
*
* _.get(object, 'a.b.c', 'default');
* // => 'default'
*/
function get(object, path, defaultValue) {
var result = object == null ? undefined : (0,_baseGet_js__WEBPACK_IMPORTED_MODULE_0__["default"])(object, path);
return result === undefined ? defaultValue : result;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (get);
/***/ }),
/***/ "./node_modules/lodash-es/identity.js":
/*!********************************************!*\
!*** ./node_modules/lodash-es/identity.js ***!
\********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/**
* This method returns the first argument it receives.
*
* @static
* @since 0.1.0
* @memberOf _
* @category Util
* @param {*} value Any value.
* @returns {*} Returns `value`.
* @example
*
* var object = { 'a': 1 };
*
* console.log(_.identity(object) === object);
* // => true
*/
function identity(value) {
return value;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (identity);
/***/ }),
/***/ "./node_modules/lodash-es/isArguments.js":
/*!***********************************************!*\
!*** ./node_modules/lodash-es/isArguments.js ***!
\***********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _baseIsArguments_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_baseIsArguments.js */ "./node_modules/lodash-es/_baseIsArguments.js");
/* harmony import */ var _isObjectLike_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./isObjectLike.js */ "./node_modules/lodash-es/isObjectLike.js");
/** Used for built-in method references. */
var objectProto = Object.prototype;
/** Used to check objects for own properties. */
var hasOwnProperty = objectProto.hasOwnProperty;
/** Built-in value references. */
var propertyIsEnumerable = objectProto.propertyIsEnumerable;
/**
* Checks if `value` is likely an `arguments` object.
*
* @static
* @memberOf _
* @since 0.1.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is an `arguments` object,
* else `false`.
* @example
*
* _.isArguments(function() { return arguments; }());
* // => true
*
* _.isArguments([1, 2, 3]);
* // => false
*/
var isArguments = (0,_baseIsArguments_js__WEBPACK_IMPORTED_MODULE_0__["default"])(function() { return arguments; }()) ? _baseIsArguments_js__WEBPACK_IMPORTED_MODULE_0__["default"] : function(value) {
return (0,_isObjectLike_js__WEBPACK_IMPORTED_MODULE_1__["default"])(value) && hasOwnProperty.call(value, 'callee') &&
!propertyIsEnumerable.call(value, 'callee');
};
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (isArguments);
/***/ }),
/***/ "./node_modules/lodash-es/isArray.js":
/*!*******************************************!*\
!*** ./node_modules/lodash-es/isArray.js ***!
\*******************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/**
* Checks if `value` is classified as an `Array` object.
*
* @static
* @memberOf _
* @since 0.1.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is an array, else `false`.
* @example
*
* _.isArray([1, 2, 3]);
* // => true
*
* _.isArray(document.body.children);
* // => false
*
* _.isArray('abc');
* // => false
*
* _.isArray(_.noop);
* // => false
*/
var isArray = Array.isArray;
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (isArray);
/***/ }),
/***/ "./node_modules/lodash-es/isArrayLike.js":
/*!***********************************************!*\
!*** ./node_modules/lodash-es/isArrayLike.js ***!
\***********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _isFunction_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./isFunction.js */ "./node_modules/lodash-es/isFunction.js");
/* harmony import */ var _isLength_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./isLength.js */ "./node_modules/lodash-es/isLength.js");
/**
* Checks if `value` is array-like. A value is considered array-like if it's
* not a function and has a `value.length` that's an integer greater than or
* equal to `0` and less than or equal to `Number.MAX_SAFE_INTEGER`.
*
* @static
* @memberOf _
* @since 4.0.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is array-like, else `false`.
* @example
*
* _.isArrayLike([1, 2, 3]);
* // => true
*
* _.isArrayLike(document.body.children);
* // => true
*
* _.isArrayLike('abc');
* // => true
*
* _.isArrayLike(_.noop);
* // => false
*/
function isArrayLike(value) {
return value != null && (0,_isLength_js__WEBPACK_IMPORTED_MODULE_0__["default"])(value.length) && !(0,_isFunction_js__WEBPACK_IMPORTED_MODULE_1__["default"])(value);
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (isArrayLike);
/***/ }),
/***/ "./node_modules/lodash-es/isArrayLikeObject.js":
/*!*****************************************************!*\
!*** ./node_modules/lodash-es/isArrayLikeObject.js ***!
\*****************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _isArrayLike_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./isArrayLike.js */ "./node_modules/lodash-es/isArrayLike.js");
/* harmony import */ var _isObjectLike_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./isObjectLike.js */ "./node_modules/lodash-es/isObjectLike.js");
/**
* This method is like `_.isArrayLike` except that it also checks if `value`
* is an object.
*
* @static
* @memberOf _
* @since 4.0.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is an array-like object,
* else `false`.
* @example
*
* _.isArrayLikeObject([1, 2, 3]);
* // => true
*
* _.isArrayLikeObject(document.body.children);
* // => true
*
* _.isArrayLikeObject('abc');
* // => false
*
* _.isArrayLikeObject(_.noop);
* // => false
*/
function isArrayLikeObject(value) {
return (0,_isObjectLike_js__WEBPACK_IMPORTED_MODULE_0__["default"])(value) && (0,_isArrayLike_js__WEBPACK_IMPORTED_MODULE_1__["default"])(value);
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (isArrayLikeObject);
/***/ }),
/***/ "./node_modules/lodash-es/isBuffer.js":
/*!********************************************!*\
!*** ./node_modules/lodash-es/isBuffer.js ***!
\********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _root_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_root.js */ "./node_modules/lodash-es/_root.js");
/* harmony import */ var _stubFalse_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./stubFalse.js */ "./node_modules/lodash-es/stubFalse.js");
/** Detect free variable `exports`. */
var freeExports = typeof exports == 'object' && exports && !exports.nodeType && exports;
/** Detect free variable `module`. */
var freeModule = freeExports && typeof module == 'object' && module && !module.nodeType && module;
/** Detect the popular CommonJS extension `module.exports`. */
var moduleExports = freeModule && freeModule.exports === freeExports;
/** Built-in value references. */
var Buffer = moduleExports ? _root_js__WEBPACK_IMPORTED_MODULE_0__["default"].Buffer : undefined;
/* Built-in method references for those with the same name as other `lodash` methods. */
var nativeIsBuffer = Buffer ? Buffer.isBuffer : undefined;
/**
* Checks if `value` is a buffer.
*
* @static
* @memberOf _
* @since 4.3.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a buffer, else `false`.
* @example
*
* _.isBuffer(new Buffer(2));
* // => true
*
* _.isBuffer(new Uint8Array(2));
* // => false
*/
var isBuffer = nativeIsBuffer || _stubFalse_js__WEBPACK_IMPORTED_MODULE_1__["default"];
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (isBuffer);
/***/ }),
/***/ "./node_modules/lodash-es/isElement.js":
/*!*********************************************!*\
!*** ./node_modules/lodash-es/isElement.js ***!
\*********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _isObjectLike_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./isObjectLike.js */ "./node_modules/lodash-es/isObjectLike.js");
/* harmony import */ var _isPlainObject_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./isPlainObject.js */ "./node_modules/lodash-es/isPlainObject.js");
/**
* Checks if `value` is likely a DOM element.
*
* @static
* @memberOf _
* @since 0.1.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a DOM element, else `false`.
* @example
*
* _.isElement(document.body);
* // => true
*
* _.isElement('<body>');
* // => false
*/
function isElement(value) {
return (0,_isObjectLike_js__WEBPACK_IMPORTED_MODULE_0__["default"])(value) && value.nodeType === 1 && !(0,_isPlainObject_js__WEBPACK_IMPORTED_MODULE_1__["default"])(value);
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (isElement);
/***/ }),
/***/ "./node_modules/lodash-es/isEqual.js":
/*!*******************************************!*\
!*** ./node_modules/lodash-es/isEqual.js ***!
\*******************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _baseIsEqual_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_baseIsEqual.js */ "./node_modules/lodash-es/_baseIsEqual.js");
/**
* Performs a deep comparison between two values to determine if they are
* equivalent.
*
* **Note:** This method supports comparing arrays, array buffers, booleans,
* date objects, error objects, maps, numbers, `Object` objects, regexes,
* sets, strings, symbols, and typed arrays. `Object` objects are compared
* by their own, not inherited, enumerable properties. Functions and DOM
* nodes are compared by strict equality, i.e. `===`.
*
* @static
* @memberOf _
* @since 0.1.0
* @category Lang
* @param {*} value The value to compare.
* @param {*} other The other value to compare.
* @returns {boolean} Returns `true` if the values are equivalent, else `false`.
* @example
*
* var object = { 'a': 1 };
* var other = { 'a': 1 };
*
* _.isEqual(object, other);
* // => true
*
* object === other;
* // => false
*/
function isEqual(value, other) {
return (0,_baseIsEqual_js__WEBPACK_IMPORTED_MODULE_0__["default"])(value, other);
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (isEqual);
/***/ }),
/***/ "./node_modules/lodash-es/isEqualWith.js":
/*!***********************************************!*\
!*** ./node_modules/lodash-es/isEqualWith.js ***!
\***********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _baseIsEqual_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_baseIsEqual.js */ "./node_modules/lodash-es/_baseIsEqual.js");
/**
* This method is like `_.isEqual` except that it accepts `customizer` which
* is invoked to compare values. If `customizer` returns `undefined`, comparisons
* are handled by the method instead. The `customizer` is invoked with up to
* six arguments: (objValue, othValue [, index|key, object, other, stack]).
*
* @static
* @memberOf _
* @since 4.0.0
* @category Lang
* @param {*} value The value to compare.
* @param {*} other The other value to compare.
* @param {Function} [customizer] The function to customize comparisons.
* @returns {boolean} Returns `true` if the values are equivalent, else `false`.
* @example
*
* function isGreeting(value) {
* return /^h(?:i|ello)$/.test(value);
* }
*
* function customizer(objValue, othValue) {
* if (isGreeting(objValue) && isGreeting(othValue)) {
* return true;
* }
* }
*
* var array = ['hello', 'goodbye'];
* var other = ['hi', 'goodbye'];
*
* _.isEqualWith(array, other, customizer);
* // => true
*/
function isEqualWith(value, other, customizer) {
customizer = typeof customizer == 'function' ? customizer : undefined;
var result = customizer ? customizer(value, other) : undefined;
return result === undefined ? (0,_baseIsEqual_js__WEBPACK_IMPORTED_MODULE_0__["default"])(value, other, undefined, customizer) : !!result;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (isEqualWith);
/***/ }),
/***/ "./node_modules/lodash-es/isFunction.js":
/*!**********************************************!*\
!*** ./node_modules/lodash-es/isFunction.js ***!
\**********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _baseGetTag_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./_baseGetTag.js */ "./node_modules/lodash-es/_baseGetTag.js");
/* harmony import */ var _isObject_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./isObject.js */ "./node_modules/lodash-es/isObject.js");
/** `Object#toString` result references. */
var asyncTag = '[object AsyncFunction]',
funcTag = '[object Function]',
genTag = '[object GeneratorFunction]',
proxyTag = '[object Proxy]';
/**
* Checks if `value` is classified as a `Function` object.
*
* @static
* @memberOf _
* @since 0.1.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a function, else `false`.
* @example
*
* _.isFunction(_);
* // => true
*
* _.isFunction(/abc/);
* // => false
*/
function isFunction(value) {
if (!(0,_isObject_js__WEBPACK_IMPORTED_MODULE_0__["default"])(value)) {
return false;
}
// The use of `Object#toString` avoids issues with the `typeof` operator
// in Safari 9 which returns 'object' for typed arrays and other constructors.
var tag = (0,_baseGetTag_js__WEBPACK_IMPORTED_MODULE_1__["default"])(value);
return tag == funcTag || tag == genTag || tag == asyncTag || tag == proxyTag;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (isFunction);
/***/ }),
/***/ "./node_modules/lodash-es/isLength.js":
/*!********************************************!*\
!*** ./node_modules/lodash-es/isLength.js ***!
\********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/** Used as references for various `Number` constants. */
var MAX_SAFE_INTEGER = 9007199254740991;
/**
* Checks if `value` is a valid array-like length.
*
* **Note:** This method is loosely based on
* [`ToLength`](http://ecma-international.org/ecma-262/7.0/#sec-tolength).
*
* @static
* @memberOf _
* @since 4.0.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a valid length, else `false`.
* @example
*
* _.isLength(3);
* // => true
*
* _.isLength(Number.MIN_VALUE);
* // => false
*
* _.isLength(Infinity);
* // => false
*
* _.isLength('3');
* // => false
*/
function isLength(value) {
return typeof value == 'number' &&
value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (isLength);
/***/ }),
/***/ "./node_modules/lodash-es/isMap.js":
/*!*****************************************!*\
!*** ./node_modules/lodash-es/isMap.js ***!
\*****************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _baseIsMap_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./_baseIsMap.js */ "./node_modules/lodash-es/_baseIsMap.js");
/* harmony import */ var _baseUnary_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./_baseUnary.js */ "./node_modules/lodash-es/_baseUnary.js");
/* harmony import */ var _nodeUtil_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_nodeUtil.js */ "./node_modules/lodash-es/_nodeUtil.js");
/* Node.js helper references. */
var nodeIsMap = _nodeUtil_js__WEBPACK_IMPORTED_MODULE_0__["default"] && _nodeUtil_js__WEBPACK_IMPORTED_MODULE_0__["default"].isMap;
/**
* Checks if `value` is classified as a `Map` object.
*
* @static
* @memberOf _
* @since 4.3.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a map, else `false`.
* @example
*
* _.isMap(new Map);
* // => true
*
* _.isMap(new WeakMap);
* // => false
*/
var isMap = nodeIsMap ? (0,_baseUnary_js__WEBPACK_IMPORTED_MODULE_1__["default"])(nodeIsMap) : _baseIsMap_js__WEBPACK_IMPORTED_MODULE_2__["default"];
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (isMap);
/***/ }),
/***/ "./node_modules/lodash-es/isObject.js":
/*!********************************************!*\
!*** ./node_modules/lodash-es/isObject.js ***!
\********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/**
* Checks if `value` is the
* [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types)
* of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
*
* @static
* @memberOf _
* @since 0.1.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is an object, else `false`.
* @example
*
* _.isObject({});
* // => true
*
* _.isObject([1, 2, 3]);
* // => true
*
* _.isObject(_.noop);
* // => true
*
* _.isObject(null);
* // => false
*/
function isObject(value) {
var type = typeof value;
return value != null && (type == 'object' || type == 'function');
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (isObject);
/***/ }),
/***/ "./node_modules/lodash-es/isObjectLike.js":
/*!************************************************!*\
!*** ./node_modules/lodash-es/isObjectLike.js ***!
\************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/**
* Checks if `value` is object-like. A value is object-like if it's not `null`
* and has a `typeof` result of "object".
*
* @static
* @memberOf _
* @since 4.0.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is object-like, else `false`.
* @example
*
* _.isObjectLike({});
* // => true
*
* _.isObjectLike([1, 2, 3]);
* // => true
*
* _.isObjectLike(_.noop);
* // => false
*
* _.isObjectLike(null);
* // => false
*/
function isObjectLike(value) {
return value != null && typeof value == 'object';
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (isObjectLike);
/***/ }),
/***/ "./node_modules/lodash-es/isPlainObject.js":
/*!*************************************************!*\
!*** ./node_modules/lodash-es/isPlainObject.js ***!
\*************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _baseGetTag_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./_baseGetTag.js */ "./node_modules/lodash-es/_baseGetTag.js");
/* harmony import */ var _getPrototype_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./_getPrototype.js */ "./node_modules/lodash-es/_getPrototype.js");
/* harmony import */ var _isObjectLike_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./isObjectLike.js */ "./node_modules/lodash-es/isObjectLike.js");
/** `Object#toString` result references. */
var objectTag = '[object Object]';
/** Used for built-in method references. */
var funcProto = Function.prototype,
objectProto = Object.prototype;
/** Used to resolve the decompiled source of functions. */
var funcToString = funcProto.toString;
/** Used to check objects for own properties. */
var hasOwnProperty = objectProto.hasOwnProperty;
/** Used to infer the `Object` constructor. */
var objectCtorString = funcToString.call(Object);
/**
* Checks if `value` is a plain object, that is, an object created by the
* `Object` constructor or one with a `[[Prototype]]` of `null`.
*
* @static
* @memberOf _
* @since 0.8.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a plain object, else `false`.
* @example
*
* function Foo() {
* this.a = 1;
* }
*
* _.isPlainObject(new Foo);
* // => false
*
* _.isPlainObject([1, 2, 3]);
* // => false
*
* _.isPlainObject({ 'x': 0, 'y': 0 });
* // => true
*
* _.isPlainObject(Object.create(null));
* // => true
*/
function isPlainObject(value) {
if (!(0,_isObjectLike_js__WEBPACK_IMPORTED_MODULE_0__["default"])(value) || (0,_baseGetTag_js__WEBPACK_IMPORTED_MODULE_1__["default"])(value) != objectTag) {
return false;
}
var proto = (0,_getPrototype_js__WEBPACK_IMPORTED_MODULE_2__["default"])(value);
if (proto === null) {
return true;
}
var Ctor = hasOwnProperty.call(proto, 'constructor') && proto.constructor;
return typeof Ctor == 'function' && Ctor instanceof Ctor &&
funcToString.call(Ctor) == objectCtorString;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (isPlainObject);
/***/ }),
/***/ "./node_modules/lodash-es/isSet.js":
/*!*****************************************!*\
!*** ./node_modules/lodash-es/isSet.js ***!
\*****************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _baseIsSet_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./_baseIsSet.js */ "./node_modules/lodash-es/_baseIsSet.js");
/* harmony import */ var _baseUnary_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./_baseUnary.js */ "./node_modules/lodash-es/_baseUnary.js");
/* harmony import */ var _nodeUtil_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_nodeUtil.js */ "./node_modules/lodash-es/_nodeUtil.js");
/* Node.js helper references. */
var nodeIsSet = _nodeUtil_js__WEBPACK_IMPORTED_MODULE_0__["default"] && _nodeUtil_js__WEBPACK_IMPORTED_MODULE_0__["default"].isSet;
/**
* Checks if `value` is classified as a `Set` object.
*
* @static
* @memberOf _
* @since 4.3.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a set, else `false`.
* @example
*
* _.isSet(new Set);
* // => true
*
* _.isSet(new WeakSet);
* // => false
*/
var isSet = nodeIsSet ? (0,_baseUnary_js__WEBPACK_IMPORTED_MODULE_1__["default"])(nodeIsSet) : _baseIsSet_js__WEBPACK_IMPORTED_MODULE_2__["default"];
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (isSet);
/***/ }),
/***/ "./node_modules/lodash-es/isString.js":
/*!********************************************!*\
!*** ./node_modules/lodash-es/isString.js ***!
\********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _baseGetTag_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./_baseGetTag.js */ "./node_modules/lodash-es/_baseGetTag.js");
/* harmony import */ var _isArray_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./isArray.js */ "./node_modules/lodash-es/isArray.js");
/* harmony import */ var _isObjectLike_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./isObjectLike.js */ "./node_modules/lodash-es/isObjectLike.js");
/** `Object#toString` result references. */
var stringTag = '[object String]';
/**
* Checks if `value` is classified as a `String` primitive or object.
*
* @static
* @since 0.1.0
* @memberOf _
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a string, else `false`.
* @example
*
* _.isString('abc');
* // => true
*
* _.isString(1);
* // => false
*/
function isString(value) {
return typeof value == 'string' ||
(!(0,_isArray_js__WEBPACK_IMPORTED_MODULE_0__["default"])(value) && (0,_isObjectLike_js__WEBPACK_IMPORTED_MODULE_1__["default"])(value) && (0,_baseGetTag_js__WEBPACK_IMPORTED_MODULE_2__["default"])(value) == stringTag);
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (isString);
/***/ }),
/***/ "./node_modules/lodash-es/isSymbol.js":
/*!********************************************!*\
!*** ./node_modules/lodash-es/isSymbol.js ***!
\********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _baseGetTag_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./_baseGetTag.js */ "./node_modules/lodash-es/_baseGetTag.js");
/* harmony import */ var _isObjectLike_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./isObjectLike.js */ "./node_modules/lodash-es/isObjectLike.js");
/** `Object#toString` result references. */
var symbolTag = '[object Symbol]';
/**
* Checks if `value` is classified as a `Symbol` primitive or object.
*
* @static
* @memberOf _
* @since 4.0.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a symbol, else `false`.
* @example
*
* _.isSymbol(Symbol.iterator);
* // => true
*
* _.isSymbol('abc');
* // => false
*/
function isSymbol(value) {
return typeof value == 'symbol' ||
((0,_isObjectLike_js__WEBPACK_IMPORTED_MODULE_0__["default"])(value) && (0,_baseGetTag_js__WEBPACK_IMPORTED_MODULE_1__["default"])(value) == symbolTag);
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (isSymbol);
/***/ }),
/***/ "./node_modules/lodash-es/isTypedArray.js":
/*!************************************************!*\
!*** ./node_modules/lodash-es/isTypedArray.js ***!
\************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _baseIsTypedArray_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./_baseIsTypedArray.js */ "./node_modules/lodash-es/_baseIsTypedArray.js");
/* harmony import */ var _baseUnary_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./_baseUnary.js */ "./node_modules/lodash-es/_baseUnary.js");
/* harmony import */ var _nodeUtil_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_nodeUtil.js */ "./node_modules/lodash-es/_nodeUtil.js");
/* Node.js helper references. */
var nodeIsTypedArray = _nodeUtil_js__WEBPACK_IMPORTED_MODULE_0__["default"] && _nodeUtil_js__WEBPACK_IMPORTED_MODULE_0__["default"].isTypedArray;
/**
* Checks if `value` is classified as a typed array.
*
* @static
* @memberOf _
* @since 3.0.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a typed array, else `false`.
* @example
*
* _.isTypedArray(new Uint8Array);
* // => true
*
* _.isTypedArray([]);
* // => false
*/
var isTypedArray = nodeIsTypedArray ? (0,_baseUnary_js__WEBPACK_IMPORTED_MODULE_1__["default"])(nodeIsTypedArray) : _baseIsTypedArray_js__WEBPACK_IMPORTED_MODULE_2__["default"];
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (isTypedArray);
/***/ }),
/***/ "./node_modules/lodash-es/keys.js":
/*!****************************************!*\
!*** ./node_modules/lodash-es/keys.js ***!
\****************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _arrayLikeKeys_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./_arrayLikeKeys.js */ "./node_modules/lodash-es/_arrayLikeKeys.js");
/* harmony import */ var _baseKeys_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./_baseKeys.js */ "./node_modules/lodash-es/_baseKeys.js");
/* harmony import */ var _isArrayLike_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./isArrayLike.js */ "./node_modules/lodash-es/isArrayLike.js");
/**
* Creates an array of the own enumerable property names of `object`.
*
* **Note:** Non-object values are coerced to objects. See the
* [ES spec](http://ecma-international.org/ecma-262/7.0/#sec-object.keys)
* for more details.
*
* @static
* @since 0.1.0
* @memberOf _
* @category Object
* @param {Object} object The object to query.
* @returns {Array} Returns the array of property names.
* @example
*
* function Foo() {
* this.a = 1;
* this.b = 2;
* }
*
* Foo.prototype.c = 3;
*
* _.keys(new Foo);
* // => ['a', 'b'] (iteration order is not guaranteed)
*
* _.keys('hi');
* // => ['0', '1']
*/
function keys(object) {
return (0,_isArrayLike_js__WEBPACK_IMPORTED_MODULE_0__["default"])(object) ? (0,_arrayLikeKeys_js__WEBPACK_IMPORTED_MODULE_1__["default"])(object) : (0,_baseKeys_js__WEBPACK_IMPORTED_MODULE_2__["default"])(object);
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (keys);
/***/ }),
/***/ "./node_modules/lodash-es/keysIn.js":
/*!******************************************!*\
!*** ./node_modules/lodash-es/keysIn.js ***!
\******************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _arrayLikeKeys_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./_arrayLikeKeys.js */ "./node_modules/lodash-es/_arrayLikeKeys.js");
/* harmony import */ var _baseKeysIn_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./_baseKeysIn.js */ "./node_modules/lodash-es/_baseKeysIn.js");
/* harmony import */ var _isArrayLike_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./isArrayLike.js */ "./node_modules/lodash-es/isArrayLike.js");
/**
* Creates an array of the own and inherited enumerable property names of `object`.
*
* **Note:** Non-object values are coerced to objects.
*
* @static
* @memberOf _
* @since 3.0.0
* @category Object
* @param {Object} object The object to query.
* @returns {Array} Returns the array of property names.
* @example
*
* function Foo() {
* this.a = 1;
* this.b = 2;
* }
*
* Foo.prototype.c = 3;
*
* _.keysIn(new Foo);
* // => ['a', 'b', 'c'] (iteration order is not guaranteed)
*/
function keysIn(object) {
return (0,_isArrayLike_js__WEBPACK_IMPORTED_MODULE_0__["default"])(object) ? (0,_arrayLikeKeys_js__WEBPACK_IMPORTED_MODULE_1__["default"])(object, true) : (0,_baseKeysIn_js__WEBPACK_IMPORTED_MODULE_2__["default"])(object);
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (keysIn);
/***/ }),
/***/ "./node_modules/lodash-es/last.js":
/*!****************************************!*\
!*** ./node_modules/lodash-es/last.js ***!
\****************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/**
* Gets the last element of `array`.
*
* @static
* @memberOf _
* @since 0.1.0
* @category Array
* @param {Array} array The array to query.
* @returns {*} Returns the last element of `array`.
* @example
*
* _.last([1, 2, 3]);
* // => 3
*/
function last(array) {
var length = array == null ? 0 : array.length;
return length ? array[length - 1] : undefined;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (last);
/***/ }),
/***/ "./node_modules/lodash-es/memoize.js":
/*!*******************************************!*\
!*** ./node_modules/lodash-es/memoize.js ***!
\*******************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _MapCache_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_MapCache.js */ "./node_modules/lodash-es/_MapCache.js");
/** Error message constants. */
var FUNC_ERROR_TEXT = 'Expected a function';
/**
* Creates a function that memoizes the result of `func`. If `resolver` is
* provided, it determines the cache key for storing the result based on the
* arguments provided to the memoized function. By default, the first argument
* provided to the memoized function is used as the map cache key. The `func`
* is invoked with the `this` binding of the memoized function.
*
* **Note:** The cache is exposed as the `cache` property on the memoized
* function. Its creation may be customized by replacing the `_.memoize.Cache`
* constructor with one whose instances implement the
* [`Map`](http://ecma-international.org/ecma-262/7.0/#sec-properties-of-the-map-prototype-object)
* method interface of `clear`, `delete`, `get`, `has`, and `set`.
*
* @static
* @memberOf _
* @since 0.1.0
* @category Function
* @param {Function} func The function to have its output memoized.
* @param {Function} [resolver] The function to resolve the cache key.
* @returns {Function} Returns the new memoized function.
* @example
*
* var object = { 'a': 1, 'b': 2 };
* var other = { 'c': 3, 'd': 4 };
*
* var values = _.memoize(_.values);
* values(object);
* // => [1, 2]
*
* values(other);
* // => [3, 4]
*
* object.a = 2;
* values(object);
* // => [1, 2]
*
* // Modify the result cache.
* values.cache.set(object, ['a', 'b']);
* values(object);
* // => ['a', 'b']
*
* // Replace `_.memoize.Cache`.
* _.memoize.Cache = WeakMap;
*/
function memoize(func, resolver) {
if (typeof func != 'function' || (resolver != null && typeof resolver != 'function')) {
throw new TypeError(FUNC_ERROR_TEXT);
}
var memoized = function() {
var args = arguments,
key = resolver ? resolver.apply(this, args) : args[0],
cache = memoized.cache;
if (cache.has(key)) {
return cache.get(key);
}
var result = func.apply(this, args);
memoized.cache = cache.set(key, result) || cache;
return result;
};
memoized.cache = new (memoize.Cache || _MapCache_js__WEBPACK_IMPORTED_MODULE_0__["default"]);
return memoized;
}
// Expose `MapCache`.
memoize.Cache = _MapCache_js__WEBPACK_IMPORTED_MODULE_0__["default"];
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (memoize);
/***/ }),
/***/ "./node_modules/lodash-es/merge.js":
/*!*****************************************!*\
!*** ./node_modules/lodash-es/merge.js ***!
\*****************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _baseMerge_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./_baseMerge.js */ "./node_modules/lodash-es/_baseMerge.js");
/* harmony import */ var _createAssigner_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_createAssigner.js */ "./node_modules/lodash-es/_createAssigner.js");
/**
* This method is like `_.assign` except that it recursively merges own and
* inherited enumerable string keyed properties of source objects into the
* destination object. Source properties that resolve to `undefined` are
* skipped if a destination value exists. Array and plain object properties
* are merged recursively. Other objects and value types are overridden by
* assignment. Source objects are applied from left to right. Subsequent
* sources overwrite property assignments of previous sources.
*
* **Note:** This method mutates `object`.
*
* @static
* @memberOf _
* @since 0.5.0
* @category Object
* @param {Object} object The destination object.
* @param {...Object} [sources] The source objects.
* @returns {Object} Returns `object`.
* @example
*
* var object = {
* 'a': [{ 'b': 2 }, { 'd': 4 }]
* };
*
* var other = {
* 'a': [{ 'c': 3 }, { 'e': 5 }]
* };
*
* _.merge(object, other);
* // => { 'a': [{ 'b': 2, 'c': 3 }, { 'd': 4, 'e': 5 }] }
*/
var merge = (0,_createAssigner_js__WEBPACK_IMPORTED_MODULE_0__["default"])(function(object, source, srcIndex) {
(0,_baseMerge_js__WEBPACK_IMPORTED_MODULE_1__["default"])(object, source, srcIndex);
});
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (merge);
/***/ }),
/***/ "./node_modules/lodash-es/mergeWith.js":
/*!*********************************************!*\
!*** ./node_modules/lodash-es/mergeWith.js ***!
\*********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _baseMerge_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./_baseMerge.js */ "./node_modules/lodash-es/_baseMerge.js");
/* harmony import */ var _createAssigner_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_createAssigner.js */ "./node_modules/lodash-es/_createAssigner.js");
/**
* This method is like `_.merge` except that it accepts `customizer` which
* is invoked to produce the merged values of the destination and source
* properties. If `customizer` returns `undefined`, merging is handled by the
* method instead. The `customizer` is invoked with six arguments:
* (objValue, srcValue, key, object, source, stack).
*
* **Note:** This method mutates `object`.
*
* @static
* @memberOf _
* @since 4.0.0
* @category Object
* @param {Object} object The destination object.
* @param {...Object} sources The source objects.
* @param {Function} customizer The function to customize assigned values.
* @returns {Object} Returns `object`.
* @example
*
* function customizer(objValue, srcValue) {
* if (_.isArray(objValue)) {
* return objValue.concat(srcValue);
* }
* }
*
* var object = { 'a': [1], 'b': [2] };
* var other = { 'a': [3], 'b': [4] };
*
* _.mergeWith(object, other, customizer);
* // => { 'a': [1, 3], 'b': [2, 4] }
*/
var mergeWith = (0,_createAssigner_js__WEBPACK_IMPORTED_MODULE_0__["default"])(function(object, source, srcIndex, customizer) {
(0,_baseMerge_js__WEBPACK_IMPORTED_MODULE_1__["default"])(object, source, srcIndex, customizer);
});
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (mergeWith);
/***/ }),
/***/ "./node_modules/lodash-es/now.js":
/*!***************************************!*\
!*** ./node_modules/lodash-es/now.js ***!
\***************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _root_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_root.js */ "./node_modules/lodash-es/_root.js");
/**
* Gets the timestamp of the number of milliseconds that have elapsed since
* the Unix epoch (1 January 1970 00:00:00 UTC).
*
* @static
* @memberOf _
* @since 2.4.0
* @category Date
* @returns {number} Returns the timestamp.
* @example
*
* _.defer(function(stamp) {
* console.log(_.now() - stamp);
* }, _.now());
* // => Logs the number of milliseconds it took for the deferred invocation.
*/
var now = function() {
return _root_js__WEBPACK_IMPORTED_MODULE_0__["default"].Date.now();
};
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (now);
/***/ }),
/***/ "./node_modules/lodash-es/pull.js":
/*!****************************************!*\
!*** ./node_modules/lodash-es/pull.js ***!
\****************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _baseRest_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_baseRest.js */ "./node_modules/lodash-es/_baseRest.js");
/* harmony import */ var _pullAll_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./pullAll.js */ "./node_modules/lodash-es/pullAll.js");
/**
* Removes all given values from `array` using
* [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
* for equality comparisons.
*
* **Note:** Unlike `_.without`, this method mutates `array`. Use `_.remove`
* to remove elements from an array by predicate.
*
* @static
* @memberOf _
* @since 2.0.0
* @category Array
* @param {Array} array The array to modify.
* @param {...*} [values] The values to remove.
* @returns {Array} Returns `array`.
* @example
*
* var array = ['a', 'b', 'c', 'a', 'b', 'c'];
*
* _.pull(array, 'a', 'c');
* console.log(array);
* // => ['b', 'b']
*/
var pull = (0,_baseRest_js__WEBPACK_IMPORTED_MODULE_0__["default"])(_pullAll_js__WEBPACK_IMPORTED_MODULE_1__["default"]);
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (pull);
/***/ }),
/***/ "./node_modules/lodash-es/pullAll.js":
/*!*******************************************!*\
!*** ./node_modules/lodash-es/pullAll.js ***!
\*******************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _basePullAll_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_basePullAll.js */ "./node_modules/lodash-es/_basePullAll.js");
/**
* This method is like `_.pull` except that it accepts an array of values to remove.
*
* **Note:** Unlike `_.difference`, this method mutates `array`.
*
* @static
* @memberOf _
* @since 4.0.0
* @category Array
* @param {Array} array The array to modify.
* @param {Array} values The values to remove.
* @returns {Array} Returns `array`.
* @example
*
* var array = ['a', 'b', 'c', 'a', 'b', 'c'];
*
* _.pullAll(array, ['a', 'c']);
* console.log(array);
* // => ['b', 'b']
*/
function pullAll(array, values) {
return (array && array.length && values && values.length)
? (0,_basePullAll_js__WEBPACK_IMPORTED_MODULE_0__["default"])(array, values)
: array;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (pullAll);
/***/ }),
/***/ "./node_modules/lodash-es/set.js":
/*!***************************************!*\
!*** ./node_modules/lodash-es/set.js ***!
\***************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _baseSet_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_baseSet.js */ "./node_modules/lodash-es/_baseSet.js");
/**
* Sets the value at `path` of `object`. If a portion of `path` doesn't exist,
* it's created. Arrays are created for missing index properties while objects
* are created for all other missing properties. Use `_.setWith` to customize
* `path` creation.
*
* **Note:** This method mutates `object`.
*
* @static
* @memberOf _
* @since 3.7.0
* @category Object
* @param {Object} object The object to modify.
* @param {Array|string} path The path of the property to set.
* @param {*} value The value to set.
* @returns {Object} Returns `object`.
* @example
*
* var object = { 'a': [{ 'b': { 'c': 3 } }] };
*
* _.set(object, 'a[0].b.c', 4);
* console.log(object.a[0].b.c);
* // => 4
*
* _.set(object, ['x', '0', 'y', 'z'], 5);
* console.log(object.x[0].y.z);
* // => 5
*/
function set(object, path, value) {
return object == null ? object : (0,_baseSet_js__WEBPACK_IMPORTED_MODULE_0__["default"])(object, path, value);
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (set);
/***/ }),
/***/ "./node_modules/lodash-es/stubArray.js":
/*!*********************************************!*\
!*** ./node_modules/lodash-es/stubArray.js ***!
\*********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/**
* This method returns a new empty array.
*
* @static
* @memberOf _
* @since 4.13.0
* @category Util
* @returns {Array} Returns the new empty array.
* @example
*
* var arrays = _.times(2, _.stubArray);
*
* console.log(arrays);
* // => [[], []]
*
* console.log(arrays[0] === arrays[1]);
* // => false
*/
function stubArray() {
return [];
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (stubArray);
/***/ }),
/***/ "./node_modules/lodash-es/stubFalse.js":
/*!*********************************************!*\
!*** ./node_modules/lodash-es/stubFalse.js ***!
\*********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/**
* This method returns `false`.
*
* @static
* @memberOf _
* @since 4.13.0
* @category Util
* @returns {boolean} Returns `false`.
* @example
*
* _.times(2, _.stubFalse);
* // => [false, false]
*/
function stubFalse() {
return false;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (stubFalse);
/***/ }),
/***/ "./node_modules/lodash-es/throttle.js":
/*!********************************************!*\
!*** ./node_modules/lodash-es/throttle.js ***!
\********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _debounce_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./debounce.js */ "./node_modules/lodash-es/debounce.js");
/* harmony import */ var _isObject_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./isObject.js */ "./node_modules/lodash-es/isObject.js");
/** Error message constants. */
var FUNC_ERROR_TEXT = 'Expected a function';
/**
* Creates a throttled function that only invokes `func` at most once per
* every `wait` milliseconds. The throttled function comes with a `cancel`
* method to cancel delayed `func` invocations and a `flush` method to
* immediately invoke them. Provide `options` to indicate whether `func`
* should be invoked on the leading and/or trailing edge of the `wait`
* timeout. The `func` is invoked with the last arguments provided to the
* throttled function. Subsequent calls to the throttled function return the
* result of the last `func` invocation.
*
* **Note:** If `leading` and `trailing` options are `true`, `func` is
* invoked on the trailing edge of the timeout only if the throttled function
* is invoked more than once during the `wait` timeout.
*
* If `wait` is `0` and `leading` is `false`, `func` invocation is deferred
* until to the next tick, similar to `setTimeout` with a timeout of `0`.
*
* See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/)
* for details over the differences between `_.throttle` and `_.debounce`.
*
* @static
* @memberOf _
* @since 0.1.0
* @category Function
* @param {Function} func The function to throttle.
* @param {number} [wait=0] The number of milliseconds to throttle invocations to.
* @param {Object} [options={}] The options object.
* @param {boolean} [options.leading=true]
* Specify invoking on the leading edge of the timeout.
* @param {boolean} [options.trailing=true]
* Specify invoking on the trailing edge of the timeout.
* @returns {Function} Returns the new throttled function.
* @example
*
* // Avoid excessively updating the position while scrolling.
* jQuery(window).on('scroll', _.throttle(updatePosition, 100));
*
* // Invoke `renewToken` when the click event is fired, but not more than once every 5 minutes.
* var throttled = _.throttle(renewToken, 300000, { 'trailing': false });
* jQuery(element).on('click', throttled);
*
* // Cancel the trailing throttled invocation.
* jQuery(window).on('popstate', throttled.cancel);
*/
function throttle(func, wait, options) {
var leading = true,
trailing = true;
if (typeof func != 'function') {
throw new TypeError(FUNC_ERROR_TEXT);
}
if ((0,_isObject_js__WEBPACK_IMPORTED_MODULE_0__["default"])(options)) {
leading = 'leading' in options ? !!options.leading : leading;
trailing = 'trailing' in options ? !!options.trailing : trailing;
}
return (0,_debounce_js__WEBPACK_IMPORTED_MODULE_1__["default"])(func, wait, {
'leading': leading,
'maxWait': wait,
'trailing': trailing
});
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (throttle);
/***/ }),
/***/ "./node_modules/lodash-es/toNumber.js":
/*!********************************************!*\
!*** ./node_modules/lodash-es/toNumber.js ***!
\********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _baseTrim_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./_baseTrim.js */ "./node_modules/lodash-es/_baseTrim.js");
/* harmony import */ var _isObject_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./isObject.js */ "./node_modules/lodash-es/isObject.js");
/* harmony import */ var _isSymbol_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./isSymbol.js */ "./node_modules/lodash-es/isSymbol.js");
/** Used as references for various `Number` constants. */
var NAN = 0 / 0;
/** Used to detect bad signed hexadecimal string values. */
var reIsBadHex = /^[-+]0x[0-9a-f]+$/i;
/** Used to detect binary string values. */
var reIsBinary = /^0b[01]+$/i;
/** Used to detect octal string values. */
var reIsOctal = /^0o[0-7]+$/i;
/** Built-in method references without a dependency on `root`. */
var freeParseInt = parseInt;
/**
* Converts `value` to a number.
*
* @static
* @memberOf _
* @since 4.0.0
* @category Lang
* @param {*} value The value to process.
* @returns {number} Returns the number.
* @example
*
* _.toNumber(3.2);
* // => 3.2
*
* _.toNumber(Number.MIN_VALUE);
* // => 5e-324
*
* _.toNumber(Infinity);
* // => Infinity
*
* _.toNumber('3.2');
* // => 3.2
*/
function toNumber(value) {
if (typeof value == 'number') {
return value;
}
if ((0,_isSymbol_js__WEBPACK_IMPORTED_MODULE_0__["default"])(value)) {
return NAN;
}
if ((0,_isObject_js__WEBPACK_IMPORTED_MODULE_1__["default"])(value)) {
var other = typeof value.valueOf == 'function' ? value.valueOf() : value;
value = (0,_isObject_js__WEBPACK_IMPORTED_MODULE_1__["default"])(other) ? (other + '') : other;
}
if (typeof value != 'string') {
return value === 0 ? value : +value;
}
value = (0,_baseTrim_js__WEBPACK_IMPORTED_MODULE_2__["default"])(value);
var isBinary = reIsBinary.test(value);
return (isBinary || reIsOctal.test(value))
? freeParseInt(value.slice(2), isBinary ? 2 : 8)
: (reIsBadHex.test(value) ? NAN : +value);
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (toNumber);
/***/ }),
/***/ "./node_modules/lodash-es/toPlainObject.js":
/*!*************************************************!*\
!*** ./node_modules/lodash-es/toPlainObject.js ***!
\*************************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _copyObject_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_copyObject.js */ "./node_modules/lodash-es/_copyObject.js");
/* harmony import */ var _keysIn_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./keysIn.js */ "./node_modules/lodash-es/keysIn.js");
/**
* Converts `value` to a plain object flattening inherited enumerable string
* keyed properties of `value` to own properties of the plain object.
*
* @static
* @memberOf _
* @since 3.0.0
* @category Lang
* @param {*} value The value to convert.
* @returns {Object} Returns the converted plain object.
* @example
*
* function Foo() {
* this.b = 2;
* }
*
* Foo.prototype.c = 3;
*
* _.assign({ 'a': 1 }, new Foo);
* // => { 'a': 1, 'b': 2 }
*
* _.assign({ 'a': 1 }, _.toPlainObject(new Foo));
* // => { 'a': 1, 'b': 2, 'c': 3 }
*/
function toPlainObject(value) {
return (0,_copyObject_js__WEBPACK_IMPORTED_MODULE_0__["default"])(value, (0,_keysIn_js__WEBPACK_IMPORTED_MODULE_1__["default"])(value));
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (toPlainObject);
/***/ }),
/***/ "./node_modules/lodash-es/toString.js":
/*!********************************************!*\
!*** ./node_modules/lodash-es/toString.js ***!
\********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _baseToString_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_baseToString.js */ "./node_modules/lodash-es/_baseToString.js");
/**
* Converts `value` to a string. An empty string is returned for `null`
* and `undefined` values. The sign of `-0` is preserved.
*
* @static
* @memberOf _
* @since 4.0.0
* @category Lang
* @param {*} value The value to convert.
* @returns {string} Returns the converted string.
* @example
*
* _.toString(null);
* // => ''
*
* _.toString(-0);
* // => '-0'
*
* _.toString([1, 2, 3]);
* // => '1,2,3'
*/
function toString(value) {
return value == null ? '' : (0,_baseToString_js__WEBPACK_IMPORTED_MODULE_0__["default"])(value);
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (toString);
/***/ }),
/***/ "./node_modules/lodash-es/unset.js":
/*!*****************************************!*\
!*** ./node_modules/lodash-es/unset.js ***!
\*****************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _baseUnset_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_baseUnset.js */ "./node_modules/lodash-es/_baseUnset.js");
/**
* Removes the property at `path` of `object`.
*
* **Note:** This method mutates `object`.
*
* @static
* @memberOf _
* @since 4.0.0
* @category Object
* @param {Object} object The object to modify.
* @param {Array|string} path The path of the property to unset.
* @returns {boolean} Returns `true` if the property is deleted, else `false`.
* @example
*
* var object = { 'a': [{ 'b': { 'c': 7 } }] };
* _.unset(object, 'a[0].b.c');
* // => true
*
* console.log(object);
* // => { 'a': [{ 'b': {} }] };
*
* _.unset(object, ['a', '0', 'b', 'c']);
* // => true
*
* console.log(object);
* // => { 'a': [{ 'b': {} }] };
*/
function unset(object, path) {
return object == null ? true : (0,_baseUnset_js__WEBPACK_IMPORTED_MODULE_0__["default"])(object, path);
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (unset);
/***/ }),
/***/ "./node_modules/lodash-es/upperFirst.js":
/*!**********************************************!*\
!*** ./node_modules/lodash-es/upperFirst.js ***!
\**********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _createCaseFirst_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./_createCaseFirst.js */ "./node_modules/lodash-es/_createCaseFirst.js");
/**
* Converts the first character of `string` to upper case.
*
* @static
* @memberOf _
* @since 4.0.0
* @category String
* @param {string} [string=''] The string to convert.
* @returns {string} Returns the converted string.
* @example
*
* _.upperFirst('fred');
* // => 'Fred'
*
* _.upperFirst('FRED');
* // => 'FRED'
*/
var upperFirst = (0,_createCaseFirst_js__WEBPACK_IMPORTED_MODULE_0__["default"])('toUpperCase');
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (upperFirst);
/***/ })
/******/ });
/************************************************************************/
/******/ // The module cache
/******/ var __webpack_module_cache__ = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ var cachedModule = __webpack_module_cache__[moduleId];
/******/ if (cachedModule !== undefined) {
/******/ return cachedModule.exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = __webpack_module_cache__[moduleId] = {
/******/ id: moduleId,
/******/ // no module.loaded needed
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/************************************************************************/
/******/ /* webpack/runtime/compat get default export */
/******/ (() => {
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = (module) => {
/******/ var getter = module && module.__esModule ?
/******/ () => (module['default']) :
/******/ () => (module);
/******/ __webpack_require__.d(getter, { a: getter });
/******/ return getter;
/******/ };
/******/ })();
/******/
/******/ /* webpack/runtime/define property getters */
/******/ (() => {
/******/ // define getter functions for harmony exports
/******/ __webpack_require__.d = (exports, definition) => {
/******/ for(var key in definition) {
/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
/******/ }
/******/ }
/******/ };
/******/ })();
/******/
/******/ /* webpack/runtime/global */
/******/ (() => {
/******/ __webpack_require__.g = (function() {
/******/ if (typeof globalThis === 'object') return globalThis;
/******/ try {
/******/ return this || new Function('return this')();
/******/ } catch (e) {
/******/ if (typeof window === 'object') return window;
/******/ }
/******/ })();
/******/ })();
/******/
/******/ /* webpack/runtime/hasOwnProperty shorthand */
/******/ (() => {
/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
/******/ })();
/******/
/******/ /* webpack/runtime/make namespace object */
/******/ (() => {
/******/ // define __esModule on exports
/******/ __webpack_require__.r = (exports) => {
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ }
/******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ };
/******/ })();
/******/
/******/ /* webpack/runtime/nonce */
/******/ (() => {
/******/ __webpack_require__.nc = undefined;
/******/ })();
/******/
/************************************************************************/
var __webpack_exports__ = {};
// This entry need to be wrapped in an IIFE because it need to be isolated against other modules in the chunk.
(() => {
/*!*************************!*\
!*** ./src/ckeditor.js ***!
\*************************/
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _ckeditor_ckeditor5_editor_classic_src_classiceditor_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @ckeditor/ckeditor5-editor-classic/src/classiceditor.js */ "./node_modules/@ckeditor/ckeditor5-editor-classic/src/classiceditor.js");
/* harmony import */ var _ckeditor_ckeditor5_alignment_src_alignment_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ckeditor/ckeditor5-alignment/src/alignment.js */ "./node_modules/@ckeditor/ckeditor5-alignment/src/alignment.js");
/* harmony import */ var _ckeditor_ckeditor5_autoformat_src_autoformat_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @ckeditor/ckeditor5-autoformat/src/autoformat.js */ "./node_modules/@ckeditor/ckeditor5-autoformat/src/autoformat.js");
/* harmony import */ var _ckeditor_ckeditor5_image_src_autoimage_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @ckeditor/ckeditor5-image/src/autoimage.js */ "./node_modules/@ckeditor/ckeditor5-image/src/autoimage.js");
/* harmony import */ var _ckeditor_ckeditor5_link_src_autolink_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! @ckeditor/ckeditor5-link/src/autolink.js */ "./node_modules/@ckeditor/ckeditor5-link/src/autolink.js");
/* harmony import */ var _ckeditor_ckeditor5_autosave_src_autosave_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! @ckeditor/ckeditor5-autosave/src/autosave.js */ "./node_modules/@ckeditor/ckeditor5-autosave/src/autosave.js");
/* harmony import */ var _ckeditor_ckeditor5_block_quote_src_blockquote_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! @ckeditor/ckeditor5-block-quote/src/blockquote.js */ "./node_modules/@ckeditor/ckeditor5-block-quote/src/blockquote.js");
/* harmony import */ var _ckeditor_ckeditor5_basic_styles_src_bold_js__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! @ckeditor/ckeditor5-basic-styles/src/bold.js */ "./node_modules/@ckeditor/ckeditor5-basic-styles/src/bold.js");
/* harmony import */ var _ckeditor_ckeditor5_adapter_ckfinder_src_uploadadapter_js__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! @ckeditor/ckeditor5-adapter-ckfinder/src/uploadadapter.js */ "./node_modules/@ckeditor/ckeditor5-adapter-ckfinder/src/uploadadapter.js");
/* harmony import */ var _ckeditor_ckeditor5_cloud_services_src_cloudservices_js__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! @ckeditor/ckeditor5-cloud-services/src/cloudservices.js */ "./node_modules/@ckeditor/ckeditor5-cloud-services/src/cloudservices.js");
/* harmony import */ var _ckeditor_ckeditor5_basic_styles_src_code_js__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! @ckeditor/ckeditor5-basic-styles/src/code.js */ "./node_modules/@ckeditor/ckeditor5-basic-styles/src/code.js");
/* harmony import */ var _ckeditor_ckeditor5_code_block_src_codeblock_js__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! @ckeditor/ckeditor5-code-block/src/codeblock.js */ "./node_modules/@ckeditor/ckeditor5-code-block/src/codeblock.js");
/* harmony import */ var _ckeditor_ckeditor5_html_support_src_datafilter_js__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! @ckeditor/ckeditor5-html-support/src/datafilter.js */ "./node_modules/@ckeditor/ckeditor5-html-support/src/datafilter.js");
/* harmony import */ var _ckeditor_ckeditor5_html_support_src_dataschema_js__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(/*! @ckeditor/ckeditor5-html-support/src/dataschema.js */ "./node_modules/@ckeditor/ckeditor5-html-support/src/dataschema.js");
/* harmony import */ var _ckeditor_ckeditor5_essentials_src_essentials_js__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(/*! @ckeditor/ckeditor5-essentials/src/essentials.js */ "./node_modules/@ckeditor/ckeditor5-essentials/src/essentials.js");
/* harmony import */ var _ckeditor_ckeditor5_find_and_replace_src_findandreplace_js__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(/*! @ckeditor/ckeditor5-find-and-replace/src/findandreplace.js */ "./node_modules/@ckeditor/ckeditor5-find-and-replace/src/findandreplace.js");
/* harmony import */ var _ckeditor_ckeditor5_font_src_fontbackgroundcolor_js__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(/*! @ckeditor/ckeditor5-font/src/fontbackgroundcolor.js */ "./node_modules/@ckeditor/ckeditor5-font/src/fontbackgroundcolor.js");
/* harmony import */ var _ckeditor_ckeditor5_font_src_fontcolor_js__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(/*! @ckeditor/ckeditor5-font/src/fontcolor.js */ "./node_modules/@ckeditor/ckeditor5-font/src/fontcolor.js");
/* harmony import */ var _ckeditor_ckeditor5_font_src_fontfamily_js__WEBPACK_IMPORTED_MODULE_18__ = __webpack_require__(/*! @ckeditor/ckeditor5-font/src/fontfamily.js */ "./node_modules/@ckeditor/ckeditor5-font/src/fontfamily.js");
/* harmony import */ var _ckeditor_ckeditor5_font_src_fontsize_js__WEBPACK_IMPORTED_MODULE_19__ = __webpack_require__(/*! @ckeditor/ckeditor5-font/src/fontsize.js */ "./node_modules/@ckeditor/ckeditor5-font/src/fontsize.js");
/* harmony import */ var _ckeditor_ckeditor5_heading_src_heading_js__WEBPACK_IMPORTED_MODULE_20__ = __webpack_require__(/*! @ckeditor/ckeditor5-heading/src/heading.js */ "./node_modules/@ckeditor/ckeditor5-heading/src/heading.js");
/* harmony import */ var _ckeditor_ckeditor5_highlight_src_highlight_js__WEBPACK_IMPORTED_MODULE_21__ = __webpack_require__(/*! @ckeditor/ckeditor5-highlight/src/highlight.js */ "./node_modules/@ckeditor/ckeditor5-highlight/src/highlight.js");
/* harmony import */ var _ckeditor_ckeditor5_horizontal_line_src_horizontalline_js__WEBPACK_IMPORTED_MODULE_22__ = __webpack_require__(/*! @ckeditor/ckeditor5-horizontal-line/src/horizontalline.js */ "./node_modules/@ckeditor/ckeditor5-horizontal-line/src/horizontalline.js");
/* harmony import */ var _ckeditor_ckeditor5_html_support_src_htmlcomment_js__WEBPACK_IMPORTED_MODULE_23__ = __webpack_require__(/*! @ckeditor/ckeditor5-html-support/src/htmlcomment.js */ "./node_modules/@ckeditor/ckeditor5-html-support/src/htmlcomment.js");
/* harmony import */ var _ckeditor_ckeditor5_image_src_image_js__WEBPACK_IMPORTED_MODULE_24__ = __webpack_require__(/*! @ckeditor/ckeditor5-image/src/image.js */ "./node_modules/@ckeditor/ckeditor5-image/src/image.js");
/* harmony import */ var _ckeditor_ckeditor5_image_src_imagecaption_js__WEBPACK_IMPORTED_MODULE_25__ = __webpack_require__(/*! @ckeditor/ckeditor5-image/src/imagecaption.js */ "./node_modules/@ckeditor/ckeditor5-image/src/imagecaption.js");
/* harmony import */ var _ckeditor_ckeditor5_image_src_imageinsert_js__WEBPACK_IMPORTED_MODULE_26__ = __webpack_require__(/*! @ckeditor/ckeditor5-image/src/imageinsert.js */ "./node_modules/@ckeditor/ckeditor5-image/src/imageinsert.js");
/* harmony import */ var _ckeditor_ckeditor5_image_src_imageresize_js__WEBPACK_IMPORTED_MODULE_27__ = __webpack_require__(/*! @ckeditor/ckeditor5-image/src/imageresize.js */ "./node_modules/@ckeditor/ckeditor5-image/src/imageresize.js");
/* harmony import */ var _ckeditor_ckeditor5_image_src_imagestyle_js__WEBPACK_IMPORTED_MODULE_28__ = __webpack_require__(/*! @ckeditor/ckeditor5-image/src/imagestyle.js */ "./node_modules/@ckeditor/ckeditor5-image/src/imagestyle.js");
/* harmony import */ var _ckeditor_ckeditor5_image_src_imagetoolbar_js__WEBPACK_IMPORTED_MODULE_29__ = __webpack_require__(/*! @ckeditor/ckeditor5-image/src/imagetoolbar.js */ "./node_modules/@ckeditor/ckeditor5-image/src/imagetoolbar.js");
/* harmony import */ var _ckeditor_ckeditor5_image_src_imageupload_js__WEBPACK_IMPORTED_MODULE_30__ = __webpack_require__(/*! @ckeditor/ckeditor5-image/src/imageupload.js */ "./node_modules/@ckeditor/ckeditor5-image/src/imageupload.js");
/* harmony import */ var _ckeditor_ckeditor5_indent_src_indent_js__WEBPACK_IMPORTED_MODULE_31__ = __webpack_require__(/*! @ckeditor/ckeditor5-indent/src/indent.js */ "./node_modules/@ckeditor/ckeditor5-indent/src/indent.js");
/* harmony import */ var _ckeditor_ckeditor5_basic_styles_src_italic_js__WEBPACK_IMPORTED_MODULE_32__ = __webpack_require__(/*! @ckeditor/ckeditor5-basic-styles/src/italic.js */ "./node_modules/@ckeditor/ckeditor5-basic-styles/src/italic.js");
/* harmony import */ var _ckeditor_ckeditor5_link_src_link_js__WEBPACK_IMPORTED_MODULE_33__ = __webpack_require__(/*! @ckeditor/ckeditor5-link/src/link.js */ "./node_modules/@ckeditor/ckeditor5-link/src/link.js");
/* harmony import */ var _ckeditor_ckeditor5_link_src_linkimage_js__WEBPACK_IMPORTED_MODULE_34__ = __webpack_require__(/*! @ckeditor/ckeditor5-link/src/linkimage.js */ "./node_modules/@ckeditor/ckeditor5-link/src/linkimage.js");
/* harmony import */ var _ckeditor_ckeditor5_list_src_list_js__WEBPACK_IMPORTED_MODULE_35__ = __webpack_require__(/*! @ckeditor/ckeditor5-list/src/list.js */ "./node_modules/@ckeditor/ckeditor5-list/src/list.js");
/* harmony import */ var _ckeditor_ckeditor5_media_embed_src_mediaembed_js__WEBPACK_IMPORTED_MODULE_36__ = __webpack_require__(/*! @ckeditor/ckeditor5-media-embed/src/mediaembed.js */ "./node_modules/@ckeditor/ckeditor5-media-embed/src/mediaembed.js");
/* harmony import */ var _ckeditor_ckeditor5_paragraph_src_paragraph_js__WEBPACK_IMPORTED_MODULE_37__ = __webpack_require__(/*! @ckeditor/ckeditor5-paragraph/src/paragraph.js */ "./node_modules/@ckeditor/ckeditor5-paragraph/src/paragraph.js");
/* harmony import */ var _ckeditor_ckeditor5_paste_from_office_src_pastefromoffice_js__WEBPACK_IMPORTED_MODULE_38__ = __webpack_require__(/*! @ckeditor/ckeditor5-paste-from-office/src/pastefromoffice.js */ "./node_modules/@ckeditor/ckeditor5-paste-from-office/src/pastefromoffice.js");
/* harmony import */ var _ckeditor_ckeditor5_special_characters_src_specialcharacters_js__WEBPACK_IMPORTED_MODULE_39__ = __webpack_require__(/*! @ckeditor/ckeditor5-special-characters/src/specialcharacters.js */ "./node_modules/@ckeditor/ckeditor5-special-characters/src/specialcharacters.js");
/* harmony import */ var _ckeditor_ckeditor5_special_characters_src_specialcharactersarrows_js__WEBPACK_IMPORTED_MODULE_40__ = __webpack_require__(/*! @ckeditor/ckeditor5-special-characters/src/specialcharactersarrows.js */ "./node_modules/@ckeditor/ckeditor5-special-characters/src/specialcharactersarrows.js");
/* harmony import */ var _ckeditor_ckeditor5_special_characters_src_specialcharacterscurrency_js__WEBPACK_IMPORTED_MODULE_41__ = __webpack_require__(/*! @ckeditor/ckeditor5-special-characters/src/specialcharacterscurrency.js */ "./node_modules/@ckeditor/ckeditor5-special-characters/src/specialcharacterscurrency.js");
/* harmony import */ var _ckeditor_ckeditor5_table_src_table_js__WEBPACK_IMPORTED_MODULE_42__ = __webpack_require__(/*! @ckeditor/ckeditor5-table/src/table.js */ "./node_modules/@ckeditor/ckeditor5-table/src/table.js");
/* harmony import */ var _ckeditor_ckeditor5_table_src_tablecaption_js__WEBPACK_IMPORTED_MODULE_43__ = __webpack_require__(/*! @ckeditor/ckeditor5-table/src/tablecaption.js */ "./node_modules/@ckeditor/ckeditor5-table/src/tablecaption.js");
/* harmony import */ var _ckeditor_ckeditor5_table_src_tablecellproperties__WEBPACK_IMPORTED_MODULE_44__ = __webpack_require__(/*! @ckeditor/ckeditor5-table/src/tablecellproperties */ "./node_modules/@ckeditor/ckeditor5-table/src/tablecellproperties.js");
/* harmony import */ var _ckeditor_ckeditor5_table_src_tableproperties__WEBPACK_IMPORTED_MODULE_45__ = __webpack_require__(/*! @ckeditor/ckeditor5-table/src/tableproperties */ "./node_modules/@ckeditor/ckeditor5-table/src/tableproperties.js");
/* harmony import */ var _ckeditor_ckeditor5_table_src_tabletoolbar_js__WEBPACK_IMPORTED_MODULE_46__ = __webpack_require__(/*! @ckeditor/ckeditor5-table/src/tabletoolbar.js */ "./node_modules/@ckeditor/ckeditor5-table/src/tabletoolbar.js");
/* harmony import */ var _ckeditor_ckeditor5_typing_src_texttransformation_js__WEBPACK_IMPORTED_MODULE_47__ = __webpack_require__(/*! @ckeditor/ckeditor5-typing/src/texttransformation.js */ "./node_modules/@ckeditor/ckeditor5-typing/src/texttransformation.js");
/* harmony import */ var _ckeditor_ckeditor5_html_embed_src_htmlembed_js__WEBPACK_IMPORTED_MODULE_48__ = __webpack_require__(/*! @ckeditor/ckeditor5-html-embed/src/htmlembed.js */ "./node_modules/@ckeditor/ckeditor5-html-embed/src/htmlembed.js");
/**
* @license Copyright (c) 2014-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
class Editor extends _ckeditor_ckeditor5_editor_classic_src_classiceditor_js__WEBPACK_IMPORTED_MODULE_0__["default"] {}
// Plugins to include in the build.
Editor.builtinPlugins = [
_ckeditor_ckeditor5_alignment_src_alignment_js__WEBPACK_IMPORTED_MODULE_1__["default"],
_ckeditor_ckeditor5_autoformat_src_autoformat_js__WEBPACK_IMPORTED_MODULE_2__["default"],
_ckeditor_ckeditor5_image_src_autoimage_js__WEBPACK_IMPORTED_MODULE_3__["default"],
_ckeditor_ckeditor5_link_src_autolink_js__WEBPACK_IMPORTED_MODULE_4__["default"],
_ckeditor_ckeditor5_autosave_src_autosave_js__WEBPACK_IMPORTED_MODULE_5__["default"],
_ckeditor_ckeditor5_block_quote_src_blockquote_js__WEBPACK_IMPORTED_MODULE_6__["default"],
_ckeditor_ckeditor5_basic_styles_src_bold_js__WEBPACK_IMPORTED_MODULE_7__["default"],
_ckeditor_ckeditor5_adapter_ckfinder_src_uploadadapter_js__WEBPACK_IMPORTED_MODULE_8__["default"],
_ckeditor_ckeditor5_cloud_services_src_cloudservices_js__WEBPACK_IMPORTED_MODULE_9__["default"],
_ckeditor_ckeditor5_basic_styles_src_code_js__WEBPACK_IMPORTED_MODULE_10__["default"],
_ckeditor_ckeditor5_code_block_src_codeblock_js__WEBPACK_IMPORTED_MODULE_11__["default"],
_ckeditor_ckeditor5_html_support_src_datafilter_js__WEBPACK_IMPORTED_MODULE_12__["default"],
_ckeditor_ckeditor5_html_support_src_dataschema_js__WEBPACK_IMPORTED_MODULE_13__["default"],
_ckeditor_ckeditor5_essentials_src_essentials_js__WEBPACK_IMPORTED_MODULE_14__["default"],
_ckeditor_ckeditor5_find_and_replace_src_findandreplace_js__WEBPACK_IMPORTED_MODULE_15__["default"],
_ckeditor_ckeditor5_font_src_fontbackgroundcolor_js__WEBPACK_IMPORTED_MODULE_16__["default"],
_ckeditor_ckeditor5_font_src_fontcolor_js__WEBPACK_IMPORTED_MODULE_17__["default"],
_ckeditor_ckeditor5_font_src_fontfamily_js__WEBPACK_IMPORTED_MODULE_18__["default"],
_ckeditor_ckeditor5_font_src_fontsize_js__WEBPACK_IMPORTED_MODULE_19__["default"],
_ckeditor_ckeditor5_heading_src_heading_js__WEBPACK_IMPORTED_MODULE_20__["default"],
_ckeditor_ckeditor5_highlight_src_highlight_js__WEBPACK_IMPORTED_MODULE_21__["default"],
_ckeditor_ckeditor5_horizontal_line_src_horizontalline_js__WEBPACK_IMPORTED_MODULE_22__["default"],
_ckeditor_ckeditor5_html_support_src_htmlcomment_js__WEBPACK_IMPORTED_MODULE_23__["default"],
_ckeditor_ckeditor5_image_src_image_js__WEBPACK_IMPORTED_MODULE_24__["default"],
_ckeditor_ckeditor5_image_src_imagecaption_js__WEBPACK_IMPORTED_MODULE_25__["default"],
_ckeditor_ckeditor5_image_src_imageinsert_js__WEBPACK_IMPORTED_MODULE_26__["default"],
_ckeditor_ckeditor5_image_src_imageresize_js__WEBPACK_IMPORTED_MODULE_27__["default"],
_ckeditor_ckeditor5_image_src_imagestyle_js__WEBPACK_IMPORTED_MODULE_28__["default"],
_ckeditor_ckeditor5_image_src_imagetoolbar_js__WEBPACK_IMPORTED_MODULE_29__["default"],
_ckeditor_ckeditor5_image_src_imageupload_js__WEBPACK_IMPORTED_MODULE_30__["default"],
_ckeditor_ckeditor5_indent_src_indent_js__WEBPACK_IMPORTED_MODULE_31__["default"],
_ckeditor_ckeditor5_basic_styles_src_italic_js__WEBPACK_IMPORTED_MODULE_32__["default"],
_ckeditor_ckeditor5_link_src_link_js__WEBPACK_IMPORTED_MODULE_33__["default"],
_ckeditor_ckeditor5_link_src_linkimage_js__WEBPACK_IMPORTED_MODULE_34__["default"],
_ckeditor_ckeditor5_list_src_list_js__WEBPACK_IMPORTED_MODULE_35__["default"],
_ckeditor_ckeditor5_media_embed_src_mediaembed_js__WEBPACK_IMPORTED_MODULE_36__["default"],
_ckeditor_ckeditor5_paragraph_src_paragraph_js__WEBPACK_IMPORTED_MODULE_37__["default"],
_ckeditor_ckeditor5_paste_from_office_src_pastefromoffice_js__WEBPACK_IMPORTED_MODULE_38__["default"],
_ckeditor_ckeditor5_special_characters_src_specialcharacters_js__WEBPACK_IMPORTED_MODULE_39__["default"],
_ckeditor_ckeditor5_special_characters_src_specialcharactersarrows_js__WEBPACK_IMPORTED_MODULE_40__["default"],
_ckeditor_ckeditor5_special_characters_src_specialcharacterscurrency_js__WEBPACK_IMPORTED_MODULE_41__["default"],
_ckeditor_ckeditor5_table_src_table_js__WEBPACK_IMPORTED_MODULE_42__["default"],
_ckeditor_ckeditor5_table_src_tablecaption_js__WEBPACK_IMPORTED_MODULE_43__["default"],
_ckeditor_ckeditor5_table_src_tablecellproperties__WEBPACK_IMPORTED_MODULE_44__["default"],
_ckeditor_ckeditor5_table_src_tableproperties__WEBPACK_IMPORTED_MODULE_45__["default"],
_ckeditor_ckeditor5_table_src_tabletoolbar_js__WEBPACK_IMPORTED_MODULE_46__["default"],
_ckeditor_ckeditor5_typing_src_texttransformation_js__WEBPACK_IMPORTED_MODULE_47__["default"],
_ckeditor_ckeditor5_html_embed_src_htmlembed_js__WEBPACK_IMPORTED_MODULE_48__["default"],
];
// Editor configuration.
Editor.defaultConfig = {
toolbar: {
items: [
'heading',
'|',
'bold',
'italic',
'link',
'bulletedList',
'numberedList',
'|',
'outdent',
'indent',
'|',
'codeBlock',
'code',
'horizontalLine',
'findAndReplace',
'|',
'fontSize',
'fontColor',
'fontFamily',
'fontBackgroundColor',
'|',
'imageInsert',
'imageUpload',
'blockQuote',
'insertTable',
'mediaEmbed',
'undo',
'redo',
'htmlEmbed',
],
},
language: 'en',
image: {
toolbar: [
'imageTextAlternative',
'imageStyle:inline',
'imageStyle:block',
'imageStyle:side',
'linkImage',
],
},
table: {
contentToolbar: [
'tableColumn',
'tableRow',
'mergeTableCells',
'tableCellProperties',
'tableProperties',
],
},
};
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (Editor);
})();
__webpack_exports__ = __webpack_exports__["default"];
/******/ return __webpack_exports__;
/******/ })()
;
});
//# sourceMappingURL=ckeditor.js.map