I wanted to share a new library that I just released called prosemirror-autocomplete, which adds triggers for #hashtags
, @mentions
, /menus
, and other more complex autocompletions. The prosemirror-autocomplete
library can be used to create suggestions similar to Notion, Google Docs, or Confluence; it is created and used by Curvenote. The library does not provide a user interface beyond the demo code.
Live Demo on Github
There are a few related projects, prosemirror-suggestions, prosemirror-mentions, prosemirror-suggest being the ones I looked at and tried using before creating this library.
prosemirror-suggestions
is similar in that it does not provide a UI, if you want a simple UI out of the box you can look at prosemirror-mentions
. All three of the above libraries trigger based on RegExp and leave the decorations in the state. This is similar to how Twitter works, but is undesirable in writing longer documents where you want to dismiss the suggestions with an escape and not see them again in that area.
For example, think of writing an email@address.com – this should not trigger an @mention, or should be dismissed by the user at most one time! This is not how Twitter or the plugins above work, but it is how an IDE like VS Code works and it is what prosemirror-autocomplete
is modelled after. I think this library is unique in that there are two plugins returned by autocomplete(opts)
:
- a decoration plugin that wraps the trigger and filter text (e.g.
[@][mention]
); and - an
InputRule
plugin that has a series of triggers that are defined in the options.
This allows you to create and dismiss the autocomplete decoration through various input rules.
Example Code
A quick look at how to use prosemirror-autocomplete
:
import autocomplete, { Options, Arrow } from 'prosemirror-autocomplete';
const options: Options = {
triggers: [
{ name: 'hashtag', trigger: '#' },
{ name: 'variable', trigger: /((?:^[a-zA-Z0-9_]+)\s?=)$/, cancelOnFirstSpace: false },
],
onOpen: ({ view, range, trigger, type }) => handleOpen(),
onClose: ({ view }) => handleClose(),
onFilter: ({ view, filter }) => handleFilter(),
onArrow: ({ view, kind }) => handleArrow(kind),
onSelect: ({ view }) => handleSelect(),
// use `reducer` to handle these in a single function!
};
const view = new EditorView(editor, {
state: EditorState.create({
doc: DOMParser.fromSchema(schema).parse(content),
plugins: [...autocomplete(options), ...otherPlugins],
}),
});
MIT License, typescript, contributions welcome! Let me know what you think!