Skip to content

TipTap integration

examples/tiptap-editor in the repository is <astl-editor> with everything on: defineEditor() from astrolabe/editor, with no options, gives TipTap 3, every built-in component and look, the insert menu, the inspector, the live issues list and a preview that grades. The example depends on the astrolabe package only.

Terminal window
pnpm dev:tiptap # http://localhost:5302 (from the repository root)
examples/tiptap-editor/src/main.ts (the wiring)
import { defineEditor } from 'astrolabe/editor';
defineEditor();
const editor = document.querySelector('astl-editor');
editor.value = '<astl-doc lang="en"><astl-page id="animals" label="Animals">...</astl-page></astl-doc>';
editor.addEventListener('astl-editor-change', () => show(editor.value));

TipTap lives in @astrolabe/editor-tiptap, the only package that imports it. Nothing in it is specific to the gap or the closed question: its editor nodes are derived from the component manifests.

File What it does
packages/editor-tiptap/src/nodes.ts TipTap nodes from the manifests: inline atoms (a gap, shown as a chip such as [grey]), blocks with rich children (a closed question with its prompt and options), other blocks as atoms, the page with an editable label, the document with its lang
packages/editor-tiptap/src/adapter.ts the EditorAdapter: source in and out, one element by id, the selection, change events
packages/editor-tiptap/src/surface.ts the built-in surface: a TipTap editor with injectCSS: false in the light DOM of <astl-editor>, imported on demand as a chunk of its own
packages/editor-tiptap/src/element.ts <astl-editor>: the kit around the surface, or around your editor in slot="surface"

To build your own TipTap editor around the kit, take astrolabeExtensions and createTiptapAdapter from @astrolabe/editor-tiptap/tiptap (Editor).

The package follows five steps, and so does an adapter for any other editor:

  1. Map elements to your editor's model, from the manifest. display: 'inline' without rich children is an inline atom: keep all its attributes and its private text children (accepted alternatives) as data. A block with content: 'inline' children is a container whose children (prompt, options) are editable rich text; keep child attributes (an option's correct) on the child. Keep unknown attributes (lang, dir) too, so nothing is lost.
  2. Serialise to the content format and parse it back (getSource, setSource). Write the real <astl-*> tags.
  3. One element by id (getElement, replaceElement): the inspector reads an element, applies a change with authoring.update and writes it back. Apply attribute-only changes in place, so the cursor stays where it was.
  4. Selection: report the component at the cursor (a selected atom, the block around the cursor), move to one on select(id) and focus the editor.
  5. Events: call the onChange listeners after every document change and the onSelectionChange listeners when the cursor enters or leaves a component.

Two details that save debugging time:

  • Keep <astl-*> custom elements out of the editing surface. The preview defines them, and an editor that renders <astl-gap> would get a live player input inside the document. The package's node views render chips and frames instead, and serialising and parsing happen in documents without a window (document.implementation.createHTMLDocument, DOMParser), where custom elements are never upgraded.
  • Never replace or nest into the component at the cursor when inserting: insert after it.

The same five steps apply; only the vocabulary changes. Put the editor's element in <astl-editor>'s slot="surface" and its adapter in adapter, and TipTap is never loaded.

  • ProseMirror: the package without the TipTap wrappers (node specs with toDOM and parseDOM, node views).
  • CKEditor 5: model elements for the atoms and blocks, with upcast and downcast converters for the <astl-*> tags; the editing view can use UI elements or widgets for the chips.
  • Lexical: a decorator node for inline atoms, element nodes for blocks and their children, importDOM and exportDOM for the content format.
  • Quill, Slate and others: an embed or void inline element for atoms, a block element for blocks, and the editor's HTML import and export.
  • A plain textarea: createTextareaAdapter does all of this on text; see the authoring kit example.