Преглед на файлове

Add basic itemRenderer support for mentions.

Maciej Gołaszewski преди 6 години
родител
ревизия
fc0eef5723

+ 53 - 20
packages/ckeditor5-mention/src/mentionui.js

@@ -15,6 +15,7 @@ import BalloonPanelView from '@ckeditor/ckeditor5-ui/src/panel/balloon/balloonpa
 import Rect from '@ckeditor/ckeditor5-utils/src/dom/rect';
 import MentionsView from './ui/mentionsview';
 import TextWatcher from './textwatcher';
+import View from '@ckeditor/ckeditor5-ui/src/view';
 
 /**
  * The mention ui feature.
@@ -49,20 +50,21 @@ export default class MentionUI extends Plugin {
 
 		this._createMentionView( editor );
 
-		this._watchers = new Map();
-		this._feeds = new Map();
+		this._mentionsConfigurations = new Map();
 
 		const config = this.editor.config.get( 'mention' );
 
 		for ( const mentionDescription of config ) {
 			const feed = mentionDescription.feed;
 
+			const marker = mentionDescription.marker || '@';
 			const feedCallback = typeof feed == 'function' ? feed : createFeedCallback( feed );
+			const watcher = this._addTextWatcher( marker );
+			const itemRenderer = mentionDescription.itemRenderer;
 
-			const marker = mentionDescription.marker || '@';
+			const definition = { watcher, marker, feedCallback, itemRenderer };
 
-			this._addTextWatcher( marker );
-			this._addFeed( marker, feedCallback );
+			this._mentionsConfigurations.set( marker, definition );
 		}
 	}
 
@@ -73,22 +75,32 @@ export default class MentionUI extends Plugin {
 
 		this.panelView.content.add( this._mentionsView );
 
+		// TODO this is huge rewrite:
 		this._mentionsView.listView.items.bindTo( this._items ).using( data => {
+			// itemRenderer
 			const { item, marker } = data;
 			const { label } = item;
 
+			const renderer = this._getItemRenderer( marker );
+
 			const listItemView = new ListItemView( editor.locale );
-			const buttonView = new ButtonView( editor.locale );
 
-			// TODO: might be better - pass as params.
-			buttonView.label = label;
-			buttonView.withText = true;
-			buttonView.item = item;
-			buttonView.marker = marker;
+			if ( renderer ) {
+				const domNode = renderer( item );
 
-			listItemView.children.add( buttonView );
+				listItemView.children.add( new DomWrapperView( editor.locale, domNode ) );
+			} else {
+				const buttonView = new ButtonView( editor.locale );
 
-			buttonView.delegate( 'execute' ).to( this._mentionsView );
+				buttonView.label = label;
+				buttonView.withText = true;
+				buttonView.item = item;
+				buttonView.marker = marker;
+
+				listItemView.children.add( buttonView );
+
+				buttonView.delegate( 'execute' ).to( this._mentionsView );
+			}
 
 			return listItemView;
 		} );
@@ -124,12 +136,16 @@ export default class MentionUI extends Plugin {
 		} );
 	}
 
-	_addFeed( marker, callback ) {
-		this._feeds.set( marker, callback );
+	_getItemRenderer( marker ) {
+		const { itemRenderer } = this._mentionsConfigurations.get( marker );
+
+		return itemRenderer;
 	}
 
 	_getFeed( marker, feedText ) {
-		return this._feeds.get( marker )( feedText );
+		const { feedCallback } = this._mentionsConfigurations.get( marker );
+
+		return feedCallback( feedText );
 	}
 
 	_addTextWatcher( marker ) {
@@ -137,8 +153,6 @@ export default class MentionUI extends Plugin {
 
 		const watcher = new TextWatcher( editor, createTestCallback( marker ), createTextMatcher( marker ) );
 
-		this._watchers.set( marker, watcher );
-
 		watcher.on( 'matched', ( evt, data ) => {
 			const matched = data.matched;
 
@@ -151,7 +165,7 @@ export default class MentionUI extends Plugin {
 					this._items.clear();
 
 					for ( const label of feed ) {
-						const item = { label };
+						const item = typeof label != 'object' ? { label } : label;
 
 						this._items.add( { item, marker } );
 					}
@@ -168,10 +182,14 @@ export default class MentionUI extends Plugin {
 		watcher.on( 'unmatched', () => {
 			this._hidePanel();
 		} );
+
+		return watcher;
 	}
 
 	_getWatcher( marker ) {
-		return this._watchers.get( marker );
+		const { watcher } = this._mentionsConfigurations.get( marker );
+
+		return watcher;
 	}
 
 	_showPanel() {
@@ -253,3 +271,18 @@ function createFeedCallback( feedItems ) {
 		return Promise.resolve( filteredItems );
 	};
 }
+
+class DomWrapperView extends View {
+	constructor( locale, domNode ) {
+		super( locale );
+
+		this.template = false;
+		this.domNode = domNode;
+	}
+
+	render() {
+		super.render();
+
+		this.element = this.domNode;
+	}
+}

+ 10 - 0
packages/ckeditor5-mention/tests/manual/mention-custom-renderer.html

@@ -0,0 +1,10 @@
+<div id="editor">
+	<p>Hello <span class="mention" data-mention="Ted">@Ted</span>.</p>
+</div>
+
+<style>
+	.mention {
+		background: hsla(341, 100%, 87%, 0.4);
+		color: hsl(340, 100%, 31%);
+	}
+</style>

+ 56 - 0
packages/ckeditor5-mention/tests/manual/mention-custom-renderer.js

@@ -0,0 +1,56 @@
+/**
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* global console, window */
+
+import global from '@ckeditor/ckeditor5-utils/src/dom/global';
+
+import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
+import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold';
+import Enter from '@ckeditor/ckeditor5-enter/src/enter';
+import Heading from '@ckeditor/ckeditor5-heading/src/heading';
+import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
+import Typing from '@ckeditor/ckeditor5-typing/src/typing';
+import Undo from '@ckeditor/ckeditor5-undo/src/undo';
+import Widget from '@ckeditor/ckeditor5-widget/src/widget';
+import Clipboard from '@ckeditor/ckeditor5-clipboard/src/clipboard';
+import ShiftEnter from '@ckeditor/ckeditor5-enter/src/shiftenter';
+import Table from '@ckeditor/ckeditor5-table/src/table';
+import Mention from '../../src/mention';
+import Link from '@ckeditor/ckeditor5-link/src/link';
+import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic';
+import Underline from '@ckeditor/ckeditor5-basic-styles/src/underline';
+
+ClassicEditor
+	.create( global.document.querySelector( '#editor' ), {
+		plugins: [ Enter, Typing, Paragraph, Heading, Link, Bold, Italic, Underline, Undo, Clipboard, Widget, ShiftEnter, Table, Mention ],
+		toolbar: [ 'heading', '|', 'bold', 'italic', 'underline', 'link', '|', 'insertTable', '|', 'undo', 'redo' ],
+		mention: [
+			{
+				feed: feedText => Promise.resolve( [
+					{ id: '1', label: 'Barney' },
+					{ id: '2', label: 'Lily' },
+					{ id: '3', label: 'Marshall' },
+					{ id: '4', label: 'Robin' },
+					{ id: '5', label: 'Ted' }
+				].filter( item => item.label.toLowerCase().includes( feedText ) ) ),
+				itemRenderer: item => {
+					const span = global.document.createElementNS( 'http://www.w3.org/1999/xhtml', 'span' );
+
+					span.innerHTML = `<span id="${ item.id }">@${ item.label }</span>`;
+
+					console.log( 'rendered node: ', span, item );
+
+					return span;
+				}
+			}
+		]
+	} )
+	.then( editor => {
+		window.editor = editor;
+	} )
+	.catch( err => {
+		console.error( err.stack );
+	} );

+ 1 - 0
packages/ckeditor5-mention/tests/manual/mention-custom-renderer.md

@@ -0,0 +1 @@
+### Mention

+ 41 - 0
packages/ckeditor5-mention/tests/mentionui.js

@@ -11,6 +11,7 @@ import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
 import BalloonPanelView from '@ckeditor/ckeditor5-ui/src/panel/balloon/balloonpanelview';
 
 import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
+import global from '@ckeditor/ckeditor5-utils/src/dom/global';
 
 import MentionUI from '../src/mentionui';
 import MentionEditing from '../src/mentionediting';
@@ -250,6 +251,46 @@ describe( 'BalloonToolbar', () => {
 		} );
 	} );
 
+	describe( 'itemRenderer', () => {
+		beforeEach( () => {
+			const issues = [
+				{ id: '1002', title: 'Some bug in editor.' },
+				{ id: '1003', title: 'Introduce this feature.' },
+				{ id: '1004', title: 'Missing docs.' }
+			];
+
+			return createClassicTestEditor( [
+				{
+					marker: '#',
+					feed: feedText => {
+						return Promise.resolve( issues.filter( issue => issue.id.includes( feedText ) ) );
+					},
+					itemRenderer: item => {
+						const span = global.document.createElementNS( 'http://www.w3.org/1999/xhtml', 'span' );
+
+						span.innerHTML = `<span id="${ item.id }">@${ item.title }</span>`;
+
+						return span;
+					}
+				}
+			] );
+		} );
+
+		it( 'should show panel for matched marker', () => {
+			setData( model, '<paragraph>foo []</paragraph>' );
+
+			model.change( writer => {
+				writer.insertText( '#', doc.selection.getFirstPosition() );
+			} );
+
+			return waitForDebounce()
+				.then( () => {
+					expect( mentionUI.panelView.isVisible ).to.be.true;
+					expect( mentionUI._mentionsView.listView.items ).to.have.length( 3 );
+				} );
+		} );
+	} );
+
 	describe( 'execute', () => {
 		beforeEach( () => createClassicTestEditor( staticConfig ) );