Skip to main content

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 color
  • EntityReferenceExtension: Use when the marked text has meta data, e.g. the text of a comment or tags assigned to highlighted text
  • AnnotationExtension: Generally avoid using annotations, and use instead EntityReferenceExtension. 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;

API​