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

Fixed mention plugin throwing errors when another ContextualBalloon is already visible #73

Merged
merged 4 commits into from
May 27, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@
"@ckeditor/ckeditor5-editor-classic": "^12.1.0",
"@ckeditor/ckeditor5-engine": "^13.1.0",
"@ckeditor/ckeditor5-font": "^11.1.0",
"@ckeditor/ckeditor5-link": "^11.0.1",
"@ckeditor/ckeditor5-paragraph": "^11.0.1",
"@ckeditor/ckeditor5-table": "^12.0.1",
"@ckeditor/ckeditor5-typing": "^12.0.1",
"@ckeditor/ckeditor5-undo": "^11.0.1",
"@ckeditor/ckeditor5-widget": "^11.0.1",
"eslint": "^5.5.0",
Expand Down
21 changes: 16 additions & 5 deletions src/mentionui.js
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,8 @@ export default class MentionUI extends Plugin {
const nodeBefore = focus.nodeBefore;

if ( hasMention || nodeBefore && nodeBefore.is( 'text' ) && nodeBefore.hasAttribute( 'mention' ) ) {
this._hideUIAndRemoveMarker();

return;
}

Expand Down Expand Up @@ -387,12 +389,13 @@ export default class MentionUI extends Plugin {
* @private
*/
_hideUIAndRemoveMarker() {
if ( this.editor.model.markers.has( 'mention' ) ) {
this.editor.model.change( writer => writer.removeMarker( 'mention' ) );
// Remove the mention view from balloon before removing marker - it is used by balloon position target().
if ( this._balloon.hasView( this._mentionsView ) ) {
this._balloon.remove( this._mentionsView );
}

if ( this._isUIVisible ) {
this._balloon.remove( this._mentionsView );
if ( this.editor.model.markers.has( 'mention' ) ) {
this.editor.model.change( writer => writer.removeMarker( 'mention' ) );
}

// Make the last matched position on panel view undefined so the #_getBalloonPanelPositionData() method will return all positions
Expand Down Expand Up @@ -447,14 +450,22 @@ export default class MentionUI extends Plugin {
* @private
*/
_getBalloonPanelPositionData( mentionMarker, preferredPosition ) {
const editor = this.editor;
const editing = this.editor.editing;
const domConverter = editing.view.domConverter;
const mapper = editing.mapper;

return {
target: () => {
const viewRange = mapper.toViewRange( mentionMarker.getRange() );
let modelRange = mentionMarker.getRange();

// Target the UI to the model selection range - the marker has been removed so probably the UI will not be shown anyway.
// The logic is used by ContextualBalloon to display another panel in the same place.
if ( modelRange.start.root.rootName == '$graveyard' ) {
modelRange = editor.model.document.selection.getFirstRange();
}

const viewRange = mapper.toViewRange( modelRange );
const rangeRects = Rect.getDomRangeRects( domConverter.viewRangeToDom( viewRange ) );

return rangeRects.pop();
Expand Down
120 changes: 119 additions & 1 deletion tests/mention-integration.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,25 @@
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/

/* global document */
/* global document, setTimeout */

import BlockQuote from '@ckeditor/ckeditor5-block-quote/src/blockquote';
import Clipboard from '@ckeditor/ckeditor5-clipboard/src/clipboard';
import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
import Table from '@ckeditor/ckeditor5-table/src/table';
import TableToolbar from '@ckeditor/ckeditor5-table/src/tabletoolbar';
import UndoEditing from '@ckeditor/ckeditor5-undo/src/undoediting';
import Link from '@ckeditor/ckeditor5-link/src/link';
import Delete from '@ckeditor/ckeditor5-typing/src/delete';
import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor';

import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
import { parse as parseView, getData as getViewData } from '@ckeditor/ckeditor5-engine/src/dev-utils/view';
import { setData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';

import MentionEditing from '../src/mentionediting';
import Mention from '../src/mention';
import MentionUI from '../src/mentionui';

describe( 'Mention feature - integration', () => {
let div, editor, model, doc;
Expand Down Expand Up @@ -208,4 +215,115 @@ describe( 'Mention feature - integration', () => {
.to.equal( expectedData );
} );
} );

describe( 'with table toolbar', () => {
beforeEach( () => {
return ClassicTestEditor
.create( div, {
plugins: [ Paragraph, Table, TableToolbar, Mention ],
table: {
contentToolbar: [ 'tableColumn', 'tableRow', 'mergeTableCells' ]
},
mention: {
feeds: [
{
marker: '@',
feed: [ '@Barney', '@Lily', '@Marshall', '@Robin', '@Ted' ]
}
]
}
} )
.then( newEditor => {
editor = newEditor;
model = editor.model;
doc = model.document;
} );
} );

it( 'should work with table toolbar', () => {
editor.ui.focusTracker.isFocused = true;

setData( model, '<table><tableRow><tableCell><paragraph>foo []</paragraph></tableCell></tableRow></table>' );

const balloon = editor.plugins.get( 'ContextualBalloon' );
const panelView = balloon.view;
const mentionUI = editor.plugins.get( MentionUI );
const mentionsView = mentionUI._mentionsView;

return new Promise( resolve => setTimeout( resolve, 200 ) )
.then( () => {
model.change( writer => {
writer.insertText( '@', doc.selection.getFirstPosition() );
} );
} )
.then( () => new Promise( resolve => setTimeout( resolve, 200 ) ) )
.then( () => {
expect( panelView.isVisible ).to.be.true;
expect( balloon.visibleView === mentionsView ).to.be.true;

model.change( writer => {
writer.setSelection( doc.getRoot().getNodeByPath( [ 0, 0, 0, 0 ] ), '1' );
} );

expect( panelView.isVisible ).to.be.true;
expect( balloon.visibleView === mentionsView ).to.be.false;
} );
} );
} );

describe( 'with link toolbar', () => {
beforeEach( () => {
return ClassicTestEditor
.create( div, {
plugins: [ Paragraph, Link, Delete, Mention ],
mention: {
feeds: [
{
marker: '@',
feed: [ '@Barney', '@Lily', '@Marshall', '@Robin', '@Ted' ]
}
]
}
} )
.then( newEditor => {
editor = newEditor;
model = editor.model;
doc = model.document;
} );
} );

it( 'should work with link toolbar', () => {
editor.ui.focusTracker.isFocused = true;

setData( model, '<paragraph>[]</paragraph>' );

const balloon = editor.plugins.get( 'ContextualBalloon' );
const panelView = balloon.view;
const mentionUI = editor.plugins.get( MentionUI );
const mentionsView = mentionUI._mentionsView;

return new Promise( resolve => setTimeout( resolve, 200 ) )
.then( () => {
// Show link UI
editor.execute( 'link', '@' );
editor.editing.view.document.fire( 'click' );

expect( panelView.isVisible ).to.be.true;
expect( balloon.visibleView === mentionsView ).to.be.false; // LinkUI

model.change( writer => {
writer.setSelection( doc.getRoot().getChild( 0 ), 'end' );
} );
} )
.then( () => new Promise( resolve => setTimeout( resolve, 200 ) ) )
.then( () => {
expect( panelView.isVisible ).to.be.true;
expect( balloon.visibleView === mentionsView ).to.be.true;

editor.execute( 'delete' );

expect( panelView.isVisible ).to.be.false;
} );
} );
} );
} );