MentionAtomExtension
Summary
This is the atom version of the MentionExtension
.
It provides mentions as atom nodes which don't support editing once being inserted into the document.
Usage
Installation
This extension is installed for you when you install the main remirror
package. The extension is provided by the @remirror/extension-mention-atom
package.
Using the setup from Getting started
, you have:
import 'remirror/styles/all.css';
import { EditorComponent, Remirror, useRemirror } from '@remirror/react';
const MyEditor: React.FC = () => {
const { manager, state } = useRemirror({
content: '<p>I love Remirror</p>',
selection: 'start',
stringHandler: 'html',
});
return (
<div className='remirror-theme'>
<Remirror manager={manager} initialContent={state}>
<EditorComponent />
</Remirror>
</div>
);
};
Add the mentionAtomExtension and create a component to use for the suggestion popup:
import 'remirror/styles/all.css';
import { useEffect, useState } from 'react';
import { MentionAtomExtension } from 'remirror/extensions';
import { cx } from '@remirror/core';
import {
EditorComponent,
FloatingWrapper,
MentionAtomNodeAttributes,
Remirror,
useMentionAtom,
useRemirror,
} from '@remirror/react';
const ALL_USERS = [
{ id: 'joe', label: 'Joe' },
{ id: 'sue', label: 'Sue' },
{ id: 'pat', label: 'Pat' },
{ id: 'tom', label: 'Tom' },
{ id: 'jim', label: 'Jim' },
];
const MentionSuggestor: React.FC = () => {
const [options, setOptions] = useState<MentionAtomNodeAttributes[]>([]);
const { state, getMenuProps, getItemProps, indexIsHovered, indexIsSelected } = useMentionAtom({
items: options,
});
useEffect(() => {
if (!state) {
return;
}
const searchTerm = state.query.full.toLowerCase();
const filteredOptions = ALL_USERS.filter((user) =>
user.label.toLowerCase().includes(searchTerm),
)
.sort()
.slice(0, 5);
setOptions(filteredOptions);
}, [state]);
const enabled = Boolean(state);
return (
<FloatingWrapper positioner='cursor' enabled={enabled} placement='bottom-start'>
<div {...getMenuProps()} className='suggestions'>
{enabled &&
options.map((user, index) => {
const isHighlighted = indexIsSelected(index);
const isHovered = indexIsHovered(index);
return (
<div
key={user.id}
className={cx('suggestion', isHighlighted && 'highlighted', isHovered && 'hovered')}
{...getItemProps({
item: user,
index,
})}
>
{user.label}
</div>
);
})}
</div>
</FloatingWrapper>
);
};
const MyEditor: React.FC = () => {
const { manager, state } = useRemirror({
extensions: () => [
new MentionAtomExtension({
matchers: [{ name: 'at', char: '@', appendText: ' ' }],
}),
],
content: '<p>I love Remirror</p>',
selection: 'start',
stringHandler: 'html',
});
return (
<div className='remirror-theme'>
<Remirror manager={manager} initialContent={state}>
<EditorComponent />
<MentionSuggestor />
</Remirror>
</div>
);
};
You can add additional machers, for example hashtags:
const ALL_TAGS = [
{ id: 'cel', label: 'Celebrity' },
{ id: 'ed', label: 'Education' },
{ id: 'tech', label: 'Tech' },
];
new MentionAtomExtension({
matchers: [
{ name: 'at', char: '@', appendText: ' ' },
{ name: 'tag', char: '#', appendText: ' ' },
],
});
Update useEffect
in MentionSuggestor
component:
useEffect(() => {
if (!state) {
return;
}
const searchTerm = state.query.full.toLowerCase();
let filteredOptions: MentionAtomNodeAttributes[] = [];
if (state.name === 'tag') {
filteredOptions = ALL_TAGS.filter((tag) => tag.label.toLowerCase().includes(searchTerm));
} else if (state.name === 'at') {
filteredOptions = ALL_USERS.filter((user) => user.label.toLowerCase().includes(searchTerm));
}
filteredOptions = filteredOptions.sort().slice(0, 5);
setOptions(filteredOptions);
}, [state]);
Examples
Source code
import React, { useCallback } from 'react';
import { useHelpers, useRemirrorContext } from '@remirror/react';
import { SocialEditor } from '@remirror/react-editors/social';
const ALL_USERS = [
{ id: 'joe', label: 'Joe' },
{ id: 'sue', label: 'Sue' },
{ id: 'pat', label: 'Pat' },
{ id: 'tom', label: 'Tom' },
{ id: 'jim', label: 'Jim' },
];
const TAGS = ['editor', 'remirror', 'opensource', 'prosemirror'];
const SAMPLE_DOC = {
type: 'doc',
content: [
{
type: 'paragraph',
attrs: { dir: null, ignoreBidiAutoUpdate: null },
content: [{ type: 'text', text: 'Loaded content' }],
},
],
};
function LoadButton() {
const { setContent } = useRemirrorContext();
const handleClick = useCallback(() => setContent(SAMPLE_DOC), [setContent]);
return (
<button onMouseDown={(event) => event.preventDefault()} onClick={handleClick}>
Load
</button>
);
}
function SaveButton() {
const { getJSON } = useHelpers();
const handleClick = useCallback(() => alert(JSON.stringify(getJSON())), [getJSON]);
return (
<button onMouseDown={(event) => event.preventDefault()} onClick={handleClick}>
Save
</button>
);
}
const Basic: React.FC = () => (
<SocialEditor placeholder='Mention @joe or add #remirror' users={ALL_USERS} tags={TAGS}>
<LoadButton />
<SaveButton />
</SocialEditor>
);
export default Basic;
See storybook for examples.