Skip to main content

Custom extension

So far, we built a UI to interact with the existing features of the editor. Now, we want to extend the feature set of the editor itself. In particular, we will add support for <samp> tags.

Remirror enables this via extensions.

Add extension

We want to add extra styling to our inline content. The way to do this in Prosemirror are marks.

Remirror provides the MarkExtension class to easily declare new Prosemirror marks. The initial SampExtension only renders our marks as <samp> tags and parses them back to marks:

import {
ApplySchemaAttributes,
extension,
ExtensionTag,
MarkExtension,
MarkExtensionSpec,
MarkSpecOverride,
} from 'remirror';
import type { TagParseRule } from '@remirror/pm/model';

export interface SampOptions {}

@extension<SampOptions>({ defaultOptions: {} })
export class SampExtension extends MarkExtension<SampOptions> {
get name() {
return 'samp' as const;
}

createTags() {
return [ExtensionTag.FormattingMark, ExtensionTag.FontStyle];
}

createMarkSpec(extra: ApplySchemaAttributes, override: MarkSpecOverride): MarkExtensionSpec {
return {
...override,
attrs: extra.defaults(),
parseDOM: [
{
tag: 'samp',
getAttrs: extra.parse,
} satisfies TagParseRule,
...(override.parseDOM ?? []),
],
toDOM: (node) => {
return ['samp', extra.dom(node), 0];
},
};
}
}

Add commands

By now, our editor knows about samp marks but we still can't create a UI to interact with them. For that, we need to add a couple of commands:

import {
// remirror imports as before
command,
CommandFunction,
getTextSelection,
PrimitiveSelection,
toggleMark,
} from 'remirror';

export class SampExtension extends MarkExtension<SampOptions> {
// ...as before

@command()
toggleSamp(selection?: PrimitiveSelection): CommandFunction {
return toggleMark({ type: this.type, selection });
}

@command()
setSamp(selection?: PrimitiveSelection): CommandFunction {
return ({ tr, dispatch }) => {
const { from, to } = getTextSelection(selection ?? tr.selection, tr.doc);
dispatch?.(tr.addMark(from, to, this.type.create()));

return true;
};
}

@command()
removeSamp(selection?: PrimitiveSelection): CommandFunction {
return ({ tr, dispatch }) => {
const { from, to } = getTextSelection(selection ?? tr.selection, tr.doc);

if (!tr.doc.rangeHasMark(from, to, this.type)) {
return false;
}

dispatch?.(tr.removeMark(from, to, this.type));

return true;
};
}
}

Use extension

Our custom extension is identical to any other extension provided by Remirror. Hence, we can use it in the same way:

import 'remirror/styles/all.css';

import React from 'react';
import { EditorComponent, Remirror, useCommands, useRemirror } from '@remirror/react';

import { SampExtension } from './samp-extension';

export const Menu = () => {
const { toggleSamp, focus } = useCommands();

return (
<button
onClick={() => {
toggleSamp();
focus();
}}
>
Samp
</button>
);
};

export const MyEditor = () => {
const { manager, state } = useRemirror({
extensions: () => [new SampExtension()],
});

return (
<div className='remirror-theme'>
<Remirror manager={manager} initialContent={state}>
<EditorComponent />
<Menu />
</Remirror>
</div>
);
};

Summing up

Congratulations! You created a manager, rendered the editor UI, added a menu, and extend the editor with your own extension.

With that, you've all the basic tools to start exploring Remirror.

Have fun & happy coding!

PS: Getting stuck with something? Reach out on Discord!