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

Commit 885901f

Browse files
authored
Merge pull request #1109 from ckeditor/t/1108
Feature: Highlights on text nodes will be now unwrapped basing on descriptor id (which by default is marker name). Closes #1108.
2 parents 25ef1a8 + 1c7d2ec commit 885901f

File tree

4 files changed

+140
-68
lines changed

4 files changed

+140
-68
lines changed

src/conversion/buildmodelconverter.js

Lines changed: 11 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
5959
* buildModelConverter().for( dispatcher ).fromAttribute( 'bold' ).toElement( 'strong' );
6060
*
6161
* 4. Model marker to view highlight converter. This is a converter that converts model markers to view highlight
62-
* described by {@link module:engine/conversion/buildmodelconverter~HighlightDescriptor} object passed to
62+
* described by {@link module:engine/conversion/model-to-view-converters~HighlightDescriptor} object passed to
6363
* {@link module:engine/conversion/buildmodelconverter~ModelConverterBuilder#toHighlight} method.
6464
*
6565
* buildModelConverter().for( dispatcher ).fromMarker( 'search' ).toHighlight( {
@@ -209,9 +209,9 @@ class ModelConverterBuilder {
209209
* from element, {@link module:engine/view/attributeelement~AttributeElement ViewAttributeElement} if you convert
210210
* from attribute and {@link module:engine/view/uielement~UIElement ViewUIElement} if you convert from marker.
211211
*
212-
* NOTE: When converting from model's marker, separate elements will be created at the beginning and at the end of the
212+
* **Note:** When converting from model's marker, separate elements will be created at the beginning and at the end of the
213213
* marker's range. If range is collapsed then only one element will be created. See how markers
214-
* {module:engine/model/buildviewconverter~ViewConverterBuilder#toMarker view -> model serialization}
214+
* {module:engine/model/buildviewconverter~ViewConverterBuilder#toMarker serialization from view to model}
215215
* works to find out what view element format is the best for you.
216216
*
217217
* buildModelConverter().for( dispatcher ).fromElement( 'paragraph' ).toElement( 'p' );
@@ -286,10 +286,11 @@ class ModelConverterBuilder {
286286
* viewElement.setCustomProperty( 'addHighlight', ( element, descriptor ) => {} );
287287
* viewElement.setCustomProperty( 'removeHighlight', ( element, descriptor ) => {} );
288288
*
289-
* {@link module:engine/conversion/buildmodelconverter~HighlightDescriptor} will be used to create
289+
* {@link module:engine/conversion/model-to-view-converters~HighlightDescriptor} will be used to create
290290
* spans over text nodes and also will be provided to `addHighlight` and `removeHighlight` methods
291291
* each time highlight should be set or removed from view elements.
292-
* NOTE: When `addHighlight` and `removeHighlight` custom properties are present, converter assumes
292+
*
293+
* **Note:** When `addHighlight` and `removeHighlight` custom properties are present, converter assumes
293294
* that element itself is taking care of presenting highlight on its child nodes, so it won't convert them.
294295
*
295296
* Highlight descriptor can be provided as plain object:
@@ -307,7 +308,7 @@ class ModelConverterBuilder {
307308
* Throws {@link module:utils/ckeditorerror~CKEditorError CKEditorError}
308309
* `build-model-converter-non-marker-to-highlight` when trying to convert not from marker.
309310
*
310-
* @param {function|module:engine/conversion/buildmodelconverter~HighlightDescriptor} highlightDescriptor
311+
* @param {function|module:engine/conversion/model-to-view-converters~HighlightDescriptor} highlightDescriptor
311312
*/
312313
toHighlight( highlightDescriptor ) {
313314
const priority = this._from.priority === null ? 'normal' : this._from.priority;
@@ -423,28 +424,9 @@ export default function buildModelConverter() {
423424
}
424425

425426
/**
426-
* @typedef MarkerViewElementCreatorData
427-
* @param {Object} data Additional information about the change.
428-
* @param {String} data.markerName Marker name.
429-
* @param {module:engine/model/range~Range} data.markerRange Marker range.
430-
* @param {Boolean} data.isOpening Defines if currently converted element is a beginning or end of the marker range.
431-
* @param {module:engine/conversion/modelconsumable~ModelConsumable} consumable Values to consume.
432-
* @param {Object} conversionApi Conversion interface to be used by callback, passed in `ModelConversionDispatcher` constructor.
433-
*/
434-
435-
/**
436-
* @typedef HighlightDescriptor
437-
* Object describing how content highlight should be created in the view. Each text node contained in highlight
438-
* will be wrapped with `span` element with CSS class, attributes and priority described by this object. Each element
439-
* can handle displaying highlight separately by providing `addHighlight` and `removeHighlight` custom
440-
* properties.
427+
* @typedef {Object} module:engine/conversion/buildmodelconverter~MarkerViewElementCreatorData
441428
*
442-
* @property {String|Array.<String>} class CSS class or array of classes that will be added to `span`
443-
* {@link module:engine/view/attributeelement~AttributeElement} wrapping each text node in the highlighted content.
444-
* @property {String} [id] Descriptor identifier. If not provided, marker's name from which given highlight is created
445-
* will be used.
446-
* @property {Number} [priority] {@link module:engine/view/attributeelement~AttributeElement#priority} of the `span`
447-
* wrapping each text node in the highlighted content. If not provided, default 10 priority will be used.
448-
* @property {Object} [attributes] Attributes that will be added to `span`
449-
* {@link module:engine/view/attributeelement~AttributeElement} wrapping each text node it the highlighted content.
429+
* @param {String} markerName Marker name.
430+
* @param {module:engine/model/range~Range} markerRange Marker range.
431+
* @param {Boolean} isOpening Defines if currently converted element is a beginning or end of the marker range.
450432
*/

src/conversion/model-selection-to-view-converters.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import ViewElement from '../view/element';
77
import ViewRange from '../view/range';
88
import viewWriter from '../view/writer';
9-
import { highlightDescriptorToAttributeElement } from './model-to-view-converters';
9+
import { createViewElementFromHighlightDescriptor } from './model-to-view-converters';
1010

1111
/**
1212
* Contains {@link module:engine/model/selection~Selection model selection} to
@@ -162,7 +162,7 @@ export function convertSelectionAttribute( elementCreator ) {
162162
* modelDispatcher.on( 'selectionMarker:searchResult', convertSelectionMarker( { class: 'search' } ) );
163163
*
164164
* @see module:engine/conversion/model-selection-to-view-converters~convertSelectionAttribute
165-
* @param {module:engine/conversion/buildmodelconverter~HighlightDescriptor|Function} highlightDescriptor Highlight
165+
* @param {module:engine/conversion/model-to-view-converters~HighlightDescriptor|Function} highlightDescriptor Highlight
166166
* descriptor object or function returning a descriptor object.
167167
* @returns {Function} Selection converter.
168168
*/
@@ -176,7 +176,7 @@ export function convertSelectionMarker( highlightDescriptor ) {
176176
return;
177177
}
178178

179-
const viewElement = highlightDescriptorToAttributeElement( descriptor );
179+
const viewElement = createViewElementFromHighlightDescriptor( descriptor );
180180
const consumableName = 'selectionMarker:' + data.markerName;
181181

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

src/conversion/model-to-view-converters.js

Lines changed: 87 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -409,7 +409,7 @@ export function remove() {
409409
* {@link module:engine/view/attributeelement~AttributeElement} created from provided descriptor.
410410
* See {link module:engine/conversion/model-to-view-converters~highlightDescriptorToAttributeElement}.
411411
*
412-
* @param {module:engine/conversion/buildmodelconverter~HighlightDescriptor|Function} highlightDescriptor
412+
* @param {module:engine/conversion/model-to-view-converters~HighlightDescriptor|Function} highlightDescriptor
413413
* @return {Function}
414414
*/
415415
export function highlightText( highlightDescriptor ) {
@@ -428,7 +428,11 @@ export function highlightText( highlightDescriptor ) {
428428
return;
429429
}
430430

431-
const viewElement = highlightDescriptorToAttributeElement( descriptor );
431+
if ( !descriptor.id ) {
432+
descriptor.id = data.markerName;
433+
}
434+
435+
const viewElement = createViewElementFromHighlightDescriptor( descriptor );
432436
const viewRange = conversionApi.mapper.toViewRange( data.range );
433437

434438
if ( evt.name.split( ':' )[ 0 ] == 'addMarker' ) {
@@ -442,16 +446,17 @@ export function highlightText( highlightDescriptor ) {
442446
/**
443447
* Function factory, creates converter that converts all elements inside marker's range. Converter checks if element has
444448
* functions stored under `addHighlight` and `removeHighlight` custom properties and calls them passing
445-
* {@link module:engine/conversion/buildmodelconverter~HighlightDescriptor}. In such case converter will consume
449+
* {@link module:engine/conversion/model-to-view-converters~HighlightDescriptor}. In such case converter will consume
446450
* all element's children, assuming that they were handled by element itself. If highlight descriptor will not provide
447-
* priority, priority 10 will be used as default, to be compliant with
451+
* priority, priority `10` will be used as default, to be compliant with
448452
* {@link module:engine/conversion/model-to-view-converters~highlightText} method which uses default priority of
449453
* {@link module:engine/view/attributeelement~AttributeElement}.
450-
* If highlight descriptor will not provide id property, name of the marker will be used.
454+
*
455+
* If highlight descriptor will not provide `id` property, name of the marker will be used.
451456
* When `addHighlight` and `removeHighlight` custom properties are not present, element is not converted
452457
* in any special way. This means that converters will proceed to convert element's child nodes.
453458
*
454-
* @param {module:engine/conversion/buildmodelconverter~HighlightDescriptor|Function} highlightDescriptor
459+
* @param {module:engine/conversion/model-to-view-converters~HighlightDescriptor|Function} highlightDescriptor
455460
* @return {Function}
456461
*/
457462
export function highlightElement( highlightDescriptor ) {
@@ -562,29 +567,6 @@ export function eventNameToConsumableType( evtName ) {
562567
return parts[ 0 ] + ':' + parts[ 1 ];
563568
}
564569

565-
/**
566-
* Creates `span` {@link module:engine/view/attributeelement~AttributeElement view attribute element} from information
567-
* provided by {@link module:engine/conversion/buildmodelconverter~HighlightDescriptor} object. If priority
568-
* is not provided in descriptor - default priority will be used.
569-
*
570-
* @param {module:engine/conversion/buildmodelconverter~HighlightDescriptor } descriptor
571-
* @return {module:engine/view/attributeelement~AttributeElement}
572-
*/
573-
export function highlightDescriptorToAttributeElement( descriptor ) {
574-
const attributeElement = new ViewAttributeElement( 'span', descriptor.attributes );
575-
576-
if ( descriptor.class ) {
577-
const cssClasses = Array.isArray( descriptor.class ) ? descriptor.class : [ descriptor.class ];
578-
attributeElement.addClass( ...cssClasses );
579-
}
580-
581-
if ( descriptor.priority ) {
582-
attributeElement.priority = descriptor.priority;
583-
}
584-
585-
return attributeElement;
586-
}
587-
588570
// Helper function that shifts given view `position` in a way that returned position is after `howMany` characters compared
589571
// to the original `position`.
590572
// Because in view there might be view ui elements splitting text nodes, we cannot simply use `ViewPosition#getShiftedBy()`.
@@ -604,3 +586,79 @@ function _shiftViewPositionByCharacters( position, howMany ) {
604586
}
605587
}
606588
}
589+
590+
/**
591+
* Creates `span` {@link module:engine/view/attributeelement~AttributeElement view attribute element} from information
592+
* provided by {@link module:engine/conversion/model-to-view-converters~HighlightDescriptor} object. If priority
593+
* is not provided in descriptor - default priority will be used.
594+
*
595+
* @param {module:engine/conversion/model-to-view-converters~HighlightDescriptor} descriptor
596+
* @return {module:engine/conversion/model-to-view-converters~HighlightAttributeElement}
597+
*/
598+
export function createViewElementFromHighlightDescriptor( descriptor ) {
599+
const viewElement = new HighlightAttributeElement( 'span', descriptor.attributes );
600+
601+
if ( descriptor.class ) {
602+
const cssClasses = Array.isArray( descriptor.class ) ? descriptor.class : [ descriptor.class ];
603+
viewElement.addClass( ...cssClasses );
604+
}
605+
606+
if ( descriptor.priority ) {
607+
viewElement.priority = descriptor.priority;
608+
}
609+
610+
viewElement.setCustomProperty( 'highlightDescriptorId', descriptor.id );
611+
612+
return viewElement;
613+
}
614+
615+
/**
616+
* Special kind of {@link module:engine/view/attributeelement~AttributeElement} that is created and used in
617+
* marker-to-highlight conversion.
618+
*
619+
* The difference between `HighlightAttributeElement` and {@link module:engine/view/attributeelement~AttributeElement}
620+
* is {@link module:engine/view/attributeelement~AttributeElement#isSimilar} method.
621+
*
622+
* For `HighlightAttributeElement` it checks just `highlightDescriptorId` custom property, that is set during marker-to-highlight
623+
* conversion basing on {@link module:engine/conversion/model-to-view-converters~HighlightDescriptor} object.
624+
* `HighlightAttributeElement`s with same `highlightDescriptorId` property are considered similar.
625+
*/
626+
class HighlightAttributeElement extends ViewAttributeElement {
627+
isSimilar( otherElement ) {
628+
if ( otherElement.is( 'attributeElement' ) ) {
629+
return this.getCustomProperty( 'highlightDescriptorId' ) === otherElement.getCustomProperty( 'highlightDescriptorId' );
630+
}
631+
632+
return false;
633+
}
634+
}
635+
636+
/**
637+
* Object describing how content highlight should be created in the view.
638+
*
639+
* Each text node contained in highlight will be wrapped with `span` element with CSS class(es), attributes and priority
640+
* described by this object.
641+
*
642+
* Each element can handle displaying highlight separately by providing `addHighlight` and `removeHighlight` custom
643+
* properties. Those properties are passed `HighlightDescriptor` object upon conversion and should use it to
644+
* change the element.
645+
*
646+
* @typedef {Object} module:engine/conversion/model-to-view-converters~HighlightDescriptor
647+
*
648+
* @property {String|Array.<String>} class CSS class or array of classes to set. If descriptor is used to
649+
* create {@link module:engine/view/attributeelement~AttributeElement} over text nodes, those classes will be set
650+
* on that {@link module:engine/view/attributeelement~AttributeElement}. If descriptor is applied to an element,
651+
* usually those class will be set on that element, however this depends on how the element converts the descriptor.
652+
*
653+
* @property {String} [id] Descriptor identifier. If not provided, defaults to converted marker's name.
654+
*
655+
* @property {Number} [priority] Descriptor priority. If not provided, defaults to `10`. If descriptor is used to create
656+
* {@link module:engine/view/attributeelement~AttributeElement}, it will be that element's
657+
* {@link module:engine/view/attributeelement~AttributeElement#priority}. If descriptor is applied to an element,
658+
* the priority will be used to determine which descriptor is more important.
659+
*
660+
* @property {Object} [attributes] Attributes to set. If descriptor is used to create
661+
* {@link module:engine/view/attributeelement~AttributeElement} over text nodes, those attributes will be set on that
662+
* {@link module:engine/view/attributeelement~AttributeElement}. If descriptor is applied to an element, usually those
663+
* attributes will be set on that element, however this depends on how the element converts the descriptor.
664+
*/

tests/conversion/model-to-view-converters.js

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import {
3030
removeUIElement,
3131
highlightText,
3232
highlightElement,
33-
highlightDescriptorToAttributeElement
33+
createViewElementFromHighlightDescriptor
3434
} from '../../src/conversion/model-to-view-converters';
3535

3636
import { createRangeOnElementOnly } from '../../tests/model/_utils/utils';
@@ -1224,14 +1224,14 @@ describe( 'model-to-view-converters', () => {
12241224
} );
12251225
} );
12261226

1227-
describe( 'highlightDescriptorToAttributeElement()', () => {
1227+
describe( 'createViewElementFromHighlightDescriptor()', () => {
12281228
it( 'should return attribute element from descriptor object', () => {
12291229
const descriptor = {
12301230
class: 'foo-class',
12311231
attributes: { one: 1, two: 2 },
12321232
priority: 7,
12331233
};
1234-
const element = highlightDescriptorToAttributeElement( descriptor );
1234+
const element = createViewElementFromHighlightDescriptor( descriptor );
12351235

12361236
expect( element.is( 'attributeElement' ) ).to.be.true;
12371237
expect( element.name ).to.equal( 'span' );
@@ -1249,7 +1249,7 @@ describe( 'model-to-view-converters', () => {
12491249
attributes: { one: 1, two: 2 },
12501250
priority: 7,
12511251
};
1252-
const element = highlightDescriptorToAttributeElement( descriptor );
1252+
const element = createViewElementFromHighlightDescriptor( descriptor );
12531253

12541254
expect( element.is( 'attributeElement' ) ).to.be.true;
12551255
expect( element.name ).to.equal( 'span' );
@@ -1267,7 +1267,7 @@ describe( 'model-to-view-converters', () => {
12671267
attributes: { one: 1, two: 2 },
12681268
priority: 7,
12691269
};
1270-
const element = highlightDescriptorToAttributeElement( descriptor );
1270+
const element = createViewElementFromHighlightDescriptor( descriptor );
12711271

12721272
expect( element.is( 'attributeElement' ) ).to.be.true;
12731273
expect( element.name ).to.equal( 'span' );
@@ -1283,7 +1283,7 @@ describe( 'model-to-view-converters', () => {
12831283
class: 'foo-class',
12841284
attributes: { one: 1, two: 2 },
12851285
};
1286-
const element = highlightDescriptorToAttributeElement( descriptor );
1286+
const element = createViewElementFromHighlightDescriptor( descriptor );
12871287

12881288
expect( element.is( 'attributeElement' ) ).to.be.true;
12891289
expect( element.name ).to.equal( 'span' );
@@ -1300,12 +1300,44 @@ describe( 'model-to-view-converters', () => {
13001300
class: 'foo-class',
13011301
priority: 7
13021302
};
1303-
const element = highlightDescriptorToAttributeElement( descriptor );
1303+
const element = createViewElementFromHighlightDescriptor( descriptor );
13041304

13051305
expect( element.is( 'attributeElement' ) ).to.be.true;
13061306
expect( element.name ).to.equal( 'span' );
13071307
expect( element.priority ).to.equal( 7 );
13081308
expect( element.hasClass( 'foo-class' ) ).to.be.true;
13091309
} );
1310+
1311+
it( 'should create similar elements if they are created using same descriptor id', () => {
1312+
const a = createViewElementFromHighlightDescriptor( {
1313+
id: 'id',
1314+
class: 'classA',
1315+
priority: 1
1316+
} );
1317+
1318+
const b = createViewElementFromHighlightDescriptor( {
1319+
id: 'id',
1320+
class: 'classB',
1321+
priority: 2
1322+
} );
1323+
1324+
expect( a.isSimilar( b ) ).to.be.true;
1325+
} );
1326+
1327+
it( 'should create non-similar elements if they have different descriptor id', () => {
1328+
const a = createViewElementFromHighlightDescriptor( {
1329+
id: 'a',
1330+
class: 'foo',
1331+
priority: 1
1332+
} );
1333+
1334+
const b = createViewElementFromHighlightDescriptor( {
1335+
id: 'b',
1336+
class: 'foo',
1337+
priority: 1
1338+
} );
1339+
1340+
expect( a.isSimilar( b ) ).to.be.false;
1341+
} );
13101342
} );
13111343
} );

0 commit comments

Comments
 (0)