koguma 0.6.4 → 0.6.6
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.
- package/cli/index.ts +584 -54
- package/package.json +1 -1
- package/src/config/define.ts +3 -3
- package/src/db/migrate.ts +7 -1
- package/src/db/sql.ts +169 -0
- package/src/rich-text/index.ts +1 -0
- package/src/rich-text/koguma-to-lexical.ts +340 -0
- package/src/rich-text/markdown-to-koguma.ts +164 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* markdown-to-koguma.ts — Lightweight markdown → KogumaDocument converter.
|
|
3
|
+
*
|
|
4
|
+
* Designed for seed content (author-controlled markdown), not arbitrary user input.
|
|
5
|
+
* Supports: headings, paragraphs, bold, italic, code, links, lists, code blocks, HR.
|
|
6
|
+
*/
|
|
7
|
+
import type {
|
|
8
|
+
KogumaDocument,
|
|
9
|
+
KogumaBlockNode,
|
|
10
|
+
KogumaInlineNode,
|
|
11
|
+
KogumaListItem
|
|
12
|
+
} from '../config/types.ts';
|
|
13
|
+
|
|
14
|
+
// ── Inline parsing ──────────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
function parseInline(text: string): KogumaInlineNode[] {
|
|
17
|
+
const nodes: KogumaInlineNode[] = [];
|
|
18
|
+
// Regex: links, bold, italic, inline code
|
|
19
|
+
const re =
|
|
20
|
+
/\[([^\]]+)\]\(([^)]+)\)|`([^`]+)`|\*\*(.+?)\*\*|\*(.+?)\*|([^[`*]+)/g;
|
|
21
|
+
let match: RegExpExecArray | null;
|
|
22
|
+
|
|
23
|
+
while ((match = re.exec(text)) !== null) {
|
|
24
|
+
if (match[1] !== undefined && match[2] !== undefined) {
|
|
25
|
+
// Link
|
|
26
|
+
nodes.push({
|
|
27
|
+
type: 'link',
|
|
28
|
+
url: match[2],
|
|
29
|
+
children: [{ type: 'text', text: match[1] }]
|
|
30
|
+
});
|
|
31
|
+
} else if (match[3] !== undefined) {
|
|
32
|
+
// Inline code
|
|
33
|
+
nodes.push({ type: 'text', text: match[3], code: true });
|
|
34
|
+
} else if (match[4] !== undefined) {
|
|
35
|
+
// Bold
|
|
36
|
+
nodes.push({ type: 'text', text: match[4], bold: true });
|
|
37
|
+
} else if (match[5] !== undefined) {
|
|
38
|
+
// Italic
|
|
39
|
+
nodes.push({ type: 'text', text: match[5], italic: true });
|
|
40
|
+
} else if (match[6] !== undefined && match[6].length > 0) {
|
|
41
|
+
// Plain text
|
|
42
|
+
nodes.push({ type: 'text', text: match[6] });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return nodes.length > 0 ? nodes : [{ type: 'text', text }];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ── Block parsing ───────────────────────────────────────────────────
|
|
50
|
+
|
|
51
|
+
export function markdownToKoguma(markdown: string): KogumaDocument {
|
|
52
|
+
const lines = markdown.split('\n');
|
|
53
|
+
const nodes: KogumaBlockNode[] = [];
|
|
54
|
+
let i = 0;
|
|
55
|
+
|
|
56
|
+
while (i < lines.length) {
|
|
57
|
+
const line = lines[i]!;
|
|
58
|
+
|
|
59
|
+
// Blank line — skip
|
|
60
|
+
if (line.trim() === '') {
|
|
61
|
+
i++;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Fenced code block
|
|
66
|
+
const codeMatch = line.match(/^```(\w*)$/);
|
|
67
|
+
if (codeMatch) {
|
|
68
|
+
const language = codeMatch[1] || undefined;
|
|
69
|
+
const codeLines: string[] = [];
|
|
70
|
+
i++;
|
|
71
|
+
while (i < lines.length && !lines[i]!.startsWith('```')) {
|
|
72
|
+
codeLines.push(lines[i]!);
|
|
73
|
+
i++;
|
|
74
|
+
}
|
|
75
|
+
i++; // skip closing ```
|
|
76
|
+
nodes.push({ type: 'code', language, text: codeLines.join('\n') });
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Heading
|
|
81
|
+
const headingMatch = line.match(/^(#{1,6})\s+(.+)$/);
|
|
82
|
+
if (headingMatch) {
|
|
83
|
+
const level = headingMatch[1]!.length as 1 | 2 | 3 | 4 | 5 | 6;
|
|
84
|
+
nodes.push({
|
|
85
|
+
type: 'heading',
|
|
86
|
+
level,
|
|
87
|
+
children: parseInline(headingMatch[2]!)
|
|
88
|
+
});
|
|
89
|
+
i++;
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Horizontal rule
|
|
94
|
+
if (/^(-{3,}|\*{3,}|_{3,})$/.test(line.trim())) {
|
|
95
|
+
nodes.push({ type: 'hr' });
|
|
96
|
+
i++;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Unordered list
|
|
101
|
+
if (/^[-*+]\s/.test(line)) {
|
|
102
|
+
const items: KogumaListItem[] = [];
|
|
103
|
+
while (i < lines.length && /^[-*+]\s/.test(lines[i]!)) {
|
|
104
|
+
items.push({
|
|
105
|
+
children: parseInline(lines[i]!.replace(/^[-*+]\s/, ''))
|
|
106
|
+
});
|
|
107
|
+
i++;
|
|
108
|
+
}
|
|
109
|
+
nodes.push({ type: 'list', ordered: false, items });
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Ordered list
|
|
114
|
+
if (/^\d+\.\s/.test(line)) {
|
|
115
|
+
const items: KogumaListItem[] = [];
|
|
116
|
+
while (i < lines.length && /^\d+\.\s/.test(lines[i]!)) {
|
|
117
|
+
items.push({
|
|
118
|
+
children: parseInline(lines[i]!.replace(/^\d+\.\s/, ''))
|
|
119
|
+
});
|
|
120
|
+
i++;
|
|
121
|
+
}
|
|
122
|
+
nodes.push({ type: 'list', ordered: true, items });
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Blockquote
|
|
127
|
+
if (line.startsWith('> ')) {
|
|
128
|
+
const quoteLines: string[] = [];
|
|
129
|
+
while (i < lines.length && lines[i]!.startsWith('> ')) {
|
|
130
|
+
quoteLines.push(lines[i]!.slice(2));
|
|
131
|
+
i++;
|
|
132
|
+
}
|
|
133
|
+
nodes.push({
|
|
134
|
+
type: 'quote',
|
|
135
|
+
children: parseInline(quoteLines.join(' '))
|
|
136
|
+
});
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Paragraph (default)
|
|
141
|
+
const paraLines: string[] = [];
|
|
142
|
+
while (
|
|
143
|
+
i < lines.length &&
|
|
144
|
+
lines[i]!.trim() !== '' &&
|
|
145
|
+
!lines[i]!.startsWith('#') &&
|
|
146
|
+
!lines[i]!.startsWith('```') &&
|
|
147
|
+
!/^[-*+]\s/.test(lines[i]!) &&
|
|
148
|
+
!/^\d+\.\s/.test(lines[i]!) &&
|
|
149
|
+
!lines[i]!.startsWith('> ') &&
|
|
150
|
+
!/^(-{3,}|\*{3,}|_{3,})$/.test(lines[i]!.trim())
|
|
151
|
+
) {
|
|
152
|
+
paraLines.push(lines[i]!);
|
|
153
|
+
i++;
|
|
154
|
+
}
|
|
155
|
+
if (paraLines.length > 0) {
|
|
156
|
+
nodes.push({
|
|
157
|
+
type: 'paragraph',
|
|
158
|
+
children: parseInline(paraLines.join(' '))
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return { nodes };
|
|
164
|
+
}
|