Inline blocks
Define inline atoms such as mentions, render them with snippets, insert and remove them from code, and export them as plain text.
An inline block, or atom, is a non-editable element inside a block’s text: a mention, a date chip, a footnote marker. It sits between text runs, moves with the text, and is selected, copied and deleted as one unit. Atoms live in a plugin’s inlineBlocks map.
In the document an atom is a part of the block’s content, with a type and data:
{
"type": "paragraph",
"content": [{ "text": "Thanks " }, { "type": "mention", "data": { "label": "Ada" } }, { "text": "!" }]
}
Content always starts and ends with text, and text runs separate atoms; the editor inserts empty text runs where needed.
Definition
snippetSnippet<[InlineBlockSnippetPayload]>
The atom's markup.
Snippet<[InlineBlockSnippetPayload]>plain?(data) => string
The atom's plain-text form on copy. Without one, it copies as nothing.
(data) => stringThe snippet receives { block }, a view of the atom:
id?string
The atom's id.
stringtype?string
The atom's type.
stringdata?object
The atom's data. Reactive.
objectselected?boolean
The atom is selected. Reactive. Always false for a suggested atom.
booleanhandle?InlineBlock | undefined
The atom's handle, for commands. undefined for an atom shown in an inline suggestion.
InlineBlock | undefinedThe core wraps the snippet in <span data-edytor-inline-block="<type>" data-edytor-id="<id>" contenteditable="false">. In HTML copied to the clipboard an atom is an empty span with the same data-edytor-inline-block attribute; content pasted into an Edytor keeps its atoms through the editor’s own clipboard format.
A mention atom
This plugin replaces a typed @ with a mention atom. It takes a pick function as a parameter, so the host app decides how to choose the person:
<script module lang="ts">
import type { Plugin } from 'edytor';
import type { InlineBlockSnippetPayload } from 'edytor';
export const createMentionPlugin =
(pick: () => string | null): Plugin =>
(edytor) => ({
inlineBlocks: {
mention: {
snippet: mention,
plain: (data) => `@${data?.label ?? ''}`
}
},
onBeforeOperation: ({ operation, payload, block, prevent }) => {
if (operation === 'insertText' && payload.value === '@') {
const { startText, yStart } = edytor.selection.state;
if (!startText) return;
const label = pick();
if (!label) return; // Nobody picked: the @ is typed as text.
prevent(() => {
const after = block.addInlineBlock({
index: yStart,
text: startText,
block: { type: 'mention', data: { label } }
});
if (after) edytor.selection.setAtTextOffset(after, 0);
});
}
}
});
</script>
{#snippet mention({ block }: InlineBlockSnippetPayload)}
<span class="mention" class:selected={block.selected}>@{block.data.label}</span>
{/snippet}
<script lang="ts">
import { Edytor } from 'edytor';
import { createMentionPlugin } from './MentionPlugin.svelte';
const plugins = [createMentionPlugin(() => prompt('Mention who?'))];
</script>
<Edytor {plugins} />
A real app would open a picker instead of prompt, then insert the atom when the user chooses.
.mention {
padding: 0 3px;
border-radius: 3px;
background: #eef3ff;
color: #2f5bd3;
}
.mention.selected {
outline: 2px solid #2f5bd3;
}
Inserting, updating and removing atoms
Insert an atom with addInlineBlock on the block, at an offset of one of its text segments:
const { startBlock, startText, yStart } = edytor.selection.state;
if (startBlock && startText) {
const after = startBlock.addInlineBlock({
index: yStart, // offset inside startText
text: startText,
block: { type: 'mention', data: { label: 'Ada' } }
});
// after is the text segment right after the atom: put the caret there.
if (after) edytor.selection.setAtTextOffset(after, 0);
}
It is an addInlineBlock operation, visible to plugins. An atom’s id is generated when you leave id out.
An atom’s handle is an InlineBlock: atom.data reads its data, atom.setData(data) replaces it (a setInlineData operation on its block, visible to plugins and refused in a readonly view), atom.parent is its block and atom.index its position in block.content. Remove it with a removeInlineBlock operation on its block:
const atom = block.content.find((part) => part.id === atomId);
if (atom) block.removeInlineBlock({ index: atom.index });
Users select an atom with the arrow keys or a click; Backspace or Delete removes a selected atom, and typing replaces it.
Atoms can also be part of the initial value, as in the JSON at the top of this page.