Skip to content

Commit

Permalink
Merge pull request #6739 from ckeditor/i/6587
Browse files Browse the repository at this point in the history
Docs (mention): Add notes about using `AttributeElement`'s priority and id attribute in mentions. Closes #6587.

Other (mention): Renamed `MentionAttribute._uid` to `MentionAttribute.uid` as it needs to be used by integrators when implementing custom converters. Closes #6587.
  • Loading branch information
Reinmar authored May 22, 2020
2 parents cb7b5d1 + 881e671 commit 94a6952
Show file tree
Hide file tree
Showing 11 changed files with 64 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,11 @@ function MentionLinks( editor ) {
class: 'mention',
'data-mention': modelAttributeValue.id,
href
}, {
// Make mention attribute to be wrapped by other attribute elements.
priority: 20,
// Prevent merging mentions together.
id: modelAttributeValue.uid
} );
},
converterPriority: 'high'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ function MentionCustomization( editor ) {
value: viewItem => {
// The mention feature expects that the mention attribute value
// in the model is a plain object with a set of additional attributes.
// In order to create a proper object, use the toMentionAttribute() helper method:
// In order to create a proper object use the toMentionAttribute() helper method:
const mentionAttribute = editor.plugins.get( 'Mention' ).toMentionAttribute( viewItem, {
// Add any other properties that you need.
link: viewItem.getAttribute( 'href' ),
Expand All @@ -79,6 +79,11 @@ function MentionCustomization( editor ) {
'data-mention': modelAttributeValue.id,
'data-user-id': modelAttributeValue.userId,
'href': modelAttributeValue.link
}, {
// Make mention attribute to be wrapped by other attribute elements.
priority: 20,
// Prevent merging mentions together.
id: modelAttributeValue.uid
} );
},
converterPriority: 'high'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,11 @@ function MentionLinks( editor ) {
class: 'mention',
'data-mention': modelAttributeValue.id,
href
}, {
// Make mention attribute to be wrapped by other attribute elements.
priority: 20,
// Prevent merging mentions together.
id: modelAttributeValue.uid
} );
},
converterPriority: 'high'
Expand Down
16 changes: 15 additions & 1 deletion packages/ckeditor5-mention/docs/features/mentions.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ A full, working demo with all possible customizations and its source code is ava

### Customizing the output

In order to change the markup generated by the editor for mentions, you can overwrite the default converter of the mention feature. To do that, you must specify both {@link module:engine/conversion/upcastdispatcher~UpcastDispatcher upcast} and {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher downcast} converters.
In order to change the markup generated by the editor for mentions, you can overwrite the default converter of the mention feature. To do that, you must specify both {@link module:engine/conversion/upcastdispatcher~UpcastDispatcher upcast} and {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher downcast} converters using {@link module:engine/view/attributeelement~AttributeElement}.

The example below defines a plugin that overrides the default output:

Expand All @@ -196,6 +196,10 @@ To a link:

The converters must be defined with a `'high'` priority to be executed before the {@link features/link link} feature's converter and before the default converter of the mention feature. A mention is stored in the model as a {@link framework/guides/architecture/editing-engine#text-attributes text attribute} that stores an object (see {@link module:mention/mention~MentionFeedItem}).

To control how the mention element is wrapped by other attribute elements (like bold, italic, etc) set its {@link module:engine/view/attributeelement~AttributeElement#priority}. To replicate default plugin behavior and make mention to be wrapped by other elements set priority to `20`.

By default, attribute elements that are next to each other and have the same value will be rendered as single HTML element. To prevent this the model attribute value object expose a unique id of each inserted mention to the model as `uid`. To prevent merging subsequent mentions set it as {@link module:engine/view/attributeelement~AttributeElement#id}.

**Note:** The feature prevents copying fragments of existing mentions. If only a part of a mention is selected, it will be copied as plain text. The internal converter with the {@link module:engine/conversion/conversion~ConverterDefinition `'highest'` priority} controls this behaviour; thus, we do not recommend adding mention converters with the `'highest'` priority to avoid collisions and quirky results.

```js
Expand Down Expand Up @@ -254,6 +258,11 @@ function MentionCustomization( editor ) {
'data-mention': modelAttributeValue.id,
'data-user-id': modelAttributeValue.userId,
'href': modelAttributeValue.link
}, {
// Make mention attribute to be wrapped by other attribute elements.
priority: 20,
// Prevent merging mentions together.
id: modelAttributeValue.uid
} );
},
converterPriority: 'high'
Expand Down Expand Up @@ -341,6 +350,11 @@ function MentionCustomization( editor ) {
'data-mention': modelAttributeValue.id,
'data-user-id': modelAttributeValue.userId,
'href': modelAttributeValue.link
}, {
// Make mention attribute to be wrapped by other attribute elements.
priority: 20,
// Prevent merging mentions together.
id: modelAttributeValue.uid
} );
},
converterPriority: 'high'
Expand Down
7 changes: 4 additions & 3 deletions packages/ckeditor5-mention/src/mention.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export default class Mention extends Plugin {
*
* // For a view element: <span data-mention="@joe">@John Doe</span>
* // it will return:
* // { id: '@joe', userId: '1234', _uid: '7a7bc7...', _text: '@John Doe' }
* // { id: '@joe', userId: '1234', uid: '7a7bc7...', _text: '@John Doe' }
*
* @param {module:engine/view/element~Element} viewElement
* @param {String|Object} [data] Additional data to be stored in the mention attribute.
Expand Down Expand Up @@ -225,8 +225,9 @@ export default class Mention extends Plugin {
* See {@link module:mention/mention~Mention#toMentionAttribute `Mention#toMentionAttribute()`}.
*
* @interface module:mention/mention~MentionAttribute
* @property {String} id The ID of a mention. It identifies the mention item in the mention feed.
* @property {String} _uid An internal mention view item ID. Should be passed as an `option.id` when using
* @property {String} id The ID of a mention. It identifies the mention item in the mention feed. There can be multiple mentions
* in the document with the same ID (e.g. the same hashtag being mentioned).
* @property {String} uid A unique ID of this mention instance. Should be passed as an `option.id` when using
* {@link module:engine/view/downcastwriter~DowncastWriter#createAttributeElement writer.createAttributeElement()}.
* @property {String} _text Helper property that stores the text of the inserted mention. Used for detecting a broken mention
* in the editing area.
Expand Down
4 changes: 2 additions & 2 deletions packages/ckeditor5-mention/src/mentionediting.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export default class MentionEditing extends Plugin {
}

export function _addMentionAttributes( baseMentionData, data ) {
return Object.assign( { _uid: uid() }, baseMentionData, data || {} );
return Object.assign( { uid: uid() }, baseMentionData, data || {} );
}

/**
Expand Down Expand Up @@ -142,7 +142,7 @@ function createViewMentionElement( mention, viewWriter ) {
};

const options = {
id: mention._uid,
id: mention.uid,
priority: 20
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,10 @@ class CustomMentionAttributeView extends Plugin {
class: 'mention',
'data-mention': modelAttributeValue.id,
'href': modelAttributeValue.link
}, { id: modelAttributeValue._uid } );
}, {
priority: 20,
id: modelAttributeValue.uid
} );
},
converterPriority: 'high'
} );
Expand Down
4 changes: 2 additions & 2 deletions packages/ckeditor5-mention/tests/mention.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ describe( 'Mention', () => {
const mentionAttribute = editor.plugins.get( 'Mention' ).toMentionAttribute( viewElement );

expect( mentionAttribute ).to.have.property( 'id', '@John' );
expect( mentionAttribute ).to.have.property( '_uid' );
expect( mentionAttribute ).to.have.property( 'uid' );
expect( mentionAttribute ).to.have.property( '_text', 'John Doe' );
} );

Expand All @@ -77,7 +77,7 @@ describe( 'Mention', () => {

expect( mentionAttribute ).to.have.property( 'id', '@John' );
expect( mentionAttribute ).to.have.property( 'foo', 'bar' );
expect( mentionAttribute ).to.have.property( '_uid' );
expect( mentionAttribute ).to.have.property( 'uid' );
expect( mentionAttribute ).to.have.property( '_text', 'John Doe' );
} );

Expand Down
2 changes: 1 addition & 1 deletion packages/ckeditor5-mention/tests/mentioncommand.js
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ describe( 'MentionCommand', () => {

function assertMention( textNode, id ) {
expect( textNode.hasAttribute( 'mention' ) ).to.be.true;
expect( textNode.getAttribute( 'mention' ) ).to.have.property( '_uid' );
expect( textNode.getAttribute( 'mention' ) ).to.have.property( 'uid' );
expect( textNode.getAttribute( 'mention' ) ).to.have.property( '_text', textNode.data );
expect( textNode.getAttribute( 'mention' ) ).to.have.property( 'id', id );
}
Expand Down
24 changes: 15 additions & 9 deletions packages/ckeditor5-mention/tests/mentionediting.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ describe( 'MentionEditing', () => {
expect( textNode.hasAttribute( 'mention' ) ).to.be.true;
expect( textNode.getAttribute( 'mention' ) ).to.have.property( 'id', '@John' );
expect( textNode.getAttribute( 'mention' ) ).to.have.property( '_text', '@John' );
expect( textNode.getAttribute( 'mention' ) ).to.have.property( '_uid' );
expect( textNode.getAttribute( 'mention' ) ).to.have.property( 'uid' );

const expectedView = '<p>foo <span class="mention" data-mention="@John">@John</span> bar</p>';

Expand All @@ -96,7 +96,7 @@ describe( 'MentionEditing', () => {
expect( textNode.hasAttribute( 'mention' ) ).to.be.true;
expect( textNode.getAttribute( 'mention' ) ).to.have.property( 'id', '@Ted Mosby' );
expect( textNode.getAttribute( 'mention' ) ).to.have.property( '_text', 'Ted Mosby' );
expect( textNode.getAttribute( 'mention' ) ).to.have.property( '_uid' );
expect( textNode.getAttribute( 'mention' ) ).to.have.property( 'uid' );

const expectedView = '<p>Hello <b class="mention" data-mention="@Ted Mosby">Ted Mosby</b></p>';

Expand All @@ -122,8 +122,8 @@ describe( 'MentionEditing', () => {
assertTextNode( paragraph.getChild( 0 ) );
assertTextNode( paragraph.getChild( 1 ) );

const firstMentionId = paragraph.getChild( 0 ).getAttribute( 'mention' )._uid;
const secondMentionId = paragraph.getChild( 1 ).getAttribute( 'mention' )._uid;
const firstMentionId = paragraph.getChild( 0 ).getAttribute( 'mention' ).uid;
const secondMentionId = paragraph.getChild( 1 ).getAttribute( 'mention' ).uid;

expect( firstMentionId ).to.not.equal( secondMentionId );

Expand All @@ -138,7 +138,7 @@ describe( 'MentionEditing', () => {
expect( textNode.hasAttribute( 'mention' ) ).to.be.true;
expect( textNode.getAttribute( 'mention' ) ).to.have.property( 'id', '@John' );
expect( textNode.getAttribute( 'mention' ) ).to.have.property( '_text', '@John' );
expect( textNode.getAttribute( 'mention' ) ).to.have.property( '_uid' );
expect( textNode.getAttribute( 'mention' ) ).to.have.property( 'uid' );
}
} );

Expand All @@ -151,7 +151,7 @@ describe( 'MentionEditing', () => {
expect( textNode.hasAttribute( 'mention' ) ).to.be.true;
expect( textNode.getAttribute( 'mention' ) ).to.have.property( 'id', '@John' );
expect( textNode.getAttribute( 'mention' ) ).to.have.property( '_text', '@Jo' );
expect( textNode.getAttribute( 'mention' ) ).to.have.property( '_uid' );
expect( textNode.getAttribute( 'mention' ) ).to.have.property( 'uid' );

const expectedView = '<p><span class="mention" data-mention="@John">@Jo</span></p>';

Expand Down Expand Up @@ -197,7 +197,10 @@ describe( 'MentionEditing', () => {
class: 'mention',
'data-mention': modelAttributeValue.id,
'href': modelAttributeValue.link
}, { id: modelAttributeValue._uid } );
}, {
priority: 20,
id: modelAttributeValue.uid
} );
},
converterPriority: 'high'
} );
Expand Down Expand Up @@ -312,7 +315,7 @@ describe( 'MentionEditing', () => {
expect( textNode.hasAttribute( 'mention' ) ).to.be.true;
expect( textNode.getAttribute( 'mention' ) ).to.have.property( 'id', '@John' );
expect( textNode.getAttribute( 'mention' ) ).to.have.property( '_text', '@John' );
expect( textNode.getAttribute( 'mention' ) ).to.have.property( '_uid' );
expect( textNode.getAttribute( 'mention' ) ).to.have.property( 'uid' );

model.change( writer => {
const paragraph = doc.getRoot().getChild( 0 );
Expand Down Expand Up @@ -677,7 +680,10 @@ function addCustomMentionConverters( editor ) {
return viewWriter.createAttributeElement( 'b', {
class: 'mention',
'data-mention': modelAttributeValue.id
}, { id: modelAttributeValue._uid } );
}, {
priority: 20,
id: modelAttributeValue.uid
} );
},
converterPriority: 'high'
} );
Expand Down
10 changes: 5 additions & 5 deletions packages/ckeditor5-mention/tests/mentionui.js
Original file line number Diff line number Diff line change
Expand Up @@ -621,7 +621,7 @@ describe( 'MentionUI', () => {
writer.createPositionAt( doc.getRoot().getChild( 0 ), 9 )
);

writer.setAttribute( 'mention', { id: '@Lily', _uid: 1234 }, range );
writer.setAttribute( 'mention', { id: '@Lily', uid: 1234 }, range );
} );

return waitForDebounce()
Expand Down Expand Up @@ -651,7 +651,7 @@ describe( 'MentionUI', () => {
writer.createPositionAt( doc.getRoot().getChild( 0 ), 9 )
);

writer.setAttribute( 'mention', { id: '@Lily', _uid: 1234 }, range );
writer.setAttribute( 'mention', { id: '@Lily', uid: 1234 }, range );
} );

return waitForDebounce()
Expand Down Expand Up @@ -725,7 +725,7 @@ describe( 'MentionUI', () => {
it( 'should not show panel when selection is after existing mention', () => {
setData( model, '<paragraph>foo [@Lily] bar[]</paragraph>' );
model.change( writer => {
writer.setAttribute( 'mention', { id: '@Lily', _uid: 1234 }, doc.selection.getFirstRange() );
writer.setAttribute( 'mention', { id: '@Lily', uid: 1234 }, doc.selection.getFirstRange() );
} );

return waitForDebounce()
Expand All @@ -750,7 +750,7 @@ describe( 'MentionUI', () => {
writer.createPositionAt( doc.getRoot().getChild( 0 ), 4 ),
writer.createPositionAt( doc.getRoot().getChild( 0 ), 9 )
);
writer.setAttribute( 'mention', { id: '@Lily', _uid: 1234 }, range );
writer.setAttribute( 'mention', { id: '@Lily', uid: 1234 }, range );
} );

return waitForDebounce()
Expand Down Expand Up @@ -1295,7 +1295,7 @@ describe( 'MentionUI', () => {
setData( model, '<paragraph>foo [#101] bar</paragraph><paragraph></paragraph>' );

model.change( writer => {
writer.setAttribute( 'mention', { id: '#101', _uid: 1234 }, doc.selection.getFirstRange() );
writer.setAttribute( 'mention', { id: '#101', uid: 1234 }, doc.selection.getFirstRange() );
} );

// Increase the response time to extend the debounce time out.
Expand Down

0 comments on commit 94a6952

Please sign in to comment.