Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce autocompleting (mentions) #998

Closed
wwalc opened this issue Apr 24, 2018 · 23 comments
Closed

Introduce autocompleting (mentions) #998

wwalc opened this issue Apr 24, 2018 · 23 comments
Assignees
Labels
type:feature This issue reports a feature request (an idea for a new functionality or a missing option).
Milestone

Comments

@wwalc
Copy link
Member

wwalc commented Apr 24, 2018

Almost every simple editor that we use in real life comes with some kind of autocomplete that helps a lot. For example I cannot imagine not being able to write messages on GitHub without being able to mention someone nickname, same on Slack with mentioning for example channels, nicknames, including emojis etc.

This issue is created to track the interest, use cases and ideas before we start working on it.

@Reinmar Reinmar added type:feature This issue reports a feature request (an idea for a new functionality or a missing option). status:confirmed labels Apr 25, 2018
@Reinmar Reinmar added this to the next milestone Apr 25, 2018
@nishammahsin
Copy link

What is the status of this feature.?

@Reinmar
Copy link
Member

Reinmar commented Jun 6, 2018

We'd love to work on it but we don't have the capacity at the moment and I'm not sure now when we'll be able to start working on it.

@nishammahsin
Copy link

@Reinmar
can you give me some heads up to implement this .what are the components/APIs may required to build this feature?

@Jetski5822
Copy link

Was wondering the same, any chance of just updating the hackathon? I have tried but my JS is lowsy

@Reinmar
Copy link
Member

Reinmar commented Jun 30, 2018

We held that hackathon ~2 years ago and since then the API changed drastically, so, unfortunately, it's more about writing it from scratch.

Anyway, I'll know more about our plans next week, so 🤞 :)

@Jetski5822
Copy link

Please!! - i guess I could fallback to V4, that has mentions? Right?

@Reinmar
Copy link
Member

Reinmar commented Jul 2, 2018

CKE4 will have the autocomplete feature tomorrow (4.10.0 is scheduled for tomorrow and this feature will be a part of it). So, yes, if you need autocomplete today, CKE4 would be the easiest option.

@crisdeoliveira
Copy link

Hei @Reinmar, here in Brazil we are also waiting for the feature also. Please, think fondly and convince your team :)

@Jetski5822
Copy link

Any word on this feature???

@ThatCheck
Copy link

Any update on this ?

@Reinmar
Copy link
Member

Reinmar commented Jan 29, 2019

It's in the roadmap for Q1 this year. We'll start working on this soon.

@jcaruso001
Copy link

There was a hackathon if your interested for this request if you want to go that route.

Autocompletion (suggestions)
Feature name: autocomplete
Issue: ckeditor/ckeditor5-hackathon#6
Code: https://github.com/ckeditor/ckeditor5-hackathon/compare/autocomplete

@Reinmar Reinmar modified the milestones: next, iteration 23 Mar 1, 2019
@3alampro
Copy link

3alampro commented Mar 2, 2019

this is wonderful feature to have in ck5 hopefully it will be flexible

can we have configurable different endpoint for different usage

  1. # will sent request for autocompleting tags
  2. @ will sent request for autocompleting users
  3. : will sent request for autocompleting emoji

@jodator
Copy link
Contributor

jodator commented Mar 6, 2019

Bootstrapping Mentions

General idea

I think that we leans toward implementing Mentions plugin + extensive guide on how to implement similar features using CKE5 API.

CKEditor 4 comparison

The CKEditor 4 some time ago introduced constellation of plugins of similar feature set;

  1. The Mentions plugin (demo) - implementation of Autocomplete plugin
  2. The Autocomplete plugin (guide base plugin that provides smart, context-aware completion feature for custom text matches based on user input.
  3. Text Watcher plugin - Checks whether an editor’s text change matches the chosen criteria
  4. Text Match plugin - Allows to search CKEDITOR.dom.range for matching text.

For the mentions MVP we need:

  1. Mentions plugin (provides high-level API/configuration)
  2. Text Watcher plugin (might be used in other feature like autoformat)
  3. Set of utils to create required functionality (ie text match plugin is not required IMO)

High level API:

Mentions plugin configuration

CKEditor 4 has 10 config options - which is too much for CKE5:

  1. Looks like essential:
    • feed- required to work - log/throw if not configured.
    • minChars - optional (default 0). Maybe rename to just chars?
    • marker - optional (default '@').
  2. Less essential (we might add them with good defaults)
    • caseSensitive - might be left to implementation (default to case insensitive)
    • itemsLimit - might be implementation detail - default display all matching with scrolls in UI
  3. Might be left as implementation details:
    • cache - left this to developer. This is bound to pattern
    • pattern
    • throttle - use default
  4. Drop
    • itemTemplate - provide API for overriding list item
    • outputTemplate - don't implement in MVP

I'd go with minimal set of requirements as we're going to expose building blocks + awesome guide :).

Examples:

// minimal:
ClassicEditor.create( '#editor', {
mentions: {
		feed: [ 'Casper', 'Mathew', 'Peter', 'Symon', 'Victor' ],
	}
} );

// async:
const mentionsConfig = {
	feed: ( text ) => {
		const filteredNames = [ 'Casper', 'Mathew', 'Peter', 'Symon', 'Victor' ]
			.filter( n => n.toLowerCase().startsWith( text ) );

		// Might be with setTimeout() ;)
		return Promise.resolve( filteredNames );
	},
	chars: 2
}

// multiple configurations:
const mentionsConfig = [
	{ feed: [ 'Casper', 'Mathew', 'Peter', 'Symon', 'Victor' ] },
	{ marker: '#', feed: ( text ) => fetchTicketIdsFromServer( text ), chars: 2 },
	{ marker: ':', feed: ( text ) => getEmojis( text ) }
]

Model-View and output customization

Model:

<paragraph>Foo <mention>@jodator</mention> bar</paragraph>

Default view:

<p>Foo <span class="ck-mention">@jodator</span> bar</p>

The mention is only inserted when user accepts it from list (more on this later in UI/UX).

Extensibility vs widget requirements

We do not have templates as in CKE 4 and I don't see them required for CKE5. We can tell people to override default converter ie:

ClassicEditor.create( '#editor', {
	mentions: {
		feed: [ 'foo', 'bar', 'baz' ]
	},
	extraPlugins: [ editor => {
		// Plugin that overrides conversion
		editor.conversion.elementToElement( 'mention', {
			view: {
				name: 'span',
				classes: 'ck-mention'
			},
			model: 'mention',
			converterPriority: 'high'
		} );
	} ]
} );

But then the returned thingy might loose widget behavior. In order to make proper separation we could go either way:

  1. Two-level model (outer - widget, inner - customizable) - less appealing.
  2. One level model (either tell devs to add toWidget() or make downcast converter "smart")
    • "smart" converter:
      • editing downcast: I think that you could check if item was consumed and then add toWidget() on already converter elements. -> POC
      • data downcast: will not be called if item was consumed
      • showcase
    • document the toWidget() requirement - might be harder then "smart" converter because I don't know ATM if we're going to need something more then toWidget()

POC in action (default sample produces <placeholder> in the view:

selection_219

UI/UX

  1. Trigger: after typing space + marker + char*minChars.
  2. Display: a panel with list view of text
  3. Select a mention:
    a) Type to further limit elements in dropdown - eventually limit to 1 or 0 elements
    b) Highlight first element
    c) Allow to move highlight with cursor (focus in the dropdown, cycle through options)
  4. Confirm selected (if 1 or more) by pressing tab, enter or just space (any other charcter if 100% match)
  5. Optional: create mention on partial match (ie typed @jodato and matched jodator so leave "partial match").
  6. Don't create mention when no match - leave plain text.
  7. Plain text while typing.

Partial match in Slack:
selection_220

Stubs of API / plugin implementation:

Bunch of API usage ideas gathered in code - just to showcase ideas of bundling stuff together. It does not show the Mentions plugin (ie missing reading configuration) but rather a stub plugin for a guide.

class MyMentions extends Plugin {
	init() {
		const editor = this.editor;

		const watcher = editor.plugins.get( 'TextWatcher' );

		const observable = watcher.watch( /foo/ );

		observable.on( 'match', ( evt, data ) => {
			if ( !this.isWatching ) {
				this.startWatching();
			}

			const visibleMentions = getMentions( data.text );

			updateItems( this._items, visibleMentions, this.getItemCreator() );
		} );

		observable.on( 'unmatch', () => this.stopWatching() );
	}

	getMentions( text ) {
		const models = [ 'peter', 'matthiew', 'marco', 'symon' ]
			.filter( name => name.startsWith( text ) )
			.map( name => new Model( { label: text } ) );

		// Might be stored also...
		return new Collection( models );
	}

	// Also provide API for extending / overriding.
	getItemCreator() {
		// The default would just return ListItemView
		return item => {
			const listItemView = new ListItemView( locale );

			const buttonView = new ButtonView( locale );

			// Bind all model properties to the button view.
			buttonView.bind( ...Object.keys( model ) ).to( model );
			buttonView.delegate( 'execute' ).to( listItemView );

			listItemView.children.add( buttonView );

			return listItemView;
		};
	}

	startWatching() {
		this._items = new Collection();

		const listView = createListView(); // util, might be similar to dropdown utils
		// or this._window.show(); if already present.

		listView.items.bindTo( items );

		// dunno the name...
		this._window = showSelectionToolWindow( listView ); // util
	}

	stopWatching( ) {
		this._window.hide(); // or destroy
	}
}

Bonus:

Possible problems (as with CKE 4 from @jacekbogdanski ):

  • fast typing duplicates text (might be solved by LiveRange/Markers)
  • loading indicator for async feeds
  • ZWS in Chrome (we operate on model so also shouldn't be a problem)

@scofalik
Copy link
Contributor

scofalik commented Mar 6, 2019

It would be good to clarify what is a part of autocomplete feature and what is already mentions (a feature using autocomplete or autocomplete implementation/configuration). Do you want to expose things like TextWatcher? I'd focus on making this feature easy to use.

I am not sure if I like having a (finished) mention as a widget. It doesn't feel right for some reason, I am not sure about UX around that. I guess I'd have to try, but I think no-one does it like that. Remember that if in a widget, that text will not be editable, there will be some weirdness with the selection, etc. I think that a mention should still be a plain-text but with some styling.

I think that it will be important to provide a way for people to hook their own callbacks when a mention gets selected (finished) and also when it was removed (undo, backspace). This way people will be able to implement their own integrations (for example, sending an e-mail when someone is mentioned). I think it is crucial to have it done in a simple way - by passing a callback function in the config. I'd go with that in MVP, we already are being asked about things like this.

@jodator
Copy link
Contributor

jodator commented Mar 6, 2019

I am not sure if I like having a (finished) mention as a widget. It doesn't feel right for some reason, I am not sure about UX around that. I guess I'd have to try, but I think no-one does it like that

Check how slack mentions are implemented - it might be an editable widget after all. But editing it would break the mention. Anyway the inserted mention is something to discuss on UI/UX part. But I don't like that inserted mention would loose a meaning - ie it would be converted down to a general link is not good IMO.

This way people will be able to implement their own integrations (for example, sending an e-mail when someone is mentioned). I think it is crucial to have it done in a simple way - by passing a callback function in the config.

That's a good point. I thought that one could listen to selection change event and check the selected element so no configuration would be necessary. (also a good thing to add to a guide.

@scofalik
Copy link
Contributor

scofalik commented Mar 6, 2019

I thought that one could listen to selection change event and check the selected element so no configuration would be necessary.

This part of the feature is probably doable with some weird hacky way outside of the feature, but why make it difficult for 3rd party integrators if we already have all the parts in the feature, we just need a nice way to expose them :).

Check how slack mentions are implemented - it might be an editable widget after all.

All I meant is that I think that not having it converted to a widget might be a better UX. To be checked and discussed.

@jodator
Copy link
Contributor

jodator commented Mar 7, 2019

To sum up some F2F talks. So for the mentions MVP:

The default implementation without any customization will support string-like data:

ClassicEditor.create( '#editor' , {
	mentions: [
		{
			marker: '@',
			feed: [ 'foo', 'bar', 'baz' ],
		}
	]
} );

The customization will allow to change item template in the UI as well as the rendered node.

ClassicEditor.create( 'foo', {
	mentions: [
		{
			marker: '@',
			feed: ( match ) => {
				return [
					{ id: 1410, name: 'John' },
					{ id: 966, name: 'Johnatan' }
				];
			},
			itemRenderer: ( item ) => {
				// Control how the the UI list items are rendered:
				return createDomFromHtml( `<div class="item">${ item.name }</div>` );
			}
		}
	]
} );

The default converter will insert span:

editor.model.change( writer => {
	const label = item.name || item; // The name being the default.

	writer.insertText( label, { mention: item }, editor.model.selection.getFirstPosition() );
} );

The created mention will be stored in the model as text node attribute:

	<$text mention="John">John</$text>

	<span class=mention data-mention="John">John</span>

Alternative (extension) to be tested in a manual test:

	<$text mention="{id:1345,name:John Watch}">John Watch</$text>
	<span class=mention data-mention="1234">John Watch</span>

Other requirements:

  • Any change to a text node which has the mention attribute, should remove the mention attribute from the entire text node.
  • Selection post-fixer which removes the mention attribute from the selection when the selection is at the end of <$text mention>

Stub for UI talks. The items in the autocomplete lists will be dipslayed using list view:

<ul class=ck-list-panel>
	<li class=ck-list-item ck-mention-item ck-item-selected>
		// <- itemRender( item )
	</li>
</ul>

The MVP should allow us to check if the approach is OK and easily extensible and:

  • using text attributes is OK
  • no widget in the view is OK
  • how the utils from my previous summary post are constructed and
  • if they are usable

@scofalik
Copy link
Contributor

scofalik commented Mar 7, 2019

No callbacks on mention create / remove in MVP? :(

@kevinlaw91
Copy link

Sometimes we would prefer to display full name instead of user name or id just like in Facebook.
It will be helpful if we can store id when choosing an option from the autocomplete list and formatted (displayed) as full name.

Reinmar added a commit to ckeditor/ckeditor5-mention that referenced this issue Mar 27, 2019
@Reinmar
Copy link
Member

Reinmar commented Apr 5, 2019

Sometimes we would prefer to display full name instead of user name or id just like in Facebook.
It will be helpful if we can store id when choosing an option from the autocomplete list and formatted (displayed) as full name.

The feature supports defining what text should be inserted into the editor and what's saved in the data attribute. The output looks like this:

<span class="mention" data-mention="@someId">Someone's name</span>

It's also possible (and covered by the documentation) how to completely customize the element that's being rendered.

@wollo
Copy link

wollo commented Feb 25, 2020

I would like to use this plugin as a kind of suggestion tool, while entereing some expression.
e.g. the user is selecting a database table.
But currently I have the following issues (I nuse $ as a trigger).

When I start typing something like $Database.ta
The sugesstion list dissappears after entering the dot ('.')

Also the trigger is not working when I type =$. I have to type a blank after the =

Any chance these limiting characters - I suppose they are a kind of limiting caharacters- can be configured. Or can I use a configurable regex for this, like in some other editors, I evaluated.
But I would prefer to use ckeditor for various reasons

@yekanchi
Copy link

yekanchi commented Mar 9, 2021

I would like to use this plugin as a kind of suggestion tool, while entereing some expression.
e.g. the user is selecting a database table.
But currently I have the following issues (I nuse $ as a trigger).

When I start typing something like $Database.ta
The sugesstion list dissappears after entering the dot ('.')

Also the trigger is not working when I type =$. I have to type a blank after the =

Any chance these limiting characters - I suppose they are a kind of limiting caharacters- can be configured. Or can I use a configurable regex for this, like in some other editors, I evaluated.
But I would prefer to use ckeditor for various reasons

did you found any solution for this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type:feature This issue reports a feature request (an idea for a new functionality or a missing option).
Projects
None yet
Development

No branches or pull requests