(function webpackUniversalModuleDefinition(root, factory) { if(typeof exports === 'object' && typeof module === 'object') module.exports = factory(); else if(typeof define === 'function' && define.amd) define([], factory); else { var a = factory(); for(var i in a) (typeof exports === 'object' ? exports : root)[i] = a[i]; } })(this, function() { return /******/ (function(modules) { // webpackBootstrap /******/ // The module cache /******/ var installedModules = {}; /******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { /******/ /******/ // Check if module is in cache /******/ if(installedModules[moduleId]) { /******/ return installedModules[moduleId].exports; /******/ } /******/ // Create a new module (and put it into the cache) /******/ var module = installedModules[moduleId] = { /******/ i: moduleId, /******/ l: false, /******/ exports: {} /******/ }; /******/ /******/ // Execute the module function /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); /******/ /******/ // Flag the module as loaded /******/ module.l = true; /******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } /******/ /******/ /******/ // expose the modules object (__webpack_modules__) /******/ __webpack_require__.m = modules; /******/ /******/ // expose the module cache /******/ __webpack_require__.c = installedModules; /******/ /******/ // define getter function for harmony exports /******/ __webpack_require__.d = function(exports, name, getter) { /******/ if(!__webpack_require__.o(exports, name)) { /******/ Object.defineProperty(exports, name, { /******/ configurable: false, /******/ enumerable: true, /******/ get: getter /******/ }); /******/ } /******/ }; /******/ /******/ // getDefaultExport function for compatibility with non-harmony modules /******/ __webpack_require__.n = function(module) { /******/ var getter = module && module.__esModule ? /******/ function getDefault() { return module['default']; } : /******/ function getModuleExports() { return module; }; /******/ __webpack_require__.d(getter, 'a', getter); /******/ return getter; /******/ }; /******/ /******/ // Object.prototype.hasOwnProperty.call /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; /******/ /******/ // __webpack_public_path__ /******/ __webpack_require__.p = ""; /******/ /******/ // Load entry module and return exports /******/ return __webpack_require__(__webpack_require__.s = 558); /******/ }) /************************************************************************/ /******/ ([ /* 0 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; /** * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.md. */ /** * @module utils/ckeditorerror */ /** * The CKEditor error class. * * All errors will be shortened during the minification process in order to reduce the code size. * Therefore, all error messages should be documented in the same way as those in {@link module:utils/log}. * * Read more in the {@link module:utils/log} module. * * @extends Error */ class CKEditorError extends Error { /** * Creates an instance of the CKEditorError class. * * Read more about error logging in the {@link module:utils/log} module. * * @param {String} message The error message in an `error-name: Error message.` format. * During the minification process the "Error message" part will be removed to limit the code size * and a link to this error documentation will be added to the `message`. * @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( message, data ) { if ( data ) { message += ' ' + JSON.stringify( data ); } super( message ); /** * @member {String} */ this.name = 'CKEditorError'; /** * The additional error data passed to the constructor. * * @member {Object} */ this.data = data; } /** * Checks if error is an instance of CKEditorError class. * * @param {Object} error Object to check. * @returns {Boolean} */ static isCKEditorError( error ) { return error instanceof CKEditorError; } } /* harmony export (immutable) */ __webpack_exports__["a"] = CKEditorError; /***/ }), /* 1 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__ckeditor_ckeditor5_utils_src_observablemixin__ = __webpack_require__(12); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__ckeditor_ckeditor5_utils_src_mix__ = __webpack_require__(4); /** * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.md. */ /** * @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. * * @readonly * @member {module:core/editor/editor~Editor} #editor */ this.editor = editor; } /** * @inheritDoc */ destroy() { this.stopListening(); } } /* harmony export (immutable) */ __webpack_exports__["a"] = Plugin; Object(__WEBPACK_IMPORTED_MODULE_1__ckeditor_ckeditor5_utils_src_mix__["a" /* default */])( Plugin, __WEBPACK_IMPORTED_MODULE_0__ckeditor_ckeditor5_utils_src_observablemixin__["a" /* default */] ); /** * The base interface for CKEditor plugins. * * In its minimal form it can be a simple function (it will be used as a constructor) which accepts * {@link module:core/editor/editor~Editor the editor} as a parm. * It can also implement a few methods which, when present, will be used to properly initialize and destroy the plugin. * * // A simple plugin which enables a data processor. * function MyPlugin( editor ) { * editor.data.processor = new MyDataProcessor(); * } * * In most cases, however, you'll 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'll ensure that the listener will be removed when * // the plugin is destroyed. * this.listenTo( this.editor, 'dataReady', () => { * // Do something when data is ready. * } ); * } * } * * @interface PluginInterface */ /** * Creates a new plugin instance. This is the first step of a 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'll 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} servers for the special "after init" scenarios (e.g. code which depends on other * plugins, but which doesn't {@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 a plugin class definition tight it's 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.|undefined} module:core/plugin~PluginInterface.requires */ /** * 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 a plugin class definition tight it's 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. * * @static * @readonly * @member {String|undefined} module:core/plugin~PluginInterface.pluginName */ /** * The second stage (after plugin {@link #constructor}) of 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 constructors of these plugins. * * **Note:** This method is optional. A plugin instance does not need to have to have it defined. * * @method #init * @returns {null|Promise} */ /** * The third (and last) stage of plugin initialization. See also {@link #constructor} and {@link #init}. * * **Note:** This method is optional. A plugin instance does not need to have 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 to have it defined. * * @method #destroy * @returns {null|Promise} */ /***/ }), /* 2 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__treewalker__ = __webpack_require__(54); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__ckeditor_ckeditor5_utils_src_lib_lodash_last__ = __webpack_require__(17); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__ckeditor_ckeditor5_utils_src_comparearrays__ = __webpack_require__(55); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_3__ckeditor_ckeditor5_utils_src_ckeditorerror__ = __webpack_require__(0); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_4__text__ = __webpack_require__(25); /** * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.md. */ /** * @module engine/model/position */ /** * Represents a position in the model tree. * * **Note:** Position is based on offsets, not indexes. This means that position in element containing two text nodes * with data `foo` and `bar`, position between them has offset `3`, not `1`. * See {@link module:engine/model/position~Position#path} for more. * * Since position in a 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 elements. * This requirement is important for {@link module:engine/model/operation/transform~transform operational transformation}. * * Also, {@link module:engine/model/operation/operation~Operation operations} * kept in {@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 document got changed. * * When changes are applied to 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.} path Position path. See {@link module:engine/model/position~Position#path}. */ constructor( root, path ) { if ( !root.is( 'element' ) && !root.is( 'documentFragment' ) ) { /** * Position root invalid. * * @error position-root-invalid. */ throw new __WEBPACK_IMPORTED_MODULE_3__ckeditor_ckeditor5_utils_src_ckeditorerror__["a" /* default */]( 'model-position-root-invalid: Position root invalid.' ); } if ( !( path instanceof Array ) || path.length === 0 ) { /** * Position path must be an Array with at least one item. * * @error position-path-incorrect * @param path */ throw new __WEBPACK_IMPORTED_MODULE_3__ckeditor_ckeditor5_utils_src_ckeditorerror__["a" /* default */]( 'model-position-path-incorrect: Position path must be an Array with at least one item.', { path } ); } // Normalize the root and path (if element was passed). path = root.getPath().concat( 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 ] * * @member {Array.} module:engine/model/position~Position#path */ this.path = path; } /** * 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 Object(__WEBPACK_IMPORTED_MODULE_1__ckeditor_ckeditor5_utils_src_lib_lodash_last__["a" /* default */])( this.path ); } /** * @param {Number} newOffset */ 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} */ get parent() { let parent = this.root; for ( let i = 0; i < this.path.length - 1; i++ ) { parent = parent.getChild( parent.offsetToIndex( this.path[ i ] ) ); } 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() { const node = this.parent.getChild( this.index ); return ( node instanceof __WEBPACK_IMPORTED_MODULE_4__text__["a" /* default */] && node.startOffset < this.offset ) ? node : null; } /** * Node directly after this position or `null` if this position is in text node. * * @readonly * @type {module:engine/model/node~Node|null} */ get nodeAfter() { return this.textNode === null ? this.parent.getChild( this.index ) : null; } /** * Node directly before this position or `null` if this position is in text node. * * @readonly * @type {Node} */ get nodeBefore() { return this.textNode === null ? this.parent.getChild( this.index - 1 ) : null; } /** * 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. * * @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 = Object(__WEBPACK_IMPORTED_MODULE_2__ckeditor_ckeditor5_utils_src_comparearrays__["a" /* default */])( this.path, otherPosition.path ); switch ( result ) { case 'same': return 'same'; case 'prefix': return 'before'; case 'extension': return 'after'; default: if ( this.path[ result ] < otherPosition.path[ result ] ) { return 'before'; } else { return 'after'; } } } /** * Gets the farthest position which matches the callback using * {@link module:engine/model/treewalker~TreeWalker TreeWalker}. * * For example: * * getLastMatchingPosition( value => value.type == 'text' ); * // []foo -> foo[] * * getLastMatchingPosition( value => value.type == 'text', { direction: 'backward' } ); * // foo[] -> []foo * * 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 __WEBPACK_IMPORTED_MODULE_0__treewalker__["a" /* 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 returns the parent path even if the parent does not exists. * * @returns {Array.} 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.} Array with ancestors. */ getAncestors() { if ( this.parent.is( 'documentFragment' ) ) { return [ this.parent ]; } else { return this.parent.getAncestors( { includeSelf: true } ); } } /** * Returns the slice of two position {@link #path paths} which is identical. The {@link #root roots} * of these two paths must be identical. * * @param {module:engine/model/position~Position} position The second position. * @returns {Array.} 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 = Object(__WEBPACK_IMPORTED_MODULE_2__ckeditor_ckeditor5_utils_src_comparearrays__["a" /* 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). * * @param {Number} shift Offset shift. Can be a negative value. * @returns {module:engine/model/position~Position} Shifted position. */ getShiftedBy( shift ) { const shifted = Position.createFromPosition( this ); const offset = shifted.offset + shift; shifted.offset = offset < 0 ? 0 : offset; return shifted; } /** * Checks whether this position is after given position. * * @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. * } * * @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. * * @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. * * **Note:** this method traverses model document so it can be only used when range is up-to-date with model document. * * @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.createFromPosition( this ); right = Position.createFromPosition( otherPosition ); break; case 'after': left = Position.createFromPosition( otherPosition ); right = Position.createFromPosition( 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 ); } } } /** * 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.createFromPosition( this ); // This position can't be affected if deletion was in a different root. if ( this.root != deletePosition.root ) { return transformed; } if ( Object(__WEBPACK_IMPORTED_MODULE_2__ckeditor_ckeditor5_utils_src_comparearrays__["a" /* 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 ( Object(__WEBPACK_IMPORTED_MODULE_2__ckeditor_ckeditor5_utils_src_comparearrays__["a" /* 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. * @param {Boolean} insertBefore Flag indicating whether nodes are inserted before or after `insertPosition`. * This is important only when `insertPosition` and this position are same. If that is the case and the flag is * set to `true`, this position will get transformed. If the flag is set to `false`, it won't. * @returns {module:engine/model/position~Position} Transformed position. */ _getTransformedByInsertion( insertPosition, howMany, insertBefore ) { const transformed = Position.createFromPosition( this ); // This position can't be affected if insertion was in a different root. if ( this.root != insertPosition.root ) { return transformed; } if ( Object(__WEBPACK_IMPORTED_MODULE_2__ckeditor_ckeditor5_utils_src_comparearrays__["a" /* 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 && insertBefore ) ) { // And are inserted before an offset of that position... // "Push" this positions offset. transformed.offset += howMany; } } else if ( Object(__WEBPACK_IMPORTED_MODULE_2__ckeditor_ckeditor5_utils_src_comparearrays__["a" /* 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`. * @param {Boolean} insertBefore Flag indicating whether moved nodes are pasted before or after `insertPosition`. * This is important only when `targetPosition` and this position are same. If that is the case and the flag is * set to `true`, this position will get transformed by range insertion. If the flag is set to `false`, it won't. * @param {Boolean} [sticky] Flag indicating whether this position "sticks" to range, that is if it should be moved * with the moved range if it is equal to one of range's boundaries. * @returns {module:engine/model/position~Position} Transformed position. */ _getTransformedByMove( sourcePosition, targetPosition, howMany, insertBefore, sticky ) { // Moving a range removes nodes from their original position. We acknowledge this by proper transformation. let transformed = this._getTransformedByDeletion( sourcePosition, howMany ); // Then we update target position, as it could be affected by nodes removal too. targetPosition = targetPosition._getTransformedByDeletion( sourcePosition, howMany ); if ( transformed === null || ( sticky && transformed.isEqual( sourcePosition ) ) ) { // 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. transformed = 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. transformed = transformed._getTransformedByInsertion( targetPosition, howMany, insertBefore ); } return transformed; } /** * 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 = new Position( root, [ 2, 3, 1 ] ); * let source = new Position( root, [ 2, 2 ] ); * let target = new Position( 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.createFromPosition( target ); // 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.concat( this.path.slice( i + 1 ) ); return combined; } /** * 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 constructors such as: * * * {@link module:engine/model/position~Position.createBefore}, * * {@link module:engine/model/position~Position.createAfter}, * * {@link module:engine/model/position~Position.createFromParentAndOffset}, * * {@link module:engine/model/position~Position.createFromPosition}. * * @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}. */ static createAt( itemOrPosition, offset ) { if ( itemOrPosition instanceof Position ) { return this.createFromPosition( itemOrPosition ); } else { const node = itemOrPosition; if ( offset == 'end' ) { offset = node.maxOffset; } else if ( offset == 'before' ) { return this.createBefore( node ); } else if ( offset == 'after' ) { return this.createAfter( node ); } else if ( !offset ) { offset = 0; } return this.createFromParentAndOffset( node, offset ); } } /** * 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. * @returns {module:engine/model/position~Position} */ static createAfter( item ) { if ( !item.parent ) { /** * You can not make position after root. * * @error position-after-root * @param {module:engine/model/item~Item} root */ throw new __WEBPACK_IMPORTED_MODULE_3__ckeditor_ckeditor5_utils_src_ckeditorerror__["a" /* default */]( 'model-position-after-root: You can not make position after root.', { root: item } ); } return this.createFromParentAndOffset( item.parent, item.endOffset ); } /** * 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. * @returns {module:engine/model/position~Position} */ static createBefore( item ) { if ( !item.parent ) { /** * You can not make position before root. * * @error position-before-root * @param {module:engine/model/item~Item} root */ throw new __WEBPACK_IMPORTED_MODULE_3__ckeditor_ckeditor5_utils_src_ckeditorerror__["a" /* default */]( 'model-position-before-root: You can not make position before root.', { root: item } ); } return this.createFromParentAndOffset( item.parent, item.startOffset ); } /** * Creates a new position from the parent element and an offset in that element. * * @param {module:engine/model/element~Element|module:engine/model/documentfragment~DocumentFragment} parent Position's parent. * @param {Number} offset Position's offset. * @returns {module:engine/model/position~Position} */ static createFromParentAndOffset( parent, offset ) { if ( !parent.is( 'element' ) && !parent.is( 'documentFragment' ) ) { /** * Position parent have to be a model element or model document fragment. * * @error position-parent-incorrect */ throw new __WEBPACK_IMPORTED_MODULE_3__ckeditor_ckeditor5_utils_src_ckeditorerror__["a" /* default */]( 'model-position-parent-incorrect: Position parent have to be a element or document fragment.' ); } const path = parent.getPath(); path.push( offset ); return new this( parent.root, path ); } /** * Creates a new position, which is equal to passed position. * * @param {module:engine/model/position~Position} position Position to be cloned. * @returns {module:engine/model/position~Position} */ static createFromPosition( position ) { return new this( position.root, position.path.slice() ); } /** * Creates a `Position` instance from given plain object (i.e. parsed JSON string). * * @param {Object} json Plain object to be converted to `Position`. * @returns {module:engine/model/position~Position} `Position` instance created using given plain object. */ static fromJSON( json, doc ) { if ( json.root === '$graveyard' ) { return new Position( doc.graveyard, json.path ); } if ( !doc.hasRoot( json.root ) ) { /** * Cannot create position for document. Root with specified name does not exist. * * @error position-fromjson-no-root * @param {String} rootName */ throw new __WEBPACK_IMPORTED_MODULE_3__ckeditor_ckeditor5_utils_src_ckeditorerror__["a" /* default */]( 'model-position-fromjson-no-root: Cannot create position for document. Root with specified name does not exist.', { rootName: json.root } ); } return new Position( doc.getRoot( json.root ), json.path ); } } /* harmony export (immutable) */ __webpack_exports__["a"] = Position; /** * 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 */ /***/ }), /* 3 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__position__ = __webpack_require__(2); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__treewalker__ = __webpack_require__(54); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__ckeditor_ckeditor5_utils_src_ckeditorerror__ = __webpack_require__(0); /** * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.md. */ /** * @module engine/model/range */ /** * Range class. Range is iterable. */ class Range { /** * Creates a range spanning from `start` position to `end` position. * * **Note:** Constructor creates it's own {@link module:engine/model/position~Position Position} instances basing on passed values. * * @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. */ constructor( start, end = null ) { /** * Start position. * * @readonly * @member {module:engine/model/position~Position} */ this.start = __WEBPACK_IMPORTED_MODULE_0__position__["a" /* default */].createFromPosition( start ); /** * End position. * * @readonly * @member {module:engine/model/position~Position} */ this.end = end ? __WEBPACK_IMPORTED_MODULE_0__position__["a" /* default */].createFromPosition( end ) : __WEBPACK_IMPORTED_MODULE_0__position__["a" /* default */].createFromPosition( start ); } /** * Returns an iterator that 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.} */ * [ Symbol.iterator ]() { yield* new __WEBPACK_IMPORTED_MODULE_1__treewalker__["a" /* 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() { return this.start.parent === this.end.parent; } /** * 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; } /** * 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 = new Range( new Position( root, [ 2, 7 ] ), new Position( root, [ 4, 0, 1 ] ) ); * let otherRange = new Range( new Position( root, [ 1 ] ), new Position( root, [ 5 ] ) ); * let transformed = range.getDifference( otherRange ); * // transformed array has no ranges because `otherRange` contains `range` * * otherRange = new Range( new Position( root, [ 1 ] ), new Position( root, [ 3 ] ) ); * transformed = range.getDifference( otherRange ); * // transformed array has one range: from [ 3 ] to [ 4, 0, 1 ] * * otherRange = new Range( new Position( root, [ 3 ] ), new Position( 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.} 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( Range.createFromRange( this ) ); } 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 = new Range( new Position( root, [ 2, 7 ] ), new Position( root, [ 4, 0, 1 ] ) ); * let otherRange = new Range( new Position( root, [ 1 ] ), new Position( root, [ 2 ] ) ); * let transformed = range.getIntersection( otherRange ); // null - ranges have no common part * * otherRange = new Range( new Position( root, [ 3 ] ), new Position( 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; } /** * 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.} Array of flat ranges covering this range. */ getMinimalFlatRanges() { const ranges = []; const diffAt = this.start.getCommonPath( this.end ).length; const pos = __WEBPACK_IMPORTED_MODULE_0__position__["a" /* default */].createFromPosition( 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. * * @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] */ getWalker( options = {} ) { options.boundaries = this; return new __WEBPACK_IMPORTED_MODULE_1__treewalker__["a" /* 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. * * @method getItems * @param {Object} options Object with configuration options. See {@link module:engine/model/treewalker~TreeWalker}. * @returns {Iterable.} */ * getItems( options = {} ) { options.boundaries = this; options.ignoreElementEnd = true; const treeWalker = new __WEBPACK_IMPORTED_MODULE_1__treewalker__["a" /* 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.} */ * getPositions( options = {} ) { options.boundaries = this; const treeWalker = new __WEBPACK_IMPORTED_MODULE_1__treewalker__["a" /* 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 `delta`. * * **Note:** transformation may break one range into multiple ranges (e.g. 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/delta/delta~Delta} delta Delta to transform range by. * @returns {Array.} Range which is the result of transformation. */ getTransformedByDelta( delta ) { const ranges = [ Range.createFromRange( this ) ]; // Operation types that a range can be transformed by. const supportedTypes = new Set( [ 'insert', 'move', 'remove', 'reinsert' ] ); for ( const operation of delta.operations ) { if ( supportedTypes.has( operation.type ) ) { for ( let i = 0; i < ranges.length; i++ ) { const result = ranges[ i ]._getTransformedByDocumentChange( operation.type, delta.type, operation.targetPosition || operation.position, operation.howMany || operation.nodes.maxOffset, operation.sourcePosition ); ranges.splice( i, 1, ...result ); i += result.length - 1; } } } return ranges; } /** * Returns a range that is a result of transforming this range by multiple `deltas`. * * **Note:** transformation may break one range into multiple ranges (e.g. 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 {Iterable.} deltas Deltas to transform the range by. * @returns {Array.} Range which is the result of transformation. */ getTransformedByDeltas( deltas ) { const ranges = [ Range.createFromRange( this ) ]; for ( const delta of deltas ) { for ( let i = 0; i < ranges.length; i++ ) { const result = ranges[ i ].getTransformedByDelta( delta ); 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 a range that is a result of transforming this range by a change in the model document. * * @protected * @param {'insert'|'move'|'remove'|'reinsert'} type Change type. * @param {String} deltaType Type of delta that introduced the change. * @param {module:engine/model/position~Position} targetPosition Position before the first changed node. * @param {Number} howMany How many nodes has been changed. * @param {module:engine/model/position~Position} sourcePosition Source position of changes. * @returns {Array.} */ _getTransformedByDocumentChange( type, deltaType, targetPosition, howMany, sourcePosition ) { if ( type == 'insert' ) { return this._getTransformedByInsertion( targetPosition, howMany, false, false ); } else { const sourceRange = Range.createFromPositionAndShift( sourcePosition, howMany ); // Edge case for merge delta. if ( deltaType == 'merge' && this.isCollapsed && ( this.start.isEqual( sourceRange.start ) || this.start.isEqual( sourceRange.end ) ) ) { // Collapsed range is in merged element, at the beginning or at the end of it. // Without fix, the range would end up in the graveyard, together with removed element. //

foo

[]bar

->

foobar

[]

->

foobar

->

foo[]bar

//

foo

bar[]

return [ new Range( targetPosition.getShiftedBy( this.start.offset ) ) ]; } // // Other edge cases: // // In all examples `[]` is `this` and `{}` is `sourceRange`, while `^` is move target position. // // Example: //

xx

^{

a[b

}

c]d

-->

xx

a[b

c]d

// ^

xx

{

a[b

}

c]d

-->

a[b

xx

c]d

// Note

xx

inclusion. // {

a[b

}
^

c]d

-->

a[b

c]d

if ( sourceRange.containsPosition( this.start ) && this.containsPosition( sourceRange.end ) && this.end.isAfter( targetPosition ) ) { const start = this.start._getCombined( sourcePosition, targetPosition._getTransformedByDeletion( sourcePosition, howMany ) ); const end = this.end._getTransformedByMove( sourcePosition, targetPosition, howMany, false, false ); return [ new Range( start, end ) ]; } // Example: //

c[d

{

a]b

}
^

xx

-->

c[d

a]b

xx

//

c[d

{

a]b

}

xx

^ -->

c[d

xx

a]b

// Note

xx

inclusion. //

c[d

^{

a]b

}
-->

c[d

a]b

if ( ( sourceRange.containsPosition( this.end ) || sourceRange.end.isEqual( this.end ) ) && this.containsPosition( sourceRange.start ) && this.start.isBefore( targetPosition ) ) { const start = this.start._getTransformedByMove( sourcePosition, targetPosition, howMany, true, false ); const end = this.end._getCombined( sourcePosition, targetPosition._getTransformedByDeletion( sourcePosition, howMany ) ); return [ new Range( start, end ) ]; } return this._getTransformedByMove( sourcePosition, targetPosition, howMany ); } } /** * 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 = new Range( new Position( root, [ 2, 7 ] ), new Position( root, [ 4, 0, 1 ] ) ); * let transformed = range._getTransformedByInsertion( new Position( root, [ 1 ] ), 2 ); * // transformed array has one range from [ 4, 7 ] to [ 6, 0, 1 ] * * transformed = range._getTransformedByInsertion( new Position( root, [ 4, 0, 0 ] ), 4 ); * // transformed array has one range from [ 2, 7 ] to [ 4, 0, 5 ] * * transformed = range._getTransformedByInsertion( new Position( root, [ 3, 2 ] ), 4 ); * // transformed array has one range, which is equal to original range * * transformed = range._getTransformedByInsertion( new Position( root, [ 3, 2 ] ), 4, true ); * // transformed array has two ranges: from [ 2, 7 ] to [ 3, 2 ] and from [ 3, 6 ] to [ 4, 0, 1 ] * * transformed = range._getTransformedByInsertion( new Position( root, [ 4, 0, 1 ] ), 4, false, false ); * // transformed array has one range which is equal to original range because insertion is after the range boundary * * transformed = range._getTransformedByInsertion( new Position( root, [ 4, 0, 1 ] ), 4, false, true ); * // transformed array has one range: from [ 2, 7 ] to [ 4, 0, 5 ] because range was expanded * * @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`. * @param {Boolean} [isSticky] Flag indicating whether insertion should expand a range if it is in a place of * range boundary. Defaults to `false`. * @returns {Array.} Result of the transformation. */ _getTransformedByInsertion( insertPosition, howMany, spread = false, isSticky = 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._getTransformedByInsertion( insertPosition, howMany, true ), this.end._getTransformedByInsertion( insertPosition, howMany, this.isCollapsed ) ) ]; } else { const range = Range.createFromRange( this ); const insertBeforeStart = range.isCollapsed ? true : !isSticky; const insertBeforeEnd = range.isCollapsed ? true : isSticky; range.start = range.start._getTransformedByInsertion( insertPosition, howMany, insertBeforeStart ); range.end = range.end._getTransformedByInsertion( insertPosition, howMany, insertBeforeEnd ); 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. * @returns {Array.} Result of the transformation. */ _getTransformedByMove( sourcePosition, targetPosition, howMany ) { if ( this.isCollapsed ) { const newPos = this.start._getTransformedByMove( sourcePosition, targetPosition, howMany, true, false ); return [ new Range( newPos ) ]; } let result; const moveRange = new Range( sourcePosition, sourcePosition.getShiftedBy( howMany ) ); const differenceSet = this.getDifference( moveRange ); let difference = null; const common = this.getIntersection( moveRange ); if ( differenceSet.length == 1 ) { // `moveRange` and this range may intersect. 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. const insertPosition = targetPosition._getTransformedByDeletion( sourcePosition, howMany ); if ( difference ) { result = difference._getTransformedByInsertion( insertPosition, howMany, common !== null ); } else { result = []; } if ( common ) { result.push( new Range( common.start._getCombined( moveRange.start, insertPosition ), common.end._getCombined( moveRange.start, insertPosition ) ) ); } return result; } /** * 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. * * @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 from given parents and offsets. * * @param {module:engine/model/element~Element} startElement Start position parent element. * @param {Number} startOffset Start position offset. * @param {module:engine/model/element~Element} endElement End position parent element. * @param {Number} endOffset End position offset. * @returns {module:engine/model/range~Range} */ static createFromParentsAndOffsets( startElement, startOffset, endElement, endOffset ) { return new this( __WEBPACK_IMPORTED_MODULE_0__position__["a" /* default */].createFromParentAndOffset( startElement, startOffset ), __WEBPACK_IMPORTED_MODULE_0__position__["a" /* default */].createFromParentAndOffset( endElement, endOffset ) ); } /** * Creates a new instance of `Range` which is equal to passed range. * * @param {module:engine/model/range~Range} range Range to clone. * @returns {module:engine/model/range~Range} */ static createFromRange( range ) { return new this( range.start, range.end ); } /** * 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. * * @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 this.createFromParentsAndOffsets( element, 0, element, element.maxOffset ); } /** * Creates a range that starts before given {@link module:engine/model/item~Item model item} and ends after it. * * @param {module:engine/model/item~Item} item * @returns {module:engine/model/range~Range} */ static createOn( item ) { return this.createFromPositionAndShift( __WEBPACK_IMPORTED_MODULE_0__position__["a" /* 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.} 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. * * @error range-create-from-ranges-empty-array */ throw new __WEBPACK_IMPORTED_MODULE_2__ckeditor_ckeditor5_utils_src_ckeditorerror__["a" /* default */]( 'range-create-from-ranges-empty-array: At least one range has to be passed.' ); } else if ( ranges.length == 1 ) { return this.createFromRange( ranges[ 0 ] ); } // 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. for ( let i = refIndex - 1; i >= 0; i++ ) { if ( ranges[ i ].end.isEqual( result.start ) ) { result.start = __WEBPACK_IMPORTED_MODULE_0__position__["a" /* default */].createFromPosition( 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 = __WEBPACK_IMPORTED_MODULE_0__position__["a" /* default */].createFromPosition( 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/element~Element} `Range` instance created using given plain object. */ static fromJSON( json, doc ) { return new this( __WEBPACK_IMPORTED_MODULE_0__position__["a" /* default */].fromJSON( json.start, doc ), __WEBPACK_IMPORTED_MODULE_0__position__["a" /* default */].fromJSON( json.end, doc ) ); } } /* harmony export (immutable) */ __webpack_exports__["a"] = Range; /***/ }), /* 4 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; /* harmony export (immutable) */ __webpack_exports__["a"] = mix; /** * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.md. */ /** * @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 ); } ); } ); } /***/ }), /* 5 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__ckeditor_ckeditor5_utils_src_ckeditorerror__ = __webpack_require__(0); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__ckeditor_ckeditor5_utils_src_mix__ = __webpack_require__(4); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__ckeditor_ckeditor5_utils_src_emittermixin__ = __webpack_require__(9); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_3__ckeditor_ckeditor5_utils_src_collection__ = __webpack_require__(73); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_4__view__ = __webpack_require__(7); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_5__viewcollection__ = __webpack_require__(153); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_6__ckeditor_ckeditor5_utils_src_lib_lodash_cloneDeepWith__ = __webpack_require__(425); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_7__ckeditor_ckeditor5_utils_src_lib_lodash_isObject__ = __webpack_require__(16); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_8__ckeditor_ckeditor5_utils_src_log__ = __webpack_require__(24); /** * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.md. */ /** * @module ui/template */ /* global document */ const xhtmlNs = 'http://www.w3.org/1999/xhtml'; /** * A basic Template class. It renders DOM HTMLElement or Text from {@link module:ui/template~TemplateDefinition} and supports * element attributes, children, bindings to {@link module:utils/observablemixin~ObservableMixin} instances and DOM events * propagation. For example: * * new Template( { * tag: 'p', * attributes: { * class: 'foo', * style: { * backgroundColor: 'yellow' * } * }, * children: [ * 'A paragraph.' * ] * } ).render(); * * will render the following HTMLElement: * *

A paragraph.

* * 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; /** * Tag of this template, i.e. `div`, indicating that the instance will render * to an HTMLElement. * * @member {String} #tag */ /** * Text of this template, indicating that the instance will render to a DOM Text. * * @member {Array.} #text */ /** * Attributes of this template, i.e. `{ id: [ 'ck-id' ] }`, corresponding with * HTML attributes on HTMLElement. * * Note: Only when {@link #tag} is defined. * * @member {Object} #attributes */ /** * Children of this template; sub–templates. Each one is an independent * instance of {@link ~Template}. * * Note: Only when {@link #tag} is defined. * * @member {module:utils/collection~Collection.} #children */ /** * DOM event listeners of this template. * * @member {Object} #eventListeners */ /** * Data used by {@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 (`HTMLElement` or `Text`) out of the template. * * @see #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 `HTMLElement` or `Text`. * * **Note:** No new DOM nodes (HTMLElement or Text) will be created. Applying extends attributes * ({@link module:ui/template~TemplateDefinition attributes}) and listeners ({@link module:ui/template~TemplateDefinition on}) only. * * **Note:** Existing "class" and "style" attributes are extended when a template * is applied to a Node, while other attributes and `textContent` are overridden. * * **Note:** The process of applying a template can be easily reverted using * {@link module:ui/template~Template#revert} method. * * const element = document.createElement( 'div' ); * const bind = Template.bind( observableInstance, emitterInstance ); * * new Template( { * attrs: { * id: 'first-div', * class: bind.to( 'divClass' ) * }, * on: { * click: bind( 'elementClicked' ) // Will be fired by the observableInstance. * } * children: [ * 'Div text.' * ] * } ).apply( element ); * * element.outerHTML == "
Div text.
" * * @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 Root node for the template to revert. In most cases, it's the same node * that {@link module:ui/template~Template#apply} has used. */ revert( node ) { if ( !this._revertData ) { /** * Attempting reverting a template which has not been applied yet. * * @error ui-template-revert-not-applied */ throw new __WEBPACK_IMPORTED_MODULE_0__ckeditor_ckeditor5_utils_src_ckeditorerror__["a" /* default */]( 'ui-template-revert-not-applied: Attempting reverting a template which has not been applied yet.' ); } this._revertTemplateFromNode( node, this._revertData ); } /** * An entry point to the interface which allows binding DOM nodes to {@link module:utils/observablemixin~ObservableMixin}. * There are two types of bindings: * * * `HTMLElement` attributes or Text Node `textContent` can be synchronized with {@link module:utils/observablemixin~ObservableMixin} * instance attributes. See {@link module:ui/template~BindChain#to} and {@link module:ui/template~BindChain#if}. * * * DOM events fired on `HTMLElement` can be propagated through {@link module:utils/observablemixin~ObservableMixin}. * See {@link module:ui/template~BindChain#to}. * * @param {module:utils/observablemixin~ObservableMixin} observable An instance of ObservableMixin class. * @param {module:utils/emittermixin~EmitterMixin} emitter An instance of `Emitter` class. It listens * to `observable` attribute changes and DOM Events, depending on the binding. Usually {@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 {@link module:ui/template~Template} instance with additional content from {@link module:ui/template~TemplateDefinition}. * * const bind = Template.bind( observable, emitterInstance ); * const instance = 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( instance, { * attributes: { * class: 'b', * data-x: bind.to( 'bar' ) * }, * children: [ * { * attributes: { * class: 'c' * } * } * ] * } ); * * // Child extension. * Template.extend( instance.children.get( 0 ), { * attributes: { * class: 'd' * } * } ); * * the `instance.render().outerHTML` is * *

* Span *

* * @param {module:ui/template~Template} template Existing Template instance to be extended. * @param {module:ui/template~TemplateDefinition} def An extension to existing an template instance. */ static extend( template, def ) { if ( template._isRendered ) { /** * Extending a template after rendering may not work as expected. To make sure * the {@link #extend extending} works for the rendered element, perform it * before {@link #render} is called. * * @error template-extend-render */ __WEBPACK_IMPORTED_MODULE_8__ckeditor_ckeditor5_utils_src_log__["a" /* default */].warn( 'template-extend-render: Attempting to extend a template which has already been rendered.' ); } extendTemplate( template, normalize( clone( def ) ) ); } /** * Renders a DOM Node (either `HTMLElement` 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 "tag" and "text" properties at the same time. * Node definition must have either "tag" or "text" when rendering new Node. * * @error ui-template-wrong-syntax */ throw new __WEBPACK_IMPORTED_MODULE_0__ckeditor_ckeditor5_utils_src_ckeditorerror__["a" /* default */]( 'ui-template-wrong-syntax: Node definition must have either "tag" or "text" when rendering new Node.' ); } if ( this.text ) { return this._renderText( data ); } else { return this._renderElement( data ); } } /** * Renders an `HTMLElement` 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 an `HTMLElement` 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 = ( Object(__WEBPACK_IMPORTED_MODULE_7__ckeditor_ckeditor5_utils_src_lib_lodash_isObject__["a" /* 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 `style` attribute of an `HTMLElement` based on {@link module:ui/template~Template#attributes}. * * 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: `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 `HTMLElement` 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 ); for ( const view of child ) { container.appendChild( view.element ); } } } else if ( isView( child ) ) { if ( !isApplying ) { container.appendChild( child.element ); } } 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` listeners in the {@link module:ui/template~TemplateDefinition} * on a passed `HTMLElement`. * * @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 given {@link module:ui/template~TemplateValueSchema} containing {@link module:ui/template~TemplateBinding} it activates the * binding and sets its initial value. * * Note: {@link module:ui/template~TemplateValueSchema} can be for HTMLElement 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 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 the original state. * * @protected * @param {HTMLElement|Text} node A node to be reverted. * @param {module:ui/template~RenderData#revertData} revertData Stores information about * what changes have been made by {@link #apply} to the node. */ _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 ] ); } } } /* harmony export (immutable) */ __webpack_exports__["a"] = Template; Object(__WEBPACK_IMPORTED_MODULE_1__ckeditor_ckeditor5_utils_src_mix__["a" /* default */])( Template, __WEBPACK_IMPORTED_MODULE_2__ckeditor_ckeditor5_utils_src_emittermixin__["c" /* default */] ); /** * Describes a binding created by {@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 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~EmitterMixin} instance used by the binding * to (either): * * * listen to the attribute change in the {@link module:ui/template~TemplateBinding#observable}, * * listen to the event in the DOM. * * @member {module:utils/emittermixin~EmitterMixin} module:ui/template~TemplateBinding#emitter */ /** * The name of the attribute of {@link module:ui/template~TemplateBinding#observable} which is observed. * * @member {String} module:ui/template~TemplateBinding#attribute */ /** * A custom function to process the value of {@link module:ui/template~TemplateBinding#attribute}. * * @member {Function} [module:ui/template~TemplateBinding#callback] */ } /** * Returns the value of the binding, which is the value of {@link module:ui/template~TemplateBinding#attribute} in * {@link module:ui/template~TemplateBinding#observable}. * * @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 for the changes of {@link module:ui/template~TemplateBinding#attribute} in * {@link module:ui/template~TemplateBinding#observable}, which then updates the DOM with the aggregated * value of {@link module:ui/template~TemplateValueSchema}. * * For instance, the `class` attribute of the `Template` element can be be bound to * the observable `foo` attribute in `ObservableMixin` instance. * * @param {module:ui/template~TemplateValueSchema} schema A full schema to generate an attribute or text in DOM. * @param {Function} updater A DOM updater function used to update 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 ); }; } } /* unused harmony export TemplateBinding */ /** * Describes either: * * * a binding to {@link module:utils/observablemixin~ObservableMixin} * * or a native DOM event binding * * created by {@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 A name of the native DOM event. * @param {String} domSelector A selector in 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 ); }; } } /* unused harmony export TemplateToBinding */ /** * Describes a binding to {@link module:utils/observablemixin~ObservableMixin} created by {@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/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] */ } /* unused harmony export TemplateIfBinding */ // 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 an 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. // @return {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 bound Observable attribute changes, which updates DOM with a value // constructed from {@link module:ui/template~TemplateValueSchema}. // // @param {module:ui/template~TemplateValueSchema} schema // @param {Function} updater A function which updates 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 = Object(__WEBPACK_IMPORTED_MODULE_6__ckeditor_ckeditor5_utils_src_lib_lodash_cloneDeepWith__["a" /* 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 = new __WEBPACK_IMPORTED_MODULE_3__ckeditor_ckeditor5_utils_src_collection__["a" /* default */](); if ( def.children ) { if ( isViewCollection( def.children ) ) { children.add( def.children ); } else { for ( const child of def.children ) { if ( isTemplate( child ) || isView( child ) ) { children.add( child ); } else { children.add( 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} attrs function normalizeAttributes( attrs ) { for ( const a in attrs ) { if ( attrs[ a ].value ) { attrs[ a ].value = [].concat( attrs[ a ].value ); } arrayify( attrs, 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 ) { if ( !Array.isArray( def.text ) ) { def.text = [ 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 ) { if ( !Array.isArray( obj[ key ] ) ) { obj[ key ] = [ 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 {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. 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 __WEBPACK_IMPORTED_MODULE_0__ckeditor_ckeditor5_utils_src_ckeditorerror__["a" /* default */]( 'ui-template-extend-children-mismatch: The number of children in extended definition does not match.' ); } let childIndex = 0; for ( const childDef of def.children ) { extendTemplate( template.children.get( 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 __WEBPACK_IMPORTED_MODULE_4__view__["a" /* 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 __WEBPACK_IMPORTED_MODULE_5__viewcollection__["a" /* 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 {@link module:ui/template~Template}. * See: {@link module:ui/template~TemplateValueSchema}. * * new Template( { * tag: 'p', * children: [ * { * tag: 'span', * attributes: { ... }, * children: [ ... ], * ... * }, * { * text: 'static–text' * }, * 'also-static–text', * <{@link module:ui/view~View} instance> * <{@link module:ui/template~Template} instance> * ... * ], * 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} * 'keyup@.some-class': {@link module:ui/template~TemplateListenerSchema}, * ... * } * } ); * * // An entire view collection can be used as a child in the definition. * new Template( { * tag: 'p', * children: <{@link module:ui/viewcollection~ViewCollection} instance> * } ); * * @typedef module:ui/template~TemplateDefinition * @type Object * @property {String} tag * @property {Array.} [children] * @property {Object.} [attributes] * @property {String|module:ui/template~TemplateValueSchema|Array.} [text] * @property {Object.} [on] */ /** * Describes a value of HTMLElement attribute or `textContent`. See: * * {@link module:ui/template~TemplateDefinition}, * * {@link module:ui/template~Template.bind}, * * const bind = Template.bind( observableInstance, emitterInstance ); * * new Template( { * tag: 'p', * attributes: { * // Plain String schema. * class: 'static-text' * * // Object schema, an `ObservableMixin` binding. * class: bind.to( 'foo' ) * * // Array schema, combines the above. * class: [ * 'static-text', * bind.to( 'bar', () => { ... } ) * ], * * // Array schema, with custom namespace. * class: { * ns: 'http://ns.url', * value: [ * bind.if( 'baz', 'value-when-true' ) * 'static-text' * ] * }, * * // Object literal schema, specific for styles. * style: { * color: 'red', * backgroundColor: bind.to( 'qux', () => { ... } ) * } * } * } ); * * @typedef module:ui/template~TemplateValueSchema * @type {Object|String|Array} */ /** * Describes a listener attached to HTMLElement. See: {@link module:ui/template~TemplateDefinition}. * * new Template( { * tag: 'p', * on: { * // Plain String schema. * click: 'clicked' * * // Object schema, an `ObservableMixin` binding. * click: {@link module:ui/template~TemplateBinding} * * // Array schema, combines the above. * click: [ * 'clicked', * {@link module:ui/template~TemplateBinding} * ], * * // Array schema, with custom callback. * // Note: It will work for "click" event on class=".foo" children only. * 'click@.foo': { * 'clicked', * {@link module:ui/template~TemplateBinding}, * () => { ... } * } * } * } ); * * @typedef module:ui/template~TemplateListenerSchema * @type {Object|String|Array} */ /** * The type of {@link ~Template.bind}'s return value. * * @interface module:ui/template~BindChain */ /** * Binds {@link module:utils/observablemixin~ObservableMixin} instance to: * * * HTMLElement attribute or Text Node `textContent` so remains in sync with the Observable when it changes: * * HTMLElement DOM event, so the DOM events are propagated through Observable. * * const bind = Template.bind( observableInstance, emitterInstance ); * * new Template( { * tag: 'p', * attributes: { * // class="..." attribute gets bound to `observableInstance#a` * 'class': bind.to( 'a' ) * }, * children: [ * //

...

gets bound to `observableInstance#b`; always `toUpperCase()`. * { text: bind.to( 'b', ( value, node ) => value.toUpperCase() ) } * ], * on: { * click: [ * // "clicked" event will be fired on `observableInstance` when "click" fires in DOM. * bind.to( 'clicked' ), * * // A custom callback function will be executed when "click" fires in DOM. * bind.to( () => { * ... * } ) * ] * } * } ).render(); * * const bind = Template.bind( observableInstance, emitterInstance ); * * new Template( { * tag: 'p', * } ).render(); * * @method #to * @param {String|Function} eventNameOrFunctionOrAttribute An attribute name of * {@link module:utils/observablemixin~ObservableMixin} or a DOM event name or an event callback. * @param {Function} [callback] Allows processing of the value. Accepts `Node` and `value` as arguments. * @return {module:ui/template~TemplateBinding} */ /** * Binds {@link module:utils/observablemixin~ObservableMixin} to HTMLElement attribute or Text Node `textContent` * so remains in sync with the Model when it changes. Unlike {@link module:ui/template~BindChain#to}, * it controls the presence of the attribute/`textContent` depending on the "falseness" of * {@link module:utils/observablemixin~ObservableMixin} attribute. * * const bind = Template.bind( observableInstance, emitterInstance ); * * new Template( { * tag: 'input', * attributes: { * // when `observableInstance#a` is not undefined/null/false/'' * // when `observableInstance#a` is undefined/null/false * checked: bind.if( 'a' ) * }, * children: [ * { * // "b-is-not-set" when `observableInstance#b` is undefined/null/false/'' * // when `observableInstance#b` is not "falsy" * text: bind.if( 'b', 'b-is-not-set', ( value, node ) => !value ) * } * ] * } ).render(); * * @method #if * @param {String} attribute An attribute name of {@link module:utils/observablemixin~ObservableMixin} used in the binding. * @param {String} [valueIfTrue] Value set when {@link module:utils/observablemixin~ObservableMixin} attribute is not * undefined/null/false/''. * @param {Function} [callback] Allows processing of the value. Accepts `Node` and `value` as arguments. * @return {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's 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 */ /***/ }), /* 6 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__node__ = __webpack_require__(63); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__nodelist__ = __webpack_require__(81); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__text__ = __webpack_require__(25); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_3__ckeditor_ckeditor5_utils_src_isiterable__ = __webpack_require__(46); /** * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.md. */ /** * @module engine/model/element */ /** * 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. */ class Element extends __WEBPACK_IMPORTED_MODULE_0__node__["a" /* default */] { /** * Creates a model element. * * @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.} [children] * One or more nodes to be inserted as children of created element. */ constructor( name, attrs, children ) { super( attrs ); /** * Element name. * * @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 __WEBPACK_IMPORTED_MODULE_1__nodelist__["a" /* default */](); if ( children ) { this.insertChildren( 0, children ); } } /** * Number of this element's children. * * @readonly * @type {Number} */ get childCount() { return this._children.length; } /** * Sum of {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 given model tree object is of given type. * * obj.name; // 'listItem' * obj instanceof Element; // true * * obj.is( 'element' ); // true * obj.is( 'listItem' ); // true * obj.is( 'element', 'listItem' ); // true * obj.is( 'text' ); // false * obj.is( 'element', 'image' ); // false * * Read more in {@link module:engine/model/node~Node#is}. * * @param {String} type Type to check when `name` parameter is present. * Otherwise, it acts like the `name` parameter. * @param {String} [name] Element name. * @returns {Boolean} */ is( type, name = null ) { if ( !name ) { return type == 'element' || type == this.name; } else { return type == 'element' && name == this.name; } } /** * 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.} */ 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 * {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 ); } /** * 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 removed. * * @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 ); } /** * 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 ); } /** * {@link module:engine/model/element~Element#insertChildren Inserts} one or more nodes at the end of this element. * * @param {module:engine/model/node~Node|Iterable.} nodes Nodes to be inserted. */ appendChildren( nodes ) { this.insertChildren( 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. * * @param {Number} index Index at which nodes should be inserted. * @param {module:engine/model/node~Node|Iterable.} nodes Nodes to be inserted. */ insertChildren( index, nodes ) { nodes = normalize( nodes ); for ( const node of nodes ) { 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`. * * @param {Number} index Index of the first node to remove. * @param {Number} [howMany=1] Number of nodes to remove. * @returns {Array.} 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; } /** * Returns a descendant node by its path relative to this element. * * // ac * this.getNodeByPath( [ 0 ] ); // -> "a" * this.getNodeByPath( [ 1 ] ); // -> * this.getNodeByPath( [ 1, 0 ] ); // -> "c" * * @param {Array.} 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( index ); } return node; } /** * 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 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( __WEBPACK_IMPORTED_MODULE_2__text__["a" /* default */].fromJSON( child ) ); } } } return new Element( json.name, json.attributes, children ); } } /* harmony export (immutable) */ __webpack_exports__["a"] = Element; // Converts strings to Text and non-iterables to arrays. // // @param {String|module:engine/model/node~Node|Iterable.} // @return {Iterable.} function normalize( nodes ) { // Separate condition because string is iterable. if ( typeof nodes == 'string' ) { return [ new __WEBPACK_IMPORTED_MODULE_2__text__["a" /* default */]( nodes ) ]; } if ( !Object(__WEBPACK_IMPORTED_MODULE_3__ckeditor_ckeditor5_utils_src_isiterable__["a" /* default */])( nodes ) ) { nodes = [ nodes ]; } // Array.from to enable .map() on non-arrays. return Array.from( nodes ) .map( node => { return typeof node == 'string' ? new __WEBPACK_IMPORTED_MODULE_2__text__["a" /* default */]( node ) : node; } ); } /***/ }), /* 7 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__ckeditor_ckeditor5_utils_src_ckeditorerror__ = __webpack_require__(0); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__viewcollection__ = __webpack_require__(153); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__template__ = __webpack_require__(5); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_3__ckeditor_ckeditor5_utils_src_dom_emittermixin__ = __webpack_require__(107); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_4__ckeditor_ckeditor5_utils_src_observablemixin__ = __webpack_require__(12); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_5__ckeditor_ckeditor5_utils_src_collection__ = __webpack_require__(73); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_6__ckeditor_ckeditor5_utils_src_mix__ = __webpack_require__(4); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_7__ckeditor_ckeditor5_utils_src_isiterable__ = __webpack_require__(46); /** * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.md. */ /** * @module ui/view */ /** * Basic View class. * * class SampleView extends View { * constructor( locale ) { * super( locale ); * * this.template = new Template( { * tag: 'p', * children: [ * 'Hello', * { * tag: 'b', * children: [ * 'world!' * ] * } * ], * attributes: { * class: 'foo' * } * } ); * } * } * * const view = new SampleView( locale ); * * view.init(); * * // Will append

Helloworld

* document.body.appendChild( view.element ); * * @mixes module:utils/observablemixin~ObservableMixin */ class View { /** * Creates an instance of the {@link module:ui/view~View} class. * * @param {module:utils/locale~Locale} [locale] The {@link module:core/editor/editor~Editor editor's locale} instance. */ constructor( locale ) { /** * A set of tools to localize the user interface. See {@link module:core/editor/editor~Editor}. * * @readonly * @member {module:utils/locale~Locale} */ this.locale = locale; /** * Shorthand for {@link module:utils/locale~Locale#t}. * * Note: If 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; /** * Set `true` after {@link #init}, which can be asynchronous. * * @readonly * @observable * @member {Boolean} #ready */ this.set( 'ready', false ); /** * Collections registered with {@link #createCollection}. * * @protected * @member {Set.} */ this._viewCollections = new __WEBPACK_IMPORTED_MODULE_5__ckeditor_ckeditor5_utils_src_collection__["a" /* 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. * * @member {module:ui/template~Template} #template */ /** * Element of this view. * * @private * @member {HTMLElement} #_element */ /** * Cached {@link module:ui/template~Template} binder object specific for this instance. * See {@link #bindTemplate}. * * @private * @member {Object} #_bindTemplate */ } /** * Element of this view. The element is rendered on first reference * using {@link #template} definition. * * @type {HTMLElement} */ get element() { if ( this._element ) { return this._element; } // No template means no element (a virtual view). if ( !this.template ) { return null; } this._addTemplateChildren(); return ( this._element = this.template.render() ); } set element( el ) { this._element = el; } /** * Shorthand for {@link module:ui/template~Template.bind}, bound to {@link ~View} on the first access. * * Cached {@link module:ui/template~Template.bind} object is stored in {@link #_bindTemplate}. * * @method #bindTemplate */ get bindTemplate() { if ( this._bindTemplate ) { return this._bindTemplate; } return ( this._bindTemplate = __WEBPACK_IMPORTED_MODULE_2__template__["a" /* default */].bind( this, this ) ); } /** * Creates a new collection of views, which can be used in this view instance, * e.g. as a member of {@link module:ui/template~TemplateDefinition TemplateDefinition} children. * * class SampleView extends View { * constructor( locale ) { * super( locale ); * * this.items = this.createCollection(); * * this.template = new Template( { * tag: 'p', * * // `items` collection will render here. * children: this.items * } ); * } * } * * const view = new SampleView( locale ); * const anotherView = new AnotherSampleView( locale ); * * view.init(); * * // Will append

* document.body.appendChild( view.element ); * * // `anotherView` becomes a child of the view, which is reflected in DOM * //

* view.items.add( anotherView ); * * @returns {module:ui/viewcollection~ViewCollection} A new collection of view instances. */ createCollection() { const collection = new __WEBPACK_IMPORTED_MODULE_1__viewcollection__["a" /* default */](); this._viewCollections.add( collection ); return collection; } /** * Registers a new child view under this view instance. Once registered, a child * view is managed by its parent, including initialization ({@link #init}) * and destruction ({@link #destroy}). * * class SampleView extends View { * constructor( locale ) { * super( locale ); * * this.childA = new SomeChildView( locale ); * this.childB = new SomeChildView( locale ); * * this.template = new Template( { tag: 'p' } ); * * // Register children. * this.addChildren( [ this.childA, this.childB ] ); * } * * init() { * this.element.appendChild( this.childA.element ); * this.element.appendChild( this.childB.element ); * * return super.init(); * } * } * * const view = new SampleView( locale ); * * view.init(); * * // Will append

* document.body.appendChild( view.element ); * * **Note**: There's no need to add child views if they're used in the * {@link #template} explicitly: * * class SampleView extends View { * constructor( locale ) { * super( locale ); * * this.childA = new SomeChildView( locale ); * this.childB = new SomeChildView( locale ); * * this.template = new Template( { * tag: 'p', * * // These children will be added automatically. There's no * // need to call {@link #addChildren} for any of them. * children: [ this.childA, this.childB ] * } ); * } * * ... * } * * @param {module:ui/view~View|Iterable.} children Children views to be registered. */ addChildren( children ) { if ( !Object(__WEBPACK_IMPORTED_MODULE_7__ckeditor_ckeditor5_utils_src_isiterable__["a" /* default */])( children ) ) { children = [ children ]; } children.map( c => this._unboundChildren.add( c ) ); } /** * Initializes the view and child views located in {@link #_viewCollections}. */ init() { if ( this.ready ) { /** * This View has already been initialized. * * @error ui-view-init-reinit */ throw new __WEBPACK_IMPORTED_MODULE_0__ckeditor_ckeditor5_utils_src_ckeditorerror__["a" /* default */]( 'ui-view-init-reinit: This View has already been initialized.' ); } // Initialize collections in #_viewCollections. this._viewCollections.map( c => c.init() ); // Spread the word that this view is ready! this.ready = true; } /** * Destroys the view instance and child views located in {@link #_viewCollections}. */ destroy() { this.stopListening(); this._viewCollections.map( c => c.destroy() ); } /** * Recursively traverses {@link #template} in search of {@link module:ui/view~View} * instances and automatically registers them using {@link #addChildren} method. * * @protected */ _addTemplateChildren() { const search = def => { if ( def.children ) { for ( const defOrView of def.children ) { if ( defOrView instanceof View ) { this.addChildren( defOrView ); } else { search( defOrView ); } } } }; search( this.template ); } } /* harmony export (immutable) */ __webpack_exports__["a"] = View; Object(__WEBPACK_IMPORTED_MODULE_6__ckeditor_ckeditor5_utils_src_mix__["a" /* default */])( View, __WEBPACK_IMPORTED_MODULE_3__ckeditor_ckeditor5_utils_src_dom_emittermixin__["a" /* default */] ); Object(__WEBPACK_IMPORTED_MODULE_6__ckeditor_ckeditor5_utils_src_mix__["a" /* default */])( View, __WEBPACK_IMPORTED_MODULE_4__ckeditor_ckeditor5_utils_src_observablemixin__["a" /* default */] ); /***/ }), /* 8 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__apply__ = __webpack_require__(158); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__toInteger__ = __webpack_require__(13); /** Used as the `TypeError` message for "Functions" methods. */ 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; /** * Creates a function that invokes `func` with the `this` binding of the * created function and arguments from `start` and beyond provided as * an array. * * **Note:** This method is based on the * [rest parameter](https://mdn.io/rest_parameters). * * @static * @memberOf _ * @since 4.0.0 * @category Function * @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. * @example * * var say = _.rest(function(what, names) { * return what + ' ' + _.initial(names).join(', ') + * (_.size(names) > 1 ? ', & ' : '') + _.last(names); * }); * * say('hello', 'fred', 'barney', 'pebbles'); * // => 'hello fred, barney, & pebbles' */ function rest(func, start) { if (typeof func != 'function') { throw new TypeError(FUNC_ERROR_TEXT); } start = nativeMax(start === undefined ? (func.length - 1) : Object(__WEBPACK_IMPORTED_MODULE_1__toInteger__["a" /* default */])(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]; } switch (start) { case 0: return func.call(this, array); case 1: return func.call(this, args[0], array); case 2: return func.call(this, args[0], args[1], array); } var otherArgs = Array(start + 1); index = -1; while (++index < start) { otherArgs[index] = args[index]; } otherArgs[start] = array; return Object(__WEBPACK_IMPORTED_MODULE_0__apply__["a" /* default */])(func, this, otherArgs); }; } /* harmony default export */ __webpack_exports__["a"] = (rest); /***/ }), /* 9 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; /* harmony export (immutable) */ __webpack_exports__["a"] = _getEmitterListenedTo; /* harmony export (immutable) */ __webpack_exports__["b"] = _setEmitterId; /* unused harmony export _getEmitterId */ /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__eventinfo__ = __webpack_require__(285); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__uid__ = __webpack_require__(61); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__priorities__ = __webpack_require__(287); /** * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.md. */ /** * @module utils/emittermixin */ const _listeningTo = Symbol( 'listeningTo' ); const _emitterId = Symbol( 'emitterId' ); /** * Mixin that injects the events API into its host. * * @mixin EmitterMixin * @implements module:utils/emittermixin~Emitter */ const EmitterMixin = { /** * Registers a callback function to be executed when an event is fired. * * Events can be grouped in namespaces using `:`. * When namespaced event is fired, it additionaly fires all callbacks for that namespace. * * 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 #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. * @param {Object} [options.context] The object that represents `this` in the callback. Defaults to the object firing the event. */ on( event, callback, options = {} ) { createEventNamespace( this, event ); const lists = getCallbacksListsForNamespace( this, event ); const priority = __WEBPACK_IMPORTED_MODULE_2__priorities__["a" /* default */].get( options.priority ); callback = { callback, context: options.context || this, 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, callback ); added = true; break; } } // Add at the end, if right place was not found. if ( !added ) { callbacks.push( callback ); } } }, /** * 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. * @param {Object} [options.context] The object that represents `this` in the callback. Defaults to the object firing the event. */ once( event, callback, options ) { const onceCallback = function( event, ...args ) { // 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.on( event, onceCallback, options ); }, /** * Stops executing the callback on the given event. * * @method #off * @param {String} event The name of the event. * @param {Function} callback The function to stop being called. * @param {Object} [context] The context object to be removed, pared with the given callback. To handle cases where * the same callback is used several times with different contexts. */ off( event, callback, context ) { const lists = getCallbacksListsForNamespace( this, event ); for ( const callbacks of lists ) { for ( let i = 0; i < callbacks.length; i++ ) { if ( callbacks[ i ].callback == callback ) { if ( !context || context == callbacks[ i ].context ) { // Remove the callback from the list (fixing the next index). callbacks.splice( i, 1 ); i--; } } } } }, /** * Registers a callback function to be executed when an event is fired in a specific (emitter) object. * * @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. * @param {Object} [options.context] The object that represents `this` in the callback. Defaults to the object firing the event. */ 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. emitter.on( event, callback, options ); }, /** * 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 object. * * @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`. */ 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 ) { emitter.off( event, callback ); } // Only `emitter` and `event` provided. off() all callbacks for that event. else if ( eventCallbacks ) { while ( ( callback = eventCallbacks.pop() ) ) { emitter.off( 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 ]; } }, /** * 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}'s value (the event info * is the first param of every callback). */ fire( eventOrInfo, ...args ) { const eventInfo = eventOrInfo instanceof __WEBPACK_IMPORTED_MODULE_0__eventinfo__["a" /* default */] ? eventOrInfo : new __WEBPACK_IMPORTED_MODULE_0__eventinfo__["a" /* 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( callbacks[ i ].context, 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.off( event, callbacks[ i ].callback, callbacks[ i ].context ); } // 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; }, /** * 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} */ delegate( ...events ) { return { to: ( emitter, nameOrFunction ) => { if ( !this._delegations ) { this._delegations = new Map(); } for ( const eventName of events ) { const destinations = this._delegations.get( eventName ); if ( !destinations ) { this._delegations.set( eventName, new Map( [ [ emitter, nameOrFunction ] ] ) ); } else { destinations.set( emitter, nameOrFunction ); } } } }; }, /** * 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. */ 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 ); } } } }; /* harmony default export */ __webpack_exports__["c"] = (EmitterMixin); /** * 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~EmitterMixin} listeningEmitter Emitter that listens. * @param {String} listenedToEmitterId Unique emitter id of emitter listened to. * @returns {module:utils/emittermixin~EmitterMixin|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~EmitterMixin} emitter 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 || Object(__WEBPACK_IMPORTED_MODULE_1__uid__["a" /* default */])(); } } /** * Returns emitter's unique id. * * @protected * @param {module:utils/emittermixin~EmitterMixin} emitter 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). // Returns empty array if given event has not been yet registered. 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.} destinations A map containing `[ {@link utils.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 __WEBPACK_IMPORTED_MODULE_0__eventinfo__["a" /* default */]( eventInfo.source, name ); delegatedInfo.path = [ ...eventInfo.path ]; emitter.fire( delegatedInfo, ...fireArgs ); } } /** * Interface representing classes which mix in {@link module:utils/emittermixin~EmitterMixin}. * * @interface Emitter */ /** * 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. */ /***/ }), /* 10 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__baseMatches__ = __webpack_require__(368); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__baseMatchesProperty__ = __webpack_require__(375); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__identity__ = __webpack_require__(192); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_3__isArray__ = __webpack_require__(15); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_4__property__ = __webpack_require__(383); /** * The base implementation of `_.iteratee`. * * @private * @param {*} [value=_.identity] The value to convert to an iteratee. * @returns {Function} Returns the iteratee. */ function baseIteratee(value) { // Don't store the `typeof` result in a variable to avoid a JIT bug in Safari 9. // See https://bugs.webkit.org/show_bug.cgi?id=156034 for more details. if (typeof value == 'function') { return value; } if (value == null) { return __WEBPACK_IMPORTED_MODULE_2__identity__["a" /* default */]; } if (typeof value == 'object') { return Object(__WEBPACK_IMPORTED_MODULE_3__isArray__["a" /* default */])(value) ? Object(__WEBPACK_IMPORTED_MODULE_1__baseMatchesProperty__["a" /* default */])(value[0], value[1]) : Object(__WEBPACK_IMPORTED_MODULE_0__baseMatches__["a" /* default */])(value); } return Object(__WEBPACK_IMPORTED_MODULE_4__property__["a" /* default */])(value); } /* harmony default export */ __webpack_exports__["a"] = (baseIteratee); /***/ }), /* 11 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__ckeditor_ckeditor5_utils_src_observablemixin__ = __webpack_require__(12); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__ckeditor_ckeditor5_utils_src_mix__ = __webpack_require__(4); /** * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.md. */ /** * @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}. * The easiest way to execute a command is through {@link module:core/editor/editor~Editor#execute}. * * By default commands are disabled when the editor is in {@link module:core/editor/editor~Editor#isReadOnly read-only} mode. * * @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 a command. Concrete command class should define what it represents. * * For example, the `bold` command's value is whether the selection starts in a bolded text. * And the value of the `link` command may be an object with links details. * * It's possible for a command to have no value (e.g. for stateless actions such as `uploadImage`). * * @observable * @readonly * @member #value */ this.set( 'value', undefined ); /** * Flag indicating whether a command is enabled or disabled. * A disabled command should do nothing when executed. * * @observable * @readonly * @member {Boolean} #isEnabled */ this.set( 'isEnabled', false ); this.decorate( 'execute' ); // By default every command is refreshed when changes are applied to the model. this.listenTo( this.editor.document, 'changesDone', () => { 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 ) { // See a ticket about overriding observable properties // https://github.com/ckeditor/ckeditor5-utils/issues/171. this.on( 'change:isEnabled', forceDisable, { priority: 'lowest' } ); this.isEnabled = false; } else { this.off( 'change:isEnabled', forceDisable ); this.refresh(); } } ); function forceDisable() { this.isEnabled = false; } } /** * Refreshes the command. The command should update its {@link #isEnabled} and {@link #value} property * in this method. * * This method is automatically called when * {@link module:engine/model/document~Document#event:changesDone any changes are applied to the model}. */ refresh() { this.isEnabled = true; } /** * Executes the command. * * A command may accept parameters. They will be passed from {@link module:core/editor/editor~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. * * @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 */ } /* harmony export (immutable) */ __webpack_exports__["a"] = Command; Object(__WEBPACK_IMPORTED_MODULE_1__ckeditor_ckeditor5_utils_src_mix__["a" /* default */])( Command, __WEBPACK_IMPORTED_MODULE_0__ckeditor_ckeditor5_utils_src_observablemixin__["a" /* default */] ); /***/ }), /* 12 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__emittermixin__ = __webpack_require__(9); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__ckeditorerror__ = __webpack_require__(0); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__lib_lodash_extend__ = __webpack_require__(44); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_3__lib_lodash_isObject__ = __webpack_require__(16); /** * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.md. */ /** * @module utils/observablemixin */ const attributesSymbol = Symbol( 'attributes' ); const boundObservablesSymbol = Symbol( 'boundObservables' ); const boundAttributesSymbol = Symbol( 'boundAttributes' ); /** * Mixin that injects the "observable attributes" and data binding functionality. * Used mainly in the {@link module:ui/model~Model} class. * * @mixin ObservableMixin * @mixes module:utils/emittermixin~EmitterMixin * @implements module:utils/observablemixin~Observable */ const ObservableMixin = { /** * Creates and sets the value of an observable attribute of this object. Such an attribute becomes a part * of the state and is be observable. * * It accepts also a single object literal containing key/value pairs with attributes to be set. * * This method throws the observable-set-cannot-override error if the observable instance already * have a property with a given attribute 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} name The attributes name. * @param {*} value The attributes value. */ set( name, value ) { // If the first parameter is an Object, iterate over its properties. if ( Object(__WEBPACK_IMPORTED_MODULE_3__lib_lodash_isObject__["a" /* default */])( name ) ) { Object.keys( name ).forEach( attr => { this.set( attr, name[ attr ] ); }, this ); return; } initObservable( this ); const attributes = this[ attributesSymbol ]; if ( ( name in this ) && !attributes.has( name ) ) { /** * Cannot override an existing property. * * This error is thrown when trying to {@link ~Observable#set set} an attribute with * a name of an already existing property. For example: * * let observable = new Model(); * observable.property = 1; * observable.set( 'property', 2 ); // throws * * observable.set( 'attr', 1 ); * observable.set( 'attr', 2 ); // ok, because this is an existing attribute. * * @error observable-set-cannot-override */ throw new __WEBPACK_IMPORTED_MODULE_1__ckeditorerror__["a" /* default */]( 'observable-set-cannot-override: Cannot override an existing property.' ); } Object.defineProperty( this, name, { enumerable: true, configurable: true, get() { return attributes.get( name ); }, set( value ) { const oldValue = attributes.get( name ); // Allow undefined as an initial value like A.define( 'x', undefined ) (#132). // Note: When attributes map has no such own property, then its value is undefined. if ( oldValue !== value || !attributes.has( name ) ) { attributes.set( name, value ); this.fire( 'change:' + name, name, value, oldValue ); } } } ); this[ name ] = value; }, /** * Binds observable attributes to another objects implementing {@link ~ObservableMixin} * interface (like {@link module:ui/model~Model}). * * Once bound, the observable will immediately share the current state of attributes * of the observable it is bound to and react to the changes to these attributes * in the future. * * **Note**: To release the binding use {@link module:utils/observablemixin~ObservableMixin#unbind}. * * A.bind( 'a' ).to( B ); * A.bind( 'a' ).to( B, 'b' ); * A.bind( 'a', 'b' ).to( B, 'c', 'd' ); * A.bind( 'a' ).to( B, 'b', C, 'd', ( b, d ) => b + d ); * * @method #bind * @param {...String} bindAttrs Observable attributes that will be bound to another observable(s). * @returns {module:utils/observablemixin~BindChain} */ bind( ...bindAttrs ) { if ( !bindAttrs.length || !isStringArray( bindAttrs ) ) { /** * All attributes must be strings. * * @error observable-bind-wrong-attrs */ throw new __WEBPACK_IMPORTED_MODULE_1__ckeditorerror__["a" /* default */]( 'observable-bind-wrong-attrs: All attributes must be strings.' ); } if ( ( new Set( bindAttrs ) ).size !== bindAttrs.length ) { /** * Attributes must be unique. * * @error observable-bind-duplicate-attrs */ throw new __WEBPACK_IMPORTED_MODULE_1__ckeditorerror__["a" /* default */]( 'observable-bind-duplicate-attrs: Attributes must be unique.' ); } initObservable( this ); const boundAttributes = this[ boundAttributesSymbol ]; bindAttrs.forEach( attrName => { if ( boundAttributes.has( attrName ) ) { /** * Cannot bind the same attribute more that once. * * @error observable-bind-rebind */ throw new __WEBPACK_IMPORTED_MODULE_1__ckeditorerror__["a" /* default */]( 'observable-bind-rebind: Cannot bind the same attribute more that once.' ); } } ); const bindings = new Map(); /** * @typedef Binding * @type Object * @property {Array} attr Attribute which is bound. * @property {Array} to Array of observable–attribute components of the binding (`{ observable: ..., attr: .. }`). * @property {Array} callback A function which processes `to` components. */ bindAttrs.forEach( a => { const binding = { attr: a, to: [] }; boundAttributes.set( a, binding ); bindings.set( a, binding ); } ); /** * @typedef BindChain * @type Object * @property {Function} to See {@link ~ObservableMixin#_bindTo}. * @property {module:utils/observablemixin~Observable} _observable The observable which initializes the binding. * @property {Array} _bindAttrs Array of `_observable` attributes to be bound. * @property {Array} _to Array of `to()` observable–attributes (`{ observable: toObservable, attrs: ...toAttrs }`). * @property {Map} _bindings Stores bindings to be kept in * {@link ~ObservableMixin#_boundAttributes}/{@link ~ObservableMixin#_boundObservables} * initiated in this binding chain. */ return { to: bindTo, _observable: this, _bindAttrs: bindAttrs, _to: [], _bindings: bindings }; }, /** * Removes the binding created with {@link ~ObservableMixin#bind}. * * A.unbind( 'a' ); * A.unbind(); * * @method #unbind * @param {...String} [unbindAttrs] Observable attributes to be unbound. All the bindings will * be released if no attributes provided. */ unbind( ...unbindAttrs ) { // Nothing to do here if not inited yet. if ( !( attributesSymbol in this ) ) { return; } const boundAttributes = this[ boundAttributesSymbol ]; const boundObservables = this[ boundObservablesSymbol ]; if ( unbindAttrs.length ) { if ( !isStringArray( unbindAttrs ) ) { /** * Attributes must be strings. * * @error observable-unbind-wrong-attrs */ throw new __WEBPACK_IMPORTED_MODULE_1__ckeditorerror__["a" /* default */]( 'observable-unbind-wrong-attrs: Attributes must be strings.' ); } unbindAttrs.forEach( attrName => { const binding = boundAttributes.get( attrName ); let toObservable, toAttr, toAttrs, toAttrBindings; binding.to.forEach( to => { // TODO: ES6 destructuring. toObservable = to[ 0 ]; toAttr = to[ 1 ]; toAttrs = boundObservables.get( toObservable ); toAttrBindings = toAttrs[ toAttr ]; toAttrBindings.delete( binding ); if ( !toAttrBindings.size ) { delete toAttrs[ toAttr ]; } if ( !Object.keys( toAttrs ).length ) { boundObservables.delete( toObservable ); this.stopListening( toObservable, 'change' ); } } ); boundAttributes.delete( attrName ); } ); } else { boundObservables.forEach( ( bindings, boundObservable ) => { this.stopListening( boundObservable, 'change' ); } ); boundObservables.clear(); boundAttributes.clear(); } }, /** * 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. * * This is a very simplified method decoration. Itself it doesn't change the behavior of a method (expect adding the event), * but it allows to modify it later on by listening to the method's event. * * For example, in order to cancel the method execution one can stop the event: * * 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: we used a high priority listener here to execute this callback before the one which * calls the orignal method (which used the default priority). * * It's also possible to change the return value: * * foo.on( 'method', ( evt ) => { * evt.return = 'Foo!'; * } ); * * foo.method(); // -> 'Foo' * * Finally, it's possible to access and modify the parameters: * * 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. */ 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 __WEBPACK_IMPORTED_MODULE_1__ckeditorerror__["a" /* default */]( 'observablemixin-cannot-decorate-undefined: Cannot decorate an undefined method.', { object: this, methodName } ); } this.on( methodName, ( evt, args ) => { evt.return = originalMethod.apply( this, args ); } ); this[ methodName ] = function( ...args ) { return this.fire( methodName, args ); }; } /** * @private * @member ~ObservableMixin#_boundAttributes */ /** * @private * @member ~ObservableMixin#_boundObservables */ /** * @private * @member ~ObservableMixin#_bindTo */ }; /* harmony default export */ __webpack_exports__["a"] = (ObservableMixin); // Init symbol properties needed to for the observable mechanism to work. // // @private // @param {module:utils/observablemixin~ObservableMixin} observable function initObservable( observable ) { // Do nothing if already inited. if ( attributesSymbol in observable ) { return; } // The internal hash containing the observable's state. // // @private // @type {Map} Object.defineProperty( observable, attributesSymbol, { value: new Map() } ); // Map containing bindings to external observables. It shares the binding objects // (`{ observable: A, attr: 'a', to: ... }`) with {@link module:utils/observablemixin~ObservableMixin#_boundAttributes} and // it is used to observe external observables to update own attributes 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, attr: 'a', to: [ [ B, 'x' ] ] }, // { observable: A, attr: 'c', to: [ [ B, 'x' ] ] } // ] ), // y: Set( [ // { observable: A, attr: '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, attr: 'a', to: [ [ B, 'x' ] ] }, // { observable: A, attr: 'c', to: [ [ B, 'x' ] ] } // ] ), // y: Set( [ // { observable: A, attr: 'b', to: [ [ B, 'y' ] ] }, // ] ), // z: Set( [ // { observable: A, attr: 'd', to: [ [ B, 'z' ], [ C, 'w' ] ], callback: callback } // ] ) // }, // C: { // w: Set( [ // { observable: A, attr: 'd', to: [ [ B, 'z' ], [ C, 'w' ] ], callback: callback } // ] ) // } // } ) // // @private // @type {Map} Object.defineProperty( observable, boundObservablesSymbol, { value: new Map() } ); // Object that stores which attributes of this observable are bound and how. It shares // the binding objects (`{ observable: A, attr: 'a', to: ... }`) with {@link utils.ObservableMixin#_boundObservables}. // This data structure is a reverse of {@link utils.ObservableMixin#_boundObservables} and it is helpful for // {@link utils.ObservableMixin#unbind}. // // See {@link utils.ObservableMixin#bind}. // // A.bind( 'a', 'b', 'c' ).to( B, 'x', 'y', 'x' ); // console.log( A._boundAttributes ); // // Map( { // a: { observable: A, attr: 'a', to: [ [ B, 'x' ] ] }, // b: { observable: A, attr: 'b', to: [ [ B, 'y' ] ] }, // c: { observable: A, attr: 'c', to: [ [ B, 'x' ] ] } // } ) // // A.bind( 'd' ).to( B, 'z' ).to( C, 'w' ).as( callback ); // console.log( A._boundAttributes ); // // Map( { // a: { observable: A, attr: 'a', to: [ [ B, 'x' ] ] }, // b: { observable: A, attr: 'b', to: [ [ B, 'y' ] ] }, // c: { observable: A, attr: 'c', to: [ [ B, 'x' ] ] }, // d: { observable: A, attr: 'd', to: [ [ B, 'z' ], [ C, 'w' ] ], callback: callback } // } ) // // @private // @type {Map} Object.defineProperty( observable, boundAttributesSymbol, { 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-no-callback */ throw new __WEBPACK_IMPORTED_MODULE_1__ckeditorerror__["a" /* default */]( 'observable-bind-to-no-callback: Binding multiple observables only possible with callback.' ); } // Eliminate A.bind( 'x', 'y' ).to( B, callback ) if ( numberOfBindings > 1 && parsedArgs.callback ) { /** * Cannot bind multiple attributes and use a callback in one binding. * * @error observable-bind-to-extra-callback */ throw new __WEBPACK_IMPORTED_MODULE_1__ckeditorerror__["a" /* default */]( 'observable-bind-to-extra-callback: Cannot bind multiple attributes and use a callback in one binding.' ); } parsedArgs.to.forEach( to => { // Eliminate A.bind( 'x', 'y' ).to( B, 'a' ) if ( to.attrs.length && to.attrs.length !== numberOfBindings ) { /** * The number of attributes must match. * * @error observable-bind-to-attrs-length */ throw new __WEBPACK_IMPORTED_MODULE_1__ckeditorerror__["a" /* default */]( 'observable-bind-to-attrs-length: The number of attributes must match.' ); } // When no to.attrs specified, observing source attributes instead i.e. // A.bind( 'x', 'y' ).to( B ) -> Observe B.x and B.y if ( !to.attrs.length ) { to.attrs = this._bindAttrs; } } ); 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._boundAttributes and observable._boundObservables. updateBindToBound( this ); // Set initial values of bound attributes. this._bindAttrs.forEach( attrName => { updateBoundObservableAttr( this._observable, attrName ); } ); } // 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, attrs: [ 'a' ] }, // { observable: C, attrs: [ '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 __WEBPACK_IMPORTED_MODULE_1__ckeditorerror__["a" /* default */]( 'observable-bind-to-parse-error: Invalid argument syntax in `to()`.' ); } const parsed = { to: [] }; let lastObservable; if ( typeof args[ args.length - 1 ] == 'function' ) { parsed.callback = args.pop(); } args.forEach( a => { if ( typeof a == 'string' ) { lastObservable.attrs.push( a ); } else if ( typeof a == 'object' ) { lastObservable = { observable: a, attrs: [] }; parsed.to.push( lastObservable ); } else { throw new __WEBPACK_IMPORTED_MODULE_1__ckeditorerror__["a" /* default */]( 'observable-bind-to-parse-error: Invalid argument syntax in `to()`.' ); } } ); 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} toAttrName A name of `toObservable`'s attribute, a new component of the `binding`. function updateBoundObservables( observable, binding, toObservable, toAttrName ) { const boundObservables = observable[ boundObservablesSymbol ]; const bindingsToObservable = boundObservables.get( toObservable ); const bindings = bindingsToObservable || {}; if ( !bindings[ toAttrName ] ) { bindings[ toAttrName ] = new Set(); } // Pass the binding to a corresponding Set in `observable._boundObservables`. bindings[ toAttrName ].add( binding ); if ( !bindingsToObservable ) { boundObservables.set( toObservable, bindings ); } } // Synchronizes {@link Observable#_boundAttributes} 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, attr: 'a', to: [] }, // b: { observable: A, attr: '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, attr: 'a', to: [ B, 'x' ] }, // b: { observable: A, attr: 'b', to: [ B, 'y' ] }, // } // // Then update the structure of {@link Observable#_boundObservables} with updated // binding, so it becomes: // // Map( { // B: { // x: Set( [ // { observable: A, attr: 'a', to: [ [ B, 'x' ] ] } // ] ), // y: Set( [ // { observable: A, attr: 'b', to: [ [ B, 'y' ] ] }, // ] ) // } // } ) // // @private // @param {BindChain} chain The binding initialized by {@link Observable#bind}. function updateBindToBound( chain ) { let toAttr; chain._bindings.forEach( ( binding, attrName ) => { // 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 => { toAttr = to.attrs[ binding.callback ? 0 : chain._bindAttrs.indexOf( attrName ) ]; binding.to.push( [ to.observable, toAttr ] ); updateBoundObservables( chain._observable, binding, to.observable, toAttr ); } ); } ); } // Updates an attribute of a {@link Observable} with a value // determined by an entry in {@link Observable#_boundAttributes}. // // @private // @param {Observable} observable A observable which attribute is to be updated. // @param {String} attrName An attribute to be updated. function updateBoundObservableAttr( observable, attrName ) { const boundAttributes = observable[ boundAttributesSymbol ]; const binding = boundAttributes.get( attrName ); let attrValue; // 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 ) { attrValue = binding.callback.apply( observable, binding.to.map( to => to[ 0 ][ to[ 1 ] ] ) ); } else { attrValue = binding.to[ 0 ]; attrValue = attrValue[ 0 ][ attrValue[ 1 ] ]; } if ( observable.hasOwnProperty( attrName ) ) { observable[ attrName ] = attrValue; } else { observable.set( attrName, attrValue ); } } // Starts listening to changes in {@link BindChain._to} observables to update // {@link BindChain._observable} {@link BindChain._bindAttrs}. 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, attrName ) => { bindings = boundObservables.get( to.observable )[ attrName ]; // Note: to.observable will fire for any attribute change, react // to changes of attributes which are bound only. if ( bindings ) { bindings.forEach( binding => { updateBoundObservableAttr( observable, binding.attr ); } ); } } ); } } ); } Object(__WEBPACK_IMPORTED_MODULE_2__lib_lodash_extend__["a" /* default */])( ObservableMixin, __WEBPACK_IMPORTED_MODULE_0__emittermixin__["c" /* default */] ); /** * Fired when an attribute changed value. * * @event module:utils/observablemixin~ObservableMixin#change:{attribute} * @param {String} name The attribute name. * @param {*} value The new attribute value. * @param {*} oldValue The previous attribute value. */ /** * Interface representing classes which mix in {@link module:utils/observablemixin~ObservableMixin}. * * @interface Observable */ /***/ }), /* 13 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__toFinite__ = __webpack_require__(291); /** * Converts `value` to an integer. * * **Note:** This function is loosely based on * [`ToInteger`](http://www.ecma-international.org/ecma-262/6.0/#sec-tointeger). * * @static * @memberOf _ * @since 4.0.0 * @category Lang * @param {*} value The value to convert. * @returns {number} Returns the converted integer. * @example * * _.toInteger(3.2); * // => 3 * * _.toInteger(Number.MIN_VALUE); * // => 0 * * _.toInteger(Infinity); * // => 1.7976931348623157e+308 * * _.toInteger('3.2'); * // => 3 */ function toInteger(value) { var result = Object(__WEBPACK_IMPORTED_MODULE_0__toFinite__["a" /* default */])(value), remainder = result % 1; return result === result ? (remainder ? result - remainder : result) : 0; } /* harmony default export */ __webpack_exports__["a"] = (toInteger); /***/ }), /* 14 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__isArrayLike__ = __webpack_require__(78); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__isObjectLike__ = __webpack_require__(43); /** * 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 Object(__WEBPACK_IMPORTED_MODULE_1__isObjectLike__["a" /* default */])(value) && Object(__WEBPACK_IMPORTED_MODULE_0__isArrayLike__["a" /* default */])(value); } /* harmony default export */ __webpack_exports__["a"] = (isArrayLikeObject); /***/ }), /* 15 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; /** * Checks if `value` is classified as an `Array` object. * * @static * @memberOf _ * @since 0.1.0 * @type {Function} * @category Lang * @param {*} value The value to check. * @returns {boolean} Returns `true` if `value` is correctly classified, * 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 */ __webpack_exports__["a"] = (isArray); /***/ }), /* 16 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; /** * Checks if `value` is the * [language type](http://www.ecma-international.org/ecma-262/6.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 && (type == 'object' || type == 'function'); } /* harmony default export */ __webpack_exports__["a"] = (isObject); /***/ }), /* 17 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; /** * 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 ? array.length : 0; return length ? array[length - 1] : undefined; } /* harmony default export */ __webpack_exports__["a"] = (last); /***/ }), /* 18 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__ckeditor_ckeditor5_utils_src_ckeditorerror__ = __webpack_require__(0); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__operation_operationfactory__ = __webpack_require__(355); /** * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.md. */ /** * @module engine/model/delta/deltafactory */ const deserializers = new Map(); /** * A factory class for creating operations. * * Delta is a single, from the user action point of view, change in the editable document, like insert, split or * rename element. Delta is composed of operations, which are unit changes needed to be done to execute user action. * * Multiple deltas are grouped into a single {@link module:engine/model/batch~Batch}. */ class DeltaFactory { /** * Creates InsertDelta from deserialized object, i.e. from parsed JSON string. * * @param {Object} json * @param {module:engine/model/document~Document} doc Document on which this delta will be applied. * @returns {module:engine/model/delta/insertdelta~InsertDelta} */ static fromJSON( json, doc ) { if ( !deserializers.has( json.__className ) ) { /** * This delta has no defined deserializer. * * @error delta-fromjson-no-deserializer * @param {String} name */ throw new __WEBPACK_IMPORTED_MODULE_0__ckeditor_ckeditor5_utils_src_ckeditorerror__["a" /* default */]( 'delta-fromjson-no-deserializer: This delta has no defined deserializer', { name: json.__className } ); } const Delta = deserializers.get( json.__className ); const delta = new Delta(); for ( const operation of json.operations ) { delta.addOperation( __WEBPACK_IMPORTED_MODULE_1__operation_operationfactory__["a" /* default */].fromJSON( operation, doc ) ); } // Rewrite all other properties. for ( const prop in json ) { if ( prop != '__className' && delta[ prop ] === undefined ) { delta[ prop ] = json[ prop ]; } } return delta; } /** * Registers a class for delta factory. * * @param {Function} Delta A delta class to register. */ static register( Delta ) { deserializers.set( Delta.className, Delta ); } } /* harmony export (immutable) */ __webpack_exports__["a"] = DeltaFactory; /***/ }), /* 19 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; /* harmony export (immutable) */ __webpack_exports__["b"] = register; /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__ckeditor_ckeditor5_utils_src_ckeditorerror__ = __webpack_require__(0); /** * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.md. */ /** * @module engine/model/batch */ /** * `Batch` instance groups document changes ({@link module:engine/model/delta/delta~Delta deltas}). All deltas grouped in a single `Batch` * can be reverted together, so you can think about `Batch` as of a single undo step. If you want to extend given undo step you * can call another method on the same `Batch` object. If you want to create a separate undo step you can create a new `Batch`. * * For example to create two separate undo steps you can call: * * doc.batch().insert( firstPosition, 'foo' ); * doc.batch().insert( secondPosition, 'bar' ); * * To create a single undo step: * * const batch = doc.batch(); * batch.insert( firstPosition, 'foo' ); * batch.insert( secondPosition, 'bar' ); * * Note that all document modification methods (insert, remove, split, etc.) are chainable so you can shorten code to: * * doc.batch().insert( firstPosition, 'foo' ).insert( secondPosition, 'bar' ); */ class Batch { /** * Creates `Batch` instance. Not recommended to use directly, use {@link module:engine/model/document~Document#batch} instead. * * @param {module:engine/model/document~Document} document Document which this Batch changes. * @param {'transparent'|'default'} [type='default'] Type of the batch. */ constructor( document, type = 'default' ) { /** * Document which this batch changes. * * @readonly * @member {module:engine/model/document~Document} module:engine/model/batch~Batch#document */ this.document = document; /** * Array of deltas which compose this batch. * * @readonly * @member {Array.} module:engine/model/batch~Batch#deltas */ this.deltas = []; /** * Type of the batch. * * Can be one of the following values: * * `'default'` - all "normal" batches, most commonly used type. * * `'transparent'` - batch that should be ignored by other features, i.e. initial batch or collaborative editing changes. * * @readonly * @member {'transparent'|'default'} module:engine/model/batch~Batch#type */ this.type = type; } /** * Returns this batch base version, which is equal to the base version of first delta in the batch. * If there are no deltas in the batch, it returns `null`. * * @readonly * @type {Number|null} */ get baseVersion() { return this.deltas.length > 0 ? this.deltas[ 0 ].baseVersion : null; } /** * Adds delta to the batch instance. All modification methods (insert, remove, split, etc.) use this method * to add created deltas. * * @param {module:engine/model/delta/delta~Delta} delta Delta to add. * @return {module:engine/model/delta/delta~Delta} Added delta. */ addDelta( delta ) { delta.batch = this; this.deltas.push( delta ); return delta; } /** * Gets an iterable collection of operations. * * @returns {Iterable.} */ * getOperations() { for ( const delta of this.deltas ) { yield* delta.operations; } } } /* harmony export (immutable) */ __webpack_exports__["a"] = Batch; /** * Function to register batch methods. To make code scalable `Batch` do not have modification * methods built in. They can be registered using this method. * * This method checks if there is no naming collision and throws `batch-register-taken` if the method name * is already taken. * * Besides that no magic happens here, the method is added to the `Batch` class prototype. * * For example: * * Batch.register( 'insert', function( position, nodes ) { * // You can use a class inheriting from `Delta` if that class should handle OT in a special way. * const delta = new Delta(); * * // Add delta to the Batch instance. It is important to add a delta to the batch before applying any operation. * this.addDelta( delta ); * * // Create operations which should be components of this delta. * const operation = new InsertOperation( position, nodes, this.document.version ); * * // Add operation to the delta. It is important to add operation before applying it. * delta.addOperation( operation ); * * // Remember to apply every operation, no magic, you need to do it manually. * this.document.applyOperation( operation ); * * // Make this method chainable. * return this; * } ); * * @method module:engine/model/batch~Batch.register * @param {String} name Method name. * @param {Function} creator Method body. */ function register( name, creator ) { if ( Batch.prototype[ name ] ) { /** * This batch method name is already taken. * * @error batch-register-taken * @param {String} name */ throw new __WEBPACK_IMPORTED_MODULE_0__ckeditor_ckeditor5_utils_src_ckeditorerror__["a" /* default */]( 'model-batch-register-taken: This batch method name is already taken.', { name } ); } Batch.prototype[ name ] = creator; } /***/ }), /* 20 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__view__ = __webpack_require__(7); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__template__ = __webpack_require__(5); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__icon_iconview__ = __webpack_require__(436); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_3__tooltip_tooltipview__ = __webpack_require__(437); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_4__ckeditor_ckeditor5_utils_src_keyboard__ = __webpack_require__(30); /** * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.md. */ /** * @module ui/button/buttonview */ /** * The button view class. * * @extends module:ui/view~View */ class ButtonView extends __WEBPACK_IMPORTED_MODULE_0__view__["a" /* default */] { /** * @inheritDoc */ constructor( locale ) { super( locale ); /** * The label of the button view visible to the user. * * @observable * @member {String} #label */ this.set( 'label' ); /** * (Optional) The keystroke associated with the button, i.e. CTRL+B, * in the string format compatible with {@link module:utils/keyboard}. * * @observable * @member {Boolean} #keystroke */ this.set( 'keystroke' ); /** * (Optional) Tooltip of the button, i.e. displayed when hovering the button with the mouse cursor. * * * If defined as a `Boolean` (e.g. `true`), then combination of `label` and `keystroke` will be set as a tooltip. * * If defined as a `String`, tooltip will equal the exact text of that `String`. * * If defined as a `Function`, `label` and `keystroke` will be passed to that function, which is to return * a string with the tooltip text. * * const view = new ButtonView( locale ); * view.tooltip = ( label, keystroke ) => `A tooltip for ${ label } and ${ keystroke }.` * * @observable * @default false * @member {Boolean|String|Function} #tooltip */ this.set( 'tooltip' ); /** * The position of the tooltip. See {@link ui/tooltip/tooltipview~TooltipView#position} * to learn more about the available position values. * * **Note:** It makes sense only when the {@link #tooltip} is active. * * @observable * @default 's' * @member {'s'|'n'} #position */ this.set( 'tooltipPosition', 's' ); /** * The HTML type of the button. Default `button`. * * @observable * @member {'button'|'submit'|'reset'|'menu'} #type */ this.set( 'type', 'button' ); /** * Controls whether the button view is "on", e.g. some feature which it represents * is currently enabled. * * @observable * @member {Boolean} #isOn */ this.set( 'isOn', false ); /** * Controls whether the button view is enabled (can be clicked). * * @observable * @member {Boolean} #isEnabled */ this.set( 'isEnabled', true ); /** * Controls whether the button view is visible. * * @observable * @member {Boolean} #isVisible */ this.set( 'isVisible', true ); /** * (Optional) Whether the label of the button is hidden (e.g. button with icon only). * * @observable * @member {Boolean} #withText */ this.set( 'withText', false ); /** * (Optional) Source of the icon. See {@link module:ui/icon/iconview~IconView#content}. * * @observable * @member {String} #icon */ this.set( 'icon' ); /** * Controls the `tabindex` attribute of the button. * * @observable * @default -1 * @member {String} #tabindex */ this.set( 'tabindex', -1 ); /** * 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 ) ); /** * Icon of the button view. * * @readonly * @member {module:ui/icon/iconview~IconView} #iconView */ /** * Tooltip of the button view. * * @readonly * @member {module:ui/tooltip/tooltipview~TooltipView} #tooltipView */ const bind = this.bindTemplate; this.template = new __WEBPACK_IMPORTED_MODULE_1__template__["a" /* default */]( { tag: 'button', attributes: { class: [ 'ck-button', bind.to( 'isEnabled', value => value ? 'ck-enabled' : 'ck-disabled' ), bind.if( 'isVisible', 'ck-hidden', value => !value ), bind.to( 'isOn', value => value ? 'ck-on' : 'ck-off' ), bind.if( 'withText', 'ck-button_with-text' ) ], type: bind.to( 'type', value => value ? value : 'button' ), tabindex: bind.to( 'tabindex' ) }, children: [ { tag: 'span', attributes: { class: [ 'ck-button__label' ] }, children: [ { text: bind.to( 'label' ) } ] } ], 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(); } } ) } } ); /** * Fired when the button view is clicked. It won't be fired when the button is disabled. * * @event #execute */ } /** * @inheritDoc */ init() { if ( this.icon ) { const iconView = this.iconView = new __WEBPACK_IMPORTED_MODULE_2__icon_iconview__["a" /* default */](); iconView.bind( 'content' ).to( this, 'icon' ); this.element.insertBefore( iconView.element, this.element.firstChild ); // Make sure the icon will be destroyed along with the button. this.addChildren( iconView ); } if ( this.tooltip ) { const tooltipView = this.tooltipView = new __WEBPACK_IMPORTED_MODULE_3__tooltip_tooltipview__["a" /* default */](); tooltipView.bind( 'text' ).to( this, '_tooltipString' ); tooltipView.bind( 'position' ).to( this, 'tooltipPosition' ); this.element.appendChild( tooltipView.element ); // Make sure the tooltip will be destroyed along with the button. this.addChildren( tooltipView ); } super.init(); } /** * Focuses the button. */ focus() { this.element.focus(); } /** * 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 = Object(__WEBPACK_IMPORTED_MODULE_4__ckeditor_ckeditor5_utils_src_keyboard__["b" /* getEnvKeystrokeText */])( keystroke ); } if ( tooltip instanceof Function ) { return tooltip( label, keystroke ); } else if ( tooltip === true ) { return `${ label }${ keystroke ? ` (${ keystroke })` : '' }`; } } } return false; } } /* harmony export (immutable) */ __webpack_exports__["a"] = ButtonView; /***/ }), /* 21 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; /* harmony export (immutable) */ __webpack_exports__["b"] = insert; /* harmony export (immutable) */ __webpack_exports__["d"] = remove; /* unused harmony export move */ /* unused harmony export setAttribute */ /* unused harmony export removeAttribute */ /* harmony export (immutable) */ __webpack_exports__["c"] = normalizeNodes; /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__node__ = __webpack_require__(63); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__text__ = __webpack_require__(25); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__textproxy__ = __webpack_require__(64); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_3__range__ = __webpack_require__(3); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_4__documentfragment__ = __webpack_require__(35); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_5__nodelist__ = __webpack_require__(81); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_6__ckeditor_ckeditor5_utils_src_ckeditorerror__ = __webpack_require__(0); /** * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.md. */ /** * @module engine/model/writer */ /** * Contains functions used for composing model tree, grouped together under "model writer" name. Those functions * are built on top of {@link module:engine/model/node~Node node}, and it's child classes', APIs. * * Model writer API has multiple advantages and it is highly recommended to use it when changing model tree and nodes: * * model writer API {@link module:engine/model/writer~writer.normalizeNodes normalizes inserted nodes}, which means that you can insert * not only {@link module:engine/model/node~Node nodes}, but also `String`s, {@link module:engine/model/textproxy~TextProxy text proxies} * and * {@link module:engine/model/documentfragment~DocumentFragment document fragments}, * * model writer API operates on {@link module:engine/model/position~Position positions}, which means that you have * better control over manipulating model tree as positions operate on offsets rather than indexes, * * model writer API automatically merges {@link module:engine/model/text~Text text nodes} with same attributes, which means * lower memory usage and better efficiency. * * @namespace writer */ const writer = { insert, remove, move, setAttribute, removeAttribute, normalizeNodes }; /* harmony default export */ __webpack_exports__["a"] = (writer); /** * Inserts given nodes at given position. * * @function module:engine/model/writer~writer.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.insertChildren( 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 __WEBPACK_IMPORTED_MODULE_3__range__["a" /* default */]( position, position.getShiftedBy( offset ) ); } /** * Removed nodes in given range. Only {@link module:engine/model/range~Range#isFlat flat} ranges are accepted. * * @function module:engine/model/writer~writer.remove * @param {module:engine/model/range~Range} range Range containing nodes to remove. * @returns {Array.} */ function remove( range ) { if ( !range.isFlat ) { /** * Trying to remove a range that starts and ends in different element. * * @error model-writer-remove-range-not-flat */ throw new __WEBPACK_IMPORTED_MODULE_6__ckeditor_ckeditor5_utils_src_ckeditorerror__["a" /* default */]( 'model-writer-remove-range-not-flat: ' + 'Trying to remove a range that starts and ends in different element.' ); } 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. * * @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 that starts and ends in different element. * * @error model-writer-move-range-not-flat */ throw new __WEBPACK_IMPORTED_MODULE_6__ckeditor_ckeditor5_utils_src_ckeditorerror__["a" /* default */]( 'model-writer-move-range-not-flat: ' + 'Trying to move a range that starts and ends in different element.' ); } const nodes = this.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 this.insert( targetPosition, nodes ); } /** * Sets given attribute on nodes in given range. * * @param {module:engine/model/range~Range} range Range containing nodes that should have the attribute set. * @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() ) { // 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 ); } /** * Removes given attribute from nodes in given range. * * @param {module:engine/model/range~Range} range Range containing nodes that should have the attribute removed. * @param {String} key Key of attribute to remove. */ function removeAttribute( range, key ) { this.setAttribute( range, key, null ); } /** * 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. * * @param {module:engine/model/node~NodeSet} nodes Objects to normalize. * @returns {Array.} 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 __WEBPACK_IMPORTED_MODULE_1__text__["a" /* default */]( nodes[ i ] ) ); } else if ( nodes[ i ] instanceof __WEBPACK_IMPORTED_MODULE_2__textproxy__["a" /* default */] ) { normalized.push( new __WEBPACK_IMPORTED_MODULE_1__text__["a" /* default */]( nodes[ i ].data, nodes[ i ].getAttributes() ) ); } else if ( nodes[ i ] instanceof __WEBPACK_IMPORTED_MODULE_4__documentfragment__["a" /* default */] || nodes[ i ] instanceof __WEBPACK_IMPORTED_MODULE_5__nodelist__["a" /* default */] ) { for ( const child of nodes[ i ] ) { normalized.push( child ); } } else if ( nodes[ i ] instanceof __WEBPACK_IMPORTED_MODULE_0__node__["a" /* 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 __WEBPACK_IMPORTED_MODULE_1__text__["a" /* default */] && prev instanceof __WEBPACK_IMPORTED_MODULE_1__text__["a" /* default */] && _haveSameAttributes( node, prev ) ) { // Doing this instead changing prev.data because .data is readonly. normalized.splice( i - 1, 2, new __WEBPACK_IMPORTED_MODULE_1__text__["a" /* 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. * * @ignore * @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 __WEBPACK_IMPORTED_MODULE_1__text__["a" /* default */]( nodeBefore.data + nodeAfter.data, nodeBefore.getAttributes() ); // Remove separate text nodes. element.removeChildren( index - 1, 2 ); // Insert merged text node. element.insertChildren( 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. * * @ignore * @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 __WEBPACK_IMPORTED_MODULE_1__text__["a" /* default */]( textNode.data.substr( 0, offsetDiff ), textNode.getAttributes() ); const secondPart = new __WEBPACK_IMPORTED_MODULE_1__text__["a" /* default */]( textNode.data.substr( offsetDiff ), textNode.getAttributes() ); element.insertChildren( index, [ firstPart, secondPart ] ); } } /** * Checks whether two given nodes have same attributes. * * @ignore * @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 */ /***/ }), /* 22 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__ckeditor_ckeditor5_utils_src_lib_lodash_clone__ = __webpack_require__(86); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__deltafactory__ = __webpack_require__(18); /** * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.md. */ /** * @module engine/model/delta/delta */ /** * Base class for all deltas. * * Delta is a single, from the user action point of view, change in the editable document, like insert, split or * rename element. Delta is composed of operations, which are unit changes needed to be done to execute user action. * * Multiple deltas are grouped into a single {@link module:engine/model/batch~Batch}. */ class Delta { /** * Creates a delta instance. */ constructor() { /** * {@link module:engine/model/batch~Batch} which delta is a part of. This property is null by default and set by the * {@link module:engine/model/batch~Batch#addDelta} method. * * @readonly * @member {module:engine/model/batch~Batch} module:engine/model/delta/delta~Delta#batch */ this.batch = null; /** * Array of operations which compose delta. * * @readonly * @member {module:engine/model/operation/operation~Operation[]} module:engine/model/delta/delta~Delta#operations */ this.operations = []; } /** * Returns delta base version which is equal to the base version of the first operation in delta. If there * are no operations in delta, returns `null`. * * @see module:engine/model/document~Document * @type {Number|null} */ get baseVersion() { if ( this.operations.length > 0 ) { return this.operations[ 0 ].baseVersion; } return null; } /** * @param {Number} baseVersion */ set baseVersion( baseVersion ) { for ( const operation of this.operations ) { operation.baseVersion = baseVersion++; } } /** * A class that will be used when creating reversed delta. * * @private * @type {Function} */ get _reverseDeltaClass() { return Delta; } /** * Delta type. * * @readonly * @member {String} #type */ /** * Add operation to the delta. * * @param {module:engine/model/operation/operation~Operation} operation Operation instance. */ addOperation( operation ) { operation.delta = this; this.operations.push( operation ); return operation; } /** * Creates and returns a delta that has the same parameters as this delta. * * @returns {module:engine/model/delta/delta~Delta} Clone of this delta. */ clone() { const delta = new this.constructor(); for ( const op of this.operations ) { delta.addOperation( op.clone() ); } return delta; } /** * Creates and returns a reverse delta. Reverse delta when executed right after the original delta will bring back * tree model state to the point before the original delta execution. In other words, it reverses changes done * by the original delta. * * Keep in mind that tree model state may change since executing the original delta, so reverse delta may be "outdated". * In that case you will need to {@link module:engine/model/delta/transform~transform} it by all deltas that were executed after * the original delta. * * @returns {module:engine/model/delta/delta~Delta} Reversed delta. */ getReversed() { const delta = new this._reverseDeltaClass(); for ( const op of this.operations ) { delta.addOperation( op.getReversed() ); } delta.operations.reverse(); for ( let i = 0; i < delta.operations.length; i++ ) { delta.operations[ i ].baseVersion = this.operations[ this.operations.length - 1 ].baseVersion + i + 1; } return delta; } /** * Custom toJSON method to make deltas serializable. * * @returns {Object} Clone of this delta with added class name. */ toJSON() { const json = Object(__WEBPACK_IMPORTED_MODULE_0__ckeditor_ckeditor5_utils_src_lib_lodash_clone__["a" /* default */])( this ); json.__className = this.constructor.className; // Remove parent batch to avoid circular dependencies. delete json.batch; return json; } /** * Delta class name. Used by {@link #toJSON} method for serialization and * {@link module:engine/model/delta/deltafactory~DeltaFactory.fromJSON} during deserialization. * * @type {String} * @readonly */ static get className() { return 'engine.model.delta.Delta'; } } /* harmony export (immutable) */ __webpack_exports__["a"] = Delta; __WEBPACK_IMPORTED_MODULE_1__deltafactory__["a" /* default */].register( Delta ); /***/ }), /* 23 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__arrayPush__ = __webpack_require__(91); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__isFlattenable__ = __webpack_require__(367); /** * The base implementation of `_.flatten` with support for restricting flattening. * * @private * @param {Array} array The array to flatten. * @param {number} depth The maximum recursion depth. * @param {boolean} [predicate=isFlattenable] The function invoked per iteration. * @param {boolean} [isStrict] Restrict to values that pass `predicate` checks. * @param {Array} [result=[]] The initial result value. * @returns {Array} Returns the new flattened array. */ function baseFlatten(array, depth, predicate, isStrict, result) { var index = -1, length = array.length; predicate || (predicate = __WEBPACK_IMPORTED_MODULE_1__isFlattenable__["a" /* default */]); result || (result = []); while (++index < length) { var value = array[index]; if (depth > 0 && predicate(value)) { if (depth > 1) { // Recursively flatten arrays (susceptible to call stack limits). baseFlatten(value, depth - 1, predicate, isStrict, result); } else { Object(__WEBPACK_IMPORTED_MODULE_0__arrayPush__["a" /* default */])(result, value); } } else if (!isStrict) { result[result.length] = value; } } return result; } /* harmony default export */ __webpack_exports__["a"] = (baseFlatten); /***/ }), /* 24 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; /** * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.md. */ /* global console */ /** * @module utils/log */ /** * The logging module. * * This object features two functions that should be used across CKEditor code base to log errors and warnings. * Despite being an overridable interface for native `console.*` this module serves also the goal to limit the * code size of a minified CKEditor package. During minification process the messages will be shortened and * links to their documentation will be logged to the console. * * All errors and warning should be documented in the following way: * * /** * * 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. * * / * log.error( 'plugin-load: It was not possible to load the "{$pluginName}" plugin in module "{$moduleName}', { * pluginName: 'foo', * moduleName: 'bar' * } ); * * ### Warning vs Error vs Throw * * * Whenever a potentially incorrect situation occurs, which does not directly lead to an incorrect behavior, * log a warning. * * Whenever an incorrect situation occurs, but the app may continue working (although perhaps incorrectly), * log an error. * * Whenever it's really bad and it does not make sense to continue working, throw a {@link module:utils/ckeditorerror~CKEditorError}. * * @namespace */ const log = { /** * Logs an error to the console. * * Read more about error logging in the {@link module:utils/log} module. * * @param {String} message The error message in an `error-name: Error message.` format. * During the minification process the "Error message" part will be removed to limit the code size * and a link to this error documentation will be logged to the console. * @param {Object} [data] Additional data describing the error. */ error( message, data ) { console.error( message, data ); }, /** * Logs a warning to the console. * * Read more about error logging in the {@link module:utils/log} module. * * @param {String} message The warning message in a `warning-name: Warning message.` format. * During the minification process the "Warning message" part will be removed to limit the code size * and a link to this error documentation will be logged to the console. * @param {Object} [data] Additional data describing the warning. */ warn( message, data ) { console.warn( message, data ); } }; /* harmony default export */ __webpack_exports__["a"] = (log); /***/ }), /* 25 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__node__ = __webpack_require__(63); /** * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.md. */ /** * @module engine/model/text */ /** * 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. */ class Text extends __WEBPACK_IMPORTED_MODULE_0__node__["a" /* default */] { /** * Creates a text node. * * @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. * * @type {String} */ this.data = data || ''; } /** * @inheritDoc */ get offsetSize() { return this.data.length; } /** * @inheritDoc */ is( type ) { return type == 'text'; } /** * Creates a copy of this text node and returns it. Created text node has same text data and attributes as original text node. */ clone() { return new Text( this.data, this.getAttributes() ); } /** * 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 `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 ); } } /* harmony export (immutable) */ __webpack_exports__["a"] = Text; /***/ }), /* 26 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__treewalker__ = __webpack_require__(56); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__ckeditor_ckeditor5_utils_src_comparearrays__ = __webpack_require__(55); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__ckeditor_ckeditor5_utils_src_ckeditorerror__ = __webpack_require__(0); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_3__editableelement__ = __webpack_require__(83); /** * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.md. */ /** * @module engine/view/position */ /** * Position in the tree. Position is always located before or after a node. */ 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. * * @member {module:engine/view/node~Node|module:engine/view/documentfragment~DocumentFragment} * module:engine/view/position~Position#parent */ this.parent = parent; /** * Position offset. * * @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 __WEBPACK_IMPORTED_MODULE_3__editableelement__["a" /* 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.createFromPosition( 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' ); //

{}foo

->

foo[]

* getLastMatchingPosition( value => value.type == 'text', { direction: 'backward' } ); //

foo[]

->

{}foo

* 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 __WEBPACK_IMPORTED_MODULE_0__treewalker__["a" /* 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 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.isEqual( otherPosition ) ) { return 'same'; } // If positions have same parent. if ( this.parent === otherPosition.parent ) { return this.offset - otherPosition.offset < 0 ? 'before' : 'after'; } // Get path from root to position's parent element. const path = this.getAncestors(); const otherPath = otherPosition.getAncestors(); // Compare both path arrays to find common ancestor. const result = Object(__WEBPACK_IMPORTED_MODULE_1__ckeditor_ckeditor5_utils_src_comparearrays__["a" /* default */])( path, otherPath ); let commonAncestorIndex; switch ( result ) { case 0: // No common ancestors found. return 'different'; case 'prefix': commonAncestorIndex = path.length - 1; break; case 'extension': commonAncestorIndex = otherPath.length - 1; break; default: commonAncestorIndex = result - 1; } // Common ancestor of two positions. const commonAncestor = path[ commonAncestorIndex ]; const nextAncestor1 = path[ commonAncestorIndex + 1 ]; const nextAncestor2 = otherPath[ commonAncestorIndex + 1 ]; // Check if common ancestor is not one of the parents. if ( commonAncestor === this.parent ) { const index = this.offset - nextAncestor2.index; return index <= 0 ? 'before' : 'after'; } else if ( commonAncestor === otherPosition.parent ) { const index = nextAncestor1.index - otherPosition.offset; return index < 0 ? 'before' : 'after'; } const index = nextAncestor1.index - nextAncestor2.index; // Compare indexes of next ancestors inside common one. return index < 0 ? 'before' : 'after'; } /** * 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}, * * {@link module:engine/view/position~Position.createFromPosition}. * * @param {module:engine/view/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/view/item~Item view item}. */ static createAt( itemOrPosition, offset ) { if ( itemOrPosition instanceof Position ) { return this.createFromPosition( itemOrPosition ); } 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 ) { offset = 0; } return new Position( node, 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} */ 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 position-after-root * @param {module:engine/view/node~Node} root */ throw new __WEBPACK_IMPORTED_MODULE_2__ckeditor_ckeditor5_utils_src_ckeditorerror__["a" /* default */]( 'view-position-after-root: You can not make position after root.', { root: item } ); } return new Position( item.parent, item.index + 1 ); } /** * 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} */ 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 position-before-root * @param {module:engine/view/node~Node} root */ throw new __WEBPACK_IMPORTED_MODULE_2__ckeditor_ckeditor5_utils_src_ckeditorerror__["a" /* default */]( 'view-position-before-root: You can not make position before root.', { root: item } ); } return new Position( item.parent, item.index ); } /** * Creates and returns a new instance of `Position`, which is equal to the passed position. * * @param {module:engine/view/position~Position} position Position to be cloned. * @returns {module:engine/view/position~Position} */ static createFromPosition( position ) { return new this( position.parent, position.offset ); } } /* harmony export (immutable) */ __webpack_exports__["a"] = Position; /** * 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 */ /***/ }), /* 27 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__node__ = __webpack_require__(82); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__text__ = __webpack_require__(34); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__ckeditor_ckeditor5_utils_src_objecttomap__ = __webpack_require__(164); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_3__ckeditor_ckeditor5_utils_src_isiterable__ = __webpack_require__(46); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_4__ckeditor_ckeditor5_utils_src_lib_lodash_isPlainObject__ = __webpack_require__(75); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_5__matcher__ = __webpack_require__(165); /** * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.md. */ /** * @module engine/view/element */ /** * View element. * * Editing engine does not define fixed HTML DTD. This is why the type of the {@link module:engine/view/element~Element} need to * be defined by the feature developer. Creating an element you should use {@link module:engine/view/containerelement~ContainerElement} * class, {@link module:engine/view/attributeelement~AttributeElement} class or {@link module:engine/view/emptyelement~EmptyElement} class. * * Note that for view elements which are not created from 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, so * these will be instances of the {@link module:engine/view/element~Element}. * * @extends module:engine/view/node~Node */ class Element extends __WEBPACK_IMPORTED_MODULE_0__node__["a" /* default */] { /** * Creates a view element. * * Attributes can be passed in various formats: * * new Element( 'div', { 'class': 'editor', 'contentEditable': 'true' } ); // object * new Element( 'div', [ [ 'class', 'editor' ], [ 'contentEditable', 'true' ] ] ); // map-like iterator * new Element( 'div', mapOfAttributes ); // map * * @param {String} name Node name. * @param {Object|Iterable} [attrs] Collection of attributes. * @param {module:engine/view/node~Node|Iterable.} [children] * List of nodes to be inserted into created element. */ constructor( name, attrs, children ) { super(); /** * 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 */ if ( Object(__WEBPACK_IMPORTED_MODULE_4__ckeditor_ckeditor5_utils_src_lib_lodash_isPlainObject__["a" /* default */])( attrs ) ) { this._attrs = Object(__WEBPACK_IMPORTED_MODULE_2__ckeditor_ckeditor5_utils_src_objecttomap__["a" /* default */])( attrs ); } else { this._attrs = new Map( attrs ); } /** * Array of child nodes. * * @protected * @member {Array.} */ this._children = []; if ( children ) { this.insertChildren( 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' ); } /** * Map of styles. * * @protected * @member {Set} module:engine/view/element~Element#_styles */ this._styles = new Map(); if ( this._attrs.has( 'style' ) ) { // Remove style attribute and handle it by styles map. parseInlineStyles( this._styles, 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 * @memeber {Map} */ this._customProperties = new Map(); } /** * 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; } /** * Checks whether given view tree object is of given type. * * Read more in {@link module:engine/view/node~Node#is}. * * @param {String} type * @param {String} [name] Element name. * @returns {Boolean} */ is( type, name = null ) { if ( !name ) { return type == 'element' || type == this.name; } else { return type == 'element' && name == this.name; } } /** * Clones provided element. * * @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.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 = new Map( this._styles ); // 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; return cloned; } /** * {@link module:engine/view/element~Element#insertChildren 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. * * @fires module:engine/view/node~Node#change * @param {module:engine/view/node~Node|Iterable.} nodes Node or the list of nodes to be inserted. * @returns {Number} Number of appended nodes. */ appendChildren( nodes ) { return this.insertChildren( this.childCount, nodes ); } /** * 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.} 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 {Iterator.} Keys for attributes. */ * getAttributeKeys() { if ( this._classes.size > 0 ) { yield 'class'; } if ( this._styles.size > 0 ) { yield 'style'; } // This is not an optimal solution because of https://github.com/ckeditor/ckeditor5-engine/issues/454. // It can be simplified to `yield* this._attrs.keys();`. for ( const key of this._attrs.keys() ) { yield key; } } /** * 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.size > 0 ) { 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' ) { if ( this._styles.size > 0 ) { let styleString = ''; for ( const [ property, value ] of this._styles ) { styleString += `${ property }:${ value };`; } return styleString; } return undefined; } 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.size > 0; } return this._attrs.has( key ); } /** * Adds or overwrite attribute with a specified key and value. * * @param {String} key Attribute key. * @param {String} value Attribute value. * @fires module:engine/view/node~Node#change */ setAttribute( key, value ) { this._fireChange( 'attributes', this ); if ( key == 'class' ) { parseClasses( this._classes, value ); } else if ( key == 'style' ) { parseInlineStyles( this._styles, value ); } else { this._attrs.set( key, value ); } } /** * Inserts a child node or a list of child nodes on the given index and sets the parent of these nodes to * this element. * * @param {Number} index Position where nodes should be inserted. * @param {module:engine/view/node~Node|Iterable.} nodes Node or the list of nodes to be inserted. * @fires module:engine/view/node~Node#change * @returns {Number} Number of inserted nodes. */ insertChildren( index, nodes ) { this._fireChange( 'children', this ); let count = 0; nodes = normalize( nodes ); for ( const node of nodes ) { node.parent = this; this._children.splice( index, 0, node ); index++; count++; } return count; } /** * Removes attribute from the element. * * @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.size > 0 ) { this._styles.clear(); return true; } return false; } // Remove other attributes. return this._attrs.delete( key ); } /** * 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.} The array of removed nodes. * @fires module:engine/view/node~Node#change */ 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 ); } /** * 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 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, value ] of this._styles ) { if ( !otherElement._styles.has( property ) || otherElement._styles.get( property ) !== value ) { return false; } } return true; } /** * Adds specified class. * * element.addClass( 'foo' ); // Adds 'foo' class. * element.addClass( 'foo', 'bar' ); // Adds 'foo' and 'bar' classes. * * @param {...String} className * @fires module:engine/view/node~Node#change */ addClass( ...className ) { this._fireChange( 'attributes', this ); className.forEach( name => this._classes.add( name ) ); } /** * Removes specified class. * * element.removeClass( 'foo' ); // Removes 'foo' class. * element.removeClass( 'foo', 'bar' ); // Removes both 'foo' and 'bar' classes. * * @param {...String} className * @fires module:engine/view/node~Node#change */ removeClass( ...className ) { this._fireChange( 'attributes', this ); className.forEach( name => this._classes.delete( name ) ); } /** * 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 {Iterator.} */ getClassNames() { return this._classes.keys(); } /** * Adds style to the element. * * element.setStyle( 'color', 'red' ); * element.setStyle( { * color: 'red', * position: 'fixed' * } ); * * @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 ); if ( Object(__WEBPACK_IMPORTED_MODULE_4__ckeditor_ckeditor5_utils_src_lib_lodash_isPlainObject__["a" /* default */])( property ) ) { const keys = Object.keys( property ); for ( const key of keys ) { this._styles.set( key, property[ key ] ); } } else { this._styles.set( property, value ); } } /** * Returns style value for given property. * Undefined is returned if style does not exist. * * @param {String} property * @returns {String|undefined} */ getStyle( property ) { return this._styles.get( property ); } /** * Returns iterator that contains all style names. * * @returns {Iterator.} */ getStyleNames() { return this._styles.keys(); } /** * 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; } /** * Removes specified style. * * element.removeStyle( 'color' ); // Removes 'color' style. * element.removeStyle( 'color', 'border-top' ); // Removes both 'color' and 'border-top' styles. * * @param {...String} property * @fires module:engine/view/node~Node#change */ removeStyle( ...property ) { this._fireChange( 'attributes', this ); property.forEach( name => this._styles.delete( name ) ); } /** * 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 __WEBPACK_IMPORTED_MODULE_5__matcher__["a" /* default */]( ...patterns ); let parent = this.parent; while ( parent ) { if ( matcher.match( parent ) ) { return parent; } parent = parent.parent; } return null; } /** * 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. * * @param {String|Symbol} key * @param {*} value */ setCustomProperty( key, value ) { this._customProperties.set( key, value ); } /** * Returns the custom property value for the given key. * * @param {String|Symbol} key * @returns {*} */ getCustomProperty( key ) { return this._customProperties.get( key ); } /** * Removes the custom property stored under the given key. * * @param {String|Symbol} key * @returns {Boolean} Returns true if property was removed. */ removeCustomProperty( key ) { return this._customProperties.delete( key ); } /** * Returns an iterator which iterates over this element's custom properties. * Iterator provides [key, value] pair for each stored property. * * @returns {Iterable.<*>} */ * getCustomProperties() { yield* this._customProperties.entries(); } /** * 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 */ } /* harmony export (immutable) */ __webpack_exports__["a"] = Element; // Parses inline styles and puts property - value pairs into styles map. // Styles map is cleared before insertion. // // @param {Map.} stylesMap Map to insert parsed properties and values. // @param {String} stylesString Styles to parse. function parseInlineStyles( stylesMap, 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; stylesMap.clear(); // Do not set anything if input string is empty. if ( stylesString === '' ) { return; } // 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; // eslint-disable-next-line no-case-declarations 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; } } } // Parses class attribute and puts all classes into classes set. // Classes set s cleared before insertion. // // @param {Set.} 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/node~Node|Iterable.} // @return {Iterable.} function normalize( nodes ) { // Separate condition because string is iterable. if ( typeof nodes == 'string' ) { return [ new __WEBPACK_IMPORTED_MODULE_1__text__["a" /* default */]( nodes ) ]; } if ( !Object(__WEBPACK_IMPORTED_MODULE_3__ckeditor_ckeditor5_utils_src_isiterable__["a" /* default */])( nodes ) ) { nodes = [ nodes ]; } // Array.from to enable .map() on non-arrays. return Array.from( nodes ) .map( node => { return typeof node == 'string' ? new __WEBPACK_IMPORTED_MODULE_1__text__["a" /* default */]( node ) : node; } ); } /***/ }), /* 28 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__operation__ = __webpack_require__(38); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__position__ = __webpack_require__(2); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__range__ = __webpack_require__(3); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_3__ckeditor_ckeditor5_utils_src_ckeditorerror__ = __webpack_require__(0); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_4__ckeditor_ckeditor5_utils_src_comparearrays__ = __webpack_require__(55); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_5__writer__ = __webpack_require__(21); /** * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.md. */ /** * @module engine/model/operation/moveoperation */ /** * 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 __WEBPACK_IMPORTED_MODULE_0__operation__["a" /* 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} baseVersion {@link module:engine/model/document~Document#version} on which operation can be applied. */ 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 = __WEBPACK_IMPORTED_MODULE_1__position__["a" /* default */].createFromPosition( sourcePosition ); /** * 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 = __WEBPACK_IMPORTED_MODULE_1__position__["a" /* default */].createFromPosition( targetPosition ); /** * Defines whether `MoveOperation` is sticky. If `MoveOperation` is sticky, during * {@link module:engine/model/operation/transform~transform operational transformation} if there will be an operation that * inserts some nodes at the position equal to the boundary of this `MoveOperation`, that operation will * get their insertion path updated to the position where this `MoveOperation` moves the range. * * @member {Boolean} module:engine/model/operation/moveoperation~MoveOperation#isSticky */ this.isSticky = false; } /** * @inheritDoc */ get type() { 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() { const op = new this.constructor( this.sourcePosition, this.howMany, this.targetPosition, this.baseVersion ); op.isSticky = this.isSticky; return op; } /** * 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 ); const op = new this.constructor( this.getMovedRangeStart(), this.howMany, newTargetPosition, this.baseVersion + 1 ); op.isSticky = this.isSticky; return op; } /** * @inheritDoc */ _execute() { 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 ( !sourceElement || !targetElement ) { /** * Source position or target position is invalid. * * @error move-operation-position-invalid */ throw new __WEBPACK_IMPORTED_MODULE_3__ckeditor_ckeditor5_utils_src_ckeditorerror__["a" /* default */]( 'move-operation-position-invalid: Source position or target position is invalid.' ); } else if ( sourceOffset + this.howMany > sourceElement.maxOffset ) { /** * The nodes which should be moved do not exist. * * @error move-operation-nodes-do-not-exist */ throw new __WEBPACK_IMPORTED_MODULE_3__ckeditor_ckeditor5_utils_src_ckeditorerror__["a" /* default */]( 'move-operation-nodes-do-not-exist: The nodes which should be moved do not exist.' ); } 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 __WEBPACK_IMPORTED_MODULE_3__ckeditor_ckeditor5_utils_src_ckeditorerror__["a" /* default */]( 'move-operation-range-into-itself: Trying to move a range of nodes to the inside of that range.' ); } else if ( this.sourcePosition.root == this.targetPosition.root ) { if ( Object(__WEBPACK_IMPORTED_MODULE_4__ckeditor_ckeditor5_utils_src_comparearrays__["a" /* 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 __WEBPACK_IMPORTED_MODULE_3__ckeditor_ckeditor5_utils_src_ckeditorerror__["a" /* default */]( 'move-operation-node-into-itself: Trying to move a range of nodes into one of nodes from that range.' ); } } } const range = __WEBPACK_IMPORTED_MODULE_5__writer__["a" /* default */].move( __WEBPACK_IMPORTED_MODULE_2__range__["a" /* default */].createFromPositionAndShift( this.sourcePosition, this.howMany ), this.targetPosition ); return { sourcePosition: this.sourcePosition, range }; } /** * @inheritDoc */ static get className() { return 'engine.model.operation.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 = __WEBPACK_IMPORTED_MODULE_1__position__["a" /* default */].fromJSON( json.sourcePosition, document ); const targetPosition = __WEBPACK_IMPORTED_MODULE_1__position__["a" /* default */].fromJSON( json.targetPosition, document ); const move = new this( sourcePosition, json.howMany, targetPosition, json.baseVersion ); if ( json.isSticky ) { move.isSticky = true; } return move; } } /* harmony export (immutable) */ __webpack_exports__["a"] = MoveOperation; /***/ }), /* 29 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; /** * 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.length, result = Array(length); while (++index < length) { result[index] = iteratee(array[index], index, array); } return result; } /* harmony default export */ __webpack_exports__["a"] = (arrayMap); /***/ }), /* 30 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; /* harmony export (immutable) */ __webpack_exports__["a"] = getCode; /* harmony export (immutable) */ __webpack_exports__["d"] = parseKeystroke; /* harmony export (immutable) */ __webpack_exports__["b"] = getEnvKeystrokeText; /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__ckeditorerror__ = __webpack_require__(0); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__env__ = __webpack_require__(405); /** * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.md. */ /** * Set of utils related to keyboard support. * * @module utils/keyboard */ /** * 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(); /* harmony export (immutable) */ __webpack_exports__["c"] = keyCodes; /** * Converts a key name or a {@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} 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 contained by the {@link module:utils/keyboard~keyCodes} can be used. * * @errror keyboard-unknown-key * @param {String} key */ throw new __WEBPACK_IMPORTED_MODULE_0__ckeditorerror__["a" /* default */]( 'keyboard-unknown-key: Unknown key name.', { key } ); } } else { keyCode = key.keyCode + ( key.altKey ? keyCodes.alt : 0 ) + ( key.ctrlKey ? keyCodes.ctrl : 0 ) + ( key.shiftKey ? keyCodes.shift : 0 ); } return keyCode; } /** * Parses keystroke and returns a keystroke code that will match the code returned by * link {@link module:utils/keyboard.getCode} for a 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). * * @param {String|Array.} keystroke Keystroke definition. * @returns {Number} Keystroke code. */ function parseKeystroke( keystroke ) { if ( typeof keystroke == 'string' ) { keystroke = splitKeystrokeText( keystroke ); } return keystroke .map( key => ( typeof key == 'string' ) ? getCode( key ) : key ) .reduce( ( key, sum ) => sum + key, 0 ); } /** * It translates any keystroke string text like `"CTRL+A"` to an * environment–specific keystroke, i.e. `"⌘A"` on Mac OSX. * * @param {String} keystroke Keystroke text. * @returns {String} Keystroke text specific for the environment. */ function getEnvKeystrokeText( keystroke ) { const split = splitKeystrokeText( keystroke ); if ( __WEBPACK_IMPORTED_MODULE_1__env__["a" /* default */].mac ) { if ( split[ 0 ].toLowerCase() == 'ctrl' ) { return '⌘' + ( split[ 1 ] || '' ); } } return keystroke; } 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, // Has the same code as ctrl, because their behaviour should be unified across the editor. // See http://ckeditor.github.io/editor-recommendations/general-policies#ctrl-vs-cmd cmd: 0x110000, shift: 0x220000, alt: 0x440000 }; // 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; } return keyCodes; } function splitKeystrokeText( keystroke ) { return keystroke.split( /\s*\+\s*/ ); } /** * Information about a 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 Alt modifier was pressed. * * @member {Bolean} module:utils/keyboard~KeystrokeInfo#altKey */ /** * Whether the Ctrl or Cmd modifier was pressed. * * @member {Bolean} module:utils/keyboard~KeystrokeInfo#ctrlKey */ /** * Whether the Shift modifier was pressed. * * @member {Bolean} module:utils/keyboard~KeystrokeInfo#shiftKey */ /***/ }), /* 31 */ /***/ (function(module, exports) { /* MIT License http://www.opensource.org/licenses/mit-license.php Author Tobias Koppers @sokra */ // css base code, injected by the css-loader module.exports = function(useSourceMap) { var list = []; // return the list of modules as css string list.toString = function toString() { return this.map(function (item) { var content = cssWithMappingToString(item, useSourceMap); if(item[2]) { return "@media " + item[2] + "{" + content + "}"; } else { return content; } }).join(""); }; // import a list of modules into the list list.i = function(modules, mediaQuery) { if(typeof modules === "string") modules = [[null, modules, ""]]; var alreadyImportedModules = {}; for(var i = 0; i < this.length; i++) { var id = this[i][0]; if(typeof id === "number") alreadyImportedModules[id] = true; } for(i = 0; i < modules.length; i++) { var item = modules[i]; // skip already imported module // this implementation is not 100% perfect for weird media query combinations // when a module is imported multiple times with different media queries. // I hope this will never occur (Hey this way we have smaller bundles) if(typeof item[0] !== "number" || !alreadyImportedModules[item[0]]) { if(mediaQuery && !item[2]) { item[2] = mediaQuery; } else if(mediaQuery) { item[2] = "(" + item[2] + ") and (" + mediaQuery + ")"; } list.push(item); } } }; return list; }; function cssWithMappingToString(item, useSourceMap) { var content = item[1] || ''; var cssMapping = item[3]; if (!cssMapping) { return content; } if (useSourceMap && typeof btoa === 'function') { var sourceMapping = toComment(cssMapping); var sourceURLs = cssMapping.sources.map(function (source) { return '/*# sourceURL=' + cssMapping.sourceRoot + source + ' */' }); return [content].concat(sourceURLs).concat([sourceMapping]).join('\n'); } return [content].join('\n'); } // Adapted from convert-source-map (MIT) function toComment(sourceMap) { // eslint-disable-next-line no-undef var base64 = btoa(unescape(encodeURIComponent(JSON.stringify(sourceMap)))); var data = 'sourceMappingURL=data:application/json;charset=utf-8;base64,' + base64; return '/*# ' + data + ' */'; } /***/ }), /* 32 */ /***/ (function(module, exports, __webpack_require__) { /* MIT License http://www.opensource.org/licenses/mit-license.php Author Tobias Koppers @sokra */ var stylesInDom = {}; var memoize = function (fn) { var memo; return function () { if (typeof memo === "undefined") memo = fn.apply(this, arguments); return memo; }; }; var isOldIE = memoize(function () { // 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 return window && document && document.all && !window.atob; }); var getElement = (function (fn) { var memo = {}; return function(selector) { if (typeof memo[selector] === "undefined") { memo[selector] = fn.call(this, selector); } return memo[selector] }; })(function (target) { return document.querySelector(target) }); var singleton = null; var singletonCounter = 0; var stylesInsertedAtTop = []; var fixUrls = __webpack_require__(429); module.exports = function(list, options) { if (typeof DEBUG !== "undefined" && DEBUG) { if (typeof document !== "object") throw new Error("The style-loader cannot be used in a non-browser environment"); } options = options || {}; options.attrs = typeof options.attrs === "object" ? options.attrs : {}; // Force single-tag solution on IE6-9, which has a hard limit on the # of