EntityReferenceExtension
Summary
This extension allows to reference external entities in the content of your editor.
Use cases
This extension enables use cases like:
- Comment content in the editor: A piece of text points to a comment (external entity).
- Highlight content: A piece of text points to a highlight (external entity), which can have shared meta data like color, tags, etc.
Comparision to similar extensions
There are multiple Remirror extensions that serve similar use cases. Here is an overview when to use which:
TextHighlightExtension
: Suitable for simple highlighting of text with a colorEntityReferenceExtension
: Use when the marked text has meta data, e.g. the text of a comment or tags assigned to highlighted textAnnotationExtension
: Generally avoid using annotations, and use insteadEntityReferenceExtension
. See this blog post for an in-depth discussion of issues with annotations.
Usage
Installation
This extension is installed for you when you install the main remirror
package.
You can use the imports in the following way:
import { EntityReferenceExtension } from 'remirror/extensions';
The extension is provided by the @remirror/extension-entity-reference
package.
Examples
Source code
import 'remirror/styles/all.css';
import './styles.css';
import React from 'react';
import { cx, uniqueId } from 'remirror';
import {
EntityReferenceExtension,
EntityReferenceMetaData,
findMinMaxRange,
} from 'remirror/extensions';
import { Decoration } from '@remirror/pm/view';
import { Remirror, ThemeProvider, useCommands, useHelpers, useRemirror } from '@remirror/react';
type HighlightTypes = 'important' | 'interesting';
const ALL_HIGHLIGHT_TYPES: HighlightTypes[] = ['important', 'interesting'];
const allHighlights = new Map<string, HighlightTypes>();
export const decorateHighlights = (highlights: EntityReferenceMetaData[][]): Decoration[] => {
const decorations = highlights.map((overlappingHighlights) => {
const types = new Set(overlappingHighlights.map((h) => allHighlights.get(h.id)));
// Mix colors to allow for overlapping highlights
const notRed = types.has('important') ? 64 : 0;
const notBlue = types.has('interesting') ? 64 : 0;
const red = 255 - notBlue;
const green = 255 - notBlue - notRed;
const blue = 255 - notRed;
const style = `background: rgb(${red}, ${green}, ${blue});padding: 6px 0;`;
const [from, to] = findMinMaxRange(overlappingHighlights);
// Add decoration to all inline nodes in the given range.
return Decoration.inline(from, to, { style });
});
return [...decorations];
};
const extensions = () => [
new EntityReferenceExtension({
getStyle: decorateHighlights,
}),
];
const Buttons = () => {
const { getEntityReferencesAt } = useHelpers<EntityReferenceExtension>(true);
const commands = useCommands<EntityReferenceExtension>();
const highlightsAt = getEntityReferencesAt();
return (
<>
{ALL_HIGHLIGHT_TYPES.map((type) => {
const highlightsOfType = highlightsAt.filter((h) => {
const highlightType = allHighlights.get(h.id);
return highlightType === type;
});
// Provide visual feedback if there is a highlight of this type at the user's cursor
const active = highlightsOfType.length > 0;
const onClick = () => {
if (!active) {
// Add highlight
const id = uniqueId();
commands.addEntityReference(id);
allHighlights.set(id, type);
} else {
// Remove highlight
highlightsOfType.forEach((highlight) => {
commands.removeEntityReference(highlight.id);
});
}
};
return (
<button
key={type}
onMouseDown={(event) => event.preventDefault()}
onClick={onClick}
className={cx(active && 'active')}
>
{type}
</button>
);
})}
</>
);
};
const Highlights = (): JSX.Element => {
const { manager, state, onChange } = useRemirror({
extensions: extensions,
content: '<p>Highlight important and interesting text</p>',
stringHandler: 'html',
});
return (
<ThemeProvider>
<Remirror
manager={manager}
autoFocus
onChange={onChange}
initialContent={state}
autoRender='end'
>
<Buttons />
</Remirror>
</ThemeProvider>
);
};
export default Highlights;