Plugins
What an Edytor plugin is, how to pass plugins to the editor, how plugin order decides conflicts, and which plugins ship with the package.
Everything the editor knows about content comes from plugins: block kinds, marks, inline atoms, key bindings, and hooks that can inspect or veto every edit. The core ships no block kinds of its own; <Edytor> adds the rich text, image and arrow move plugins by default, so <Edytor /> alone is a working rich text editor.
What a plugin is
A plugin is a function of the editor that returns definitions and hooks:
import type { Plugin } from 'edytor';
export const loggerPlugin: Plugin = (edytor) => ({
onBeforeOperation: ({ operation }) => {
console.log('about to run', operation);
},
onChange: (value) => {
console.log('document now has', value.children?.length, 'top-level blocks');
}
});
The function runs once, when the editor is created. It receives the Edytor instance, so it can keep state in its closure and call editor methods from its hooks. Everything it returns is optional:
| Field | What it contributes |
|---|---|
blocks |
Block kinds, see Blocks |
marks |
Text marks, see Marks |
inlineBlocks |
Inline atoms, see Inline blocks |
hotkeys |
Key bindings, see Hotkeys |
commands |
Named commands for menus such as the slash menu |
placeholder |
Text for empty blocks, see Placeholder |
onBeforeOperation and more |
Hooks into every edit, the DOM and the clipboard |
Writing plugins documents every field and hook.
Passing plugins to the editor
Pass an array to the plugins prop of <Edytor>:
<script lang="ts">
import {
Edytor,
codePlugin,
slashMenuPlugin,
toolbarPlugin,
markdownShortcutsPlugin,
blockMenuPlugin
} from 'edytor';
const plugins = [codePlugin, markdownShortcutsPlugin, slashMenuPlugin, toolbarPlugin, blockMenuPlugin];
</script>
<Edytor {plugins} />
Plugins are read once, when the component mounts. Changing the array afterwards has no effect.
Default plugins
<Edytor> completes the list with the plugins every editor needs, after yours so your kinds, marks and keys take precedence, unless the list already has them:
| Plugin | Position | Replaced by |
|---|---|---|
arrowMovePlugin |
after yours | arrowMovePlugin in your list |
imagePlugin |
after yours | imagePlugin or any createImagePlugin(...) in your list |
richTextPlugin |
last | richTextPlugin in your list |
| Block handles | first | the blockHandles prop, or a handles plugin in your list |
The example above therefore runs block handles, your five plugins, then arrow move, image and rich text. A default you list yourself stays where you put it, so older code that passes plugins={[richTextPlugin]} still works.
Pass defaultPlugins={false} to get exactly your list (block handles still follow blockHandles). The defaults are added by the <Edytor> component, per view; the editor instance underneath has none of its own.
Plugin order
The array order is the precedence order. Two rules follow from it:
- Definitions: first wins. When two plugins define the same block kind, mark, inline atom or command id, the one listed first is used and the later one is ignored. To replace a definition from a bundled plugin, list your plugin before it.
- Prevention: first wins. Hooks and key bindings run in list order. The first plugin that calls
prevent()stops the operation or claims the key, and the plugins after it are not asked.
Order therefore matters whenever two plugins touch the same thing: the same kind name, the same chord, or the same operation. A plugin that overrides or refuses something a bundled plugin does goes before it in the list. The three defaults are added after your plugins, so yours already come first; to let a default win over one of your plugins, list the default yourself before it.
One exception: two plugins that declare different defaultChild values for the same block kind are an error, whatever their order. The editor throws when it is created.
Why plugins are Svelte files
Block, mark and inline atom definitions render through Svelte snippets. A snippet can only be declared in a .svelte file, and a component’s <script module> block can reference the snippets declared in its markup. So a plugin that renders anything is usually a .svelte file that exports the plugin from <script module>:
<script module lang="ts">
import type { Snippet } from 'svelte';
import type { Plugin } from 'edytor';
export const highlightPlugin: Plugin = () => ({
marks: { marker: { snippet: marker } }
});
</script>
{#snippet marker({ content }: { content: Snippet })}
<mark class="marker">{@render content()}</mark>
{/snippet}
Import it like any other module: import { highlightPlugin } from './HighlightPlugin.svelte'. A plugin that only adds hooks or key bindings can be a plain .ts file.
Bundled plugins
| Plugin | Export | What it adds |
|---|---|---|
| Rich text | richTextPlugin |
Paragraphs, headings, lists, to-dos, toggles, callouts, quotes, dividers, ten marks, and Notion’s shortcuts. On by default |
| Code | codePlugin |
Code blocks with syntax highlighting and a Copy button |
| Image | imagePlugin, createImagePlugin |
Notion’s image block: embed from a link or your upload, with an editable caption. On by default |
| Block handles | blockHandlesPlugin, createBlockHandlesPlugin |
A + and a drag grip beside each block, drag and drop, keyboard moves. On by default |
| Block menu | blockMenuPlugin, createBlockMenuPlugin |
Notion’s block menu on a grip click: search, Turn into, Duplicate, Move, Delete |
| Slash menu | slashMenuPlugin, createSlashMenuPlugin |
A sectioned command menu opened by typing / |
| Toolbar | toolbarPlugin, createToolbarPlugin |
A floating toolbar over text selections: Turn into, link, marks and colors |
| Markdown shortcuts | markdownShortcutsPlugin |
Block prefixes such as # or - , and inline **bold**, *italic*, `code`, ~strike~ |
| Arrow move | arrowMovePlugin |
Mod + Shift + ↑/↓ moves the caret’s block; Mod + ↑/↓ moves selected blocks. On by default |
| Mention | (not exported) | A reference implementation of an inline atom |
All exports come from the package root, edytor. The create… factories take options, including snippets that replace the menus’ and handles’ markup (see Menus and handles). For the matching document look, add the Notion theme.