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

Commit

Permalink
Merge a300c1f into bc1c3e5
Browse files Browse the repository at this point in the history
  • Loading branch information
scofalik committed Jan 19, 2018
2 parents bc1c3e5 + a300c1f commit adb78ca
Show file tree
Hide file tree
Showing 11 changed files with 735 additions and 960 deletions.
5 changes: 0 additions & 5 deletions src/conversion/buildmodelconverter.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ import {
removeHighlight
} from './model-to-view-converters';

import { convertSelectionAttribute, convertSelectionMarker } from './model-selection-to-view-converters';

import ViewAttributeElement from '../view/attributeelement';
import ViewContainerElement from '../view/containerelement';
import ViewUIElement from '../view/uielement';
Expand Down Expand Up @@ -257,7 +255,6 @@ class ModelConverterBuilder {
element = typeof element == 'string' ? new ViewAttributeElement( element ) : element;

dispatcher.on( 'attribute:' + this._from.key, wrap( element ), { priority } );
dispatcher.on( 'selectionAttribute:' + this._from.key, convertSelectionAttribute( element ), { priority } );
} else {
// From marker to element.
const priority = this._from.priority === null ? 'normal' : this._from.priority;
Expand Down Expand Up @@ -327,8 +324,6 @@ class ModelConverterBuilder {
dispatcher.on( 'addMarker:' + this._from.name, highlightElement( highlightDescriptor ), { priority } );

dispatcher.on( 'removeMarker:' + this._from.name, removeHighlight( highlightDescriptor ), { priority } );

dispatcher.on( 'selectionMarker:' + this._from.name, convertSelectionMarker( highlightDescriptor ), { priority } );
}
}

Expand Down
138 changes: 0 additions & 138 deletions src/conversion/model-selection-to-view-converters.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@
* For licensing, see LICENSE.md.
*/

import ViewElement from '../view/element';
import ViewRange from '../view/range';
import viewWriter from '../view/writer';
import { createViewElementFromHighlightDescriptor } from './model-to-view-converters';

/**
* Contains {@link module:engine/model/selection~Selection model selection} to
Expand Down Expand Up @@ -91,142 +89,6 @@ export function convertCollapsedSelection() {
};
}

/**
* Function factory, creates a converter that converts {@link module:engine/model/selection~Selection model selection} attributes to
* {@link module:engine/view/attributeelement~AttributeElement view attribute elements}. The converter works only for collapsed selection.
* The converter consumes appropriate value from `consumable` object, maps model selection position to view position and
* wraps that position into a view attribute element.
*
* The wrapping node depends on passed parameter. If {@link module:engine/view/element~Element} was passed, it will be cloned and
* the copy will become the wrapping element. If `Function` is provided, it is passed all the parameters of the
* {@link module:engine/conversion/modelconversiondispatcher~ModelConversionDispatcher#event:selectionAttribute selectionAttribute event}.
* It's expected that the function returns a {@link module:engine/view/attributeelement~AttributeElement}.
* The result of the function will be the wrapping element.
*
* modelDispatcher.on( 'selectionAttribute:italic', convertSelectionAttribute( new ViewAttributeElement( 'em' ) ) );
*
* function styleElementCreator( styleValue ) {
* if ( styleValue == 'important' ) {
* return new ViewAttributeElement( 'strong', { style: 'text-transform:uppercase;' } );
* } else if ( styleValue == 'gold' ) {
* return new ViewAttributeElement( 'span', { style: 'color:yellow;' } );
* }
* }
* modelDispatcher.on( 'selectionAttribute:style', convertSelectionAttribute( styleCreator ) );
* modelDispatcher.on( 'selection', convertCollapsedSelection() );
* modelDispatcher.on( 'selectionAttribute:italic', convertSelectionAttribute( new ViewAttributeElement( 'em' ) ) );
* modelDispatcher.on( 'selectionAttribute:bold', convertSelectionAttribute( new ViewAttributeElement( 'strong' ) ) );
*
* Example of view states before and after converting collapsed selection:
*
* <p><em>f^oo</em>bar</p>
* -> <p><em>f</em>^<em>oo</em>bar</p>
* -> <p><em>f^oo</em>bar</p>
*
* Example of view state after converting collapsed selection. The scenario is: selection is inside bold text (`<strong>` element)
* but it does not have bold attribute itself and has italic attribute instead (let's assume that user turned off bold and turned
* on italic with selection collapsed):
*
* <p><strong>f^oo<strong>bar</p>
* -> <p><strong>f</strong>^<strong>oo<strong>bar</p>
* -> <p><strong>f</strong><em>^</em><strong>oo</strong>bar</p>
*
* In first example, nothing has changed, because first `<em>` element got broken by `convertCollapsedSelection()` converter,
* but then it got wrapped-back by `convertSelectionAttribute()` converter. In second example, notice how `<strong>` element
* is broken to prevent putting selection in it, since selection has no `bold` attribute.
*
* @param {module:engine/view/attributeelement~AttributeElement|Function} elementCreator View element,
* or function returning a view element, which will be used for wrapping.
* @returns {Function} Selection converter.
*/
export function convertSelectionAttribute( elementCreator ) {
return ( evt, data, consumable, conversionApi ) => {
const viewElement = elementCreator instanceof ViewElement ?
elementCreator.clone( true ) :
elementCreator( data.value, data, data.selection, consumable, conversionApi );

if ( !viewElement ) {
return;
}

const consumableName = 'selectionAttribute:' + data.key;

wrapCollapsedSelectionPosition( data.selection, conversionApi.viewSelection, viewElement, consumable, consumableName );
};
}

/**
* Performs similar conversion as {@link ~convertSelectionAttribute}, but depends on a marker name of a marker in which
* collapsed selection is placed.
*
* modelDispatcher.on( 'selectionMarker:searchResult', convertSelectionMarker( { class: 'search' } ) );
*
* @see module:engine/conversion/model-selection-to-view-converters~convertSelectionAttribute
* @param {module:engine/conversion/model-to-view-converters~HighlightDescriptor|Function} highlightDescriptor Highlight
* descriptor object or function returning a descriptor object.
* @returns {Function} Selection converter.
*/
export function convertSelectionMarker( highlightDescriptor ) {
return ( evt, data, consumable, conversionApi ) => {
const descriptor = typeof highlightDescriptor == 'function' ?
highlightDescriptor( data, consumable, conversionApi ) :
highlightDescriptor;

if ( !descriptor ) {
return;
}

if ( !descriptor.id ) {
descriptor.id = data.markerName;
}

const viewElement = createViewElementFromHighlightDescriptor( descriptor );

wrapCollapsedSelectionPosition( data.selection, conversionApi.viewSelection, viewElement, consumable, evt.name );
};
}

// Helper function for `convertSelectionAttribute` and `convertSelectionMarker`, which perform similar task.
function wrapCollapsedSelectionPosition( modelSelection, viewSelection, viewElement, consumable, eventName ) {
if ( !modelSelection.isCollapsed ) {
return;
}

if ( !consumable.consume( modelSelection, eventName ) ) {
return;
}

let viewPosition = viewSelection.getFirstPosition();

// This hack is supposed to place attribute element *after* all ui elements if the attribute element would be
// the only non-ui child and thus receive a block filler.
// This is needed to properly render ui elements. Block filler is a <br /> element. If it is placed before
// UI element, the ui element will most probably be incorrectly rendered (in next line). #1072.
if ( shouldPushAttributeElement( viewPosition.parent ) ) {
viewPosition = viewPosition.getLastMatchingPosition( value => value.item.is( 'uiElement' ) );
}
// End of hack.

viewPosition = viewWriter.wrapPosition( viewPosition, viewElement );

viewSelection.removeAllRanges();
viewSelection.addRange( new ViewRange( viewPosition, viewPosition ) );
}

function shouldPushAttributeElement( parent ) {
if ( !parent.is( 'element' ) ) {
return false;
}

for ( const child of parent.getChildren() ) {
if ( !child.is( 'uiElement' ) ) {
return false;
}
}

return true;
}

/**
* Function factory, creates a converter that clears artifacts after the previous
* {@link module:engine/model/selection~Selection model selection} conversion. It removes all empty
Expand Down
52 changes: 32 additions & 20 deletions src/conversion/model-to-view-converters.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
*/

import ModelRange from '../model/range';
import ModelSelection from '../model/selection';
import ModelElement from '../model/element';

import ViewElement from '../view/element';
import ViewAttributeElement from '../view/attributeelement';
Expand Down Expand Up @@ -278,6 +280,8 @@ export function changeAttribute( attributeCreator ) {

/**
* Function factory, creates a converter that converts set/change/remove attribute changes from the model to the view.
* Also can be used to convert selection attributes. In that case, an empty attribute element will be created and the
* selection will be put inside it.
*
* Attributes from model are converted to a view element that will be wrapping those view nodes that are bound to
* model elements having given attribute. This is useful for attributes like `bold`, which may be set on text nodes in model
Expand Down Expand Up @@ -325,16 +329,21 @@ export function wrap( elementCreator ) {
return;
}

let viewRange = conversionApi.mapper.toViewRange( data.range );
if ( data.item instanceof ModelSelection ) {
// Selection attribute conversion.
viewWriter.wrap( conversionApi.viewSelection.getFirstRange(), newViewElement, conversionApi.viewSelection );
} else {
// Node attribute conversion.
let viewRange = conversionApi.mapper.toViewRange( data.range );

// First, unwrap the range from current wrapper.
if ( data.attributeOldValue !== null ) {
viewRange = viewWriter.unwrap( viewRange, oldViewElement );
}
// First, unwrap the range from current wrapper.
if ( data.attributeOldValue !== null ) {
viewRange = viewWriter.unwrap( viewRange, oldViewElement );
}

// Then wrap with the new wrapper.
if ( data.attributeNewValue !== null ) {
viewWriter.wrap( viewRange, newViewElement );
if ( data.attributeNewValue !== null ) {
viewWriter.wrap( viewRange, newViewElement );
}
}
};
}
Expand All @@ -344,6 +353,9 @@ export function wrap( elementCreator ) {
* {@link module:engine/view/attributeelement~AttributeElement} created from provided descriptor.
* See {link module:engine/conversion/model-to-view-converters~createViewElementFromHighlightDescriptor}.
*
* Also can be used to convert selection that is inside a marker. In that case, an empty attribute element will be
* created and the selection will be put inside it.
*
* If the highlight descriptor will not provide `priority` property, `10` will be used.
*
* If the highlight descriptor will not provide `id` property, name of the marker will be used.
Expand All @@ -357,9 +369,7 @@ export function highlightText( highlightDescriptor ) {
return;
}

const modelItem = data.item;

if ( !modelItem.is( 'textProxy' ) ) {
if ( !( data.item instanceof ModelSelection ) && !data.item.is( 'textProxy' ) ) {
return;
}

Expand All @@ -369,14 +379,18 @@ export function highlightText( highlightDescriptor ) {
return;
}

if ( !consumable.consume( modelItem, evt.name ) ) {
if ( !consumable.consume( data.item, evt.name ) ) {
return;
}

const viewElement = createViewElementFromHighlightDescriptor( descriptor );
const viewRange = conversionApi.mapper.toViewRange( data.range );

viewWriter.wrap( viewRange, viewElement );
if ( data.item instanceof ModelSelection ) {
viewWriter.wrap( conversionApi.viewSelection.getFirstRange(), viewElement, conversionApi.viewSelection );
} else {
const viewRange = conversionApi.mapper.toViewRange( data.range );
viewWriter.wrap( viewRange, viewElement );
}
};
}

Expand All @@ -403,9 +417,7 @@ export function highlightElement( highlightDescriptor ) {
return;
}

const modelItem = data.item;

if ( !modelItem.is( 'element' ) ) {
if ( !( data.item instanceof ModelElement ) ) {
return;
}

Expand All @@ -415,18 +427,18 @@ export function highlightElement( highlightDescriptor ) {
return;
}

if ( !consumable.test( modelItem, evt.name ) ) {
if ( !consumable.test( data.item, evt.name ) ) {
return;
}

const viewElement = conversionApi.mapper.toViewElement( modelItem );
const viewElement = conversionApi.mapper.toViewElement( data.item );

if ( viewElement && viewElement.getCustomProperty( 'addHighlight' ) ) {
// Consume element itself.
consumable.consume( data.item, evt.name );

// Consume all children nodes.
for ( const value of ModelRange.createIn( modelItem ) ) {
for ( const value of ModelRange.createIn( data.item ) ) {
consumable.consume( value.item, evt.name );
}

Expand Down
Loading

0 comments on commit adb78ca

Please sign in to comment.