Extras
Mentions
Add @mentions with the Mention extension and mentionMenu, store checked ids, and verify every mention again on your server.
Mentions let a writer type @ and pick a person, a team or any other entry from a list you supply. The editor stores an opaque id and a plain-text label. Your server decides who the id really is, who may be mentioned and who gets notified.
Mentions ship as npm packages only. The one-script build does not include them.
Set it up
- Install the extension and the UI:
npm install @viveeditor/ext-mention @viveeditor/ui @viveeditor/core. - Add
Mentionto the editor’s extensions. - Add
mentionMenufrom@viveeditor/ui/extrasto thepluginsofcreateUI, with anitemsfunction that returns matching entries.
import { Editor } from '@viveeditor/core'
import { basicExtensions } from '@viveeditor/ext-basic'
import { Mention } from '@viveeditor/ext-mention'
import { createUI } from '@viveeditor/ui'
import { mentionMenu } from '@viveeditor/ui/extras'
import '@viveeditor/ext-mention/style.css'
const editor = await Editor.create(document.querySelector('#editor'), {
extensions: [...basicExtensions, Mention],
})
createUI(editor, {
plugins: [
mentionMenu({
items: async (query, signal) => {
const response = await fetch(`/mentions?q=${encodeURIComponent(query)}`, { signal })
if (!response.ok) return []
return response.json()
},
}),
],
})
/mentions stands for your own search endpoint. Each entry you return has an id, a label and an optional description.
How the menu behaves
- The menu opens when the writer types
@at the start of a line or after a space. The query is up to 32 letters, digits,_,.or-. - Your
itemsfunction runs 150 ms after the last keystroke. When the writer keeps typing or closes the menu, thesignalis aborted, so pass it tofetch. - The menu shows at most 8 entries, as plain text. Your labels are never read as HTML.
- Enter or Tab picks an entry and Esc closes the menu.
You can also insert a mention from code with editor.commands.insertMention({ id: 'u_42', label: 'Editor' }).
What is stored
A mention is saved as one span:
<span class="vive-mention" data-mention="u_42">@Editor</span>
The id must match ^[A-Za-z0-9_][A-Za-z0-9_.:@-]{0,127}$ and must not contain ... The pattern is exported as MENTION_ID_PATTERN, and isMentionId() tests a value against it. The editor checks the id when it reads HTML, when it reads JSON and when it writes HTML. A span whose id fails the check is not read as a mention.
The label loses control, bidi and zero-width characters and is capped at 100 characters. The maxLabelLength option of Mention changes the cap. An id never becomes a link address.
Check mentions on the server
The browser is not a security boundary. Anyone can post a span with any id and any label. When you save the content:
- clean the HTML with the server sanitizer. It keeps mentions in exactly the form above and reduces a mention with a bad id, or one placed inside a link or a footnote, to its text;
- look every id up in your own directory and drop the ones that do not exist;
- check that the writer may mention that person or group;
- build the displayed name from your own data, not from the stored label;
- keep ids opaque. Do not use e-mail addresses or other personal data as ids.
The search endpoint behind items needs authentication, a rate limit and tenant scoping, so a writer only finds people they are allowed to see.
Notifications
viveEditor sends no notifications. If you notify people who were mentioned:
- compare the ids before and after an edit and notify only new mentions;
- deduplicate, so one person mentioned five times gets one notice;
- rate-limit the notices you send per writer.
Related pages
Where does the list of people come from?
From your own items function. viveEditor has no user directory; it calls your function with the typed text and an AbortSignal and shows what you return.
Can I trust the name stored in a mention?
No. The label is whatever the writer's browser sent. Look the id up in your own directory and build the displayed name from your data.