Some checks failed
CD / Build and push images (push) Successful in 1m50s
CI / Lint, typecheck, test (push) Failing after 52s
CI / Auth e2e pack (push) Successful in 1m47s
CI / Build container images (push) Has been skipped
CD / Deploy to Test (push) Successful in 8s
CD / Smoke tests against Test (push) Successful in 1m5s
CD / Promote to Int (push) Successful in 10s
ProseMirror schema (headings 1-4, lists incl. task lists, blockquote, code block, tables via prosemirror-tables, images, hard breaks; bold/ italic/code/strikethrough/link marks) plus docToMarkdown, markdownToDoc, docToPlainText, docToHtml, and extractOutline built on it. Markdown parsing extends markdown-it's default preset with a token-stream transform for GFM task lists and table-cell paragraph wrapping. docToHtml hand-rolls escaping and link-protocol allowlisting with zero DOM dependencies, so it runs in the API/collab server as well as the browser. Node names `wikilink` and `plugin_block` are reserved for later stories. Closes #24
32 lines
992 B
TypeScript
32 lines
992 B
TypeScript
import { Node } from 'prosemirror-model';
|
|
|
|
import { slugify } from '../ponds';
|
|
|
|
export interface OutlineEntry {
|
|
id: string;
|
|
level: number;
|
|
text: string;
|
|
}
|
|
|
|
/**
|
|
* Heading tree for TOC plugins (data-model.md `page_content_cache`). Ids
|
|
* are stable across re-derivation: same heading text + position in the
|
|
* duplicate sequence always yields the same id (deterministic suffixes,
|
|
* mirroring the pond-slug convention in `../ponds`).
|
|
*/
|
|
export function extractOutline(doc: Node): OutlineEntry[] {
|
|
const entries: OutlineEntry[] = [];
|
|
const seen = new Map<string, number>();
|
|
doc.descendants((node) => {
|
|
if (node.type.name !== 'heading') return true;
|
|
const text = node.textContent.trim();
|
|
const base = slugify(text) || 'section';
|
|
const count = seen.get(base) ?? 0;
|
|
seen.set(base, count + 1);
|
|
const id = count === 0 ? base : `${base}-${count + 1}`;
|
|
entries.push({ id, level: node.attrs.level as number, text });
|
|
return false;
|
|
});
|
|
return entries;
|
|
}
|