Skip to content
This repository has been archived by the owner on Jun 26, 2020. It is now read-only.

Commit

Permalink
Merge pull request #1579 from ckeditor/t/1549
Browse files Browse the repository at this point in the history
Feature: Introduced `createDocumentFragment()`, `createElement()` and `createText()` methods in `UpcastWriter`. Additionally, the `View.change()` method now returns the return value of its callback. Closes #1549.
  • Loading branch information
Reinmar authored Nov 8, 2018
2 parents 18bab70 + f0d6d5d commit ec13c85
Show file tree
Hide file tree
Showing 18 changed files with 335 additions and 156 deletions.
7 changes: 4 additions & 3 deletions src/dev-utils/model.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import { isPlainObject } from 'lodash-es';
import toMap from '@ckeditor/ckeditor5-utils/src/tomap';

/**
* Writes the content of the {@link module:engine/model/document~Document document} to an HTML-like string.
* Writes the content of a model {@link module:engine/model/document~Document document} to an HTML-like string.
*
* **Note:** A {@link module:engine/model/text~Text text} node that contains attributes will be represented as:
*
Expand Down Expand Up @@ -72,9 +72,10 @@ export function getData( model, options = {} ) {
getData._stringify = stringify;

/**
* Sets the content of the {@link module:engine/model/document~Document document} provided as an HTML-like string.
* Sets the content of a model {@link module:engine/model/document~Document document} provided as an HTML-like string.
*
* **Note:** Remember to register elements in the {@link module:engine/model/model~Model#schema model's schema} before inserting them.
* **Note:** Remember to register elements in the {@link module:engine/model/model~Model#schema model's schema} before
* trying to use them.
*
* **Note:** To create a {@link module:engine/model/text~Text text} node that contains attributes use:
*
Expand Down
65 changes: 34 additions & 31 deletions src/dev-utils/view.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export function getData( view, options = {} ) {
getData._stringify = stringify;

/**
* Sets the content of the {@link module:engine/view/document~Document document} provided as an HTML-like string.
* Sets the content of a view {@link module:engine/view/document~Document document} provided as an HTML-like string.
*
* @param {module:engine/view/view~View} view
* @param {String} data An HTML-like string to write into the document.
Expand Down Expand Up @@ -111,44 +111,47 @@ setData._parse = parse;

/**
* Converts view elements to HTML-like string representation.
*
* A root element can be provided as {@link module:engine/view/text~Text text}:
*
* const text = new Text( 'foobar' );
* const text = downcastWriter.createText( 'foobar' );
* stringify( text ); // 'foobar'
*
* or as an {@link module:engine/view/element~Element element}:
*
* const element = new Element( 'p', null, new Text( 'foobar' ) );
* const element = downcastWriter.createElement( 'p', null, downcastWriter.createText( 'foobar' ) );
* stringify( element ); // '<p>foobar</p>'
*
* or as a {@link module:engine/view/documentfragment~DocumentFragment document fragment}:
*
* const text = new Text( 'foobar' );
* const b = new Element( 'b', { name: 'test' }, text );
* const p = new Element( 'p', { style: 'color:red;' } );
* const fragment = new DocumentFragment( [ p, b ] );
* const text = downcastWriter.createText( 'foobar' );
* const b = downcastWriter.createElement( 'b', { name: 'test' }, text );
* const p = downcastWriter.createElement( 'p', { style: 'color:red;' } );
* const fragment = downcastWriter.createDocumentFragment( [ p, b ] );
*
* stringify( fragment ); // '<p style="color:red;"></p><b name="test">foobar</b>'
*
* Additionally, a {@link module:engine/view/documentselection~DocumentSelection selection} instance can be provided.
* Ranges from the selection will then be included in output data.
* Ranges from the selection will then be included in the output data.
* If a range position is placed inside the element node, it will be represented with `[` and `]`:
*
* const text = new Text( 'foobar' );
* const b = new Element( 'b', null, text );
* const p = new Element( 'p', null, b );
* const selection = new Selection(
* Range._createFromParentsAndOffsets( p, 0, p, 1 )
* const text = downcastWriter.createText( 'foobar' );
* const b = downcastWriter.createElement( 'b', null, text );
* const p = downcastWriter.createElement( 'p', null, b );
* const selection = downcastWriter.createSelection(
* downcastWriter.createRangeIn( p )
* );
*
* stringify( p, selection ); // '<p>[<b>foobar</b>]</p>'
*
* If a range is placed inside the text node, it will be represented with `{` and `}`:
*
* const text = new Text( 'foobar' );
* const b = new Element( 'b', null, text );
* const p = new Element( 'p', null, b );
* const selection = new Selection( Range._createFromParentsAndOffsets( text, 1, text, 5 ) );
* const text = downcastWriter.createText( 'foobar' );
* const b = downcastWriter.createElement( 'b', null, text );
* const p = downcastWriter.createElement( 'p', null, b );
* const selection = downcastWriter.createSelection(
* downcastWriter.createRange( downcastWriter.createPositionAt( text, 1 ), downcastWriter.createPositionAt( text, 5 ) )
* );
*
* stringify( p, selection ); // '<p><b>f{ooba}r</b></p>'
*
Expand All @@ -159,10 +162,10 @@ setData._parse = parse;
*
* Multiple ranges are supported:
*
* const text = new Text( 'foobar' );
* const selection = new Selection( [
* Range._createFromParentsAndOffsets( text, 0, text, 1 ) ),
* Range._createFromParentsAndOffsets( text, 3, text, 5 ) )
* const text = downcastWriter.createText( 'foobar' );
* const selection = downcastWriter.createSelection( [
* downcastWriter.createRange( downcastWriter.createPositionAt( text, 0 ), downcastWriter.createPositionAt( text, 1 ) ),
* downcastWriter.createRange( downcastWriter.createPositionAt( text, 3 ), downcastWriter.createPositionAt( text, 5 ) )
* ] );
*
* stringify( text, selection ); // '{f}oo{ba}r'
Expand All @@ -172,9 +175,9 @@ setData._parse = parse;
* is provided, it will be converted to a selection containing this range. If a position instance is provided, it will
* be converted to a selection containing one range collapsed at this position.
*
* const text = new Text( 'foobar' );
* const range = Range._createFromParentsAndOffsets( text, 0, text, 1 );
* const position = new Position( text, 3 );
* const text = downcastWriter.createText( 'foobar' );
* const range = downcastWriter.createRange( downcastWriter.createPositionAt( text, 0 ), downcastWriter.createPositionAt( text, 1 ) );
* const position = downcastWriter.createPositionAt( text, 3 );
*
* stringify( text, range ); // '{f}oobar'
* stringify( text, position ); // 'foo{}bar'
Expand All @@ -186,10 +189,10 @@ setData._parse = parse;
* {@link module:engine/view/emptyelement~EmptyElement empty elements}
* and {@link module:engine/view/uielement~UIElement UI elements}:
*
* const attribute = new AttributeElement( 'b' );
* const container = new ContainerElement( 'p' );
* const empty = new EmptyElement( 'img' );
* const ui = new UIElement( 'span' );
* const attribute = downcastWriter.createAttributeElement( 'b' );
* const container = downcastWriter.createContainerElement( 'p' );
* const empty = downcastWriter.createEmptyElement( 'img' );
* const ui = downcastWriter.createUIElement( 'span' );
* getData( attribute, null, { showType: true } ); // '<attribute:b></attribute:b>'
* getData( container, null, { showType: true } ); // '<container:p></container:p>'
* getData( empty, null, { showType: true } ); // '<empty:img></empty:img>'
Expand All @@ -198,14 +201,14 @@ setData._parse = parse;
* If `options.showPriority` is set to `true`, a priority will be displayed for all
* {@link module:engine/view/attributeelement~AttributeElement attribute elements}.
*
* const attribute = new AttributeElement( 'b' );
* const attribute = downcastWriter.createAttributeElement( 'b' );
* attribute._priority = 20;
* getData( attribute, null, { showPriority: true } ); // <b view-priority="20"></b>
*
* If `options.showAttributeElementId` is set to `true`, the attribute element's id will be displayed for all
* {@link module:engine/view/attributeelement~AttributeElement attribute elements} that have it set.
*
* const attribute = new AttributeElement( 'span' );
* const attribute = downcastWriter.createAttributeElement( 'span' );
* attribute._id = 'marker:foo';
* getData( attribute, null, { showAttributeElementId: true } ); // <span view-id="marker:foo"></span>
*
Expand Down Expand Up @@ -249,7 +252,7 @@ export function stringify( node, selectionOrPositionOrRange = null, options = {}
}

/**
* Parses an HTML-like string and returns view tree nodes.
* Parses an HTML-like string and returns a view tree.
* A simple string will be converted to a {@link module:engine/view/text~Text text} node:
*
* parse( 'foobar' ); // Returns an instance of text.
Expand Down
17 changes: 10 additions & 7 deletions src/view/attributeelement.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,25 @@ import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
const DEFAULT_PRIORITY = 10;

/**
* Attributes are elements which define document presentation. They are mostly elements like `<b>` or `<span>`.
* Attributes can be broken and merged by the {@link module:engine/view/downcastwriter~DowncastWriter view downcast writer}.
* Attribute elements are used to represent formatting elements in the view (think – `<b>`, `<span style="font-size: 2em">`, etc.).
* Most often they are created when downcasting model text attributes.
*
* Editing engine does not define fixed HTML DTD. This is why the type of the {@link module:engine/view/element~Element} need to
* be defined by the feature developer. Creating an element you should use {@link module:engine/view/containerelement~ContainerElement}
* class or `AttributeElement`.
* Editing engine does not define a fixed HTML DTD. This is why a feature developer needs to choose between various
* types (container element, {@link module:engine/view/attributeelement~AttributeElement attribute element},
* {@link module:engine/view/emptyelement~EmptyElement empty element}, etc) when developing a feature.
*
* To create a new attribute element instance use the
* {@link module:engine/view/downcastwriter~DowncastWriter#createAttributeElement `DowncastWriter#createAttributeElement()`} method.
*
* @extends module:engine/view/element~Element
*/
export default class AttributeElement extends Element {
/**
* Creates a attribute element.
* Creates an attribute element.
*
* @see module:engine/view/downcastwriter~DowncastWriter#createAttributeElement
* @protected
* @see module:engine/view/element~Element
* @protected
*/
constructor( name, attrs, children ) {
super( name, attrs, children );
Expand Down
39 changes: 13 additions & 26 deletions src/view/containerelement.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,44 +11,31 @@ import Element from './element';

/**
* Containers are elements which define document structure. They define boundaries for
* {@link module:engine/view/attributeelement~AttributeElement attributes}. They are mostly use for block elements like `<p>` or `<div>`.
* {@link module:engine/view/attributeelement~AttributeElement attributes}. They are mostly used for block elements like `<p>` or `<div>`.
*
* Editing engine does not define fixed HTML DTD. This is why the type of the {@link module:engine/view/element~Element} need to
* be defined by the feature developer.
* Editing engine does not define a fixed HTML DTD. This is why a feature developer needs to choose between various
* types (container element, {@link module:engine/view/attributeelement~AttributeElement attribute element},
* {@link module:engine/view/emptyelement~EmptyElement empty element}, etc) when developing a feature.
*
* Creating an element you should use `ContainerElement` class or {@link module:engine/view/attributeelement~AttributeElement}. This is
* important to define the type of the element because of two reasons:
* The container element should be your default choice when writing a converter, unless:
*
* Firstly, {@link module:engine/view/domconverter~DomConverter} needs the information what is an editable block to convert elements to
* DOM properly. {@link module:engine/view/domconverter~DomConverter} will ensure that `ContainerElement` is editable and it is possible
* to put caret inside it, even if the container is empty.
* * this element represents a model text attribute (then use {@link module:engine/view/attributeelement~AttributeElement}),
* * this is an empty element like `<img>` (then use {@link module:engine/view/emptyelement~EmptyElement}),
* * this is a root element,
* * this is a nested editable element (then use {@link module:engine/view/editableelement~EditableElement}).
*
* Secondly, {@link module:engine/view/downcastwriter~DowncastWriter view downcast writer} uses this information.
* Nodes {@link module:engine/view/downcastwriter~DowncastWriter#breakAttributes breaking} and
* {@link module:engine/view/downcastwriter~DowncastWriter#mergeAttributes merging} is performed only in a bounds of a container nodes.
*
* For instance if `<p>` is an container and `<b>` is attribute:
*
* <p><b>fo^o</b></p>
*
* {@link module:engine/view/downcastwriter~DowncastWriter#breakAttributes breakAttributes} will create:
*
* <p><b>fo</b><b>o</b></p>
*
* There might be a need to mark `<span>` element as a container node, for example in situation when it will be a
* container of an inline widget:
*
* <span color="red">foobar</span> // attribute
* <span data-widget>foobar</span> // container
* To create a new container element instance use the
* {@link module:engine/view/downcastwriter~DowncastWriter#createContainerElement `DowncastWriter#createContainerElement()`}
* method.
*
* @extends module:engine/view/element~Element
*/
export default class ContainerElement extends Element {
/**
* Creates a container element.
*
* @see module:engine/view/element~Element
* @see module:engine/view/downcastwriter~DowncastWriter#createContainerElement
* @see module:engine/view/element~Element
* @protected
*/
constructor( name, attrs, children ) {
Expand Down
10 changes: 7 additions & 3 deletions src/view/documentfragment.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,19 @@ import isIterable from '@ckeditor/ckeditor5-utils/src/isiterable';
import EmitterMixin from '@ckeditor/ckeditor5-utils/src/emittermixin';

/**
* DocumentFragment class.
* Document fragment.
*
* To create a new document fragment instance use the
* {@link module:engine/view/upcastwriter~UpcastWriter#createDocumentFragment `UpcastWriter#createDocumentFragment()`}
* method.
*/
export default class DocumentFragment {
/**
* Creates new DocumentFragment instance.
*
* @protected
* @param {module:engine/view/node~Node|Iterable.<module:engine/view/node~Node>} [children] List of nodes to be inserted into
* created document fragment.
* @param {module:engine/view/node~Node|Iterable.<module:engine/view/node~Node>} [children]
* A list of nodes to be inserted into the created document fragment.
*/
constructor( children ) {
/**
Expand Down
9 changes: 5 additions & 4 deletions src/view/downcastwriter.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,11 @@ import { isPlainObject } from 'lodash-es';
* It provides a set of methods used to manipulate view nodes.
*
* The `DowncastWriter` is designed to work with semantic views which are the views that were/are being downcasted from the model.
* To work with ordinary views (e.g. parsed from a string) use the {@link module:engine/view/upcastwriter~UpcastWriter upcast writer}.
* To work with ordinary views (e.g. parsed from a pasted content) use the
* {@link module:engine/view/upcastwriter~UpcastWriter upcast writer}.
*
* Do not create an instance of this writer manually. To modify a view structure, use
* the {@link module:engine/view/view~View#change View#change()) block.
* the {@link module:engine/view/view~View#change `View#change()`) block.
*/
export default class DowncastWriter {
constructor( document ) {
Expand Down Expand Up @@ -141,8 +142,8 @@ export default class DowncastWriter {
*
* writer.createText( 'foo' );
*
* @param {String} data Text data.
* @returns {module:engine/view/text~Text} Created text node.
* @param {String} data The text's data.
* @returns {module:engine/view/text~Text} The created text node.
*/
createText( data ) {
return new Text( data );
Expand Down
3 changes: 3 additions & 0 deletions src/view/editableelement.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ const documentSymbol = Symbol( 'document' );
*
* Editable is automatically read-only when its {@link module:engine/view/document~Document Document} is read-only.
*
* The constructor of this class shouldn't be used directly. To create new `EditableElement` use the
* {@link module:engine/view/downcastwriter~DowncastWriter#createEditableElement `downcastWriter#createEditableElement()`} method.
*
* @extends module:engine/view/containerelement~ContainerElement
* @mixes module:utils/observablemixin~ObservableMixin
*/
Expand Down
Loading

0 comments on commit ec13c85

Please sign in to comment.