From 889869e9ca2d023dfb558b705f9ebaa1c78479f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotrek=20Koszuli=C5=84ski?= Date: Mon, 31 Oct 2016 19:18:26 +0100 Subject: [PATCH] Used a build which works around https://github.com/ckeditor/ckeditor5-heading/issues/40. --- lib/ckeditor5/ckeditor.js | 14044 ++++++++++++++++---------------- lib/ckeditor5/ckeditor.min.js | 20 +- 2 files changed, 7032 insertions(+), 7032 deletions(-) diff --git a/lib/ckeditor5/ckeditor.js b/lib/ckeditor5/ckeditor.js index 26bda57fab1..01eb9499fdf 100644 --- a/lib/ckeditor5/ckeditor.js +++ b/lib/ckeditor5/ckeditor.js @@ -42220,180 +42220,377 @@ var ClassicEditor$1 = function (_StandardEditor) { */ /** - * The block autoformatting engine. Allows to format various block patterns. For example, - * it can be configured to make a paragraph starting with "* " a list item. - * - * The autoformatting operation is integrated with the undo manager, - * so the autoformatting step can be undone, if the user's intention wasn't to format the text. + * The base class for CKEditor feature classes. Features are main way to enhance CKEditor abilities with tools, + * utilities, services and components. * - * See the constructors documentation to learn how to create custom inline autoformatters. You can also use - * the {@link autoformat.Autoformat} feature which enables a set of default autoformatters (lists, headings, bold and italic). + * The main responsibilities for Feature are: + * * setting required dependencies (see {@link core.Plugin#requires}, + * * configuring, instantiating and registering commands to editor, + * * registering converters to editor (if the feature operates on Tree Model), + * * setting and registering UI components (if the feature uses it). * - * @memberOf autoformat + * @memberOf core */ -var BlockAutoformatEngine = +var Feature = function (_Plugin) { + inherits(Feature, _Plugin); + + function Feature() { + classCallCheck(this, Feature); + return possibleConstructorReturn(this, Object.getPrototypeOf(Feature).apply(this, arguments)); + } + + return Feature; +}(Plugin); + /** - * Creates listener triggered on `change` event in document. - * Calls callback when inserted text matches regular expression or command name - * if provided instead of callback. + * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +/** + * Provides chainable, high-level API to easily build basic model-to-view converters that are appended to given + * dispatchers. In many cases, this is the API that should be used to specify how abstract model elements and + * attributes should be represented in the view (and then later in DOM). Instances of this class are created by + * {@link engine.conversion.buildModelConverter}. * - * Examples of usage: + * If you need more complex converters, see {@link engine.conversion.ModelConversionDispatcher}, + * {@link engine.conversion.modelToView}, {@link engine.conversion.ModelConsumable}, {@link engine.conversion.Mapper}. * - * To convert paragraph to heading1 when `- ` is typed, using just commmand name: + * Using this API it is possible to create three kinds of converters: * - * new BlockAutoformatEngine( editor, /^\- $/, 'heading1' ); + * 1. Model element to view element converter. This is a converter that takes the model element and represents it + * in the view. * - * To convert paragraph to heading1 when `- ` is typed, using just callback: + * buildModelConverter().for( dispatcher ).fromElement( 'paragraph' ).toElement( 'p' ); + * buildModelConverter().for( dispatcher ).fromElement( 'image' ).toElement( 'img' ); * - * new BlockAutoformatEngine( editor, /^\- $/, ( context ) => { - * const { batch, match } = context; - * const headingLevel = match[ 1 ].length; + * 2. Model attribute to view attribute converter. This is a converter that operates on model element attributes + * and converts them to view element attributes. It is suitable for elements like `image` (`src`, `title` attributes). * - * editor.execute( 'heading', { - * batch, - * formatId: `heading${ headingLevel }` - * } ); - * } ); + * buildModelConverter().for( dispatcher ).fromElement( 'image' ).toElement( 'img' ); + * buildModelConverter().for( dispatcher ).fromAttribute( 'src' ).toAttribute(); * - * @param {core.editor.Editor} editor Editor instance. - * @param {RegExp} pattern Regular expression to exec on just inserted text. - * @param {Function|String} callbackOrCommand Callback to execute or command to run when text is matched. - * In case of providing callback it receives following parameters: - * * {engine.model.Batch} batch Newly created batch for autoformat changes. - * * {Object} match RegExp.exec() result of matching pattern to inserted text. + * 3. Model attribute to view element converter. This is a converter that takes model attributes and represents them + * as view elements. Elements created by this kind of converter are wrapping other view elements. Wrapped view nodes + * correspond to model nodes had converter attribute. It is suitable for attributes like `bold`, where `bold` attribute + * set on model text nodes is converter to `strong` view element. + * + * buildModelConverter().for( dispatcher ).fromAttribute( 'bold' ).toElement( 'strong' ); + * + * It is possible to provide various different parameters for {@link engine.conversion.ModelConverterBuilder#toElement} + * and {@link engine.conversion.ModelConverterBuilder#toAttribute} methods. See their descriptions to learn more. + * + * It is also possible to {@link engine.conversion.ModelConverterBuilder#withPriority change default priority} + * of created converters to decide which converter should be fired earlier and which later. This is useful if you have + * a general converter but also want to provide different special-case converters (i.e. given model element is converted + * always to given view element, but if it has given attribute it is converter to other view element). For this, + * use {@link engine.conversion.ModelConverterBuilder#withPriority withPriority} right after `from...` method. + * + * Note that `to...` methods are "terminators", which means that should be the last one used in building converter. + * + * You can use {@link engine.conversion.ViewConverterBuilder} to create "opposite" converters - from view to model. + * + * @memberOf engine.conversion */ -function BlockAutoformatEngine(editor, pattern, callbackOrCommand) { - classCallCheck(this, BlockAutoformatEngine); - - var callback = void 0; - - if (typeof callbackOrCommand == 'function') { - callback = callbackOrCommand; - } else { - (function () { - // We assume that the actual command name was provided. - var command = callbackOrCommand; - callback = function callback(context) { - var batch = context.batch; +var ModelConverterBuilder = function () { + /** + * Creates `ModelConverterBuilder` with given `dispatchers` registered to it. + */ + function ModelConverterBuilder() { + classCallCheck(this, ModelConverterBuilder); - // Create new batch for removal and command execution. + /** + * Dispatchers to which converters will be attached. + * + * @type {Array.} + * @private + */ + this._dispatchers = []; - editor.execute(command, { batch: batch }); - }; - })(); + /** + * Contains data about registered "from" query. + * + * @type {Object} + * @private + */ + this._from = null; } - editor.document.on('change', function (event, type, changes) { - if (type != 'insert') { - return; - } - - // Take the first element. Typing shouldn't add more than one element at once. - // And if it is not typing (e.g. paste), Autoformat should not be fired. - var value = changes.range.getItems().next().value; - - if (!(value instanceof TextProxy)) { - return; - } + /** + * Set one or more dispatchers which the built converter will be attached to. + * + * @chainable + * @param {...engine.conversion.ModelConversionDispatcher} dispatchers One or more dispatchers. + * @returns {engine.conversion.ModelConverterBuilder} + */ - var textNode = value.textNode; - var text = textNode.data; - // Run matching only on non-empty paragraphs. - if (textNode.parent.name !== 'paragraph' || !text) { - return; - } + createClass(ModelConverterBuilder, [{ + key: 'for', + value: function _for() { + for (var _len = arguments.length, dispatchers = Array(_len), _key = 0; _key < _len; _key++) { + dispatchers[_key] = arguments[_key]; + } - var match = pattern.exec(text); + this._dispatchers = dispatchers; - if (!match) { - return; + return this; } - editor.document.enqueueChanges(function () { - // Create new batch to separate typing batch from the Autoformat changes. - var batch = editor.document.batch(); + /** + * Registers what model element should be converted. + * + * @chainable + * @param {String} elementName Name of element to convert. + * @returns {engine.conversion.ModelConverterBuilder} + */ - // Matched range. - var range = Range$1.createFromParentsAndOffsets(textNode.parent, 0, textNode.parent, match[0].length); + }, { + key: 'fromElement', + value: function fromElement(elementName) { + this._from = { + type: 'element', + name: elementName, + priority: null + }; - // Remove matched text. - batch.remove(range); + return this; + } - callback({ batch: batch, match: match }); - }); - }); -}; + /** + * Registers what model attribute should be converted. + * + * @chainable + * @param {String} key Key of attribute to convert. + * @returns {engine.conversion.ModelConverterBuilder} + */ -/** - * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md. - */ + }, { + key: 'fromAttribute', + value: function fromAttribute(key) { + this._from = { + type: 'attribute', + key: key, + priority: null + }; -/** - * Walks through given array of ranges and removes parts of them that are not allowed by passed schema to have the - * attribute set. This is done by breaking a range in two and omitting the not allowed part. - * - * @param {String} attribute Attribute key. - * @param {Array.} ranges Ranges to be validated. - * @param {engine.model.Schema} schema Document schema. - * @returns {Array.} Ranges without invalid parts. - */ -function getSchemaValidRanges(attribute, ranges, schema) { - var validRanges = []; + return this; + } - var _iteratorNormalCompletion = true; - var _didIteratorError = false; - var _iteratorError = undefined; + /** + * Changes default priority for built converter. The lower the number, the earlier converter will be fired. + * Default priority is `10`. + * + * **Note:** Keep in mind that event priority, that is set by this modifier, is used for attribute priority + * when {@link engine.view.writer} is used. This changes how view elements are ordered, + * i.e.: `foo` vs `foo`. Using priority you can also + * prevent node merging, i.e.: `foo` vs `foo`. + * If you want to prevent merging, just set different priority for both converters. + * + * buildModelConverter().for( dispatcher ).fromAttribute( 'bold' ).withPriority( 2 ).toElement( 'strong' ); + * buildModelConverter().for( dispatcher ).fromAttribute( 'italic' ).withPriority( 3 ).toElement( 'em' ); + * + * @chainable + * @param {Number} priority Converter priority. + * @returns {engine.conversion.ModelConverterBuilder} + */ - try { - for (var _iterator = ranges[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { - var range = _step.value; + }, { + key: 'withPriority', + value: function withPriority(priority) { + this._from.priority = priority; - var walker = new TreeWalker({ boundaries: range, mergeCharacters: true }); - var step = walker.next(); + return this; + } - var last = range.start; - var from = range.start; - var to = range.end; + /** + * Registers what view element will be created by converter. + * + * Method accepts various ways of providing how the view element will be created. You can pass view element name as + * `string`, view element instance which will be cloned and used, or creator function which returns view element that + * will be used. Keep in mind that when you view element instance or creator function, it has to be/return a + * proper type of view element: {@link engine.view.ViewContainerElement ViewContainerElement} if you convert + * from element or {@link engine.view.ViewAttributeElement ViewAttributeElement} if you convert from attribute. + * + * buildModelConverter().for( dispatcher ).fromElement( 'paragraph' ).toElement( 'p' ); + * + * buildModelConverter().for( dispatcher ).fromElement( 'image' ).toElement( new ViewContainerElement( 'img' ) ); + * + * buildModelConverter().for( dispatcher ) + * .fromElement( 'header' ) + * .toElement( ( data ) => new ViewContainerElement( 'h' + data.item.getAttribute( 'level' ) ) ); + * + * buildModelConverter().for( dispatcher ).fromAttribute( 'bold' ).toElement( new ViewAttributeElement( 'strong' ) ); + * + * Creator function will be passed different values depending whether conversion is from element or from attribute: + * + * * from element: dispatcher's {@link engine.conversion.ModelConversionDispatcher#event:insert insert event} parameters + * will be passed, + * * from attribute: there is one parameter and it is attribute value. + * + * This method also registers model selection to view selection converter, if conversion is from attribute. + * + * This method creates the converter and adds it as a callback to a proper + * {@link engine.conversion.ModelConversionDispatcher conversion dispatcher} event. + * + * @param {String|engine.view.ViewElement|Function} element Element created by converter. + */ - while (!step.done) { - var name = step.value.item.name || '$text'; + }, { + key: 'toElement', + value: function toElement(element) { + var priority = this._from.priority === null ? 'normal' : this._from.priority; - if (!schema.check({ name: name, inside: last, attributes: attribute })) { - if (!from.isEqual(last)) { - validRanges.push(new Range$1(from, last)); - } + var _iteratorNormalCompletion = true; + var _didIteratorError = false; + var _iteratorError = undefined; - from = walker.position; - } + try { + for (var _iterator = this._dispatchers[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { + var dispatcher = _step.value; - last = walker.position; - step = walker.next(); - } + if (this._from.type == 'element') { + // From model element to view element -> insert element. + element = typeof element == 'string' ? new ContainerElement(element) : element; - if (from && !from.isEqual(to)) { - validRanges.push(new Range$1(from, to)); + dispatcher.on('insert:' + this._from.name, insertElement(element), { priority: priority }); + } else { + // From model attribute to view element -> wrap and unwrap. + element = typeof element == 'string' ? new AttributeElement(element) : element; + + dispatcher.on('addAttribute:' + this._from.key, wrap(element), { priority: priority }); + dispatcher.on('changeAttribute:' + this._from.key, wrap(element), { priority: priority }); + dispatcher.on('removeAttribute:' + this._from.key, unwrap(element), { priority: priority }); + + dispatcher.on('selectionAttribute:' + this._from.key, convertSelectionAttribute(element), { priority: priority }); + } + } + } catch (err) { + _didIteratorError = true; + _iteratorError = err; + } finally { + try { + if (!_iteratorNormalCompletion && _iterator.return) { + _iterator.return(); + } + } finally { + if (_didIteratorError) { + throw _iteratorError; + } + } } } - } catch (err) { - _didIteratorError = true; - _iteratorError = err; - } finally { - try { - if (!_iteratorNormalCompletion && _iterator.return) { - _iterator.return(); + + /** + * Registers what view attribute will be created by converter. Keep in mind, that only model attribute to + * view attribute conversion is supported. + * + * Method accepts various ways of providing how the view attribute will be created: + * + * * for no passed parameter, attribute key and value will be converted 1-to-1 to view attribute, + * * if you pass one `string`, it will be used as new attribute key while attribute value will be copied, + * * if you pass two `string`s, first one will be used as new attribute key and second one as new attribute value, + * * if you pass a function, it is expected to return an object with `key` and `value` properties representing attribute key and value. + * This function will be passed model attribute value and model attribute key as first two parameters and then + * all dispatcher's {engine.conversion.ModelConversionDispatcher#event:changeAttribute changeAttribute event} parameters. + * + * buildModelConverter().for( dispatcher ).fromAttribute( 'class' ).toAttribute( '' ); + * + * buildModelConverter().for( dispatcher ).fromAttribute( 'linkTitle' ).toAttribute( 'title' ); + * + * buildModelConverter().for( dispatcher ).fromAttribute( 'highlighted' ).toAttribute( 'style', 'background:yellow' ); + * + * buildModelConverter().for( dispatcher ) + * .fromAttribute( 'theme' ) + * .toAttribute( ( value ) => ( { key: 'class', value: value + '-theme' } ) ); + * + * This method creates the converter and adds it as a callback to a proper + * {@link engine.conversion.ModelConversionDispatcher conversion dispatcher} event. + * + * @param {String|Function} [keyOrCreator] Attribute key or a creator function. + * @param {*} [value] Attribute value. + */ + + }, { + key: 'toAttribute', + value: function toAttribute(keyOrCreator, value) { + if (this._from.type == 'element') { + // Converting from model element to view attribute is unsupported. + return; } - } finally { - if (_didIteratorError) { - throw _iteratorError; + + var attributeCreator = void 0; + + if (!keyOrCreator) { + // If `keyOrCreator` is not set, we assume default behavior which is 1:1 attribute re-write. + // This is also a default behavior for `setAttribute` converter when no attribute creator is passed. + attributeCreator = undefined; + } else if (typeof keyOrCreator == 'string') { + // `keyOrCreator` is an attribute key. + + if (value) { + // If value is set, create "dumb" creator that always returns the same object. + attributeCreator = function attributeCreator() { + return { key: keyOrCreator, value: value }; + }; + } else { + // If value is not set, take it from the passed parameter. + attributeCreator = function attributeCreator(value) { + return { key: keyOrCreator, value: value }; + }; + } + } else { + // `keyOrCreator` is an attribute creator function. + attributeCreator = keyOrCreator; + } + + var _iteratorNormalCompletion2 = true; + var _didIteratorError2 = false; + var _iteratorError2 = undefined; + + try { + for (var _iterator2 = this._dispatchers[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { + var dispatcher = _step2.value; + + var options = { priority: this._from.priority || 'normal' }; + + dispatcher.on('addAttribute:' + this._from.key, setAttribute(attributeCreator), options); + dispatcher.on('changeAttribute:' + this._from.key, setAttribute(attributeCreator), options); + dispatcher.on('removeAttribute:' + this._from.key, removeAttribute(attributeCreator), options); + } + } catch (err) { + _didIteratorError2 = true; + _iteratorError2 = err; + } finally { + try { + if (!_iteratorNormalCompletion2 && _iterator2.return) { + _iterator2.return(); + } + } finally { + if (_didIteratorError2) { + throw _iteratorError2; + } + } } } - } + }]); + return ModelConverterBuilder; +}(); - return validRanges; +/** + * Entry point for model-to-view converters builder. This chainable API makes it easy to create basic, most common + * model-to-view converters and attach them to provided dispatchers. The method returns an instance of + * {@link engine.conversion.ModelConverterBuilder}. + * + * @external engine.conversion.buildModelConverter + * @memberOf engine.conversion + */ + + +function buildModelConverter() { + return new ModelConverterBuilder(); } /** @@ -42402,1760 +42599,539 @@ function getSchemaValidRanges(attribute, ranges, schema) { */ /** - * The inline autoformatting engine. Allows to format various inline patterns. For example, - * it can be configured to make "foo" bold when typed `**foo**` (the `**` markers will be removed). - * - * The autoformatting operation is integrated with the undo manager, - * so the autoformatting step can be undone, if the user's intention wasn't to format the text. - * - * See the constructors documentation to learn how to create custom inline autoformatters. You can also use - * the {@link autoformat.Autoformat} feature which enables a set of default autoformatters (lists, headings, bold and italic). + * View matcher class. + * Instance of this class can be used to find {@link engine.view.Element elements} that match given pattern. * - * @memberOf autoformat + * @memberOf engine.view */ +var Matcher = function () { + /** + * Creates new instance of Matcher. + * + * @param {String|RegExp|Object} [pattern] Match patterns. See {@link engine.view.Matcher#add add method} for + * more information. + */ + function Matcher() { + classCallCheck(this, Matcher); -var InlineAutoformatEngine = -/** - * Enables autoformatting mechanism on a given {@link core.editor.Editor}. - * - * It formats the matched text by applying given model attribute or by running the provided formatting callback. - * Each time data model changes text from given node (from the beginning of the current node to the collapsed - * selection location) will be tested. - * - * @param {core.editor.Editor} editor Editor instance. - * @param {Function|RegExp} testRegexpOrCallback RegExp or callback to execute on text. - * Provided RegExp *must* have three capture groups. First and third capture groups - * should match opening/closing delimiters. Second capture group should match text to format. - * - * // Matches `**bold text**` pattern. - * // There are three capturing groups: - * // - first to match starting `**` delimiter, - * // - second to match text to format, - * // - third to match ending `**` delimiter. - * new InlineAutoformatEngine( this.editor, /(\*\*)([^\*]+?)(\*\*)$/g, 'bold' ); - * - * When function is provided instead of RegExp, it will be executed with text to match as a parameter. Function - * should return proper "ranges" to delete and format. - * - * { - * remove: [ - * [ 0, 1 ], // Remove first letter from the given text. - * [ 5, 6 ] // Remove 6th letter from the given text. - * ], - * format: [ - * [ 1, 5 ] // Format all letters from 2nd to 5th. - * ] - * } - * - * @param {Function|String} attributeOrCallback Name of attribute to apply on matching text or callback for manual - * formatting. - * - * // Use attribute name: - * new InlineAutoformatEngine( this.editor, /(\*\*)([^\*]+?)(\*\*)$/g, 'bold' ); - * - * // Use formatting callback: - * new InlineAutoformatEngine( this.editor, /(\*\*)([^\*]+?)(\*\*)$/g, ( batch, validRanges ) => { - * for ( let range of validRanges ) { - * batch.setAttribute( range, command, true ); - * } - * } ); - */ -function InlineAutoformatEngine(editor, testRegexpOrCallback, attributeOrCallback) { - var _this = this; - - classCallCheck(this, InlineAutoformatEngine); - - this.editor = editor; - - var regExp = void 0; - var command = void 0; - var testCallback = void 0; - var formatCallback = void 0; - - if (testRegexpOrCallback instanceof RegExp) { - regExp = testRegexpOrCallback; - } else { - testCallback = testRegexpOrCallback; - } + this._patterns = []; - if (typeof attributeOrCallback == 'string') { - command = attributeOrCallback; - } else { - formatCallback = attributeOrCallback; + this.add.apply(this, arguments); } - // A test callback run on changed text. - testCallback = testCallback || function (text) { - var result = void 0; - var remove = []; - var format = []; - - while ((result = regExp.exec(text)) !== null) { - // There should be full match and 3 capture groups. - if (result && result.length < 4) { - break; - } - - var _result = result; - var index = _result.index; - var leftDel = _result['1']; - var content = _result['2']; - var rightDel = _result['3']; - - // Real matched string - there might be some non-capturing groups so we need to recalculate starting index. - - var found = leftDel + content + rightDel; - index += result[0].length - found.length; - - // Start and End offsets of delimiters to remove. - var delStart = [index, index + leftDel.length]; - var delEnd = [index + leftDel.length + content.length, index + leftDel.length + content.length + rightDel.length]; - - remove.push(delStart); - remove.push(delEnd); - - format.push([index + leftDel.length, index + leftDel.length + content.length]); - } - - return { - remove: remove, - format: format - }; - }; - - // A format callback run on matched text. - formatCallback = formatCallback || function (batch, validRanges) { - var _iteratorNormalCompletion = true; - var _didIteratorError = false; - var _iteratorError = undefined; - - try { - for (var _iterator = validRanges[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { - var range = _step.value; - - batch.setAttribute(range, command, true); - } - } catch (err) { - _didIteratorError = true; - _iteratorError = err; - } finally { - try { - if (!_iteratorNormalCompletion && _iterator.return) { - _iterator.return(); - } - } finally { - if (_didIteratorError) { - throw _iteratorError; - } - } - } - }; - - editor.document.on('change', function (evt, type) { - if (type !== 'insert') { - return; - } - - var selection = _this.editor.document.selection; - - if (!selection.isCollapsed || !selection.focus || !selection.focus.parent) { - return; - } - - var block = selection.focus.parent; - var text = getText(block).slice(0, selection.focus.offset + 1); - var ranges = testCallback(text); - var rangesToFormat = []; - - // Apply format before deleting text. - ranges.format.forEach(function (range) { - if (range[0] === undefined || range[1] === undefined) { - return; - } - - rangesToFormat.push(LiveRange.createFromParentsAndOffsets(block, range[0], block, range[1])); - }); - - var rangesToRemove = []; - - // Reverse order to not mix the offsets while removing. - ranges.remove.slice().reverse().forEach(function (range) { - if (range[0] === undefined || range[1] === undefined) { - return; - } - - rangesToRemove.push(LiveRange.createFromParentsAndOffsets(block, range[0], block, range[1])); - }); - - if (!(rangesToFormat.length && rangesToRemove.length)) { - return; - } - - var batch = editor.document.batch(); - - editor.document.enqueueChanges(function () { - var validRanges = getSchemaValidRanges(command, rangesToFormat, editor.document.schema); - - // Apply format. - formatCallback(batch, validRanges); - - // Remove delimiters. - var _iteratorNormalCompletion2 = true; - var _didIteratorError2 = false; - var _iteratorError2 = undefined; + /** + * Adds pattern or patterns to matcher instance. + * + * Example patterns matching element's name: + * + * // String. + * matcher.add( 'div' ); + * matcher.add( { name: 'div' } ); + * + * // Regular expression. + * matcher.add( /^\w/ ); + * matcher.add( { name: /^\w/ } ); + * + * Example pattern matching element's attributes: + * + * matcher.add( { + * attributes: { + * title: 'foobar', + * foo: /^\w+/ + * } + * } ); + * + * Example patterns matching element's classes: + * + * // Single class. + * matcher.add( { + * class: 'foobar' + * } ); + * + * // Single class using regular expression. + * matcher.add( { + * class: /foo.../ + * } ); + * + * // Multiple classes to match. + * matcher.add( { + * class: [ 'baz', 'bar', /foo.../ ] + * } ): + * + * Example pattern matching element's styles: + * + * matcher.add( { + * style: { + * position: 'absolute', + * color: /^\w*blue$/ + * } + * } ); + * + * Example function pattern: + * + * matcher.add( ( element ) => { + * // Result of this function will be included in `match` + * // property of the object returned from matcher.match() call. + * if ( element.name === 'div' && element.childCount > 0 ) { + * return { name: true }; + * } + * + * return null; + * } ); + * + * Multiple patterns can be added in one call: + * + * matcher.add( 'div', { class: 'foobar' } ); + * + * @param {Object|String|RegExp|function} pattern Object describing pattern details. If string or regular expression + * is provided it will be used to match element's name. Pattern can be also provided in a form + * of a function - then this function will be called with each {@link engine.view.Element element} as a parameter. + * Function's return value will be stored under `match` key of the object returned from + * {@link engine.view.Matcher#match match} or {@link engine.view.Matcher#matchAll matchAll} methods. + * @param {String|RegExp} [pattern.name] Name or regular expression to match element's name. + * @param {Object} [pattern.attribute] Object with key-value pairs representing attributes to match. Each object key + * represents attribute name. Value under that key can be either a string or a regular expression and it will be + * used to match attribute value. + * @param {String|RegExp|Array} [pattern.class] Class name or array of class names to match. Each name can be + * provided in a form of string or regular expression. + * @param {Object} [pattern.style] Object with key-value pairs representing styles to match. Each object key + * represents style name. Value under that key can be either a string or a regular expression and it will be used + * to match style value. + */ - try { - for (var _iterator2 = rangesToRemove[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { - var range = _step2.value; - batch.remove(range); - } - } catch (err) { - _didIteratorError2 = true; - _iteratorError2 = err; - } finally { - try { - if (!_iteratorNormalCompletion2 && _iterator2.return) { - _iterator2.return(); - } - } finally { - if (_didIteratorError2) { - throw _iteratorError2; - } - } + createClass(Matcher, [{ + key: 'add', + value: function add() { + for (var _len = arguments.length, pattern = Array(_len), _key = 0; _key < _len; _key++) { + pattern[_key] = arguments[_key]; } - }); - }); -}; - -function getText(element) { - return Array.from(element.getChildren()).reduce(function (a, b) { - return a + b.data; - }, ''); -} - -/** - * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md. - */ - -/** - * The base class for CKEditor feature classes. Features are main way to enhance CKEditor abilities with tools, - * utilities, services and components. - * - * The main responsibilities for Feature are: - * * setting required dependencies (see {@link core.Plugin#requires}, - * * configuring, instantiating and registering commands to editor, - * * registering converters to editor (if the feature operates on Tree Model), - * * setting and registering UI components (if the feature uses it). - * - * @memberOf core - */ - -var Feature = function (_Plugin) { - inherits(Feature, _Plugin); - - function Feature() { - classCallCheck(this, Feature); - return possibleConstructorReturn(this, Object.getPrototypeOf(Feature).apply(this, arguments)); - } - - return Feature; -}(Plugin); - -/** - * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md. - */ - -/** - * Provides chainable, high-level API to easily build basic model-to-view converters that are appended to given - * dispatchers. In many cases, this is the API that should be used to specify how abstract model elements and - * attributes should be represented in the view (and then later in DOM). Instances of this class are created by - * {@link engine.conversion.buildModelConverter}. - * - * If you need more complex converters, see {@link engine.conversion.ModelConversionDispatcher}, - * {@link engine.conversion.modelToView}, {@link engine.conversion.ModelConsumable}, {@link engine.conversion.Mapper}. - * - * Using this API it is possible to create three kinds of converters: - * - * 1. Model element to view element converter. This is a converter that takes the model element and represents it - * in the view. - * - * buildModelConverter().for( dispatcher ).fromElement( 'paragraph' ).toElement( 'p' ); - * buildModelConverter().for( dispatcher ).fromElement( 'image' ).toElement( 'img' ); - * - * 2. Model attribute to view attribute converter. This is a converter that operates on model element attributes - * and converts them to view element attributes. It is suitable for elements like `image` (`src`, `title` attributes). - * - * buildModelConverter().for( dispatcher ).fromElement( 'image' ).toElement( 'img' ); - * buildModelConverter().for( dispatcher ).fromAttribute( 'src' ).toAttribute(); - * - * 3. Model attribute to view element converter. This is a converter that takes model attributes and represents them - * as view elements. Elements created by this kind of converter are wrapping other view elements. Wrapped view nodes - * correspond to model nodes had converter attribute. It is suitable for attributes like `bold`, where `bold` attribute - * set on model text nodes is converter to `strong` view element. - * - * buildModelConverter().for( dispatcher ).fromAttribute( 'bold' ).toElement( 'strong' ); - * - * It is possible to provide various different parameters for {@link engine.conversion.ModelConverterBuilder#toElement} - * and {@link engine.conversion.ModelConverterBuilder#toAttribute} methods. See their descriptions to learn more. - * - * It is also possible to {@link engine.conversion.ModelConverterBuilder#withPriority change default priority} - * of created converters to decide which converter should be fired earlier and which later. This is useful if you have - * a general converter but also want to provide different special-case converters (i.e. given model element is converted - * always to given view element, but if it has given attribute it is converter to other view element). For this, - * use {@link engine.conversion.ModelConverterBuilder#withPriority withPriority} right after `from...` method. - * - * Note that `to...` methods are "terminators", which means that should be the last one used in building converter. - * - * You can use {@link engine.conversion.ViewConverterBuilder} to create "opposite" converters - from view to model. - * - * @memberOf engine.conversion - */ - -var ModelConverterBuilder = function () { - /** - * Creates `ModelConverterBuilder` with given `dispatchers` registered to it. - */ - function ModelConverterBuilder() { - classCallCheck(this, ModelConverterBuilder); - - /** - * Dispatchers to which converters will be attached. - * - * @type {Array.} - * @private - */ - this._dispatchers = []; - - /** - * Contains data about registered "from" query. - * - * @type {Object} - * @private - */ - this._from = null; - } - - /** - * Set one or more dispatchers which the built converter will be attached to. - * - * @chainable - * @param {...engine.conversion.ModelConversionDispatcher} dispatchers One or more dispatchers. - * @returns {engine.conversion.ModelConverterBuilder} - */ - - - createClass(ModelConverterBuilder, [{ - key: 'for', - value: function _for() { - for (var _len = arguments.length, dispatchers = Array(_len), _key = 0; _key < _len; _key++) { - dispatchers[_key] = arguments[_key]; - } - - this._dispatchers = dispatchers; - - return this; - } - - /** - * Registers what model element should be converted. - * - * @chainable - * @param {String} elementName Name of element to convert. - * @returns {engine.conversion.ModelConverterBuilder} - */ - - }, { - key: 'fromElement', - value: function fromElement(elementName) { - this._from = { - type: 'element', - name: elementName, - priority: null - }; - - return this; - } - - /** - * Registers what model attribute should be converted. - * - * @chainable - * @param {String} key Key of attribute to convert. - * @returns {engine.conversion.ModelConverterBuilder} - */ - - }, { - key: 'fromAttribute', - value: function fromAttribute(key) { - this._from = { - type: 'attribute', - key: key, - priority: null - }; - - return this; - } - - /** - * Changes default priority for built converter. The lower the number, the earlier converter will be fired. - * Default priority is `10`. - * - * **Note:** Keep in mind that event priority, that is set by this modifier, is used for attribute priority - * when {@link engine.view.writer} is used. This changes how view elements are ordered, - * i.e.: `foo` vs `foo`. Using priority you can also - * prevent node merging, i.e.: `foo` vs `foo`. - * If you want to prevent merging, just set different priority for both converters. - * - * buildModelConverter().for( dispatcher ).fromAttribute( 'bold' ).withPriority( 2 ).toElement( 'strong' ); - * buildModelConverter().for( dispatcher ).fromAttribute( 'italic' ).withPriority( 3 ).toElement( 'em' ); - * - * @chainable - * @param {Number} priority Converter priority. - * @returns {engine.conversion.ModelConverterBuilder} - */ - - }, { - key: 'withPriority', - value: function withPriority(priority) { - this._from.priority = priority; - - return this; - } - - /** - * Registers what view element will be created by converter. - * - * Method accepts various ways of providing how the view element will be created. You can pass view element name as - * `string`, view element instance which will be cloned and used, or creator function which returns view element that - * will be used. Keep in mind that when you view element instance or creator function, it has to be/return a - * proper type of view element: {@link engine.view.ViewContainerElement ViewContainerElement} if you convert - * from element or {@link engine.view.ViewAttributeElement ViewAttributeElement} if you convert from attribute. - * - * buildModelConverter().for( dispatcher ).fromElement( 'paragraph' ).toElement( 'p' ); - * - * buildModelConverter().for( dispatcher ).fromElement( 'image' ).toElement( new ViewContainerElement( 'img' ) ); - * - * buildModelConverter().for( dispatcher ) - * .fromElement( 'header' ) - * .toElement( ( data ) => new ViewContainerElement( 'h' + data.item.getAttribute( 'level' ) ) ); - * - * buildModelConverter().for( dispatcher ).fromAttribute( 'bold' ).toElement( new ViewAttributeElement( 'strong' ) ); - * - * Creator function will be passed different values depending whether conversion is from element or from attribute: - * - * * from element: dispatcher's {@link engine.conversion.ModelConversionDispatcher#event:insert insert event} parameters - * will be passed, - * * from attribute: there is one parameter and it is attribute value. - * - * This method also registers model selection to view selection converter, if conversion is from attribute. - * - * This method creates the converter and adds it as a callback to a proper - * {@link engine.conversion.ModelConversionDispatcher conversion dispatcher} event. - * - * @param {String|engine.view.ViewElement|Function} element Element created by converter. - */ - - }, { - key: 'toElement', - value: function toElement(element) { - var priority = this._from.priority === null ? 'normal' : this._from.priority; - - var _iteratorNormalCompletion = true; - var _didIteratorError = false; - var _iteratorError = undefined; - - try { - for (var _iterator = this._dispatchers[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { - var dispatcher = _step.value; - - if (this._from.type == 'element') { - // From model element to view element -> insert element. - element = typeof element == 'string' ? new ContainerElement(element) : element; - - dispatcher.on('insert:' + this._from.name, insertElement(element), { priority: priority }); - } else { - // From model attribute to view element -> wrap and unwrap. - element = typeof element == 'string' ? new AttributeElement(element) : element; - - dispatcher.on('addAttribute:' + this._from.key, wrap(element), { priority: priority }); - dispatcher.on('changeAttribute:' + this._from.key, wrap(element), { priority: priority }); - dispatcher.on('removeAttribute:' + this._from.key, unwrap(element), { priority: priority }); - - dispatcher.on('selectionAttribute:' + this._from.key, convertSelectionAttribute(element), { priority: priority }); - } - } - } catch (err) { - _didIteratorError = true; - _iteratorError = err; - } finally { - try { - if (!_iteratorNormalCompletion && _iterator.return) { - _iterator.return(); - } - } finally { - if (_didIteratorError) { - throw _iteratorError; - } - } - } - } - - /** - * Registers what view attribute will be created by converter. Keep in mind, that only model attribute to - * view attribute conversion is supported. - * - * Method accepts various ways of providing how the view attribute will be created: - * - * * for no passed parameter, attribute key and value will be converted 1-to-1 to view attribute, - * * if you pass one `string`, it will be used as new attribute key while attribute value will be copied, - * * if you pass two `string`s, first one will be used as new attribute key and second one as new attribute value, - * * if you pass a function, it is expected to return an object with `key` and `value` properties representing attribute key and value. - * This function will be passed model attribute value and model attribute key as first two parameters and then - * all dispatcher's {engine.conversion.ModelConversionDispatcher#event:changeAttribute changeAttribute event} parameters. - * - * buildModelConverter().for( dispatcher ).fromAttribute( 'class' ).toAttribute( '' ); - * - * buildModelConverter().for( dispatcher ).fromAttribute( 'linkTitle' ).toAttribute( 'title' ); - * - * buildModelConverter().for( dispatcher ).fromAttribute( 'highlighted' ).toAttribute( 'style', 'background:yellow' ); - * - * buildModelConverter().for( dispatcher ) - * .fromAttribute( 'theme' ) - * .toAttribute( ( value ) => ( { key: 'class', value: value + '-theme' } ) ); - * - * This method creates the converter and adds it as a callback to a proper - * {@link engine.conversion.ModelConversionDispatcher conversion dispatcher} event. - * - * @param {String|Function} [keyOrCreator] Attribute key or a creator function. - * @param {*} [value] Attribute value. - */ - - }, { - key: 'toAttribute', - value: function toAttribute(keyOrCreator, value) { - if (this._from.type == 'element') { - // Converting from model element to view attribute is unsupported. - return; - } - - var attributeCreator = void 0; - - if (!keyOrCreator) { - // If `keyOrCreator` is not set, we assume default behavior which is 1:1 attribute re-write. - // This is also a default behavior for `setAttribute` converter when no attribute creator is passed. - attributeCreator = undefined; - } else if (typeof keyOrCreator == 'string') { - // `keyOrCreator` is an attribute key. - - if (value) { - // If value is set, create "dumb" creator that always returns the same object. - attributeCreator = function attributeCreator() { - return { key: keyOrCreator, value: value }; - }; - } else { - // If value is not set, take it from the passed parameter. - attributeCreator = function attributeCreator(value) { - return { key: keyOrCreator, value: value }; - }; - } - } else { - // `keyOrCreator` is an attribute creator function. - attributeCreator = keyOrCreator; - } - - var _iteratorNormalCompletion2 = true; - var _didIteratorError2 = false; - var _iteratorError2 = undefined; - - try { - for (var _iterator2 = this._dispatchers[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { - var dispatcher = _step2.value; - - var options = { priority: this._from.priority || 'normal' }; - - dispatcher.on('addAttribute:' + this._from.key, setAttribute(attributeCreator), options); - dispatcher.on('changeAttribute:' + this._from.key, setAttribute(attributeCreator), options); - dispatcher.on('removeAttribute:' + this._from.key, removeAttribute(attributeCreator), options); - } - } catch (err) { - _didIteratorError2 = true; - _iteratorError2 = err; - } finally { - try { - if (!_iteratorNormalCompletion2 && _iterator2.return) { - _iterator2.return(); - } - } finally { - if (_didIteratorError2) { - throw _iteratorError2; - } - } - } - } - }]); - return ModelConverterBuilder; -}(); - -/** - * Entry point for model-to-view converters builder. This chainable API makes it easy to create basic, most common - * model-to-view converters and attach them to provided dispatchers. The method returns an instance of - * {@link engine.conversion.ModelConverterBuilder}. - * - * @external engine.conversion.buildModelConverter - * @memberOf engine.conversion - */ - - -function buildModelConverter() { - return new ModelConverterBuilder(); -} - -/** - * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md. - */ - -/** - * View matcher class. - * Instance of this class can be used to find {@link engine.view.Element elements} that match given pattern. - * - * @memberOf engine.view - */ -var Matcher = function () { - /** - * Creates new instance of Matcher. - * - * @param {String|RegExp|Object} [pattern] Match patterns. See {@link engine.view.Matcher#add add method} for - * more information. - */ - function Matcher() { - classCallCheck(this, Matcher); - - this._patterns = []; - - this.add.apply(this, arguments); - } - - /** - * Adds pattern or patterns to matcher instance. - * - * Example patterns matching element's name: - * - * // String. - * matcher.add( 'div' ); - * matcher.add( { name: 'div' } ); - * - * // Regular expression. - * matcher.add( /^\w/ ); - * matcher.add( { name: /^\w/ } ); - * - * Example pattern matching element's attributes: - * - * matcher.add( { - * attributes: { - * title: 'foobar', - * foo: /^\w+/ - * } - * } ); - * - * Example patterns matching element's classes: - * - * // Single class. - * matcher.add( { - * class: 'foobar' - * } ); - * - * // Single class using regular expression. - * matcher.add( { - * class: /foo.../ - * } ); - * - * // Multiple classes to match. - * matcher.add( { - * class: [ 'baz', 'bar', /foo.../ ] - * } ): - * - * Example pattern matching element's styles: - * - * matcher.add( { - * style: { - * position: 'absolute', - * color: /^\w*blue$/ - * } - * } ); - * - * Example function pattern: - * - * matcher.add( ( element ) => { - * // Result of this function will be included in `match` - * // property of the object returned from matcher.match() call. - * if ( element.name === 'div' && element.childCount > 0 ) { - * return { name: true }; - * } - * - * return null; - * } ); - * - * Multiple patterns can be added in one call: - * - * matcher.add( 'div', { class: 'foobar' } ); - * - * @param {Object|String|RegExp|function} pattern Object describing pattern details. If string or regular expression - * is provided it will be used to match element's name. Pattern can be also provided in a form - * of a function - then this function will be called with each {@link engine.view.Element element} as a parameter. - * Function's return value will be stored under `match` key of the object returned from - * {@link engine.view.Matcher#match match} or {@link engine.view.Matcher#matchAll matchAll} methods. - * @param {String|RegExp} [pattern.name] Name or regular expression to match element's name. - * @param {Object} [pattern.attribute] Object with key-value pairs representing attributes to match. Each object key - * represents attribute name. Value under that key can be either a string or a regular expression and it will be - * used to match attribute value. - * @param {String|RegExp|Array} [pattern.class] Class name or array of class names to match. Each name can be - * provided in a form of string or regular expression. - * @param {Object} [pattern.style] Object with key-value pairs representing styles to match. Each object key - * represents style name. Value under that key can be either a string or a regular expression and it will be used - * to match style value. - */ - - - createClass(Matcher, [{ - key: 'add', - value: function add() { - for (var _len = arguments.length, pattern = Array(_len), _key = 0; _key < _len; _key++) { - pattern[_key] = arguments[_key]; - } - - var _iteratorNormalCompletion = true; - var _didIteratorError = false; - var _iteratorError = undefined; - - try { - for (var _iterator = pattern[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { - var item = _step.value; - - // String or RegExp pattern is used as element's name. - if (typeof item == 'string' || item instanceof RegExp) { - item = { name: item }; - } - - // Single class name/RegExp can be provided. - if (item.class && (typeof item.class == 'string' || item.class instanceof RegExp)) { - item.class = [item.class]; - } - - this._patterns.push(item); - } - } catch (err) { - _didIteratorError = true; - _iteratorError = err; - } finally { - try { - if (!_iteratorNormalCompletion && _iterator.return) { - _iterator.return(); - } - } finally { - if (_didIteratorError) { - throw _iteratorError; - } - } - } - } - - /** - * Matches elements for currently stored patterns. Returns match information about first found - * {@link engine.view.Element element}, otherwise returns `null`. - * - * Example of returned object: - * - * { - * element: , - * pattern: , - * match: { - * name: true, - * attributes: [ 'title', 'href' ], - * classes: [ 'foo' ], - * styles: [ 'color', 'position' ] - * } - * } - * - * @see engine.view.Matcher#add - * @see engine.view.Matcher#matchAll - * @param {...core.view.Element} element View element to match against stored patterns. - * @returns {Object|null} result - * @returns {core.view.Element} result.element Matched view element. - * @returns {Object|String|RegExp|function} result.pattern Pattern that was used to find matched element. - * @returns {Object} result.match Object representing matched element parts. - * @returns {Boolean} [result.match.name] True if name of the element was matched. - * @returns {Array} [result.match.attribute] Array with matched attribute names. - * @returns {Array} [result.match.class] Array with matched class names. - * @returns {Array} [result.match.style] Array with matched style names. - */ - - }, { - key: 'match', - value: function match() { - for (var _len2 = arguments.length, element = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { - element[_key2] = arguments[_key2]; - } - - var _iteratorNormalCompletion2 = true; - var _didIteratorError2 = false; - var _iteratorError2 = undefined; - - try { - for (var _iterator2 = element[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { - var singleElement = _step2.value; - var _iteratorNormalCompletion3 = true; - var _didIteratorError3 = false; - var _iteratorError3 = undefined; - - try { - for (var _iterator3 = this._patterns[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { - var _pattern = _step3.value; - - var match = isElementMatching(singleElement, _pattern); - - if (match) { - return { - element: singleElement, - pattern: _pattern, - match: match - }; - } - } - } catch (err) { - _didIteratorError3 = true; - _iteratorError3 = err; - } finally { - try { - if (!_iteratorNormalCompletion3 && _iterator3.return) { - _iterator3.return(); - } - } finally { - if (_didIteratorError3) { - throw _iteratorError3; - } - } - } - } - } catch (err) { - _didIteratorError2 = true; - _iteratorError2 = err; - } finally { - try { - if (!_iteratorNormalCompletion2 && _iterator2.return) { - _iterator2.return(); - } - } finally { - if (_didIteratorError2) { - throw _iteratorError2; - } - } - } - - return null; - } - - /** - * Matches elements for currently stored patterns. Returns array of match information with all found - * {@link engine.view.Element elements}. If no element is found - returns `null`. - * - * @see engine.view.Matcher#add - * @see engine.view.Matcher#match - * @param {...engine.view.Element} element View element to match against stored patterns. - * @returns {Array.|null} Array with match information about found elements or `null`. For more information - * see {@link engine.view.Matcher#match match method} description. - */ - - }, { - key: 'matchAll', - value: function matchAll() { - var results = []; - - for (var _len3 = arguments.length, element = Array(_len3), _key3 = 0; _key3 < _len3; _key3++) { - element[_key3] = arguments[_key3]; - } - - var _iteratorNormalCompletion4 = true; - var _didIteratorError4 = false; - var _iteratorError4 = undefined; - - try { - for (var _iterator4 = element[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) { - var singleElement = _step4.value; - var _iteratorNormalCompletion5 = true; - var _didIteratorError5 = false; - var _iteratorError5 = undefined; - - try { - for (var _iterator5 = this._patterns[Symbol.iterator](), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) { - var _pattern2 = _step5.value; - - var match = isElementMatching(singleElement, _pattern2); - - if (match) { - results.push({ - element: singleElement, - pattern: _pattern2, - match: match - }); - } - } - } catch (err) { - _didIteratorError5 = true; - _iteratorError5 = err; - } finally { - try { - if (!_iteratorNormalCompletion5 && _iterator5.return) { - _iterator5.return(); - } - } finally { - if (_didIteratorError5) { - throw _iteratorError5; - } - } - } - } - } catch (err) { - _didIteratorError4 = true; - _iteratorError4 = err; - } finally { - try { - if (!_iteratorNormalCompletion4 && _iterator4.return) { - _iterator4.return(); - } - } finally { - if (_didIteratorError4) { - throw _iteratorError4; - } - } - } - - return results.length > 0 ? results : null; - } - - /** - * Returns the name of the element to match if there is exactly one pattern added to the matcher instance - * and it matches element name defined by `string` (not `RegExp`). Otherwise, returns `null`. - * - * @returns {String|null} Element name trying to match. - */ - - }, { - key: 'getElementName', - value: function getElementName() { - return this._patterns.length == 1 && this._patterns[0].name && !(this._patterns[0].name instanceof RegExp) ? this._patterns[0].name : null; - } - }]); - return Matcher; -}(); - -function isElementMatching(element, pattern) { - // If pattern is provided as function - return result of that function; - if (typeof pattern == 'function') { - return pattern(element); - } - - var match = {}; - // Check element's name. - if (pattern.name) { - match.name = matchName(pattern.name, element.name); - - if (!match.name) { - return null; - } - } - - // Check element's attributes. - if (pattern.attribute) { - match.attribute = matchAttributes(pattern.attribute, element); - - if (!match.attribute) { - return null; - } - } - - // Check element's classes. - if (pattern.class) { - match.class = matchClasses(pattern.class, element); - - if (!match.class) { - return false; - } - } - - // Check element's styles. - if (pattern.style) { - match.style = matchStyles(pattern.style, element); - - if (!match.style) { - return false; - } - } - - return match; -} - -// Checks if name can be matched by provided pattern. -// -// @param {String|RegExp} pattern -// @param {String} name -// @returns {Boolean} Returns `true` if name can be matched, `false` otherwise. -function matchName(pattern, name) { - // If pattern is provided as RegExp - test against this regexp. - if (pattern instanceof RegExp) { - return pattern.test(name); - } - - return pattern === name; -} - -// Checks if attributes of provided element can be matched against provided patterns. -// -// @param {Object} patterns Object with information about attributes to match. Each key of the object will be -// used as attribute name. Value of each key can be a string or regular expression to match against attribute value. -// @param {engine.view.Element} element Element which attributes will be tested. -// @returns {Array|null} Returns array with matched attribute names or `null` if no attributes were matched. -function matchAttributes(patterns, element) { - var match = []; - - for (var name in patterns) { - var pattern = patterns[name]; - - if (element.hasAttribute(name)) { - var attribute = element.getAttribute(name); - - if (pattern instanceof RegExp) { - if (pattern.test(attribute)) { - match.push(name); - } else { - return null; - } - } else if (attribute === pattern) { - match.push(name); - } else { - return null; - } - } else { - return null; - } - } - - return match; -} - -// Checks if classes of provided element can be matched against provided patterns. -// -// @param {Array.} patterns Array of strings or regular expressions to match against element's classes. -// @param {engine.view.Element} element Element which classes will be tested. -// @returns {Array|null} Returns array with matched class names or `null` if no classes were matched. -function matchClasses(patterns, element) { - var match = []; - - var _iteratorNormalCompletion6 = true; - var _didIteratorError6 = false; - var _iteratorError6 = undefined; - - try { - for (var _iterator6 = patterns[Symbol.iterator](), _step6; !(_iteratorNormalCompletion6 = (_step6 = _iterator6.next()).done); _iteratorNormalCompletion6 = true) { - var pattern = _step6.value; - - if (pattern instanceof RegExp) { - var classes = element.getClassNames(); - - var _iteratorNormalCompletion7 = true; - var _didIteratorError7 = false; - var _iteratorError7 = undefined; - - try { - for (var _iterator7 = classes[Symbol.iterator](), _step7; !(_iteratorNormalCompletion7 = (_step7 = _iterator7.next()).done); _iteratorNormalCompletion7 = true) { - var name = _step7.value; - - if (pattern.test(name)) { - match.push(name); - } - } - } catch (err) { - _didIteratorError7 = true; - _iteratorError7 = err; - } finally { - try { - if (!_iteratorNormalCompletion7 && _iterator7.return) { - _iterator7.return(); - } - } finally { - if (_didIteratorError7) { - throw _iteratorError7; - } - } - } - - if (match.length === 0) { - return null; - } - } else if (element.hasClass(pattern)) { - match.push(pattern); - } else { - return null; - } - } - } catch (err) { - _didIteratorError6 = true; - _iteratorError6 = err; - } finally { - try { - if (!_iteratorNormalCompletion6 && _iterator6.return) { - _iterator6.return(); - } - } finally { - if (_didIteratorError6) { - throw _iteratorError6; - } - } - } - - return match; -} - -// Checks if styles of provided element can be matched against provided patterns. -// -// @param {Object} patterns Object with information about styles to match. Each key of the object will be -// used as style name. Value of each key can be a string or regular expression to match against style value. -// @param {engine.view.Element} element Element which styles will be tested. -// @returns {Array|null} Returns array with matched style names or `null` if no styles were matched. -function matchStyles(patterns, element) { - var match = []; - - for (var name in patterns) { - var pattern = patterns[name]; - - if (element.hasStyle(name)) { - var style = element.getStyle(name); - - if (pattern instanceof RegExp) { - if (pattern.test(style)) { - match.push(name); - } else { - return null; - } - } else if (style === pattern) { - match.push(name); - } else { - return null; - } - } else { - return null; - } - } - - return match; -} - -/** - * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md. - */ - -/** - * Provides chainable, high-level API to easily build basic view-to-model converters that are appended to given - * dispatchers. View-to-model converters are used when external data is added to the editor, i.e. when a user pastes - * HTML content to the editor. Then, converters are used to translate this structure, possibly removing unknown/incorrect - * nodes, and add it to the model. Also multiple, different elements might be translated into the same thing in the - * model, i.e. `` and `` elements might be converted to `bold` attribute (even though `bold` attribute will - * be then converted only to `` tag). Instances of this class are created by {@link engine.conversion.buildViewConverter}. - * - * If you need more complex converters, see {@link engine.conversion.ViewConversionDispatcher}, - * {@link engine.conversion.viewToModel}, {@link engine.conversion.ViewConsumable}. - * - * Using this API it is possible to create various kind of converters: - * - * 1. View element to model element: - * - * buildViewConverter().for( dispatcher ).fromElement( 'p' ).toElement( 'paragraph' ); - * - * 2. View element to model attribute: - * - * buildViewConverter().for( dispatcher ).fromElement( 'b' ).fromElement( 'strong' ).toAttribute( 'bold', 'true' ); - * - * 3. View attribute to model attribute: - * - * buildViewConverter().for( dispatcher ).fromAttribute( 'style', { 'font-weight': 'bold' } ).toAttribute( 'bold', 'true' ); - * buildViewConverter().for( dispatcher ) - * .fromAttribute( 'class' ) - * .toAttribute( ( viewElement ) => ( { class: viewElement.getAttribute( 'class' ) } ) ); - * - * 4. View elements and attributes to model attribute: - * - * buildViewConverter().for( dispatcher ) - * .fromElement( 'b' ).fromElement( 'strong' ).fromAttribute( 'style', { 'font-weight': 'bold' } ) - * .toAttribute( 'bold', 'true' ); - * - * 5. View {@link engine.view.Matcher view element matcher instance} or {@link engine.view.Matcher#add matcher pattern} - * to model element or attribute: - * - * const matcher = new ViewMatcher(); - * matcher.add( 'div', { class: 'quote' } ); - * buildViewConverter().for( dispatcher ).from( matcher ).toElement( 'quote' ); - * - * buildViewConverter().for( dispatcher ).from( { name: 'span', class: 'bold' } ).toAttribute( 'bold', 'true' ); - * - * Note, that converters built using `ViewConverterBuilder` automatically check {@link engine.model.Schema schema} - * if created model structure is valid. If given conversion would be invalid according to schema, it is ignored. - * - * It is possible to provide creator functions as parameters for {@link engine.conversion.ViewConverterBuilder#toElement} - * and {@link engine.conversion.ViewConverterBuilder#toAttribute} methods. See their descriptions to learn more. - * - * By default, converter will {@link engine.conversion.ViewConsumable#consume consume} every value specified in - * given `from...` query, i.e. `.from( { name: 'span', class: 'bold' } )` will make converter consume both `span` name - * and `bold` class. It is possible to change this behavior using {@link engine.conversion.ViewConverterBuilder#consuming consuming} - * modifier. The modifier alters the last `fromXXX` query used before it. To learn more about consuming values, - * see {@link engine.conversion.ViewConsumable}. - * - * It is also possible to {@link engine.conversion.ViewConverterBuilder#withPriority change default priority} - * of created converters to decide which converter should be fired earlier and which later. This is useful if you provide - * a general converter but want to provide different converter for a specific-case (i.e. given view element is converted - * always to given model element, but if it has given class it is converter to other model element). For this, - * use {@link engine.conversion.ViewConverterBuilder#withPriority withPriority} modifier. The modifier alters - * the last `from...` query used before it. - * - * Note that `to...` methods are "terminators", which means that should be the last one used in building converter. - * - * You can use {@link engine.conversion.ModelConverterBuilder} to create "opposite" converters - from model to view. - * - * @memberOf engine.conversion - */ - -var ViewConverterBuilder = function () { - /** - * Creates `ViewConverterBuilder` with given `dispatchers` registered to it. - */ - function ViewConverterBuilder() { - classCallCheck(this, ViewConverterBuilder); - - /** - * Dispatchers to which converters will be attached. - * - * @type {Array.} - * @private - */ - this._dispatchers = []; - - /** - * Stores "from" queries. - * - * @type {Array} - * @private - */ - this._from = []; - } - - /** - * Set one or more dispatchers which the built converter will be attached to. - * - * @chainable - * @param {...engine.conversion.ViewConversionDispatcher} dispatchers One or more dispatchers. - * @returns {engine.conversion.ViewConverterBuilder} - */ - - - createClass(ViewConverterBuilder, [{ - key: 'for', - value: function _for() { - for (var _len = arguments.length, dispatchers = Array(_len), _key = 0; _key < _len; _key++) { - dispatchers[_key] = arguments[_key]; - } - - this._dispatchers = dispatchers; - - return this; - } - - /** - * Registers what view element should be converted. - * - * buildViewConverter().for( dispatcher ).fromElement( 'p' ).toElement( 'paragraph' ); - * - * @chainable - * @param {String} elementName View element name. - * @returns {engine.conversion.ViewConverterBuilder} - */ - - }, { - key: 'fromElement', - value: function fromElement(elementName) { - return this.from({ name: elementName }); - } - - /** - * Registers what view attribute should be converted. - * - * buildViewConverter().for( dispatcher ).fromAttribute( 'style', { 'font-weight': 'bold' } ).toAttribute( 'bold', 'true' ); - * - * @chainable - * @param {String|RegExp} key View attribute key. - * @param {String|RegExp} [value] View attribute value. - * @returns {engine.conversion.ViewConverterBuilder} - */ - - }, { - key: 'fromAttribute', - value: function fromAttribute(key) { - var value = arguments.length <= 1 || arguments[1] === undefined ? /.*/ : arguments[1]; - - var pattern = {}; - pattern[key] = value; - - return this.from(pattern); - } - - /** - * Registers what view pattern should be converted. The method accepts either {@link engine.view.Matcher view matcher} - * or view matcher pattern. - * - * const matcher = new ViewMatcher(); - * matcher.add( 'div', { class: 'quote' } ); - * buildViewConverter().for( dispatcher ).from( matcher ).toElement( 'quote' ); - * - * buildViewConverter().for( dispatcher ).from( { name: 'span', class: 'bold' } ).toAttribute( 'bold', 'true' ); - * - * @chainable - * @param {Object|engine.view.Matcher} matcher View matcher or view matcher pattern. - * @returns {engine.conversion.ViewConverterBuilder} - */ - - }, { - key: 'from', - value: function from(matcher) { - if (!(matcher instanceof Matcher)) { - matcher = new Matcher(matcher); - } - - this._from.push({ - matcher: matcher, - consume: false, - priority: null - }); - - return this; - } - - /** - * Modifies which consumable values will be {@link engine.conversion.ViewConsumable#consume consumed} by built converter. - * It modifies the last `from...` query. Can be used after each `from...` query in given chain. Useful for providing - * more specific matches. - * - * // This converter will only handle class bold conversion (to proper attribute) but span element - * // conversion will have to be done in separate converter. - * // Without consuming modifier, the converter would consume both class and name, so a converter for - * // span element would not be fired. - * buildViewConverter().for( dispatcher ) - * .from( { name: 'span', class: 'bold' } ).consuming( { class: 'bold' } ) - * .toAttribute( 'bold', 'true' } ); - * - * buildViewConverter().for( dispatcher ) - * .fromElement( 'img' ).consuming( { name: true, attributes: [ 'src', 'title' ] } ) - * .toElement( ( viewElement ) => new ModelElement( 'image', { src: viewElement.getAttribute( 'src' ), - * title: viewElement.getAttribute( 'title' ) } ); - * - * **Note:** All and only values from passed object has to be consumable on converted view element. This means that - * using `consuming` method, you can either make looser conversion conditions (like in first example) or tighter - * conversion conditions (like in second example). So, the view element, to be converter, has to match query of - * `from...` method and then have to have enough consumable values to consume. - * - * @see engine.conversion.ViewConsumable - * @chainable - * @param {Object} consume Values to consume. - * @returns {engine.conversion.ViewConverterBuilder} - */ - - }, { - key: 'consuming', - value: function consuming(consume) { - var lastFrom = this._from[this._from.length - 1]; - lastFrom.consume = consume; - - return this; - } - - /** - * Changes default priority for built converter. It modifies the last `from...` query. Can be used after each - * `from...` query in given chain. Useful for overwriting converters. The lower the number, the earlier converter will be fired. - * - * buildViewConverter().for( dispatcher ).fromElement( 'p' ).toElement( 'paragraph' ); - * // Register converter with proper priority, otherwise "p" element would get consumed by first - * // converter and the second converter would not be fired. - * buildViewConverter().for( dispatcher ) - * .from( { name: 'p', class: 'custom' } ).withPriority( 9 ) - * .toElement( 'customParagraph' ); - * - * **Note:** `ViewConverterBuilder` takes care so all `toElement` conversions takes place before all `toAttribute` - * conversions. This is done by setting default `toElement` priority to `10` and `toAttribute` priority to `1000`. - * It is recommended to set converter priority for `toElement` conversions below `500` and `toAttribute` priority - * above `500`. It is important that model elements are created before attributes, otherwise attributes would - * not be applied or other errors may occur. - * - * @chainable - * @param {Number} priority Converter priority. - * @returns {engine.conversion.ViewConverterBuilder} - */ - - }, { - key: 'withPriority', - value: function withPriority(priority) { - var lastFrom = this._from[this._from.length - 1]; - lastFrom.priority = priority; - - return this; - } - - /** - * Registers what model element will be created by converter. - * - * Method accepts two ways of providing what kind of model element will be created. You can pass model element - * name as a `string` or a function that will return model element instance. If you provide creator function, - * it will be passed converted view element as first and only parameter. - * - * buildViewConverter().for( dispatcher ).fromElement( 'p' ).toElement( 'paragraph' ); - * buildViewConverter().for( dispatcher ) - * .fromElement( 'img' ) - * .toElement( ( viewElement ) => new ModelElement( 'image', { src: viewElement.getAttribute( 'src' ) } ); - * - * @param {String|Function} element Model element name or model element creator function. - */ - - }, { - key: 'toElement', - value: function toElement(element) { - var eventCallbackGen = function eventCallbackGen(from) { - return function (evt, data, consumable, conversionApi) { - // There is one callback for all patterns in the matcher. - // This will be usually just one pattern but we support matchers with many patterns too. - var matchAll = from.matcher.matchAll(data.input); - - // If there is no match, this callback should not do anything. - if (!matchAll) { - return; - } - - // Now, for every match between matcher and actual element, we will try to consume the match. - var _iteratorNormalCompletion = true; - var _didIteratorError = false; - var _iteratorError = undefined; - - try { - for (var _iterator = matchAll[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { - var match = _step.value; - - // Create model element basing on creator function or element name. - var modelElement = element instanceof Function ? element(data.input) : new Element(element); - - // Check whether generated structure is okay with `Schema`. - var keys = Array.from(modelElement.getAttributeKeys()); - - if (!conversionApi.schema.check({ name: modelElement.name, attributes: keys, inside: data.context })) { - continue; - } - - // Try to consume appropriate values from consumable values list. - if (!consumable.consume(data.input, from.consume || match.match)) { - continue; - } - - // If everything is fine, we are ready to start the conversion. - // Add newly created `modelElement` to the parents stack. - data.context.push(modelElement); - // Convert children of converted view element and append them to `modelElement`. - var modelChildren = conversionApi.convertChildren(data.input, consumable, data); - var insertPosition = Position.createAt(modelElement, 'end'); - modelWriter.insert(insertPosition, modelChildren); + var _iteratorNormalCompletion = true; + var _didIteratorError = false; + var _iteratorError = undefined; - // Remove created `modelElement` from the parents stack. - data.context.pop(); + try { + for (var _iterator = pattern[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { + var item = _step.value; - // Add `modelElement` as a result. - data.output = modelElement; + // String or RegExp pattern is used as element's name. + if (typeof item == 'string' || item instanceof RegExp) { + item = { name: item }; + } - // Prevent multiple conversion if there are other correct matches. - break; - } - } catch (err) { - _didIteratorError = true; - _iteratorError = err; - } finally { - try { - if (!_iteratorNormalCompletion && _iterator.return) { - _iterator.return(); - } - } finally { - if (_didIteratorError) { - throw _iteratorError; - } - } + // Single class name/RegExp can be provided. + if (item.class && (typeof item.class == 'string' || item.class instanceof RegExp)) { + item.class = [item.class]; } - }; - }; - this._setCallback(eventCallbackGen, 'normal'); + this._patterns.push(item); + } + } catch (err) { + _didIteratorError = true; + _iteratorError = err; + } finally { + try { + if (!_iteratorNormalCompletion && _iterator.return) { + _iterator.return(); + } + } finally { + if (_didIteratorError) { + throw _iteratorError; + } + } + } } /** - * Registers what model attribute will be created by converter. + * Matches elements for currently stored patterns. Returns match information about first found + * {@link engine.view.Element element}, otherwise returns `null`. * - * Method accepts two ways of providing what kind of model attribute will be created. You can either pass two strings - * representing attribute key and attribute value or a function that returns an object with `key` and `value` properties. - * If you provide creator function, it will be passed converted view element as first and only parameter. + * Example of returned object: * - * buildViewConverter().for( dispatcher ).fromAttribute( 'style', { 'font-weight': 'bold' } ).toAttribute( 'bold', 'true' ); - * buildViewConverter().for( dispatcher ) - * .fromAttribute( 'class' ) - * .toAttribute( ( viewElement ) => ( { key: 'class', value: viewElement.getAttribute( 'class' ) } ) ); + * { + * element: , + * pattern: , + * match: { + * name: true, + * attributes: [ 'title', 'href' ], + * classes: [ 'foo' ], + * styles: [ 'color', 'position' ] + * } + * } * - * @param {String|Function} keyOrCreator Attribute key or a creator function. - * @param {String} [value] Attribute value. Required if `keyOrCreator` is a `string`. Ignored otherwise. + * @see engine.view.Matcher#add + * @see engine.view.Matcher#matchAll + * @param {...core.view.Element} element View element to match against stored patterns. + * @returns {Object|null} result + * @returns {core.view.Element} result.element Matched view element. + * @returns {Object|String|RegExp|function} result.pattern Pattern that was used to find matched element. + * @returns {Object} result.match Object representing matched element parts. + * @returns {Boolean} [result.match.name] True if name of the element was matched. + * @returns {Array} [result.match.attribute] Array with matched attribute names. + * @returns {Array} [result.match.class] Array with matched class names. + * @returns {Array} [result.match.style] Array with matched style names. */ }, { - key: 'toAttribute', - value: function toAttribute(keyOrCreator, value) { - var eventCallbackGen = function eventCallbackGen(from) { - return function (evt, data, consumable, conversionApi) { - // There is one callback for all patterns in the matcher. - // This will be usually just one pattern but we support matchers with many patterns too. - var matchAll = from.matcher.matchAll(data.input); + key: 'match', + value: function match() { + for (var _len2 = arguments.length, element = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { + element[_key2] = arguments[_key2]; + } - // If there is no match, this callback should not do anything. - if (!matchAll) { - return; - } + var _iteratorNormalCompletion2 = true; + var _didIteratorError2 = false; + var _iteratorError2 = undefined; - // Now, for every match between matcher and actual element, we will try to consume the match. - var _iteratorNormalCompletion2 = true; - var _didIteratorError2 = false; - var _iteratorError2 = undefined; + try { + for (var _iterator2 = element[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { + var singleElement = _step2.value; + var _iteratorNormalCompletion3 = true; + var _didIteratorError3 = false; + var _iteratorError3 = undefined; try { - for (var _iterator2 = matchAll[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { - var match = _step2.value; + for (var _iterator3 = this._patterns[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { + var _pattern = _step3.value; - // Try to consume appropriate values from consumable values list. - if (!consumable.consume(data.input, from.consume || match.match)) { - continue; - } + var match = isElementMatching(singleElement, _pattern); - // Since we are converting to attribute we need an output on which we will set the attribute. - // If the output is not created yet, we will create it. - if (!data.output) { - data.output = conversionApi.convertChildren(data.input, consumable, data); + if (match) { + return { + element: singleElement, + pattern: _pattern, + match: match + }; } - - // Use attribute creator function, if provided. - var attribute = keyOrCreator instanceof Function ? keyOrCreator(data.input) : { key: keyOrCreator, value: value }; - - // Set attribute on current `output`. `Schema` is checked inside this helper function. - setAttributeOn(data.output, attribute, data, conversionApi); - - // Prevent multiple conversion if there are other correct matches. - break; } } catch (err) { - _didIteratorError2 = true; - _iteratorError2 = err; + _didIteratorError3 = true; + _iteratorError3 = err; } finally { try { - if (!_iteratorNormalCompletion2 && _iterator2.return) { - _iterator2.return(); + if (!_iteratorNormalCompletion3 && _iterator3.return) { + _iterator3.return(); } } finally { - if (_didIteratorError2) { - throw _iteratorError2; + if (_didIteratorError3) { + throw _iteratorError3; } } } - }; - }; + } + } catch (err) { + _didIteratorError2 = true; + _iteratorError2 = err; + } finally { + try { + if (!_iteratorNormalCompletion2 && _iterator2.return) { + _iterator2.return(); + } + } finally { + if (_didIteratorError2) { + throw _iteratorError2; + } + } + } - this._setCallback(eventCallbackGen, 'low'); + return null; } /** - * Helper function that uses given callback generator to created callback function and sets it on registered dispatchers. + * Matches elements for currently stored patterns. Returns array of match information with all found + * {@link engine.view.Element elements}. If no element is found - returns `null`. * - * @param eventCallbackGen - * @param defaultPriority - * @private + * @see engine.view.Matcher#add + * @see engine.view.Matcher#match + * @param {...engine.view.Element} element View element to match against stored patterns. + * @returns {Array.|null} Array with match information about found elements or `null`. For more information + * see {@link engine.view.Matcher#match match method} description. */ }, { - key: '_setCallback', - value: function _setCallback(eventCallbackGen, defaultPriority) { - // We will add separate event callback for each registered `from` entry. - var _iteratorNormalCompletion3 = true; - var _didIteratorError3 = false; - var _iteratorError3 = undefined; - - try { - for (var _iterator3 = this._from[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { - var from = _step3.value; + key: 'matchAll', + value: function matchAll() { + var results = []; - // We have to figure out event name basing on matcher's patterns. - // If there is exactly one pattern and it has `name` property we will used that name. - var matcherElementName = from.matcher.getElementName(); - var eventName = matcherElementName ? 'element:' + matcherElementName : 'element'; - var eventCallback = eventCallbackGen(from); + for (var _len3 = arguments.length, element = Array(_len3), _key3 = 0; _key3 < _len3; _key3++) { + element[_key3] = arguments[_key3]; + } - var priority = from.priority === null ? defaultPriority : from.priority; + var _iteratorNormalCompletion4 = true; + var _didIteratorError4 = false; + var _iteratorError4 = undefined; - // Add event to each registered dispatcher. - var _iteratorNormalCompletion4 = true; - var _didIteratorError4 = false; - var _iteratorError4 = undefined; + try { + for (var _iterator4 = element[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) { + var singleElement = _step4.value; + var _iteratorNormalCompletion5 = true; + var _didIteratorError5 = false; + var _iteratorError5 = undefined; try { - for (var _iterator4 = this._dispatchers[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) { - var dispatcher = _step4.value; + for (var _iterator5 = this._patterns[Symbol.iterator](), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) { + var _pattern2 = _step5.value; - dispatcher.on(eventName, eventCallback, { priority: priority }); + var match = isElementMatching(singleElement, _pattern2); + + if (match) { + results.push({ + element: singleElement, + pattern: _pattern2, + match: match + }); + } } } catch (err) { - _didIteratorError4 = true; - _iteratorError4 = err; + _didIteratorError5 = true; + _iteratorError5 = err; } finally { try { - if (!_iteratorNormalCompletion4 && _iterator4.return) { - _iterator4.return(); + if (!_iteratorNormalCompletion5 && _iterator5.return) { + _iterator5.return(); } } finally { - if (_didIteratorError4) { - throw _iteratorError4; + if (_didIteratorError5) { + throw _iteratorError5; } } } } } catch (err) { - _didIteratorError3 = true; - _iteratorError3 = err; + _didIteratorError4 = true; + _iteratorError4 = err; } finally { try { - if (!_iteratorNormalCompletion3 && _iterator3.return) { - _iterator3.return(); + if (!_iteratorNormalCompletion4 && _iterator4.return) { + _iterator4.return(); } } finally { - if (_didIteratorError3) { - throw _iteratorError3; + if (_didIteratorError4) { + throw _iteratorError4; } } } + + return results.length > 0 ? results : null; + } + + /** + * Returns the name of the element to match if there is exactly one pattern added to the matcher instance + * and it matches element name defined by `string` (not `RegExp`). Otherwise, returns `null`. + * + * @returns {String|null} Element name trying to match. + */ + + }, { + key: 'getElementName', + value: function getElementName() { + return this._patterns.length == 1 && this._patterns[0].name && !(this._patterns[0].name instanceof RegExp) ? this._patterns[0].name : null; } }]); - return ViewConverterBuilder; + return Matcher; }(); -// Helper function that sets given attributes on given `engine.model.Node` or `engine.model.DocumentFragment`. +function isElementMatching(element, pattern) { + // If pattern is provided as function - return result of that function; + if (typeof pattern == 'function') { + return pattern(element); + } + var match = {}; + // Check element's name. + if (pattern.name) { + match.name = matchName(pattern.name, element.name); -function setAttributeOn(toChange, attribute, data, conversionApi) { - if (isIterable(toChange)) { - var _iteratorNormalCompletion5 = true; - var _didIteratorError5 = false; - var _iteratorError5 = undefined; + if (!match.name) { + return null; + } + } - try { - for (var _iterator5 = toChange[Symbol.iterator](), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) { - var node = _step5.value; + // Check element's attributes. + if (pattern.attribute) { + match.attribute = matchAttributes(pattern.attribute, element); - setAttributeOn(node, attribute, data, conversionApi); - } - } catch (err) { - _didIteratorError5 = true; - _iteratorError5 = err; - } finally { - try { - if (!_iteratorNormalCompletion5 && _iterator5.return) { - _iterator5.return(); - } - } finally { - if (_didIteratorError5) { - throw _iteratorError5; - } - } + if (!match.attribute) { + return null; } + } - return; + // Check element's classes. + if (pattern.class) { + match.class = matchClasses(pattern.class, element); + + if (!match.class) { + return false; + } } - var keys = Array.from(toChange.getAttributeKeys()); - keys.push(attribute.key); + // Check element's styles. + if (pattern.style) { + match.style = matchStyles(pattern.style, element); - var schemaQuery = { - name: toChange.name || '$text', - attributes: keys, - inside: data.context - }; + if (!match.style) { + return false; + } + } - if (conversionApi.schema.check(schemaQuery)) { - toChange.setAttribute(attribute.key, attribute.value); + return match; +} + +// Checks if name can be matched by provided pattern. +// +// @param {String|RegExp} pattern +// @param {String} name +// @returns {Boolean} Returns `true` if name can be matched, `false` otherwise. +function matchName(pattern, name) { + // If pattern is provided as RegExp - test against this regexp. + if (pattern instanceof RegExp) { + return pattern.test(name); } + + return pattern === name; } -/** - * Entry point for view-to-model converters builder. This chainable API makes it easy to create basic, most common - * view-to-model converters and attach them to provided dispatchers. The method returns an instance of - * {@link engine.conversion.ViewConverterBuilder}. - * - * @external engine.conversion.buildViewConverter - * @memberOf engine.conversion - */ -function buildViewConverter() { - return new ViewConverterBuilder(); +// Checks if attributes of provided element can be matched against provided patterns. +// +// @param {Object} patterns Object with information about attributes to match. Each key of the object will be +// used as attribute name. Value of each key can be a string or regular expression to match against attribute value. +// @param {engine.view.Element} element Element which attributes will be tested. +// @returns {Array|null} Returns array with matched attribute names or `null` if no attributes were matched. +function matchAttributes(patterns, element) { + var match = []; + + for (var name in patterns) { + var pattern = patterns[name]; + + if (element.hasAttribute(name)) { + var attribute = element.getAttribute(name); + + if (pattern instanceof RegExp) { + if (pattern.test(attribute)) { + match.push(name); + } else { + return null; + } + } else if (attribute === pattern) { + match.push(name); + } else { + return null; + } + } else { + return null; + } + } + + return match; } -/** - * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md. - */ +// Checks if classes of provided element can be matched against provided patterns. +// +// @param {Array.} patterns Array of strings or regular expressions to match against element's classes. +// @param {engine.view.Element} element Element which classes will be tested. +// @returns {Array|null} Returns array with matched class names or `null` if no classes were matched. +function matchClasses(patterns, element) { + var match = []; -/** - * The paragraph feature for the editor. - * Introduces the `` element in the model which renders as a `

` element in the DOM and data. - * - * @memberOf paragraph - * @extends core.Feature - */ + var _iteratorNormalCompletion6 = true; + var _didIteratorError6 = false; + var _iteratorError6 = undefined; -var Paragraph = function (_Feature) { - inherits(Paragraph, _Feature); + try { + for (var _iterator6 = patterns[Symbol.iterator](), _step6; !(_iteratorNormalCompletion6 = (_step6 = _iterator6.next()).done); _iteratorNormalCompletion6 = true) { + var pattern = _step6.value; - function Paragraph() { - classCallCheck(this, Paragraph); - return possibleConstructorReturn(this, Object.getPrototypeOf(Paragraph).apply(this, arguments)); + if (pattern instanceof RegExp) { + var classes = element.getClassNames(); + + var _iteratorNormalCompletion7 = true; + var _didIteratorError7 = false; + var _iteratorError7 = undefined; + + try { + for (var _iterator7 = classes[Symbol.iterator](), _step7; !(_iteratorNormalCompletion7 = (_step7 = _iterator7.next()).done); _iteratorNormalCompletion7 = true) { + var name = _step7.value; + + if (pattern.test(name)) { + match.push(name); + } + } + } catch (err) { + _didIteratorError7 = true; + _iteratorError7 = err; + } finally { + try { + if (!_iteratorNormalCompletion7 && _iterator7.return) { + _iterator7.return(); + } + } finally { + if (_didIteratorError7) { + throw _iteratorError7; + } + } + } + + if (match.length === 0) { + return null; + } + } else if (element.hasClass(pattern)) { + match.push(pattern); + } else { + return null; + } + } + } catch (err) { + _didIteratorError6 = true; + _iteratorError6 = err; + } finally { + try { + if (!_iteratorNormalCompletion6 && _iterator6.return) { + _iterator6.return(); + } + } finally { + if (_didIteratorError6) { + throw _iteratorError6; + } + } } - createClass(Paragraph, [{ - key: 'init', + return match; +} - /** - * @inheritDoc - */ - value: function init() { - var editor = this.editor; - var data = editor.data; - var editing = editor.editing; +// Checks if styles of provided element can be matched against provided patterns. +// +// @param {Object} patterns Object with information about styles to match. Each key of the object will be +// used as style name. Value of each key can be a string or regular expression to match against style value. +// @param {engine.view.Element} element Element which styles will be tested. +// @returns {Array|null} Returns array with matched style names or `null` if no styles were matched. +function matchStyles(patterns, element) { + var match = []; - // Schema. - editor.document.schema.registerItem('paragraph', '$block'); + for (var name in patterns) { + var pattern = patterns[name]; - // Build converter from model to view for data and editing pipelines. - buildModelConverter().for(data.modelToView, editing.modelToView).fromElement('paragraph').toElement('p'); + if (element.hasStyle(name)) { + var style = element.getStyle(name); - // Build converter from view to model for data pipeline. - buildViewConverter().for(data.viewToModel).fromElement('p').toElement('paragraph'); + if (pattern instanceof RegExp) { + if (pattern.test(style)) { + match.push(name); + } else { + return null; + } + } else if (style === pattern) { + match.push(name); + } else { + return null; + } + } else { + return null; } - }]); - return Paragraph; -}(Feature); + } + + return match; +} /** * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved. @@ -44163,584 +43139,557 @@ var Paragraph = function (_Feature) { */ /** - * The base class for CKEditor commands. + * Provides chainable, high-level API to easily build basic view-to-model converters that are appended to given + * dispatchers. View-to-model converters are used when external data is added to the editor, i.e. when a user pastes + * HTML content to the editor. Then, converters are used to translate this structure, possibly removing unknown/incorrect + * nodes, and add it to the model. Also multiple, different elements might be translated into the same thing in the + * model, i.e. `` and `` elements might be converted to `bold` attribute (even though `bold` attribute will + * be then converted only to `` tag). Instances of this class are created by {@link engine.conversion.buildViewConverter}. * - * Commands are main way to manipulate editor contents and state. They are mostly used by UI elements (or by other - * commands) to make changes in Tree Model. Commands are available in every part of code that has access to - * {@link core.editor.Editor} instance, since they are registered in it and executed through {@link core.editor.Editor#execute}. - * Commands instances are available through {@link core.editor.Editor#commands}. + * If you need more complex converters, see {@link engine.conversion.ViewConversionDispatcher}, + * {@link engine.conversion.viewToModel}, {@link engine.conversion.ViewConsumable}. * - * This is an abstract base class for all commands. + * Using this API it is possible to create various kind of converters: * - * @memberOf core.command - * @mixes utils.ObservableMixin + * 1. View element to model element: + * + * buildViewConverter().for( dispatcher ).fromElement( 'p' ).toElement( 'paragraph' ); + * + * 2. View element to model attribute: + * + * buildViewConverter().for( dispatcher ).fromElement( 'b' ).fromElement( 'strong' ).toAttribute( 'bold', 'true' ); + * + * 3. View attribute to model attribute: + * + * buildViewConverter().for( dispatcher ).fromAttribute( 'style', { 'font-weight': 'bold' } ).toAttribute( 'bold', 'true' ); + * buildViewConverter().for( dispatcher ) + * .fromAttribute( 'class' ) + * .toAttribute( ( viewElement ) => ( { class: viewElement.getAttribute( 'class' ) } ) ); + * + * 4. View elements and attributes to model attribute: + * + * buildViewConverter().for( dispatcher ) + * .fromElement( 'b' ).fromElement( 'strong' ).fromAttribute( 'style', { 'font-weight': 'bold' } ) + * .toAttribute( 'bold', 'true' ); + * + * 5. View {@link engine.view.Matcher view element matcher instance} or {@link engine.view.Matcher#add matcher pattern} + * to model element or attribute: + * + * const matcher = new ViewMatcher(); + * matcher.add( 'div', { class: 'quote' } ); + * buildViewConverter().for( dispatcher ).from( matcher ).toElement( 'quote' ); + * + * buildViewConverter().for( dispatcher ).from( { name: 'span', class: 'bold' } ).toAttribute( 'bold', 'true' ); + * + * Note, that converters built using `ViewConverterBuilder` automatically check {@link engine.model.Schema schema} + * if created model structure is valid. If given conversion would be invalid according to schema, it is ignored. + * + * It is possible to provide creator functions as parameters for {@link engine.conversion.ViewConverterBuilder#toElement} + * and {@link engine.conversion.ViewConverterBuilder#toAttribute} methods. See their descriptions to learn more. + * + * By default, converter will {@link engine.conversion.ViewConsumable#consume consume} every value specified in + * given `from...` query, i.e. `.from( { name: 'span', class: 'bold' } )` will make converter consume both `span` name + * and `bold` class. It is possible to change this behavior using {@link engine.conversion.ViewConverterBuilder#consuming consuming} + * modifier. The modifier alters the last `fromXXX` query used before it. To learn more about consuming values, + * see {@link engine.conversion.ViewConsumable}. + * + * It is also possible to {@link engine.conversion.ViewConverterBuilder#withPriority change default priority} + * of created converters to decide which converter should be fired earlier and which later. This is useful if you provide + * a general converter but want to provide different converter for a specific-case (i.e. given view element is converted + * always to given model element, but if it has given class it is converter to other model element). For this, + * use {@link engine.conversion.ViewConverterBuilder#withPriority withPriority} modifier. The modifier alters + * the last `from...` query used before it. + * + * Note that `to...` methods are "terminators", which means that should be the last one used in building converter. + * + * You can use {@link engine.conversion.ModelConverterBuilder} to create "opposite" converters - from model to view. + * + * @memberOf engine.conversion */ -var Command = function () { +var ViewConverterBuilder = function () { /** - * Creates a new Command instance. - * - * @param {core.editor.Editor} editor Editor on which this command will be used. + * Creates `ViewConverterBuilder` with given `dispatchers` registered to it. */ - function Command(editor) { - var _this = this; - - classCallCheck(this, Command); + function ViewConverterBuilder() { + classCallCheck(this, ViewConverterBuilder); /** - * Editor on which this command will be used. + * Dispatchers to which converters will be attached. * - * @readonly - * @member {core.editor.Editor} core.command.Command#editor + * @type {Array.} + * @private */ - this.editor = editor; + this._dispatchers = []; /** - * Flag indicating whether a command is enabled or disabled. - * A disabled command should do nothing upon it's execution. + * Stores "from" queries. * - * @observable - * @member {Boolean} core.command.Command#isEnabled + * @type {Array} + * @private */ - this.set('isEnabled', true); - - // If schema checking function is specified, add it to the `refreshState` listeners. - // Feature will be disabled if it does not apply to schema requirements. - if (this._checkEnabled) { - this.on('refreshState', function (evt, data) { - data.isEnabled = _this._checkEnabled(); - }); - } + this._from = []; } - createClass(Command, [{ - key: 'destroy', - value: function destroy() { - this.stopListening(); - } - - /** - * Fires `refreshState` event and checks it's resolve value to decide whether command should be enabled or not. - * Other parts of code might listen to `refreshState` event on this command and add their callbacks. This - * way the responsibility of deciding whether a command should be enabled is shared. - * - * @fires {@link core.command.Command#refreshState refreshState} - */ - - }, { - key: 'refreshState', - value: function refreshState() { - var data = { isEnabled: true }; - this.fire('refreshState', data); - - this.isEnabled = data.isEnabled; - } + /** + * Set one or more dispatchers which the built converter will be attached to. + * + * @chainable + * @param {...engine.conversion.ViewConversionDispatcher} dispatchers One or more dispatchers. + * @returns {engine.conversion.ViewConverterBuilder} + */ - /** - * Executes the command if it is enabled. - * - * @protected - * @param {*} param Parameter passed to {@link core.command.Command#execute execute} method of this command. - */ - }, { - key: '_execute', - value: function _execute(param) { - if (this.isEnabled) { - this._doExecute(param); + createClass(ViewConverterBuilder, [{ + key: 'for', + value: function _for() { + for (var _len = arguments.length, dispatchers = Array(_len), _key = 0; _key < _len; _key++) { + dispatchers[_key] = arguments[_key]; } - } - - /** - * Disables the command. This should be used only by the command itself. Other parts of code should add - * listeners to `refreshState` event. - * - * @protected - */ - - }, { - key: '_disable', - value: function _disable() { - this.on('refreshState', disableCallback); - this.refreshState(); - } - - /** - * Enables the command (internally). This should be used only by the command itself. Command will be enabled if - * other listeners does not return false on `refreshState` event callbacks. Firing {@link core.command.Command#_enable} - * does not guarantee that {@link core.command.Command#isEnabled} will be set to true, as it depends on other listeners. - * - * @protected - */ - - }, { - key: '_enable', - value: function _enable() { - this.off('refreshState', disableCallback); - this.refreshState(); - } - - /** - * Executes command. - * This is an abstract method that should be overwritten in child classes. - * - * @protected - */ - - }, { - key: '_doExecute', - value: function _doExecute() {} - - /** - * Checks if a command should be enabled according to its own rules. Mostly it will check schema to see if the command - * is allowed to be executed in given position. This method can be defined in child class (but is not obligatory). - * If it is defined, it will be added as a callback to `refreshState` event. - * - * @protected - * @method core.command.Command#_checkEnabled - * @returns {Boolean} `true` if command should be enabled according to {@link engine.model.Document#schema}. `false` otherwise. - */ - - }]); - return Command; -}(); - -function disableCallback(evt, data) { - data.isEnabled = false; -} - -mix(Command, ObservableMixin); - -/** - * Fired whenever command has to have its {@link core.command.Command#isEnabled} property refreshed. Every feature, - * command or other class which needs to disable command (set `isEnabled` to `false`) should listen to this - * event. - * - * @event core.command.Command#refreshState - * @param {Object} data - * @param {Boolean} [data.isEnabled=true] - */ - -/** - * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md. - */ - -/** - * The heading command. It is used by the {@link heading.Heading heading feature} to apply headings. - * - * @memberOf heading - * @extends core.command.Command - */ -var HeadingCommand = function (_Command) { - inherits(HeadingCommand, _Command); + this._dispatchers = dispatchers; - /** - * Creates an instance of the command. - * - * @param {core.editor.Editor} editor Editor instance. - * @param {Array.} formats Heading formats to be used by the command instance. - */ - function HeadingCommand(editor, formats) { - classCallCheck(this, HeadingCommand); + return this; + } /** - * Heading formats used by this command. + * Registers what view element should be converted. * - * @readonly - * @member {heading.HeadingFormat} heading.HeadingCommand#formats + * buildViewConverter().for( dispatcher ).fromElement( 'p' ).toElement( 'paragraph' ); + * + * @chainable + * @param {String} elementName View element name. + * @returns {engine.conversion.ViewConverterBuilder} */ - var _this = possibleConstructorReturn(this, Object.getPrototypeOf(HeadingCommand).call(this, editor)); - _this.formats = formats; + }, { + key: 'fromElement', + value: function fromElement(elementName) { + return this.from({ name: elementName }); + } /** - * The currently selected heading format. + * Registers what view attribute should be converted. * - * @readonly - * @observable - * @member {heading.HeadingFormat} heading.HeadingCommand#value + * buildViewConverter().for( dispatcher ).fromAttribute( 'style', { 'font-weight': 'bold' } ).toAttribute( 'bold', 'true' ); + * + * @chainable + * @param {String|RegExp} key View attribute key. + * @param {String|RegExp} [value] View attribute value. + * @returns {engine.conversion.ViewConverterBuilder} */ - _this.set('value', _this.defaultFormat); - // Listen on selection change and set current command's format to format in the current selection. - _this.listenTo(editor.document.selection, 'change', function () { - var position = editor.document.selection.getFirstPosition(); - var block = findTopmostBlock(position); + }, { + key: 'fromAttribute', + value: function fromAttribute(key) { + var value = arguments.length <= 1 || arguments[1] === undefined ? /.*/ : arguments[1]; - if (block) { - var format = _this._getFormatById(block.name); + var pattern = {}; + pattern[key] = value; - // TODO: What should happen if format is not found? - _this.value = format; - } - }); - return _this; - } + return this.from(pattern); + } - /** - * The default format. - * - * @type {heading.HeadingFormat} - */ + /** + * Registers what view pattern should be converted. The method accepts either {@link engine.view.Matcher view matcher} + * or view matcher pattern. + * + * const matcher = new ViewMatcher(); + * matcher.add( 'div', { class: 'quote' } ); + * buildViewConverter().for( dispatcher ).from( matcher ).toElement( 'quote' ); + * + * buildViewConverter().for( dispatcher ).from( { name: 'span', class: 'bold' } ).toAttribute( 'bold', 'true' ); + * + * @chainable + * @param {Object|engine.view.Matcher} matcher View matcher or view matcher pattern. + * @returns {engine.conversion.ViewConverterBuilder} + */ + }, { + key: 'from', + value: function from(matcher) { + if (!(matcher instanceof Matcher)) { + matcher = new Matcher(matcher); + } - createClass(HeadingCommand, [{ - key: '_doExecute', + this._from.push({ + matcher: matcher, + consume: false, + priority: null + }); + return this; + } /** - * Executes command. + * Modifies which consumable values will be {@link engine.conversion.ViewConsumable#consume consumed} by built converter. + * It modifies the last `from...` query. Can be used after each `from...` query in given chain. Useful for providing + * more specific matches. * - * @protected - * @param {Object} [options] Options for executed command. - * @param {String} [options.formatId] The identifier of the heading format that should be applied. It should be one of the - * {@link heading.HeadingFormat heading formats} provided to the command constructor. If this parameter is not provided, - * the value from {@link heading.HeadingCommand#defaultFormat defaultFormat} will be used. - * @param {engine.model.Batch} [options.batch] Batch to collect all the change steps. - * New batch will be created if this option is not set. + * // This converter will only handle class bold conversion (to proper attribute) but span element + * // conversion will have to be done in separate converter. + * // Without consuming modifier, the converter would consume both class and name, so a converter for + * // span element would not be fired. + * buildViewConverter().for( dispatcher ) + * .from( { name: 'span', class: 'bold' } ).consuming( { class: 'bold' } ) + * .toAttribute( 'bold', 'true' } ); + * + * buildViewConverter().for( dispatcher ) + * .fromElement( 'img' ).consuming( { name: true, attributes: [ 'src', 'title' ] } ) + * .toElement( ( viewElement ) => new ModelElement( 'image', { src: viewElement.getAttribute( 'src' ), + * title: viewElement.getAttribute( 'title' ) } ); + * + * **Note:** All and only values from passed object has to be consumable on converted view element. This means that + * using `consuming` method, you can either make looser conversion conditions (like in first example) or tighter + * conversion conditions (like in second example). So, the view element, to be converter, has to match query of + * `from...` method and then have to have enough consumable values to consume. + * + * @see engine.conversion.ViewConsumable + * @chainable + * @param {Object} consume Values to consume. + * @returns {engine.conversion.ViewConverterBuilder} */ - value: function _doExecute() { - var _this2 = this; - var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; + }, { + key: 'consuming', + value: function consuming(consume) { + var lastFrom = this._from[this._from.length - 1]; + lastFrom.consume = consume; - // TODO: What should happen if format is not found? - var formatId = options.formatId || this.defaultFormat.id; - var doc = this.editor.document; - var selection = doc.selection; - var startPosition = selection.getFirstPosition(); - var elements = []; - // Storing selection ranges and direction to fix selection after renaming. See ckeditor5-engine#367. - var ranges = [].concat(toConsumableArray(selection.getRanges())); - var isSelectionBackward = selection.isBackward; - // If current format is same as new format - toggle already applied format back to default one. - var shouldRemove = formatId === this.value.id; + return this; + } - // Collect elements to change format. - // This implementation may not be future proof but it's satisfactory at this stage. - if (selection.isCollapsed) { - var block = findTopmostBlock(startPosition); + /** + * Changes default priority for built converter. It modifies the last `from...` query. Can be used after each + * `from...` query in given chain. Useful for overwriting converters. The lower the number, the earlier converter will be fired. + * + * buildViewConverter().for( dispatcher ).fromElement( 'p' ).toElement( 'paragraph' ); + * // Register converter with proper priority, otherwise "p" element would get consumed by first + * // converter and the second converter would not be fired. + * buildViewConverter().for( dispatcher ) + * .from( { name: 'p', class: 'custom' } ).withPriority( 9 ) + * .toElement( 'customParagraph' ); + * + * **Note:** `ViewConverterBuilder` takes care so all `toElement` conversions takes place before all `toAttribute` + * conversions. This is done by setting default `toElement` priority to `10` and `toAttribute` priority to `1000`. + * It is recommended to set converter priority for `toElement` conversions below `500` and `toAttribute` priority + * above `500`. It is important that model elements are created before attributes, otherwise attributes would + * not be applied or other errors may occur. + * + * @chainable + * @param {Number} priority Converter priority. + * @returns {engine.conversion.ViewConverterBuilder} + */ - if (block) { - elements.push(block); - } - } else { - var _iteratorNormalCompletion = true; - var _didIteratorError = false; - var _iteratorError = undefined; + }, { + key: 'withPriority', + value: function withPriority(priority) { + var lastFrom = this._from[this._from.length - 1]; + lastFrom.priority = priority; - try { - for (var _iterator = ranges[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { - var range = _step.value; + return this; + } - var startBlock = findTopmostBlock(range.start); - var endBlock = findTopmostBlock(range.end, false); + /** + * Registers what model element will be created by converter. + * + * Method accepts two ways of providing what kind of model element will be created. You can pass model element + * name as a `string` or a function that will return model element instance. If you provide creator function, + * it will be passed converted view element as first and only parameter. + * + * buildViewConverter().for( dispatcher ).fromElement( 'p' ).toElement( 'paragraph' ); + * buildViewConverter().for( dispatcher ) + * .fromElement( 'img' ) + * .toElement( ( viewElement ) => new ModelElement( 'image', { src: viewElement.getAttribute( 'src' ) } ); + * + * @param {String|Function} element Model element name or model element creator function. + */ - elements.push(startBlock); + }, { + key: 'toElement', + value: function toElement(element) { + var eventCallbackGen = function eventCallbackGen(from) { + return function (evt, data, consumable, conversionApi) { + // There is one callback for all patterns in the matcher. + // This will be usually just one pattern but we support matchers with many patterns too. + var matchAll = from.matcher.matchAll(data.input); - while (startBlock !== endBlock) { - startBlock = startBlock.nextSibling; - elements.push(startBlock); - } + // If there is no match, this callback should not do anything. + if (!matchAll) { + return; } - } catch (err) { - _didIteratorError = true; - _iteratorError = err; - } finally { + + // Now, for every match between matcher and actual element, we will try to consume the match. + var _iteratorNormalCompletion = true; + var _didIteratorError = false; + var _iteratorError = undefined; + try { - if (!_iteratorNormalCompletion && _iterator.return) { - _iterator.return(); - } - } finally { - if (_didIteratorError) { - throw _iteratorError; - } - } - } - } + for (var _iterator = matchAll[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { + var match = _step.value; + + // Create model element basing on creator function or element name. + var modelElement = element instanceof Function ? element(data.input) : new Element(element); + + // Check whether generated structure is okay with `Schema`. + var keys = Array.from(modelElement.getAttributeKeys()); + + if (!conversionApi.schema.check({ name: modelElement.name, attributes: keys, inside: data.context })) { + continue; + } + + // Try to consume appropriate values from consumable values list. + if (!consumable.consume(data.input, from.consume || match.match)) { + continue; + } - doc.enqueueChanges(function () { - var batch = options.batch || doc.batch(); + // If everything is fine, we are ready to start the conversion. + // Add newly created `modelElement` to the parents stack. + data.context.push(modelElement); - var _iteratorNormalCompletion2 = true; - var _didIteratorError2 = false; - var _iteratorError2 = undefined; + // Convert children of converted view element and append them to `modelElement`. + var modelChildren = conversionApi.convertChildren(data.input, consumable, data); + var insertPosition = Position.createAt(modelElement, 'end'); + modelWriter.insert(insertPosition, modelChildren); - try { - for (var _iterator2 = elements[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { - var element = _step2.value; + // Remove created `modelElement` from the parents stack. + data.context.pop(); - // When removing applied format. - if (shouldRemove) { - if (element.name === formatId) { - batch.rename(element, _this2.defaultFormat.id); - } - } - // When applying new format. - else { - batch.rename(element, formatId); - } - } + // Add `modelElement` as a result. + data.output = modelElement; - // If range's selection start/end is placed directly in renamed block - we need to restore it's position - // after renaming, because renaming puts new element there. - } catch (err) { - _didIteratorError2 = true; - _iteratorError2 = err; - } finally { - try { - if (!_iteratorNormalCompletion2 && _iterator2.return) { - _iterator2.return(); + // Prevent multiple conversion if there are other correct matches. + break; } + } catch (err) { + _didIteratorError = true; + _iteratorError = err; } finally { - if (_didIteratorError2) { - throw _iteratorError2; + try { + if (!_iteratorNormalCompletion && _iterator.return) { + _iterator.return(); + } + } finally { + if (_didIteratorError) { + throw _iteratorError; + } } } - } + }; + }; - doc.selection.setRanges(ranges, isSelectionBackward); - }); + this._setCallback(eventCallbackGen, 'normal'); } /** - * Returns the format by a given ID. + * Registers what model attribute will be created by converter. * - * @private - * @param {String} id - * @returns {heading.HeadingFormat} + * Method accepts two ways of providing what kind of model attribute will be created. You can either pass two strings + * representing attribute key and attribute value or a function that returns an object with `key` and `value` properties. + * If you provide creator function, it will be passed converted view element as first and only parameter. + * + * buildViewConverter().for( dispatcher ).fromAttribute( 'style', { 'font-weight': 'bold' } ).toAttribute( 'bold', 'true' ); + * buildViewConverter().for( dispatcher ) + * .fromAttribute( 'class' ) + * .toAttribute( ( viewElement ) => ( { key: 'class', value: viewElement.getAttribute( 'class' ) } ) ); + * + * @param {String|Function} keyOrCreator Attribute key or a creator function. + * @param {String} [value] Attribute value. Required if `keyOrCreator` is a `string`. Ignored otherwise. */ }, { - key: '_getFormatById', - value: function _getFormatById(id) { - return this.formats.find(function (item) { - return item.id === id; - }) || this.defaultFormat; - } - }, { - key: 'defaultFormat', - get: function get() { - // See https://github.com/ckeditor/ckeditor5/issues/98. - return this._getFormatById('paragraph'); - } - }]); - return HeadingCommand; -}(Command); - -function findTopmostBlock(position) { - var nodeAfter = arguments.length <= 1 || arguments[1] === undefined ? true : arguments[1]; - - var parent = position.parent; - - // If position is placed inside root - get element after/before it. - if (parent instanceof RootElement) { - return nodeAfter ? position.nodeAfter : position.nodeBefore; - } - - while (!(parent.parent instanceof RootElement)) { - parent = parent.parent; - } + key: 'toAttribute', + value: function toAttribute(keyOrCreator, value) { + var eventCallbackGen = function eventCallbackGen(from) { + return function (evt, data, consumable, conversionApi) { + // There is one callback for all patterns in the matcher. + // This will be usually just one pattern but we support matchers with many patterns too. + var matchAll = from.matcher.matchAll(data.input); - return parent; -} + // If there is no match, this callback should not do anything. + if (!matchAll) { + return; + } -/** - * Heading format descriptor. - * - * @typedef {Object} heading.HeadingFormat - * @property {String} id Format identifier. It will be used as the element's name in the model. - * @property {String} viewElement The name of the view element that will be used to represent the model element in the view. - * @property {String} label The display name of the format. - */ + // Now, for every match between matcher and actual element, we will try to consume the match. + var _iteratorNormalCompletion2 = true; + var _didIteratorError2 = false; + var _iteratorError2 = undefined; -/** - * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md. - */ + try { + for (var _iterator2 = matchAll[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { + var match = _step2.value; -var formats = [{ id: 'paragraph', viewElement: 'p', label: 'Paragraph' }, { id: 'heading1', viewElement: 'h2', label: 'Heading 1' }, { id: 'heading2', viewElement: 'h3', label: 'Heading 2' }, { id: 'heading3', viewElement: 'h4', label: 'Heading 3' }]; + // Try to consume appropriate values from consumable values list. + if (!consumable.consume(data.input, from.consume || match.match)) { + continue; + } -/** - * The headings engine feature. It handles switching between block formats – headings and paragraph. - * This class represents the engine part of the heading feature. See also {@link heading.Heading}. - * - * @memberOf heading - * @extends core.Feature - */ + // Since we are converting to attribute we need an output on which we will set the attribute. + // If the output is not created yet, we will create it. + if (!data.output) { + data.output = conversionApi.convertChildren(data.input, consumable, data); + } -var HeadingEngine = function (_Feature) { - inherits(HeadingEngine, _Feature); + // Use attribute creator function, if provided. + var attribute = keyOrCreator instanceof Function ? keyOrCreator(data.input) : { key: keyOrCreator, value: value }; - function HeadingEngine() { - classCallCheck(this, HeadingEngine); - return possibleConstructorReturn(this, Object.getPrototypeOf(HeadingEngine).apply(this, arguments)); - } + // Set attribute on current `output`. `Schema` is checked inside this helper function. + setAttributeOn(data.output, attribute, data, conversionApi); - createClass(HeadingEngine, [{ - key: 'init', + // Prevent multiple conversion if there are other correct matches. + break; + } + } catch (err) { + _didIteratorError2 = true; + _iteratorError2 = err; + } finally { + try { + if (!_iteratorNormalCompletion2 && _iterator2.return) { + _iterator2.return(); + } + } finally { + if (_didIteratorError2) { + throw _iteratorError2; + } + } + } + }; + }; + this._setCallback(eventCallbackGen, 'low'); + } /** - * @inheritDoc + * Helper function that uses given callback generator to created callback function and sets it on registered dispatchers. + * + * @param eventCallbackGen + * @param defaultPriority + * @private */ - value: function init() { - var editor = this.editor; - var data = editor.data; - var editing = editor.editing; - var _iteratorNormalCompletion = true; - var _didIteratorError = false; - var _iteratorError = undefined; + }, { + key: '_setCallback', + value: function _setCallback(eventCallbackGen, defaultPriority) { + // We will add separate event callback for each registered `from` entry. + var _iteratorNormalCompletion3 = true; + var _didIteratorError3 = false; + var _iteratorError3 = undefined; try { - for (var _iterator = formats[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { - var format = _step.value; + for (var _iterator3 = this._from[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { + var from = _step3.value; - // Skip paragraph - it is defined in required Paragraph feature. - if (format.id !== 'paragraph') { - // Schema. - editor.document.schema.registerItem(format.id, '$block'); + // We have to figure out event name basing on matcher's patterns. + // If there is exactly one pattern and it has `name` property we will used that name. + var matcherElementName = from.matcher.getElementName(); + var eventName = matcherElementName ? 'element:' + matcherElementName : 'element'; + var eventCallback = eventCallbackGen(from); - // Build converter from model to view for data and editing pipelines. - buildModelConverter().for(data.modelToView, editing.modelToView).fromElement(format.id).toElement(format.viewElement); + var priority = from.priority === null ? defaultPriority : from.priority; - // Build converter from view to model for data pipeline. - buildViewConverter().for(data.viewToModel).fromElement(format.viewElement).toElement(format.id); - } - } + // Add event to each registered dispatcher. + var _iteratorNormalCompletion4 = true; + var _didIteratorError4 = false; + var _iteratorError4 = undefined; - // Register the heading command. + try { + for (var _iterator4 = this._dispatchers[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) { + var dispatcher = _step4.value; + + dispatcher.on(eventName, eventCallback, { priority: priority }); + } + } catch (err) { + _didIteratorError4 = true; + _iteratorError4 = err; + } finally { + try { + if (!_iteratorNormalCompletion4 && _iterator4.return) { + _iterator4.return(); + } + } finally { + if (_didIteratorError4) { + throw _iteratorError4; + } + } + } + } } catch (err) { - _didIteratorError = true; - _iteratorError = err; + _didIteratorError3 = true; + _iteratorError3 = err; } finally { try { - if (!_iteratorNormalCompletion && _iterator.return) { - _iterator.return(); + if (!_iteratorNormalCompletion3 && _iterator3.return) { + _iterator3.return(); } } finally { - if (_didIteratorError) { - throw _iteratorError; + if (_didIteratorError3) { + throw _iteratorError3; } } } - - var command = new HeadingCommand(editor, formats); - editor.commands.set('heading', command); - - // If the enter command is added to the editor, alter its behavior. - // Enter at the end of a heading element should create a paragraph. - var enterCommand = editor.commands.get('enter'); - - if (enterCommand) { - this.listenTo(enterCommand, 'afterExecute', function (evt, data) { - var positionParent = editor.document.selection.getFirstPosition().parent; - var batch = data.batch; - var isHeading = formats.some(function (format) { - return format.id == positionParent.name; - }); - - if (isHeading && positionParent.name != command.defaultFormat.id && positionParent.childCount === 0) { - batch.rename(positionParent, command.defaultFormat.id); - } - }); - } - } - }], [{ - key: 'requires', - - /** - * @inheritDoc - */ - get: function get() { - return [Paragraph]; } }]); - return HeadingEngine; -}(Feature); + return ViewConverterBuilder; +}(); -/** - * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md. - */ +// Helper function that sets given attributes on given `engine.model.Node` or `engine.model.DocumentFragment`. -/** - * Utilities used in modules from {@link list list} package. - * - * @memberOf list - * @namespace list.utils - */ -/** - * For given {@link engine.model.Position position}, returns the closest ancestor of that position which is a - * `listItem` element. - * - * @function list.utils.getClosestListItem - * @param {engine.model.Position} position Position which ancestor should be check looking for `listItem` element. - * @returns {engine.model.Element|null} Element with `listItem` name that is a closest ancestor of given `position`, or - * `null` if neither of `position` ancestors is a `listItem`. - */ -function getClosestListItem(position) { - return Array.from(position.getAncestors()).find(function (parent) { - return parent.name == 'listItem'; - }) || null; -} +function setAttributeOn(toChange, attribute, data, conversionApi) { + if (isIterable(toChange)) { + var _iteratorNormalCompletion5 = true; + var _didIteratorError5 = false; + var _iteratorError5 = undefined; -/** - * For given {@link engine.model.Selection selection} and {@link engine.model.Schema schema}, returns an array with - * all elements that are in the selection and are extending `$block` schema item. - * - * @function list.utils.getSelectedBlocks - * @param {engine.model.Selection} selection Selection from which blocks will be taken. - * @param {engine.model.Schema} schema Schema which will be used to check if a model element extends `$block`. - * @returns {Array.} All blocks from the selection. - */ -function getSelectedBlocks(selection, schema) { - var position = getPositionBeforeBlock(selection.getFirstPosition(), schema); + try { + for (var _iterator5 = toChange[Symbol.iterator](), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) { + var node = _step5.value; - var endPosition = selection.getLastPosition(); - var blocks = []; + setAttributeOn(node, attribute, data, conversionApi); + } + } catch (err) { + _didIteratorError5 = true; + _iteratorError5 = err; + } finally { + try { + if (!_iteratorNormalCompletion5 && _iterator5.return) { + _iterator5.return(); + } + } finally { + if (_didIteratorError5) { + throw _iteratorError5; + } + } + } - // Traverse model from the first position before a block to the end position of selection. - // Store all elements that were after the correct positions. - while (position !== null && position.isBefore(endPosition)) { - blocks.push(position.nodeAfter); + return; + } - position.offset++; - position = getPositionBeforeBlock(position, schema); - } + var keys = Array.from(toChange.getAttributeKeys()); + keys.push(attribute.key); - return blocks; + var schemaQuery = { + name: toChange.name || '$text', + attributes: keys, + inside: data.context + }; + + if (conversionApi.schema.check(schemaQuery)) { + toChange.setAttribute(attribute.key, attribute.value); + } } /** - * For given {@link engine.model.Position position}, finds a model element extending `$block` schema item which is - * closest element to that position. First node after the position is checked and then the position's ancestors. `null` - * is returned if such element has not been found or found element is a root element. + * Entry point for view-to-model converters builder. This chainable API makes it easy to create basic, most common + * view-to-model converters and attach them to provided dispatchers. The method returns an instance of + * {@link engine.conversion.ViewConverterBuilder}. * - * @param position - * @param schema - * @returns {*} + * @external engine.conversion.buildViewConverter + * @memberOf engine.conversion */ -function getPositionBeforeBlock(position, schema) { - // Start from the element right after the position. Maybe it is already a `$block` element. - var element = position.nodeAfter; - - // If the position is not before an element, check the parent. - if (!element) { - element = position.parent; - } - - // If proper element is still not found, check the ancestors. - while (element !== null && !schema.itemExtends(element.name || '$text', '$block')) { - element = element.parent; - } - - // If proper element has been found, return position before it, otherwise return null; - return element !== null && element.parent !== null ? Position.createBefore(element) : null; +function buildViewConverter() { + return new ViewConverterBuilder(); } /** @@ -44749,265 +43698,301 @@ function getPositionBeforeBlock(position, schema) { */ /** - * The list command. It is used by the {@link list.List list feature}. + * The base class for CKEditor commands. * - * @memberOf list - * @extends core.command.Command + * Commands are main way to manipulate editor contents and state. They are mostly used by UI elements (or by other + * commands) to make changes in Tree Model. Commands are available in every part of code that has access to + * {@link core.editor.Editor} instance, since they are registered in it and executed through {@link core.editor.Editor#execute}. + * Commands instances are available through {@link core.editor.Editor#commands}. + * + * This is an abstract base class for all commands. + * + * @memberOf core.command + * @mixes utils.ObservableMixin */ -var ListCommand = function (_Command) { - inherits(ListCommand, _Command); - +var Command = function () { /** - * Creates an instance of the command. + * Creates a new Command instance. * - * @param {core.editor.Editor} editor Editor instance. - * @param {'numbered'|'bulleted'} type List type that will be handled by this command. + * @param {core.editor.Editor} editor Editor on which this command will be used. */ - function ListCommand(editor, type) { - classCallCheck(this, ListCommand); + function Command(editor) { + var _this = this; + + classCallCheck(this, Command); /** - * The type of list created by the command. + * Editor on which this command will be used. * * @readonly - * @member {'numbered'|'bulleted'} list.ListCommand#type + * @member {core.editor.Editor} core.command.Command#editor */ - var _this = possibleConstructorReturn(this, Object.getPrototypeOf(ListCommand).call(this, editor)); - - _this.type = type == 'bulleted' ? 'bulleted' : 'numbered'; + this.editor = editor; /** - * Flag indicating whether the command is active, which means that selection starts in a list of the same type. + * Flag indicating whether a command is enabled or disabled. + * A disabled command should do nothing upon it's execution. * * @observable - * @member {Boolean} list.ListCommand#value + * @member {Boolean} core.command.Command#isEnabled */ - _this.set('value', false); - - var changeCallback = function changeCallback() { - _this.refreshValue(); - _this.refreshState(); - }; + this.set('isEnabled', true); - // Listen on selection and document changes and set the current command's value. - _this.listenTo(editor.document.selection, 'change:range', changeCallback); - _this.listenTo(editor.document, 'changesDone', changeCallback); - return _this; + // If schema checking function is specified, add it to the `refreshState` listeners. + // Feature will be disabled if it does not apply to schema requirements. + if (this._checkEnabled) { + this.on('refreshState', function (evt, data) { + data.isEnabled = _this._checkEnabled(); + }); + } } - /** - * Sets command's value based on the document selection. - */ + createClass(Command, [{ + key: 'destroy', + value: function destroy() { + this.stopListening(); + } + + /** + * Fires `refreshState` event and checks it's resolve value to decide whether command should be enabled or not. + * Other parts of code might listen to `refreshState` event on this command and add their callbacks. This + * way the responsibility of deciding whether a command should be enabled is shared. + * + * @fires {@link core.command.Command#refreshState refreshState} + */ + + }, { + key: 'refreshState', + value: function refreshState() { + var data = { isEnabled: true }; + this.fire('refreshState', data); + + this.isEnabled = data.isEnabled; + } + + /** + * Executes the command if it is enabled. + * + * @protected + * @param {*} param Parameter passed to {@link core.command.Command#execute execute} method of this command. + */ + + }, { + key: '_execute', + value: function _execute(param) { + if (this.isEnabled) { + this._doExecute(param); + } + } + + /** + * Disables the command. This should be used only by the command itself. Other parts of code should add + * listeners to `refreshState` event. + * + * @protected + */ + }, { + key: '_disable', + value: function _disable() { + this.on('refreshState', disableCallback); + this.refreshState(); + } - createClass(ListCommand, [{ - key: 'refreshValue', - value: function refreshValue() { - var position = this.editor.document.selection.getFirstPosition(); + /** + * Enables the command (internally). This should be used only by the command itself. Command will be enabled if + * other listeners does not return false on `refreshState` event callbacks. Firing {@link core.command.Command#_enable} + * does not guarantee that {@link core.command.Command#isEnabled} will be set to true, as it depends on other listeners. + * + * @protected + */ - // Check whether closest `listItem` ancestor of the position has a correct type. - var listItem = getClosestListItem(position); - this.value = listItem !== null && listItem.getAttribute('type') == this.type; + }, { + key: '_enable', + value: function _enable() { + this.off('refreshState', disableCallback); + this.refreshState(); } /** * Executes command. + * This is an abstract method that should be overwritten in child classes. * * @protected - * @param {Object} [options] Options for executed command. - * @param {engine.model.Batch} [options.batch] Batch to collect all the change steps. - * New batch will be created if this option is not set. */ }, { key: '_doExecute', - value: function _doExecute() { - var _this2 = this; - - var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; + value: function _doExecute() {} - var document = this.editor.document; - var blocks = getSelectedBlocks(document.selection, document.schema); + /** + * Checks if a command should be enabled according to its own rules. Mostly it will check schema to see if the command + * is allowed to be executed in given position. This method can be defined in child class (but is not obligatory). + * If it is defined, it will be added as a callback to `refreshState` event. + * + * @protected + * @method core.command.Command#_checkEnabled + * @returns {Boolean} `true` if command should be enabled according to {@link engine.model.Document#schema}. `false` otherwise. + */ - // Whether we are turning off some items. - var turnOff = this.value === true; - // If we are turning off items, we are going to rename them to paragraphs. + }]); + return Command; +}(); - document.enqueueChanges(function () { - var batch = options.batch || document.batch(); +function disableCallback(evt, data) { + data.isEnabled = false; +} - // If part of a list got turned off, we need to handle (outdent) all of sub-items of the last turned-off item. - // To be sure that model is all the time in a good state, we first fix items below turned-off item. - if (turnOff) { - // Start from the model item that is just after the last turned-off item. - var next = blocks[blocks.length - 1].nextSibling; - var currentIndent = Number.POSITIVE_INFINITY; - var changes = []; +mix(Command, ObservableMixin); - // Correct indent of all items after the last turned off item. - // Rules that should be followed: - // 1. All direct sub-items of turned-off item should become indent 0, because the first item after it - // will be the first item of a new list. Other items are at the same level, so should have same 0 index. - // 2. All items with indent lower than indent of turned-off item should become indent 0, because they - // should not end up as a child of any of list items that they were not children of before. - // 3. All other items should have their indent changed relatively to it's parent. - // - // For example: - // 1 * -------- - // 2 * -------- - // 3 * -------- <- this is turned off. - // 4 * -------- <- this has to become indent = 0, because it will be first item on a new list. - // 5 * -------- <- this should be still be a child of item above, so indent = 1. - // 6 * -------- <- this also has to become indent = 0, because it shouldn't end up as a child of any of items above. - // 7 * -------- <- this should be still be a child of item above, so indent = 1. - // 8 * -------- <- this has to become indent = 0. - // 9 * -------- <- this should still be a child of item above, so indent = 1. - // 10 * -------- <- this should still be a child of item above, so indent = 2. - // 11 * -------- <- this should still be at the same level as item above, so indent = 2. - // 12 * -------- <- this and all below are left unchanged. - // 13 * -------- - // 14 * -------- - // - // After turning off 3 the list becomes: - // - // 1 * -------- - // 2 * -------- - // - // 3 -------- - // - // 4 * -------- - // 5 * -------- - // 6 * -------- - // 7 * -------- - // 8 * -------- - // 9 * -------- - // 10 * -------- - // 11 * -------- - // 12 * -------- - // 13 * -------- - // 14 * -------- - // - // Thanks to this algorithm no lists are mismatched and no items get unexpected children/parent, while - // those parent-child connection which are possible to maintain are still maintained. It's worth noting - // that this is the same effect that we would be get by multiple use of outdent command. However doing - // it like this is much more efficient because it's less operation (less memory usage, easier OT) and - // less conversion (faster). - while (next && next.name == 'listItem' && next.getAttribute('indent') !== 0) { - // Check each next list item, as long as its indent is bigger than 0. - // If the indent is 0 we are not going to change anything anyway. - var indent = next.getAttribute('indent'); +/** + * Fired whenever command has to have its {@link core.command.Command#isEnabled} property refreshed. Every feature, + * command or other class which needs to disable command (set `isEnabled` to `false`) should listen to this + * event. + * + * @event core.command.Command#refreshState + * @param {Object} data + * @param {Boolean} [data.isEnabled=true] + */ - // We check if that's item indent is lower as current relative indent. - if (indent < currentIndent) { - // If it is, current relative indent becomes that indent. - currentIndent = indent; - } +/** + * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ - // Fix indent relatively to current relative indent. - // Note, that if we just changed the current relative indent, the newIndent will be equal to 0. - var newIndent = indent - currentIndent; +/** + * Walks through given array of ranges and removes parts of them that are not allowed by passed schema to have the + * attribute set. This is done by breaking a range in two and omitting the not allowed part. + * + * @param {String} attribute Attribute key. + * @param {Array.} ranges Ranges to be validated. + * @param {engine.model.Schema} schema Document schema. + * @returns {Array.} Ranges without invalid parts. + */ +function getSchemaValidRanges(attribute, ranges, schema) { + var validRanges = []; - // Save the entry in changes array. We do not apply it at the moment, because we will need to - // reverse the changes so the last item is changed first. - // This is to keep model in correct state all the time. - changes.push({ element: next, indent: newIndent }); + var _iteratorNormalCompletion = true; + var _didIteratorError = false; + var _iteratorError = undefined; - // Find next item. - next = next.nextSibling; - } + try { + for (var _iterator = ranges[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { + var range = _step.value; - changes = changes.reverse(); + var walker = new TreeWalker({ boundaries: range, mergeCharacters: true }); + var step = walker.next(); - var _iteratorNormalCompletion = true; - var _didIteratorError = false; - var _iteratorError = undefined; + var last = range.start; + var from = range.start; + var to = range.end; - try { - for (var _iterator = changes[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { - var item = _step.value; + while (!step.done) { + var name = step.value.item.name || '$text'; - batch.setAttribute(item.element, 'indent', item.indent); - } - } catch (err) { - _didIteratorError = true; - _iteratorError = err; - } finally { - try { - if (!_iteratorNormalCompletion && _iterator.return) { - _iterator.return(); - } - } finally { - if (_didIteratorError) { - throw _iteratorError; - } - } + if (!schema.check({ name: name, inside: last, attributes: attribute })) { + if (!from.isEqual(last)) { + validRanges.push(new Range$1(from, last)); } + + from = walker.position; } - // Phew! Now it will be easier :). - // For each block element that was in the selection, we will either: turn it to list item, - // turn it to paragraph, or change it's type. Or leave it as it is. - var _iteratorNormalCompletion2 = true; - var _didIteratorError2 = false; - var _iteratorError2 = undefined; + last = walker.position; + step = walker.next(); + } + + if (from && !from.isEqual(to)) { + validRanges.push(new Range$1(from, to)); + } + } + } catch (err) { + _didIteratorError = true; + _iteratorError = err; + } finally { + try { + if (!_iteratorNormalCompletion && _iterator.return) { + _iterator.return(); + } + } finally { + if (_didIteratorError) { + throw _iteratorError; + } + } + } + + return validRanges; +} + +/** + * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +/** + * Checks {@link engine.model.Document#schema} if attribute is allowed in selection: + * * if selection is on range, the command is enabled if any of nodes in that range can have bold, + * * if selection is collapsed, the command is enabled if text with bold is allowed in that node. + * + * @param {String} attribute Attribute key. + * @param {engine.model.Selection} selection Selection which ranges will be validate. + * @param {engine.model.Schema} schema Document schema. + * @returns {Boolean} + */ +function isAttributeAllowedInSelection(attribute, selection, schema) { + if (selection.isCollapsed) { + // Check whether schema allows for a test with `attributeKey` in caret position. + return schema.check({ name: '$text', inside: selection.getFirstPosition(), attributes: attribute }); + } else { + var ranges = selection.getRanges(); - try { - for (var _iterator2 = blocks[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { - var element = _step2.value; + // For all ranges, check nodes in them until you find a node that is allowed to have `attributeKey` attribute. + var _iteratorNormalCompletion = true; + var _didIteratorError = false; + var _iteratorError = undefined; - if (turnOff && element.name == 'listItem') { - // We are turning off and the element is a `listItem` - it should be converted to `paragraph`. - // The order is important to keep model in correct state. - batch.rename(element, 'paragraph').removeAttribute(element, 'type').removeAttribute(element, 'indent'); - } else if (!turnOff && element.name != 'listItem') { - // We are turning on and the element is not a `listItem` - it should be converted to `listItem`. - // The order is important to keep model in correct state. - batch.setAttribute(element, 'type', _this2.type).setAttribute(element, 'indent', 0).rename(element, 'listItem'); - } else if (!turnOff && element.name == 'listItem' && element.getAttribute('type') != _this2.type) { - // We are turning on and the element is a `listItem` but has different type - change type. - batch.setAttribute(element, 'type', _this2.type); - } - } - } catch (err) { - _didIteratorError2 = true; - _iteratorError2 = err; - } finally { - try { - if (!_iteratorNormalCompletion2 && _iterator2.return) { - _iterator2.return(); - } - } finally { - if (_didIteratorError2) { - throw _iteratorError2; - } - } - } - }); - } + try { + for (var _iterator = ranges[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { + var range = _step.value; - /** - * @inheritDoc - */ + var walker = new TreeWalker({ boundaries: range, mergeCharacters: true }); + var last = walker.position; + var step = walker.next(); - }, { - key: '_checkEnabled', - value: function _checkEnabled() { - // If command is enabled it means that we are in list item, so the command should be enabled. - if (this.value) { - return true; - } + // Walk the range. + while (!step.done) { + // If returned item does not have name property, it is a model.TextFragment. + var name = step.value.item.name || '$text'; - var selection = this.editor.document.selection; - var schema = this.editor.document.schema; - var position = getPositionBeforeBlock(selection.getFirstPosition(), schema); + if (schema.check({ name: name, inside: last, attributes: attribute })) { + // If we found a node that is allowed to have the attribute, return true. + return true; + } - // Otherwise, check if list item can be inserted at the position start. - return schema.check({ name: 'listItem', inside: position, attributes: ['type', 'indent'] }); + last = walker.position; + step = walker.next(); + } + } + } catch (err) { + _didIteratorError = true; + _iteratorError = err; + } finally { + try { + if (!_iteratorNormalCompletion && _iterator.return) { + _iterator.return(); + } + } finally { + if (_didIteratorError) { + throw _iteratorError; + } + } } - }]); - return ListCommand; -}(Command); + } + + // If we haven't found such node, return false. + return false; +} /** * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved. @@ -45015,159 +44000,148 @@ var ListCommand = function (_Command) { */ /** - * The list indent command. It is used by the {@link list.List list feature}. + * An extension of basic {@link core.command.Command} class, which provides utilities for a command that toggle a single + * attribute on a text or element with value `true`. ToggleAttributeCommand uses {@link engine.model.Document#selection} + * to decide which nodes (if any) should be changed, and applies or removes attributes from them. + * See {@link engine.view.Converter#execute} for more. * - * @memberOf list - * @extends core.command.Command + * The command checks {@link engine.model.Document#schema} to decide if it should be enabled. + * See {@link engine.view.Converter#checkSchema} for more. + * + * @memberOf core.command */ -var IndentCommand = function (_Command) { - inherits(IndentCommand, _Command); +var ToggleAttributeCommand = function (_Command) { + inherits(ToggleAttributeCommand, _Command); /** - * Creates an instance of the command. - * - * @param {core.editor.Editor} editor Editor instance. - * @param {'forward'|'backward'} indentDirection Direction of indent. If it is equal to `backward`, the command - * will outdent a list item. + * @see core.command.Command + * @param {core.editor.Editor} editor + * @param {String} attributeKey Attribute that will be set by the command. */ - function IndentCommand(editor, indentDirection) { - classCallCheck(this, IndentCommand); + function ToggleAttributeCommand(editor, attributeKey) { + classCallCheck(this, ToggleAttributeCommand); /** - * By how much the command will change list item's indent attribute. + * Attribute that will be set by the command. * - * @readonly - * @private - * @member {Number} list.IndentCommand#_indentBy + * @member {String} core.command.ToggleAttributeCommand#attributeKey */ - var _this = possibleConstructorReturn(this, Object.getPrototypeOf(IndentCommand).call(this, editor)); + var _this = possibleConstructorReturn(this, Object.getPrototypeOf(ToggleAttributeCommand).call(this, editor)); - _this._indentBy = indentDirection == 'forward' ? 1 : -1; + _this.attributeKey = attributeKey; - // Refresh command state after selection is changed or changes has been done to the document. - _this.listenTo(editor.document.selection, 'change:range', function () { - _this.refreshState(); - }); + /** + * Flag indicating whether command is active. For collapsed selection it means that typed characters will have + * the command's attribute set. For range selection it means that all nodes inside have the attribute applied. + * + * @observable + * @member {Boolean} core.command.ToggleAttributeCommand#value + */ + _this.set('value', false); - _this.listenTo(editor.document, 'changesDone', function () { - _this.refreshState(); + _this.listenTo(_this.editor.document.selection, 'change:attribute', function () { + _this.value = _this.editor.document.selection.hasAttribute(_this.attributeKey); }); return _this; } /** - * @inheritDoc + * Checks if {@link engine.model.Document#schema} allows to create attribute in {@link engine.model.Document#selection} + * + * @private + * @returns {Boolean} */ - createClass(IndentCommand, [{ - key: '_doExecute', - value: function _doExecute() { - var _this2 = this; - - var doc = this.editor.document; - var batch = doc.batch(); - var element = getClosestListItem(doc.selection.getFirstPosition()); - - doc.enqueueChanges(function () { - var oldIndent = element.getAttribute('indent'); + createClass(ToggleAttributeCommand, [{ + key: '_checkEnabled', + value: function _checkEnabled() { + var document = this.editor.document; - var itemsToChange = [element]; + return isAttributeAllowedInSelection(this.attributeKey, document.selection, document.schema); + } - // Indenting a list item should also indent all the items that are already sub-items of indented item. - var next = element.nextSibling; + /** + * Executes the command: adds or removes attributes to nodes or selection. + * + * If the command is active (`value == true`), it will remove attributes. Otherwise, it will set attributes. + * + * The execution result differs, depending on the {@link engine.model.Document#selection}: + * * if selection is on a range, the command applies the attribute on all nodes in that ranges + * (if they are allowed to have this attribute by the {@link engine.model.Schema schema}), + * * if selection is collapsed in non-empty node, the command applies attribute to the {@link engine.model.Document#selection} + * itself (note that typed characters copy attributes from selection), + * * if selection is collapsed in empty node, the command applies attribute to the parent node of selection (note + * that selection inherits all attributes from a node if it is in empty node). + * + * If the command is disabled (`isEnabled == false`) when it is executed, nothing will happen. + * + * @private + * @param {Object} [options] Options of command. + * @param {Boolean} [options.forceValue] If set it will force command behavior. If `true`, command will apply attribute, + * otherwise command will remove attribute. If not set, command will look for it's current value to decide what it should do. + * @param {engine.model.Batch} [options.batch] Batch to group undo steps. + */ - // Check all items as long as their indent is bigger than indent of changed list item. - while (next && next.name == 'listItem' && next.getAttribute('indent') > oldIndent) { - itemsToChange.push(next); + }, { + key: '_doExecute', + value: function _doExecute() { + var _this2 = this; - next = next.nextSibling; - } + var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; - // We need to be sure to keep model in correct state after each small change, because converters - // bases on that state and assumes that model is correct. - // Because of that, if the command outdented items, we will outdent them starting from the last item, as - // it is safer. - if (_this2._indentBy < 0) { - itemsToChange = itemsToChange.reverse(); - } + var document = this.editor.document; + var selection = document.selection; + var value = options.forceValue === undefined ? !this.value : options.forceValue; - var _iteratorNormalCompletion = true; - var _didIteratorError = false; - var _iteratorError = undefined; + // If selection has non-collapsed ranges, we change attribute on nodes inside those ranges. + document.enqueueChanges(function () { + if (selection.isCollapsed) { + if (value) { + selection.setAttribute(_this2.attributeKey, true); + } else { + selection.removeAttribute(_this2.attributeKey); + } + } else { + var ranges = getSchemaValidRanges(_this2.attributeKey, selection.getRanges(), document.schema); - try { - for (var _iterator = itemsToChange[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { - var item = _step.value; + // Keep it as one undo step. + var batch = options.batch || document.batch(); - var indent = item.getAttribute('indent') + _this2._indentBy; + var _iteratorNormalCompletion = true; + var _didIteratorError = false; + var _iteratorError = undefined; - // If indent is lower than 0, it means that the item got outdented when it was not indented. - // This means that we need to convert that list item to paragraph. - if (indent < 0) { - // To keep the model as correct as possible, first rename listItem, then remove attributes, - // as listItem without attributes is very incorrect and will cause problems in converters. - batch.rename(item, 'paragraph').removeAttribute(item, 'indent').removeAttribute(item, 'type'); - } else { - // If indent is >= 0, just change the attribute value. - batch.setAttribute(item, 'indent', indent); - } - } - } catch (err) { - _didIteratorError = true; - _iteratorError = err; - } finally { try { - if (!_iteratorNormalCompletion && _iterator.return) { - _iterator.return(); + for (var _iterator = ranges[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { + var range = _step.value; + + if (value) { + batch.setAttribute(range, _this2.attributeKey, value); + } else { + batch.removeAttribute(range, _this2.attributeKey); + } } + } catch (err) { + _didIteratorError = true; + _iteratorError = err; } finally { - if (_didIteratorError) { - throw _iteratorError; + try { + if (!_iteratorNormalCompletion && _iterator.return) { + _iterator.return(); + } + } finally { + if (_didIteratorError) { + throw _iteratorError; + } } } } }); } - - /** - * @inheritDoc - */ - - }, { - key: '_checkEnabled', - value: function _checkEnabled() { - // Check whether any of position's ancestor is a list item. - var listItem = getClosestListItem(this.editor.document.selection.getFirstPosition()); - - // If selection is not in a list item, the command is disabled. - if (!listItem) { - return false; - } - - var prev = listItem.previousSibling; - var oldIndent = listItem.getAttribute('indent'); - var newIndent = oldIndent + this._indentBy; - - if (this._indentBy > 0) { - // If we are indenting, there are some conditions to meet. - // Cannot indent first list item. - if (!prev || prev.name != 'listItem') { - return false; - } - - // Indent can be at most greater by one than indent of previous item. - if (prev.getAttribute('indent') + 1 < newIndent) { - return false; - } - } - - // If we are outdenting it is enough to be in list item. Every list item can always be outdented. - return true; - } }]); - return IndentCommand; + return ToggleAttributeCommand; }(Command); /** @@ -45175,625 +44149,595 @@ var IndentCommand = function (_Command) { * For licensing, see LICENSE.md. */ +var BOLD = 'bold'; + /** - * View element class representing list item (`

  • `). It extends {@link engine.view.ContainerElement} and overwrites - * {@link list.ViewListItemElement#getFillerOffset evaluating whether filler offset} is needed. + * The bold engine feature. * - * @memberOf list - * @extends engine.view.ContainerElement + * It registers the `bold` command and introduces the `bold` attribute in the model which renders to the view + * as a `` element. + * + * @memberOf basic-styles + * @extends core.Feature */ -var ViewListItemElement = function (_ViewContainerElement) { - inherits(ViewListItemElement, _ViewContainerElement); +var BoldEngine = function (_Feature) { + inherits(BoldEngine, _Feature); - /** - * Creates `
  • ` view item. - * - * @param {Object|Iterable} [attrs] Collection of attributes. - * @param {engine.view.Node|Iterable.} [children] List of nodes to be inserted into created element. - */ - function ViewListItemElement(attrs, children) { - classCallCheck(this, ViewListItemElement); - return possibleConstructorReturn(this, Object.getPrototypeOf(ViewListItemElement).call(this, 'li', attrs, children)); + function BoldEngine() { + classCallCheck(this, BoldEngine); + return possibleConstructorReturn(this, Object.getPrototypeOf(BoldEngine).apply(this, arguments)); } - /** - * @inheritDoc - */ + createClass(BoldEngine, [{ + key: 'init', + /** + * @inheritDoc + */ + value: function init() { + var editor = this.editor; + var data = editor.data; + var editing = editor.editing; - createClass(ViewListItemElement, [{ - key: 'getFillerOffset', - value: function getFillerOffset() { - var hasOnlyLists = !this.isEmpty && (this.getChild(0).name == 'ul' || this.getChild(0).name == 'ol'); + // Allow bold attribute on all inline nodes. + editor.document.schema.allow({ name: '$inline', attributes: [BOLD] }); - return this.isEmpty || hasOnlyLists ? 0 : null; + // Build converter from model to view for data and editing pipelines. + buildModelConverter().for(data.modelToView, editing.modelToView).fromAttribute(BOLD).toElement('strong'); + + // Build converter from view to model for data pipeline. + buildViewConverter().for(data.viewToModel).fromElement('strong').fromElement('b').fromAttribute('style', { 'font-weight': 'bold' }).toAttribute(BOLD, true); + + // Create bold command. + editor.commands.set(BOLD, new ToggleAttributeCommand(editor, BOLD)); } }]); - return ViewListItemElement; -}(ContainerElement); + return BoldEngine; +}(Feature); /** - * The list indent command. It is used by the {@link list.List list feature}. - * - * @memberOf list - * @namespace list.converters + * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. */ -// Helper function that creates a `
    ` structure out of given `modelItem` model `listItem` element. -// Then, it binds created view list item (LI) with model `listItem` element. -// The function then returns created view list item (LI). -function generateLiInUl(modelItem, mapper) { - var listType = modelItem.getAttribute('type') == 'numbered' ? 'ol' : 'ul'; - var viewItem = new ViewListItemElement(); - - var viewList = new ContainerElement(listType, null); - viewList.appendChildren(viewItem); - - mapper.bindElements(modelItem, viewItem); - - return viewItem; -} - -// Helper function that seeks for a sibling of given `modelItem` that is a `listItem` element and meets given criteria. -// `options` object may contain one or more of given values (by default they are `false`): -// `options.getNext` - whether next or previous siblings should be checked (default = previous) -// `options.checkAllSiblings` - whether all siblings or just the first one should be checked (default = only one), -// `options.sameIndent` - whether sought sibling should have same indent (default = no), -// `options.biggerIndent` - whether sought sibling should have bigger indent (default = no). -// Either `options.sameIndent` or `options.biggerIndent` should be set to `true`. -function getSiblingListItem(modelItem, options) { - var direction = options.getNext ? 'nextSibling' : 'previousSibling'; - var checkAllSiblings = !!options.checkAllSiblings; - var sameIndent = !!options.sameIndent; - var biggerIndent = !!options.biggerIndent; +/** + * The icon controller class. + * + * const model = new Model( { + * name: 'bold' + * } ); + * + * // An instance of "bold" Icon. + * new Icon( model, new IconView() ); + * + * See {@link ui.icon.IconView}, {@link ui.iconManager.IconManager}. + * + * @memberOf ui.icon + * @extends ui.Controller + */ - var indent = modelItem.getAttribute('indent'); +var Icon = function (_Controller) { + inherits(Icon, _Controller); - var item = modelItem[direction]; + /** + * Creates an instance of {@link ui.icon.Icon} class. + * + * @param {ui.icon.IconModel} model Model of this icon. + * @param {ui.View} view View of this icon. + */ + function Icon(model, view) { + classCallCheck(this, Icon); - while (item && item.name == 'listItem') { - var itemIndent = item.getAttribute('indent'); + var _this = possibleConstructorReturn(this, Object.getPrototypeOf(Icon).call(this, model, view)); - if (sameIndent && indent == itemIndent || biggerIndent && indent < itemIndent) { - return item; - } else if (!checkAllSiblings || indent > itemIndent) { - return null; - } + view.bind('name').to(model); + return _this; + } - item = item[direction]; - } + return Icon; +}(Controller); - return null; -} +/** + * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ -// Helper function that takes two parameters, that are expected to be view list elements, and merges them. -// The merge happen only if both parameters are UL or OL elements. -function mergeViewLists(firstList, secondList) { - if (firstList && secondList && (firstList.name == 'ul' || firstList.name == 'ol') && firstList.name == secondList.name) { - viewWriter.mergeContainers(Position$1.createAfter(firstList)); - } -} +/** + * The icon view class. + * + * See {@link ui.icon.Icon}. + * + * @memberOf ui.icon + * @extends ui.View + */ -// Helper function that takes model list item element `modelItem`, corresponding view list item element `injectedItem` -// that is not added to the view and is inside a view list element (`ul` or `ol`) and is that's list only child. -// The list is inserted at correct position (element breaking may be needed) and then merged with it's siblings. -// See comments below to better understand the algorithm. -function injectViewList(modelItem, injectedItem, mapper) { - var injectedList = injectedItem.parent; +var IconView = function (_View) { + inherits(IconView, _View); - // 1. Break after previous `listItem` if it has same or bigger indent. - var prevModelItem = getSiblingListItem(modelItem, { sameIndent: true, biggerIndent: true }); + /** + * @inheritDoc + */ + function IconView() { + classCallCheck(this, IconView); - if (prevModelItem) { - var viewItem = mapper.toViewElement(prevModelItem); - var viewPosition = Position$1.createAfter(viewItem); - viewWriter.breakContainer(viewPosition); - } + var _this = possibleConstructorReturn(this, Object.getPrototypeOf(IconView).call(this)); - // 2. Break after closest previous `listItem` sibling with same indent. - var sameIndentModelItem = getSiblingListItem(modelItem, { sameIndent: true, checkAllSiblings: true }); - // Position between broken lists will be a place where new list is inserted. - // If there is nothing to break (`sameIndentModelItem` is falsy) it means that converted list item - // is (will be) the first list item. - var insertionPosition = void 0; + var bind = _this.bindTemplate; - if (sameIndentModelItem) { - var _viewItem = mapper.toViewElement(sameIndentModelItem); - var _viewPosition = Position$1.createAfter(_viewItem); - insertionPosition = viewWriter.breakContainer(_viewPosition); - } else { - // If there is a list item before converted list item, it means that that list item has lower indent. - // In such case the created view list should be appended as a child of that item. - var prevSibling = modelItem.previousSibling; + _this.template = new Template({ + tag: 'svg', + ns: 'http://www.w3.org/2000/svg', + attributes: { + class: ['ck-icon'] + }, + children: [{ + tag: 'use', + ns: 'http://www.w3.org/2000/svg', + attributes: { + href: { + ns: 'http://www.w3.org/1999/xlink', + value: bind.to('name', function (i) { + return '#ck-icon-' + i; + }) + } + } + }] + }); - if (prevSibling && prevSibling.name == 'listItem') { - insertionPosition = Position$1.createAt(mapper.toViewElement(prevSibling), 'end'); - } else { - // This is the very first list item, use position mapping to get correct insertion position. - insertionPosition = mapper.toViewPosition(Position.createBefore(modelItem)); - } + /** + * The name of the icon. It corresponds with the name of the + * file in the {@link ui.iconManager.IconManager}. + * + * @observable + * @member {String} ui.icon.IconView#name + */ + return _this; } - // 3. Append new UL/OL in position after breaking in step 2. - viewWriter.insert(insertionPosition, injectedList); - - // 4. If next sibling is list item with bigger indent, append it's UL/OL to new LI. - var nextModelItem = getSiblingListItem(modelItem, { getNext: true, biggerIndent: true }); - var nextViewItem = mapper.toViewElement(nextModelItem); - - /* istanbul ignore if */ // Part of code connected with indenting that is not yet complete. - if (nextViewItem) { - var sourceRange = Range$2.createOn(nextViewItem.parent); - var targetPosition = Position$1.createAt(injectedItem, 'end'); - viewWriter.move(sourceRange, targetPosition); - } + return IconView; +}(View); - // 5. Merge new UL/OL with above and below items (ULs/OLs or LIs). - mergeViewLists(injectedList, injectedList.nextSibling); - mergeViewLists(injectedList.previousSibling, injectedList); -} +/** + * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ /** - * Model to view converter for `listItem` model element insertion. + * The button controller class. It uses {@link ui.icon.Icon} component + * to display an icon. * - * It creates `