Skip to content

Block tools ​

Block tools define block types: how they render, how they serialize, and how they behave under editing operations. Ezynota ships ten built-ins, and they're all built on the same public SDK you can use for your own.

Built-in block types ​

TypeClassHighlights
paragraphParagraphDefault block; converts to heading/list/quote/code/delimiter; inline tools enabled.
headingHeadingLevels 1–3 by default (configurable); level switcher in block settings. Shortcut Cmd/Ctrl+Shift+2.
quoteQuoteBlockquote with rich content.
codeCodeToolPlain-text editing (Enter = newline, no inline marks). Shortcut Cmd/Ctrl+Shift+C.
delimiterDelimiterVisual separator; Enter is ignored.
listListToolBulleted / ordered / task lists; checkboxes; style switcher. Shortcut Cmd/Ctrl+Shift+L.
tableTableToolRich-text cells, header toggle, add/delete rows & columns, Tab navigation that grows the table.
imageImageToolUpload, paste, drag & drop, URL; alt text, width slider, captions; asset:<id> sources.
calloutCalloutToolinfo / warning / success / danger variants with a variant picker.
toggleToggleToolCollapsible sections hosting nested child blocks; open/closed state is data (undoable).

There's also UnknownBlockTool — a read-only fallback that preserves raw data for block types that fail to load or no longer exist.

Tool static configuration ​

Tools declare capabilities as static properties:

ts
class Paragraph extends TextBlockTool {
  static toolbox = { title: "Paragraph", category: "Basic blocks" };
  static conversion = { to: ["heading", "list", "quote", "code", "delimiter"] };
  static enableInlineTools = true;
}
StaticTypeEffect
toolbox{ title, icon?, category? }Entry in the slash menu / picker.
shortcutstringGlobal shortcut, e.g. "CMD+SHIFT+L".
paste{ tags?, files?, patterns? }Route pasted tags/files/regex matches to this tool.
conversion{ from?, to? }Enable convert-to entries in the block menu (to), and being converted (from mappers).
enterKey"split" | "newline" | "ignore"Enter behavior inside the block.
enableInlineToolsbooleanFloating inline toolbar available.
supportsReadOnlybooleanUsable in read-only mode.
sanitize?functionData sanitizer applied on save.
apiVersionstringFor forward compatibility.

Lifecycle hooks ​

ts
interface BlockTool {
  render(): HTMLElement;                      // build the DOM (required)
  save(element: HTMLElement): unknown;        // read DOM back into data (required)
  validate?(data): boolean;                   // gate save() output
  merge?(incoming): void;                     // Backspace/Delete merging
  renderSettings?(): MenuItem[];              // block settings menu entries
  onPaste?(event: PasteEvent): void;          // custom paste handling
  rendered?(): void;                          // after DOM attach
  updated?(): void;                           // after data update
  moved?(pos: { from: number; to: number }): void;
  removed?(): void;
  destroy?(): void;
}

Editing primitives ​

The editor drives structural edits through tool helpers (splitAtRange, merge, updated, isEmpty, …). TextBlockTool implements all of these for rich-text blocks, so most custom text-ish tools just extend it:

ts
import { TextBlockTool } from "@bookklik/ezynota";

class HintTool extends TextBlockTool {
  static toolbox = { title: "Hint", icon: "💡", category: "Basic blocks" };
  static enableInlineTools = true;

  render() {
    const el = document.createElement("div");
    el.className = "my-hint";
    /* … */
    return el;
  }
}

Registering tools ​

ts
const editor = new Ezynota({
  target: "#app",
  tools: {
    hint: HintTool,        // usable as type: "hint", in slash menu, conversions
  },
});

Built-ins stay registered; your entries are additive. See the full walkthrough in Custom tools.

Lazy loading ​

The ToolRegistry supports lazy tool loaders — register a loader that resolves a constructor on demand. Broken loaders fall back to UnknownBlockTool so documents remain openable.

Nested blocks (toggles) ​

Tools that host child blocks implement the NestedBlockHost capability (as ToggleTool does). Children live in block.children and participate in the same transactions, undo, and events (children:update).

Released under the MIT License. Zero runtime dependencies.