@rightkit/doc-render 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/LICENSE-APACHE +201 -0
  2. package/LICENSE-MIT +21 -0
  3. package/README.md +2 -2
  4. package/dist/citations/formatter.d.ts +7 -0
  5. package/dist/citations/formatter.js +106 -0
  6. package/dist/citations/types.d.ts +46 -0
  7. package/dist/citations/types.js +6 -0
  8. package/dist/detectors/callout.d.ts +13 -0
  9. package/dist/detectors/callout.js +32 -0
  10. package/dist/detectors/comparison.d.ts +15 -0
  11. package/dist/detectors/comparison.js +38 -0
  12. package/dist/detectors/descriptionList.d.ts +9 -0
  13. package/dist/detectors/descriptionList.js +16 -0
  14. package/dist/detectors/faq.d.ts +14 -0
  15. package/dist/detectors/faq.js +37 -0
  16. package/dist/detectors/frontmatter.d.ts +11 -0
  17. package/dist/detectors/frontmatter.js +23 -0
  18. package/dist/detectors/index.d.ts +22 -0
  19. package/dist/detectors/index.js +85 -0
  20. package/dist/detectors/nodeText.d.ts +3 -0
  21. package/dist/detectors/nodeText.js +11 -0
  22. package/dist/detectors/richTable.d.ts +10 -0
  23. package/dist/detectors/richTable.js +35 -0
  24. package/dist/detectors/stepper.d.ts +12 -0
  25. package/dist/detectors/stepper.js +58 -0
  26. package/dist/detectors/types.d.ts +26 -0
  27. package/dist/detectors/types.js +2 -0
  28. package/dist/footnotes.d.ts +12 -0
  29. package/dist/footnotes.js +23 -0
  30. package/dist/headingSlugs.d.ts +4 -0
  31. package/dist/headingSlugs.js +19 -0
  32. package/dist/index.d.ts +15 -0
  33. package/dist/index.js +11 -0
  34. package/dist/parseMarkdown.d.ts +7 -0
  35. package/dist/parseMarkdown.js +39 -0
  36. package/dist/remarkCitations.d.ts +13 -0
  37. package/dist/remarkCitations.js +78 -0
  38. package/dist/remarkDefListAbbr.d.ts +30 -0
  39. package/dist/remarkDefListAbbr.js +292 -0
  40. package/dist/remarkWikiLink.d.ts +10 -0
  41. package/dist/remarkWikiLink.js +63 -0
  42. package/dist/renderers.d.ts +17 -0
  43. package/dist/renderers.js +88 -0
  44. package/dist/security.d.ts +12 -0
  45. package/dist/security.js +78 -0
  46. package/dist/shikiHighlighter.d.ts +26 -0
  47. package/dist/shikiHighlighter.js +184 -0
  48. package/package.json +64 -3
  49. package/styles.css +10 -0
@@ -0,0 +1,85 @@
1
+ import { CONFIDENCE_THRESHOLD } from "./types";
2
+ import { calloutDetector } from "./callout.js";
3
+ import { frontmatterDetector } from "./frontmatter.js";
4
+ import { stepperDetector } from "./stepper.js";
5
+ import { comparisonDetector } from "./comparison.js";
6
+ import { faqDetector } from "./faq.js";
7
+ import { richTableDetector } from "./richTable.js";
8
+ import { descriptionListDetector } from "./descriptionList.js";
9
+ export * from "./types";
10
+ // Priority order: most specific / least ambiguous first.
11
+ const DETECTORS = [
12
+ frontmatterDetector,
13
+ calloutDetector,
14
+ richTableDetector,
15
+ faqDetector,
16
+ descriptionListDetector,
17
+ comparisonDetector,
18
+ stepperDetector,
19
+ ];
20
+ const HINT_RE = /<!--\s*(?:doc|viewright):\s*(frontmatter|callout|stepper|comparison|faq|richTable|descriptionList)\s*-->/i;
21
+ function parseHint(node) {
22
+ if (node.type !== "html")
23
+ return null;
24
+ const m = HINT_RE.exec(node.value ?? "");
25
+ return m ? m[1] : null;
26
+ }
27
+ /**
28
+ * The confidence gate. Walks top-level nodes, runs detectors, and decides per block:
29
+ * - hit at/above threshold (or forced by a hint) → semantic component
30
+ * - hit below threshold → plain markdown (silent fallback)
31
+ * - no hit → plain markdown
32
+ * Returns a flat block list the renderer maps to React.
33
+ */
34
+ export function buildBlocks(nodes) {
35
+ const blocks = [];
36
+ let pendingHint = null;
37
+ let plainRun = [];
38
+ const flushPlain = () => {
39
+ if (plainRun.length) {
40
+ blocks.push({ kind: "plain", nodes: plainRun });
41
+ plainRun = [];
42
+ }
43
+ };
44
+ let i = 0;
45
+ while (i < nodes.length) {
46
+ const node = nodes[i];
47
+ const hint = parseHint(node);
48
+ if (hint) {
49
+ pendingHint = hint;
50
+ i += 1; // the hint comment itself never renders
51
+ continue;
52
+ }
53
+ let chosen = null;
54
+ for (const detect of DETECTORS) {
55
+ const hit = detect(nodes, i, pendingHint);
56
+ if (hit && (!chosen || hit.confidence > chosen.confidence || hit.forced)) {
57
+ chosen = hit;
58
+ if (hit.forced)
59
+ break;
60
+ }
61
+ }
62
+ if (chosen && (chosen.confidence >= CONFIDENCE_THRESHOLD || chosen.forced)) {
63
+ flushPlain();
64
+ const startNode = nodes[i];
65
+ const endNode = nodes[i + chosen.consumed - 1];
66
+ const start = startNode.position?.start.offset ?? 0;
67
+ const end = endNode.position?.end.offset ?? startNode.position?.start.offset ?? 0;
68
+ blocks.push({ kind: "semantic", hit: chosen, position: { start, end } });
69
+ i += chosen.consumed;
70
+ }
71
+ else if (chosen) {
72
+ // Below threshold: render the nodes the detector eyed as ordinary markdown.
73
+ for (let k = 0; k < chosen.consumed; k++)
74
+ plainRun.push(nodes[i + k]);
75
+ i += chosen.consumed;
76
+ }
77
+ else {
78
+ plainRun.push(node);
79
+ i += 1;
80
+ }
81
+ pendingHint = null;
82
+ }
83
+ flushPlain();
84
+ return blocks;
85
+ }
@@ -0,0 +1,3 @@
1
+ import type { RootContent } from "mdast";
2
+ /** Flatten the visible text of an mdast node (headings, paragraphs, list items, etc.). */
3
+ export declare function nodeText(node: RootContent | undefined): string;
@@ -0,0 +1,11 @@
1
+ /** Flatten the visible text of an mdast node (headings, paragraphs, list items, etc.). */
2
+ export function nodeText(node) {
3
+ if (!node)
4
+ return "";
5
+ const anyNode = node;
6
+ if (typeof anyNode.value === "string")
7
+ return anyNode.value;
8
+ if (Array.isArray(anyNode.children))
9
+ return anyNode.children.map((c) => nodeText(c)).join("");
10
+ return "";
11
+ }
@@ -0,0 +1,10 @@
1
+ import type { Detector } from "./types";
2
+ export interface RichTableData {
3
+ headers: string[];
4
+ rows: string[][];
5
+ /** Column index → true when every body cell parses as a number (enables sort + bar viz). */
6
+ numericColumns: boolean[];
7
+ align: Array<"left" | "right" | "center" | null>;
8
+ }
9
+ /** GFM table → sortable rich table. Always claims a table; confidence reflects how "data-like" it is. */
10
+ export declare const richTableDetector: Detector;
@@ -0,0 +1,35 @@
1
+ import { nodeText } from "./nodeText.js";
2
+ function cells(row) {
3
+ return row.children.map((c) => nodeText(c).trim());
4
+ }
5
+ function isNumeric(v) {
6
+ if (v === "")
7
+ return false;
8
+ return !Number.isNaN(Number(v.replace(/[,$%\s]/g, "")));
9
+ }
10
+ /** GFM table → sortable rich table. Always claims a table; confidence reflects how "data-like" it is. */
11
+ export const richTableDetector = (nodes, index, hint) => {
12
+ const node = nodes[index];
13
+ if (node?.type !== "table")
14
+ return null;
15
+ const table = node;
16
+ const [headRow, ...bodyRows] = table.children;
17
+ if (!headRow)
18
+ return null;
19
+ const headers = cells(headRow);
20
+ const rows = bodyRows.map(cells);
21
+ const cols = headers.length;
22
+ const numericColumns = [];
23
+ for (let c = 0; c < cols; c++) {
24
+ const colVals = rows.map((r) => r[c] ?? "");
25
+ numericColumns[c] = colVals.length > 0 && colVals.every(isNumeric);
26
+ }
27
+ const align = (table.align ?? []).map((a) => a ?? null);
28
+ const data = { headers, rows, numericColumns, align };
29
+ if (hint === "richTable")
30
+ return { kind: "richTable", confidence: 1, consumed: 1, data, forced: true };
31
+ // A table with numeric columns benefits from sort/bars; a tiny 1-row table barely does.
32
+ const hasNumeric = numericColumns.some(Boolean);
33
+ const confidence = hasNumeric && rows.length >= 2 ? 0.85 : rows.length >= 2 ? 0.65 : 0.3;
34
+ return { kind: "richTable", confidence, consumed: 1, data };
35
+ };
@@ -0,0 +1,12 @@
1
+ import type { Detector } from "./types";
2
+ export interface StepperData {
3
+ title: string | null;
4
+ steps: string[];
5
+ }
6
+ /**
7
+ * Stepper: an ordered list, optionally introduced by a process-keyword heading.
8
+ * - heading + ordered list with a process word → high confidence
9
+ * - bare ordered list → low confidence (gate falls back to plain)
10
+ * - explicit hint → forced
11
+ */
12
+ export declare const stepperDetector: Detector;
@@ -0,0 +1,58 @@
1
+ import { nodeText } from "./nodeText.js";
2
+ // Process-keyword vocabulary — one signal among several.
3
+ const PROCESS_WORDS = /\b(steps?|how\s*to|guide|recipe|instructions?|setup|set\s*up|process|walkthrough|tutorial|procedure|getting\s+started)\b/i;
4
+ // Imperative verbs that step instructions commonly start with — a strong structural signal
5
+ // that an ordered list is procedural (vs. e.g. a ranked list of nouns).
6
+ const STEP_VERB = /^(add|allow|apply|build|call|change|check|choose|click|clone|close|configure|connect|copy|create|define|delete|deploy|disable|download|edit|enable|enter|export|extract|fill|find|fix|fold|get|go|import|init|insert|install|launch|load|lock|make|mark|merge|move|navigate|open|paste|pick|place|press|pull|push|read|register|reload|remove|rename|repeat|replace|restart|run|save|select|set|ship|start|stop|switch|tap|test|toggle|turn|type|update|upload|use|verify|visit|wait|write)\b/i;
7
+ /**
8
+ * Stepper: an ordered list, optionally introduced by a process-keyword heading.
9
+ * - heading + ordered list with a process word → high confidence
10
+ * - bare ordered list → low confidence (gate falls back to plain)
11
+ * - explicit hint → forced
12
+ */
13
+ export const stepperDetector = (nodes, index, hint) => {
14
+ let listIndex = index;
15
+ let heading = null;
16
+ if (nodes[index]?.type === "heading" && nodes[index + 1]?.type === "list") {
17
+ heading = nodes[index];
18
+ listIndex = index + 1;
19
+ }
20
+ else if (nodes[index]?.type !== "list") {
21
+ return null;
22
+ }
23
+ const list = nodes[listIndex];
24
+ if (list.type !== "list" || !list.ordered)
25
+ return null;
26
+ const steps = list.children
27
+ .filter((c) => c.type === "listItem")
28
+ .map((li) => nodeText(li).trim())
29
+ .filter(Boolean);
30
+ if (steps.length < 2)
31
+ return null;
32
+ const title = heading ? nodeText(heading).trim() : null;
33
+ const consumed = heading ? 2 : 1;
34
+ if (hint === "stepper") {
35
+ return {
36
+ kind: "stepper",
37
+ confidence: 1,
38
+ consumed,
39
+ data: { title, steps },
40
+ forced: true,
41
+ };
42
+ }
43
+ // Multi-signal score: combine independent weak signals rather than a single keyword.
44
+ const verbRatio = steps.filter((s) => STEP_VERB.test(s)).length / steps.length;
45
+ let confidence = 0.2; // bare ordered list — below threshold, renders as plain
46
+ if (title && PROCESS_WORDS.test(title))
47
+ confidence += 0.45; // process-keyword heading
48
+ else if (title)
49
+ confidence += 0.12; // some heading
50
+ if (verbRatio >= 0.6)
51
+ confidence += 0.4; // most items start with an imperative verb
52
+ else if (verbRatio >= 0.3)
53
+ confidence += 0.2;
54
+ if (steps.length >= 3)
55
+ confidence += 0.1; // a real procedure tends to have ≥3 steps
56
+ confidence = Math.min(confidence, 0.95);
57
+ return { kind: "stepper", confidence, consumed, data: { title, steps } };
58
+ };
@@ -0,0 +1,26 @@
1
+ import type { RootContent } from "mdast";
2
+ /** Confidence threshold above which a semantic component renders instead of plain markdown. */
3
+ export declare const CONFIDENCE_THRESHOLD = 0.6;
4
+ export type SemanticKind = "frontmatter" | "callout" | "stepper" | "comparison" | "faq" | "richTable" | "descriptionList";
5
+ /**
6
+ * A detector inspects a slice of top-level mdast nodes and either claims them
7
+ * (returning a hit) or declines (returning null → silent fallback to plain markdown).
8
+ * Detectors are pure: no I/O, no side effects. Fixture-testable in isolation.
9
+ */
10
+ export interface DetectorHit {
11
+ kind: SemanticKind;
12
+ /** 0..1. The gate renders a component only at or above CONFIDENCE_THRESHOLD. */
13
+ confidence: number;
14
+ /** How many consecutive top-level nodes (starting at the offered index) this hit consumes. */
15
+ consumed: number;
16
+ /** Parsed payload the matching component renders from. */
17
+ data: unknown;
18
+ /** True when an explicit `<!-- doc: KIND --> (legacy `viewright:` accepted)` hint forced this hit. */
19
+ forced?: boolean;
20
+ }
21
+ /**
22
+ * @param nodes full list of top-level nodes
23
+ * @param index current position to attempt a match at
24
+ * @param hint explicit component hint active at this position, if any
25
+ */
26
+ export type Detector = (nodes: RootContent[], index: number, hint: SemanticKind | null) => DetectorHit | null;
@@ -0,0 +1,2 @@
1
+ /** Confidence threshold above which a semantic component renders instead of plain markdown. */
2
+ export const CONFIDENCE_THRESHOLD = 0.6;
@@ -0,0 +1,12 @@
1
+ import type { Root, RootContent } from "mdast";
2
+ export interface Footnote {
3
+ num: number;
4
+ children: RootContent[];
5
+ }
6
+ export type FootnoteMap = Map<string, Footnote>;
7
+ /** Resolved footnote definitions for the current document, keyed by identifier. */
8
+ export declare const FootnotesContext: import("react").Context<FootnoteMap | null>;
9
+ /** Collect footnote definitions (top-level nodes) and number them in document order. */
10
+ export declare function collectFootnotes(tree: Root): FootnoteMap;
11
+ /** Heading slug matching GitHub's exact algorithm — so anchors match rendered GitHub READMEs. */
12
+ export declare function slug(text: string): string;
@@ -0,0 +1,23 @@
1
+ import { createContext } from "react";
2
+ /** Resolved footnote definitions for the current document, keyed by identifier. */
3
+ export const FootnotesContext = createContext(null);
4
+ /** Collect footnote definitions (top-level nodes) and number them in document order. */
5
+ export function collectFootnotes(tree) {
6
+ const map = new Map();
7
+ let n = 0;
8
+ for (const node of tree.children) {
9
+ if (node.type === "footnoteDefinition") {
10
+ const def = node;
11
+ if (!map.has(def.identifier)) {
12
+ n += 1;
13
+ map.set(def.identifier, { num: n, children: def.children ?? [] });
14
+ }
15
+ }
16
+ }
17
+ return map;
18
+ }
19
+ import { slug as ghSlug } from "github-slugger";
20
+ /** Heading slug matching GitHub's exact algorithm — so anchors match rendered GitHub READMEs. */
21
+ export function slug(text) {
22
+ return ghSlug(text);
23
+ }
@@ -0,0 +1,4 @@
1
+ import type { Root, Heading } from "mdast";
2
+ export type HeadingSlugMap = Map<Heading, string>;
3
+ export declare const HeadingSlugContext: import("react").Context<HeadingSlugMap | null>;
4
+ export declare function buildHeadingSlugs(tree: Root): HeadingSlugMap;
@@ -0,0 +1,19 @@
1
+ import { createContext } from "react";
2
+ import GithubSlugger from "github-slugger";
3
+ import { nodeText } from "./detectors/nodeText.js";
4
+ export const HeadingSlugContext = createContext(null);
5
+ export function buildHeadingSlugs(tree) {
6
+ const slugger = new GithubSlugger();
7
+ const map = new Map();
8
+ const walk = (nodes) => {
9
+ for (const n of nodes) {
10
+ if (n.type === "heading")
11
+ map.set(n, slugger.slug(nodeText(n)));
12
+ const kids = n.children;
13
+ if (kids)
14
+ walk(kids);
15
+ }
16
+ };
17
+ walk(tree.children);
18
+ return map;
19
+ }
@@ -0,0 +1,15 @@
1
+ export { MarkdownRenderer, HtmlRenderer, renderMarkdown, renderHtml } from "./renderers.js";
2
+ export type { MarkdownRendererProps, HtmlRendererProps } from "./renderers.js";
3
+ export { parseMarkdown, parseToTree, clearMarkdownParseCache, getMarkdownParseCacheEntryCount } from "./parseMarkdown.js";
4
+ export { safeLinkHref, safeImageHref, sanitizeHtml, sanitizeMarkdownHtml, isSafeUri } from "./security.js";
5
+ export { buildHeadingSlugs } from "./headingSlugs.js";
6
+ export { collectFootnotes } from "./footnotes.js";
7
+ export { formatCitation, formatBibliographyEntry, formatNames, formatYear, buildBibliographyMap } from "./citations/formatter.js";
8
+ export type { CslEntry, CslName, BibliographyEntry, CitationMatch } from "./citations/types.js";
9
+ export { buildBlocks } from "./detectors/index.js";
10
+ export type { Block } from "./detectors/index.js";
11
+ export type { Detector, DetectorHit, SemanticKind } from "./detectors/types.js";
12
+ export { remarkCitations } from "./remarkCitations.js";
13
+ export { remarkDefListAbbr } from "./remarkDefListAbbr.js";
14
+ export { remarkWikiLink } from "./remarkWikiLink.js";
15
+ export { resolveShikiLanguage, ensureShikiLanguage, getShikiHighlighter } from "./shikiHighlighter.js";
package/dist/index.js ADDED
@@ -0,0 +1,11 @@
1
+ export { MarkdownRenderer, HtmlRenderer, renderMarkdown, renderHtml } from "./renderers.js";
2
+ export { parseMarkdown, parseToTree, clearMarkdownParseCache, getMarkdownParseCacheEntryCount } from "./parseMarkdown.js";
3
+ export { safeLinkHref, safeImageHref, sanitizeHtml, sanitizeMarkdownHtml, isSafeUri } from "./security.js";
4
+ export { buildHeadingSlugs } from "./headingSlugs.js";
5
+ export { collectFootnotes } from "./footnotes.js";
6
+ export { formatCitation, formatBibliographyEntry, formatNames, formatYear, buildBibliographyMap } from "./citations/formatter.js";
7
+ export { buildBlocks } from "./detectors/index.js";
8
+ export { remarkCitations } from "./remarkCitations.js";
9
+ export { remarkDefListAbbr } from "./remarkDefListAbbr.js";
10
+ export { remarkWikiLink } from "./remarkWikiLink.js";
11
+ export { resolveShikiLanguage, ensureShikiLanguage, getShikiHighlighter } from "./shikiHighlighter.js";
@@ -0,0 +1,7 @@
1
+ import type { Root } from "mdast";
2
+ import { type Block } from "./detectors/index.js";
3
+ export declare function parseToTree(source: string): Root;
4
+ export declare function parseMarkdown(source: string): Block[];
5
+ export declare function clearMarkdownParseCache(): void;
6
+ export declare function getMarkdownParseCacheEntryCount(): number;
7
+ export type { Block } from "./detectors/index.js";
@@ -0,0 +1,39 @@
1
+ import { unified } from "unified";
2
+ import remarkFrontmatter from "remark-frontmatter";
3
+ import remarkGfm from "remark-gfm";
4
+ import remarkMath from "remark-math";
5
+ import remarkParse from "remark-parse";
6
+ import remarkSmartypants from "remark-smartypants";
7
+ import { buildBlocks } from "./detectors/index.js";
8
+ import { remarkCitations } from "./remarkCitations.js";
9
+ import { remarkDefListAbbr } from "./remarkDefListAbbr.js";
10
+ import { remarkWikiLink } from "./remarkWikiLink.js";
11
+ const processor = unified()
12
+ .use(remarkParse)
13
+ .use(remarkGfm)
14
+ .use(remarkFrontmatter, ["yaml"])
15
+ .use(remarkMath)
16
+ .use(remarkWikiLink)
17
+ .use(remarkDefListAbbr)
18
+ .use(remarkCitations)
19
+ .use(remarkSmartypants);
20
+ const cache = new Map();
21
+ const MAX_CACHE = 8;
22
+ export function parseToTree(source) {
23
+ const cached = cache.get(source);
24
+ if (cached) {
25
+ cache.delete(source);
26
+ cache.set(source, cached);
27
+ return cached;
28
+ }
29
+ const tree = processor.runSync(processor.parse(source));
30
+ cache.set(source, tree);
31
+ while (cache.size > MAX_CACHE)
32
+ cache.delete(cache.keys().next().value);
33
+ return tree;
34
+ }
35
+ export function parseMarkdown(source) {
36
+ return buildBlocks(parseToTree(source).children);
37
+ }
38
+ export function clearMarkdownParseCache() { cache.clear(); }
39
+ export function getMarkdownParseCacheEntryCount() { return cache.size; }
@@ -0,0 +1,13 @@
1
+ import type { Root } from "mdast";
2
+ export interface CitationNode {
3
+ type: "citation";
4
+ key: string;
5
+ locator?: string;
6
+ children: [];
7
+ }
8
+ export interface BibliographyNode {
9
+ type: "bibliography";
10
+ children: [];
11
+ }
12
+ /** Transform `[@key]` / `[@key p.12]` into citation nodes and `[bibliography]` markers into bibliography blocks. */
13
+ export declare function remarkCitations(): (tree: Root) => void;
@@ -0,0 +1,78 @@
1
+ const CITATION_RE = /\[(@([^\]\s]+)(?:\s+([^\]]+))?)\]/g;
2
+ const BIBLIOGRAPHY_RE = /^\s*\[bibliography\]\s*$/i;
3
+ /** Transform `[@key]` / `[@key p.12]` into citation nodes and `[bibliography]` markers into bibliography blocks. */
4
+ export function remarkCitations() {
5
+ return (tree) => {
6
+ tree.children = processTopLevel(tree.children);
7
+ };
8
+ }
9
+ function processTopLevel(children) {
10
+ const out = [];
11
+ for (const child of children) {
12
+ if (child.type === "paragraph" && isBibliographyMarker(child)) {
13
+ out.push({
14
+ type: "bibliography",
15
+ children: [],
16
+ position: child.position,
17
+ });
18
+ continue;
19
+ }
20
+ if ("children" in child && Array.isArray(child.children)) {
21
+ out.push({
22
+ ...child,
23
+ children: processChildren(child.children),
24
+ });
25
+ continue;
26
+ }
27
+ out.push(child);
28
+ }
29
+ return out;
30
+ }
31
+ function isBibliographyMarker(node) {
32
+ const text = (node.children ?? [])
33
+ .map((child) => (child.type === "text" ? child.value : ""))
34
+ .join("");
35
+ return BIBLIOGRAPHY_RE.test(text);
36
+ }
37
+ function processChildren(children) {
38
+ const out = [];
39
+ for (const child of children) {
40
+ if (child.type === "text" && typeof child.value === "string") {
41
+ const split = splitCitations(child.value);
42
+ if (split) {
43
+ out.push(...split);
44
+ continue;
45
+ }
46
+ }
47
+ if ("children" in child && Array.isArray(child.children)) {
48
+ out.push({
49
+ ...child,
50
+ children: processChildren(child.children),
51
+ });
52
+ continue;
53
+ }
54
+ out.push(child);
55
+ }
56
+ return out;
57
+ }
58
+ function splitCitations(value) {
59
+ const parts = [];
60
+ let lastIndex = 0;
61
+ let match = CITATION_RE.exec(value);
62
+ while (match !== null) {
63
+ if (match.index > lastIndex) {
64
+ parts.push({ type: "text", value: value.slice(lastIndex, match.index) });
65
+ }
66
+ const locator = match[3]?.trim();
67
+ parts.push({ type: "citation", key: match[2] ?? "", locator, children: [] });
68
+ lastIndex = match.index + match[0].length;
69
+ CITATION_RE.lastIndex = lastIndex;
70
+ match = CITATION_RE.exec(value);
71
+ }
72
+ if (parts.length === 0)
73
+ return null;
74
+ if (lastIndex < value.length) {
75
+ parts.push({ type: "text", value: value.slice(lastIndex) });
76
+ }
77
+ return parts;
78
+ }
@@ -0,0 +1,30 @@
1
+ import type { Root, RootContent, Text } from "mdast";
2
+ export interface DescriptionTerm {
3
+ type: "descriptionTerm";
4
+ children: RootContent[];
5
+ }
6
+ export interface DescriptionDetails {
7
+ type: "descriptionDetails";
8
+ children: RootContent[];
9
+ }
10
+ export interface DescriptionItem {
11
+ type: "descriptionItem";
12
+ terms: DescriptionTerm[];
13
+ details: DescriptionDetails[];
14
+ }
15
+ export interface DescriptionList {
16
+ type: "descriptionList";
17
+ children: DescriptionItem[];
18
+ }
19
+ export interface AbbreviationDefinition {
20
+ type: "abbreviationDefinition";
21
+ short: string;
22
+ long: string;
23
+ }
24
+ export interface Abbr {
25
+ type: "abbr";
26
+ title: string;
27
+ children: Text[];
28
+ }
29
+ /** Transform Pandoc-style definition lists and abbreviation definitions into custom mdast nodes. */
30
+ export declare function remarkDefListAbbr(): (tree: Root) => void;