Custom Document Outline¶
Use this guide to wire a table-of-contents sidebar that follows the document structure and supports click-to-jump.
When to Derive the Outline¶
| Context | Argument |
|---|---|
| Web component | editor.document (ProseMirror) or editor-change payload (detail.doc) |
Core Editor instance |
editor.getDoc() |
Use the JSON snapshot from editor-change to derive heading data. For click-to-jump behavior, pair the heading positions with the stable position helpers exposed on the web component (editor.positions) or core editor APIs.
Rendering Your Own Panel¶
function collectOutline(doc) {
const headings = [];
// Derive headings from the JSON snapshot.
(doc?.content ?? []).forEach((node) => {
if (node.type === 'heading') {
headings.push({ text: node.content?.[0]?.text ?? '', level: node.attrs?.level ?? 1, position: null });
}
});
return { headings };
}
function renderOutline(doc, container) {
const outline = collectOutline(doc);
container.innerHTML = '';
outline.headings.forEach((heading) => {
const item = document.createElement('li');
item.textContent = heading.text || 'Untitled section';
item.dataset.level = String(heading.level ?? 1);
container.appendChild(item);
});
}
editor.addEventListener('editor-change', (event) => {
renderOutline(event.detail.doc, outlineContainer);
});
Scrolling Into View¶
The outline handler can call commands.setSelection(position, { scroll: false }) followed by commands.scrollIntoView() and commands.focus() to keep behavior consistent with the editor’s native navigation.
const runner = editor.commands;
if (runner?.commands?.setSelection) {
const didSet = runner.commands.setSelection(position, { scroll: false });
if (didSet && runner.commands.scrollIntoView) {
runner.commands.scrollIntoView();
}
runner.commands.focus?.();
}
Re-render Triggers¶
editor-change– update the outline whenever the document structure changes.- Feature toggles – if you enable/disable heading support on the fly, re-run
renderOutlineafter callingeditor.configureFeatures(...).
Position helpers
Use the positions API to map document offsets to screen coordinates (coordsAtPos) and to resolve positional context (resolve). See Web Components Basics for the full position API surface.
Using the sciflow-outline Web Component¶
<sciflow-outline> is a ready-made outline panel shipped by @sciflow/editor-start. It handles document syncing, heading/figure display, click-to-jump navigation, drag-and-drop cross references, and Insert ref buttons automatically.
<sciflow-outline for="my-editor"></sciflow-outline>
<sciflow-editor id="my-editor"></sciflow-editor>
Or pass the editor reference directly:
const outline = document.querySelector('sciflow-outline');
outline.editor = document.querySelector('sciflow-editor');
Insert ref button¶
Each heading and figure row with an id attribute shows an Insert ref button. It is enabled only when the editor has an active cursor (the component tracks this internally via editor-selection-change).
Clicking the button fires a sciflow-insert-cross-reference event (bubbles, composed). Wire it to the insertCrossReference command:
outline.addEventListener('sciflow-insert-cross-reference', (event) => {
const { id, href, text, refType } = event.detail;
editor.commands?.commands?.insertCrossReference?.({ id, href, text, refType });
editor.commands?.commands?.focus?.();
});
insertCrossReference is provided by crossReferenceFeature from @sciflow/editor-core. Make sure it is included in your feature list.
Ghost cursor
The Insert ref button calls event.preventDefault() on mousedown to keep the editor cursor alive while the user interacts with the sidebar. Enable ghostCursorFeature so the user can see the insertion point. See Reference Integration for setup details.
Events¶
| Event | When fired | detail |
|---|---|---|
sciflow-insert-cross-reference |
User clicks Insert ref on a heading or figure row | { type, refType, id, href, text } |
sciflow-outline-navigate |
User clicks a heading to jump to it | { heading, behavior, selectionPosition } |