Rich-Text Editors
Alap links can live inside rich-text editors. The @alap/tiptap extension adds <alap-link> as an inline node in Tiptap/ProseMirror, so content authors can insert multi-target links while they write.
The idea
A CMS author writes a blog post in a Tiptap editor. They select "coffee spots" and press Cmd+Shift+A — it becomes an Alap link with a query. On publish, the HTML contains <alap-link query=".coffee">coffee spots</alap-link>, and the web component takes over on the reader's side.
The editor treats Alap links as first-class inline nodes — not marks, not decorations. They appear in the document model, serialize to clean HTML, and parse back from it.
Setup
npm install @alap/tiptap @tiptap/core @tiptap/starter-kitimport { Editor } from '@tiptap/core';
import StarterKit from '@tiptap/starter-kit';
import { AlapExtension } from '@alap/tiptap';
const editor = new Editor({
extensions: [
StarterKit,
AlapExtension.configure({
HTMLAttributes: { class: 'alap-link' },
}),
],
});Commands
Three commands are added to the editor:
Insert
editor.commands.setAlapLink({ query: '.coffee' });Wraps the current selection in an <alap-link>. If nothing is selected, inserts a node with placeholder text.
Update
editor.commands.updateAlapLink({ query: '.bridge' });Changes the query on the Alap link at the cursor position. In the example app, typing in the query field auto-updates the current link (no button click needed).
Remove
editor.commands.unsetAlapLink();Removes the node but keeps the text inline.
Keyboard shortcut
Mod-Shift-A inserts a new Alap link at the cursor. If the cursor is already inside one, it's a no-op (your bubble menu or toolbar can handle the update).
HTML round-trip
The extension parses <alap-link query="...">text</alap-link> from HTML input and serializes back to the same format. This means:
- Paste HTML with Alap links → they become editable nodes
editor.getHTML()→ clean output ready for the web component- Content stored in a CMS database round-trips without loss
Styling in the editor
Alap links inside the editor are regular DOM elements — style them with CSS:
.alap-link {
color: #2563eb;
text-decoration: underline dotted;
background: #eef2ff;
padding: 0.1rem 0.25rem;
border-radius: 3px;
}
/* Selected node */
.alap-link.ProseMirror-selectednode {
outline: 2px solid #2563eb;
}Live config editing
The example app includes an editable config panel below the editor. Changes to the JSON config take effect immediately — registerConfig() accepts being called again to replace the active config and engine. The preview re-renders with the new data.
// Re-register on config change
const newConfig = JSON.parse(configTextarea.value);
registerConfig(newConfig);
previewEl.innerHTML = editor.getHTML();A Reset button restores the original config.
Preview
To preview the rendered output with working menus, inject the editor's HTML into a container that has the web component registered:
import { registerConfig, defineAlapLink } from 'alap';
registerConfig(myConfig);
defineAlapLink();
// On each editor update:
previewEl.innerHTML = editor.getHTML();The <alap-link> elements in the preview become interactive immediately. In the example app, Preview and HTML Output are tabbed views in the right column — switch between the live interactive preview and the formatted HTML source.
Help panel
The example includes a slide-over help panel (activated by the ? button in the toolbar) that shows:
- Expression syntax cheat sheet (tags, operators, macros)
- Available tags extracted from the current config
- Available macros from the current config
The tag and macro lists update dynamically when the config changes.
Example
See examples/sites/tiptap/ for the working demo.