Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 | 21x 21x | /**
* Content Serializer
*
* Converts Tiptap JSON page tree into markdown for agent prompts.
* Uses @tiptap/markdown's MarkdownManager for standard node serialization,
* with custom handling only for pageLink nodes.
*/
import { Node, type JSONContent } from '@tiptap/core';
import StarterKit from '@tiptap/starter-kit';
import Link from '@tiptap/extension-link';
import { MarkdownManager } from '@tiptap/markdown';
/**
* Minimal page shape needed for serialization.
*/
export interface PageForSerialization {
id: string;
parentPageId: string | null;
title: string;
content: string | null; // JSON string from Tiptap
sortOrder: number;
}
/**
* Server-side pageLink node — only needs schema definition + markdown rendering.
*/
const ServerPageLink = Node.create({
name: 'pageLink',
group: 'block',
atom: true,
addAttributes() {
return {
pageId: { default: null },
};
},
renderMarkdown(node: JSONContent) {
const pageId = (node.attrs?.pageId as string) ?? '';
return `[[page:${pageId}]]\n\n`;
},
});
let _manager: MarkdownManager | null = null;
function getManager(): MarkdownManager {
if (!_manager) {
_manager = new MarkdownManager({
extensions: [StarterKit, Link, ServerPageLink],
});
}
return _manager;
}
/**
* Convert a Tiptap JSON document to markdown.
*/
export function tiptapJsonToMarkdown(json: unknown): string {
if (!json || typeof json !== 'object') return '';
const doc = json as JSONContent;
if (doc.type !== 'doc' || !Array.isArray(doc.content)) return '';
return getManager().serialize(doc).trim();
}
/**
* Serialize an array of pages into a single markdown document.
* Pages are organized as a tree (root first, then children by sortOrder).
*
* Each page is marked with <!-- page:$id --> so the agent can reference them.
*/
export function serializePageTree(pages: PageForSerialization[]): string {
if (pages.length === 0) return '';
// Build parent→children map
const childrenMap = new Map<string | null, PageForSerialization[]>();
for (const page of pages) {
const parentKey = page.parentPageId;
if (!childrenMap.has(parentKey)) {
childrenMap.set(parentKey, []);
}
childrenMap.get(parentKey)!.push(page);
}
// Sort children by sortOrder
for (const children of childrenMap.values()) {
children.sort((a, b) => a.sortOrder - b.sortOrder);
}
// Render tree depth-first
const sections: string[] = [];
function renderPage(page: PageForSerialization, depth: number): void {
const headerPrefix = '#'.repeat(Math.min(depth + 1, 6));
let section = `<!-- page:${page.id} -->\n${headerPrefix} ${page.title}`;
if (page.content) {
try {
const parsed = JSON.parse(page.content);
const md = tiptapJsonToMarkdown(parsed);
if (md.trim()) {
section += `\n\n${md}`;
}
} catch {
// Invalid JSON — skip content
}
}
sections.push(section);
const children = childrenMap.get(page.id) ?? [];
for (const child of children) {
renderPage(child, depth + 1);
}
}
// Start from root pages (parentPageId is null)
const roots = childrenMap.get(null) ?? [];
for (const root of roots) {
renderPage(root, 1);
}
return sections.join('\n\n');
}
|