From a40fc174c8524d17703e5e476eadc00c438c6f1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Tue, 30 Oct 2018 16:41:52 +0100 Subject: [PATCH] Add Position/Range/Selection factory methods to UpcastWriter. --- src/view/downcastwriter.js | 4 +- src/view/upcastwriter.js | 144 +++++++++++++++++++++++++++++++++++++ tests/view/upcastwriter.js | 50 +++++++++++++ 3 files changed, 196 insertions(+), 2 deletions(-) diff --git a/src/view/downcastwriter.js b/src/view/downcastwriter.js index cd850e968..e2f205894 100644 --- a/src/view/downcastwriter.js +++ b/src/view/downcastwriter.js @@ -1012,8 +1012,8 @@ export default class DowncastWriter { * @param {module:engine/view/element~Element} element Element which is a parent for the range. * @returns {module:engine/view/range~Range} */ - createRangeIn( item ) { - return Range._createIn( item ); + createRangeIn( element ) { + return Range._createIn( element ); } /** diff --git a/src/view/upcastwriter.js b/src/view/upcastwriter.js index 2599a9490..05d4b1321 100644 --- a/src/view/upcastwriter.js +++ b/src/view/upcastwriter.js @@ -9,6 +9,9 @@ import Element from './element'; import { isPlainObject } from 'lodash-es'; +import Position from './position'; +import Range from './range'; +import Selection from './selection'; /** * View upcast writer class. Provides set of methods used to properly manipulate nodes attached to @@ -245,4 +248,145 @@ export default class UpcastWriter { removeCustomProperty( key, element ) { return element._removeCustomProperty( key ); } + + /** + * Creates position at the given location. The location can be specified as: + * + * * a {@link module:engine/view/position~Position position}, + * * parent element and offset (offset defaults to `0`), + * * parent element and `'end'` (sets position at the end of that element), + * * {@link module:engine/view/item~Item view item} and `'before'` or `'after'` (sets position before or after given view item). + * + * This method is a shortcut to other constructors such as: + * + * * {@link #createPositionBefore}, + * * {@link #createPositionAfter}, + * + * @param {module:engine/view/item~Item|module:engine/model/position~Position} itemOrPosition + * @param {Number|'end'|'before'|'after'} [offset] Offset or one of the flags. Used only when + * first parameter is a {@link module:engine/view/item~Item view item}. + */ + createPositionAt( itemOrPosition, offset ) { + return Position._createAt( itemOrPosition, offset ); + } + + /** + * Creates a new position after given view item. + * + * @param {module:engine/view/item~Item} item View item after which the position should be located. + * @returns {module:engine/view/position~Position} + */ + createPositionAfter( item ) { + return Position._createAfter( item ); + } + + /** + * Creates a new position before given view item. + * + * @param {module:engine/view/item~Item} item View item before which the position should be located. + * @returns {module:engine/view/position~Position} + */ + createPositionBefore( item ) { + return Position._createBefore( item ); + } + + /** + * Creates a range spanning from `start` position to `end` position. + * + * **Note:** This factory method creates it's own {@link module:engine/view/position~Position} instances basing on passed values. + * + * @param {module:engine/view/position~Position} start Start position. + * @param {module:engine/view/position~Position} [end] End position. If not set, range will be collapsed at `start` position. + * @returns {module:engine/view/range~Range} + */ + createRange( start, end ) { + return new Range( start, end ); + } + + /** + * Creates a range that starts before given {@link module:engine/view/item~Item view item} and ends after it. + * + * @param {module:engine/view/item~Item} item + * @returns {module:engine/view/range~Range} + */ + createRangeOn( item ) { + return Range._createOn( item ); + } + + /** + * Creates a range inside an {@link module:engine/view/element~Element element} which starts before the first child of + * that element and ends after the last child of that element. + * + * @param {module:engine/view/element~Element} element Element which is a parent for the range. + * @returns {module:engine/view/range~Range} + */ + createRangeIn( element ) { + return Range._createIn( element ); + } + + /** + Creates new {@link module:engine/view/selection~Selection} instance. + * + * // Creates empty selection without ranges. + * const selection = writer.createSelection(); + * + * // Creates selection at the given range. + * const range = writer.createRange( start, end ); + * const selection = writer.createSelection( range ); + * + * // Creates selection at the given ranges + * const ranges = [ writer.createRange( start1, end2 ), writer.createRange( star2, end2 ) ]; + * const selection = writer.createSelection( ranges ); + * + * // Creates selection from the other selection. + * const otherSelection = writer.createSelection(); + * const selection = writer.createSelection( otherSelection ); + * + * // Creates selection from the document selection. + * const selection = writer.createSelection( editor.editing.view.document.selection ); + * + * // Creates selection at the given position. + * const position = writer.createPositionFromPath( root, path ); + * const selection = writer.createSelection( position ); + * + * // Creates collapsed selection at the position of given item and offset. + * const paragraph = writer.createContainerElement( 'paragraph' ); + * const selection = writer.createSelection( paragraph, offset ); + * + * // Creates a range inside an {@link module:engine/view/element~Element element} which starts before the + * // first child of that element and ends after the last child of that element. + * const selection = writer.createSelection( paragraph, 'in' ); + * + * // Creates a range on an {@link module:engine/view/item~Item item} which starts before the item and ends + * // just after the item. + * const selection = writer.createSelection( paragraph, 'on' ); + * + * `Selection`'s constructor allow passing additional options (`backward`, `fake` and `label`) as the last argument. + * + * // Creates backward selection. + * const selection = writer.createSelection( range, { backward: true } ); + * + * Fake selection does not render as browser native selection over selected elements and is hidden to the user. + * This way, no native selection UI artifacts are displayed to the user and selection over elements can be + * represented in other way, for example by applying proper CSS class. + * + * Additionally fake's selection label can be provided. It will be used to describe fake selection in DOM + * (and be properly handled by screen readers). + * + * // Creates fake selection with label. + * const selection = writer.createSelection( range, { fake: true, label: 'foo' } ); + * + * @param {module:engine/view/selection~Selection|module:engine/view/documentselection~DocumentSelection| + * module:engine/view/position~Position|Iterable.|module:engine/view/range~Range| + * module:engine/view/item~Item|null} [selectable=null] + * @param {Number|'before'|'end'|'after'|'on'|'in'} [placeOrOffset] Offset or place when selectable is an `Item`. + * @param {Object} [options] + * @param {Boolean} [options.backward] Sets this selection instance to be backward. + * @param {Boolean} [options.fake] Sets this selection instance to be marked as `fake`. + * @param {String} [options.label] Label for the fake selection. + * @returns {module:engine/view/selection~Selection} + */ + createSelection( selectable, placeOrOffset, options ) { + return new Selection( selectable, placeOrOffset, options ); + } } diff --git a/tests/view/upcastwriter.js b/tests/view/upcastwriter.js index 7cd3b5ceb..67a899536 100644 --- a/tests/view/upcastwriter.js +++ b/tests/view/upcastwriter.js @@ -6,6 +6,9 @@ import Element from '../../src/view/element'; import UpcastWriter from '../../src/view/upcastwriter'; import HtmlDataProcessor from '../../src/dataprocessor/htmldataprocessor'; +import ViewPosition from '../../src/view/position'; +import ViewRange from '../../src/view/range'; +import ViewSelection from '../../src/view/selection'; describe( 'UpcastWriter', () => { let writer, view, dataprocessor; @@ -478,4 +481,51 @@ describe( 'UpcastWriter', () => { expect( Array.from( el.getCustomProperties() ).length ).to.equal( 0 ); } ); } ); + + describe( 'createPositionAt()', () => { + it( 'should return instance of Position', () => { + const span = new Element( 'span' ); + expect( writer.createPositionAt( span, 0 ) ).to.be.instanceof( ViewPosition ); + } ); + } ); + + describe( 'createPositionAfter()', () => { + it( 'should return instance of Position', () => { + const span = new Element( 'span', undefined, new Element( 'span' ) ); + expect( writer.createPositionAfter( span.getChild( 0 ) ) ).to.be.instanceof( ViewPosition ); + } ); + } ); + + describe( 'createPositionBefore()', () => { + it( 'should return instance of Position', () => { + const span = new Element( 'span', undefined, new Element( 'span' ) ); + expect( writer.createPositionBefore( span.getChild( 0 ) ) ).to.be.instanceof( ViewPosition ); + } ); + } ); + + describe( 'createRange()', () => { + it( 'should return instance of Range', () => { + expect( writer.createRange( writer.createPositionAt( new Element( 'span' ), 0 ) ) ).to.be.instanceof( ViewRange ); + } ); + } ); + + describe( 'createRangeIn()', () => { + it( 'should return instance of Range', () => { + const span = new Element( 'span', undefined, new Element( 'span' ) ); + expect( writer.createRangeIn( span.getChild( 0 ) ) ).to.be.instanceof( ViewRange ); + } ); + } ); + + describe( 'createRangeOn()', () => { + it( 'should return instance of Range', () => { + const span = new Element( 'span', undefined, new Element( 'span' ) ); + expect( writer.createRangeOn( span.getChild( 0 ) ) ).to.be.instanceof( ViewRange ); + } ); + } ); + + describe( 'createSelection()', () => { + it( 'should return instance of Selection', () => { + expect( writer.createSelection() ).to.be.instanceof( ViewSelection ); + } ); + } ); } );