wikimem 0.8.4 → 0.8.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/README.md +1 -0
- package/dist/cli/commands/publish.d.ts +3 -0
- package/dist/cli/commands/publish.d.ts.map +1 -0
- package/dist/cli/commands/publish.js +83 -0
- package/dist/cli/commands/publish.js.map +1 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +2 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/core/claude-code.d.ts.map +1 -1
- package/dist/core/claude-code.js +2 -1
- package/dist/core/claude-code.js.map +1 -1
- package/dist/core/improve.d.ts.map +1 -1
- package/dist/core/improve.js +8 -0
- package/dist/core/improve.js.map +1 -1
- package/dist/core/ingest.d.ts.map +1 -1
- package/dist/core/ingest.js +8 -0
- package/dist/core/ingest.js.map +1 -1
- package/dist/core/observer.d.ts.map +1 -1
- package/dist/core/observer.js +8 -0
- package/dist/core/observer.js.map +1 -1
- package/dist/core/publish.d.ts +20 -0
- package/dist/core/publish.d.ts.map +1 -0
- package/dist/core/publish.js +760 -0
- package/dist/core/publish.js.map +1 -0
- package/dist/core/scrape.d.ts.map +1 -1
- package/dist/core/scrape.js +10 -0
- package/dist/core/scrape.js.map +1 -1
- package/dist/mcp-entry.js +0 -0
- package/package.json +1 -1
|
@@ -0,0 +1,760 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content Distribution Engine — generates static site, RSS, JSON feed, and digest
|
|
3
|
+
* from wiki content. The outbound complement to ingest/scrape/improve.
|
|
4
|
+
*/
|
|
5
|
+
import { mkdirSync, writeFileSync, statSync } from 'node:fs';
|
|
6
|
+
import { join, basename, extname, relative } from 'node:path';
|
|
7
|
+
import { getVaultConfig, listWikiPages, readWikiPage, getVaultStats } from './vault.js';
|
|
8
|
+
// ── Main publish function ──────────────────────────────────────────────────
|
|
9
|
+
export function publishWiki(vaultRoot, options) {
|
|
10
|
+
const config = getVaultConfig(vaultRoot);
|
|
11
|
+
const pages = listWikiPages(config.wikiDir);
|
|
12
|
+
if (pages.length === 0) {
|
|
13
|
+
return { pagesPublished: 0, filesWritten: 0, outputDir: options.outDir, formats: [] };
|
|
14
|
+
}
|
|
15
|
+
mkdirSync(options.outDir, { recursive: true });
|
|
16
|
+
// Parse all pages
|
|
17
|
+
const pageMetas = [];
|
|
18
|
+
for (const pagePath of pages) {
|
|
19
|
+
try {
|
|
20
|
+
const page = readWikiPage(pagePath);
|
|
21
|
+
const slug = basename(pagePath, extname(pagePath));
|
|
22
|
+
const category = page.frontmatter['type']
|
|
23
|
+
?? page.frontmatter['category']
|
|
24
|
+
?? inferCategory(pagePath, config);
|
|
25
|
+
const summary = page.frontmatter['summary']
|
|
26
|
+
?? page.content.split(/[.!?]\s/)[0]?.substring(0, 160)
|
|
27
|
+
?? '';
|
|
28
|
+
const updated = getUpdatedDate(page, pagePath);
|
|
29
|
+
pageMetas.push({
|
|
30
|
+
slug,
|
|
31
|
+
title: page.title,
|
|
32
|
+
category,
|
|
33
|
+
summary,
|
|
34
|
+
wordCount: page.wordCount,
|
|
35
|
+
updated,
|
|
36
|
+
content: page.content,
|
|
37
|
+
wikilinks: page.wikilinks,
|
|
38
|
+
path: relative(config.wikiDir, pagePath),
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// Skip unreadable pages
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// Sort by updated date (newest first)
|
|
46
|
+
pageMetas.sort((a, b) => b.updated.localeCompare(a.updated));
|
|
47
|
+
let filesWritten = 0;
|
|
48
|
+
const formatsWritten = [];
|
|
49
|
+
for (const fmt of options.format) {
|
|
50
|
+
switch (fmt) {
|
|
51
|
+
case 'html': {
|
|
52
|
+
const count = generateStaticSite(pageMetas, options, config);
|
|
53
|
+
filesWritten += count;
|
|
54
|
+
formatsWritten.push('html');
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
case 'rss': {
|
|
58
|
+
generateRssFeed(pageMetas, options);
|
|
59
|
+
filesWritten++;
|
|
60
|
+
formatsWritten.push('rss');
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
case 'json-feed': {
|
|
64
|
+
generateJsonFeed(pageMetas, options);
|
|
65
|
+
filesWritten++;
|
|
66
|
+
formatsWritten.push('json-feed');
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
case 'digest': {
|
|
70
|
+
generateDigest(pageMetas, options, config);
|
|
71
|
+
filesWritten++;
|
|
72
|
+
formatsWritten.push('digest');
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
pagesPublished: pageMetas.length,
|
|
79
|
+
filesWritten,
|
|
80
|
+
outputDir: options.outDir,
|
|
81
|
+
formats: formatsWritten,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
// ── Static HTML Site ───────────────────────────────────────────────────────
|
|
85
|
+
function generateStaticSite(pages, options, config) {
|
|
86
|
+
const pagesDir = join(options.outDir, 'pages');
|
|
87
|
+
mkdirSync(pagesDir, { recursive: true });
|
|
88
|
+
const titleMap = new Map(pages.map((p) => [p.title, p.slug]));
|
|
89
|
+
const titleMapLower = new Map(pages.map((p) => [p.title.toLowerCase(), p.slug]));
|
|
90
|
+
let count = 0;
|
|
91
|
+
// Generate individual pages
|
|
92
|
+
for (const page of pages) {
|
|
93
|
+
const html = renderPageHtml(page, options, titleMap, titleMapLower);
|
|
94
|
+
writeFileSync(join(pagesDir, `${page.slug}.html`), html, 'utf-8');
|
|
95
|
+
count++;
|
|
96
|
+
}
|
|
97
|
+
// Generate index
|
|
98
|
+
const indexHtml = renderIndexHtml(pages, options, config);
|
|
99
|
+
writeFileSync(join(options.outDir, 'index.html'), indexHtml, 'utf-8');
|
|
100
|
+
count++;
|
|
101
|
+
// Generate CSS
|
|
102
|
+
writeFileSync(join(options.outDir, 'style.css'), getStylesheet(), 'utf-8');
|
|
103
|
+
count++;
|
|
104
|
+
return count;
|
|
105
|
+
}
|
|
106
|
+
function renderPageHtml(page, options, titleMap, titleMapLower) {
|
|
107
|
+
const renderedContent = markdownToHtml(page.content, titleMap, titleMapLower);
|
|
108
|
+
const categoryLabel = page.category.charAt(0).toUpperCase() + page.category.slice(1);
|
|
109
|
+
return `<!DOCTYPE html>
|
|
110
|
+
<html lang="en">
|
|
111
|
+
<head>
|
|
112
|
+
<meta charset="UTF-8">
|
|
113
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
114
|
+
<title>${escapeHtml(page.title)} — ${escapeHtml(options.title)}</title>
|
|
115
|
+
<meta name="description" content="${escapeHtml(page.summary)}">
|
|
116
|
+
<link rel="stylesheet" href="../style.css">
|
|
117
|
+
<link rel="alternate" type="application/rss+xml" title="${escapeHtml(options.title)}" href="../feed.xml">
|
|
118
|
+
</head>
|
|
119
|
+
<body>
|
|
120
|
+
<nav class="topbar">
|
|
121
|
+
<a href="../index.html" class="site-title">${escapeHtml(options.title)}</a>
|
|
122
|
+
<span class="breadcrumb">${escapeHtml(categoryLabel)} / ${escapeHtml(page.title)}</span>
|
|
123
|
+
</nav>
|
|
124
|
+
<main class="page-content">
|
|
125
|
+
<article>
|
|
126
|
+
<header class="page-header">
|
|
127
|
+
<span class="category-badge category-${page.category}">${escapeHtml(categoryLabel)}</span>
|
|
128
|
+
<h1>${escapeHtml(page.title)}</h1>
|
|
129
|
+
<div class="page-meta">
|
|
130
|
+
<span>${page.wordCount.toLocaleString()} words</span>
|
|
131
|
+
<span>Updated ${page.updated}</span>
|
|
132
|
+
${page.wikilinks.length > 0 ? `<span>${page.wikilinks.length} links</span>` : ''}
|
|
133
|
+
</div>
|
|
134
|
+
</header>
|
|
135
|
+
<div class="prose">
|
|
136
|
+
${renderedContent}
|
|
137
|
+
</div>
|
|
138
|
+
${page.wikilinks.length > 0 ? renderRelatedLinks(page.wikilinks, titleMap, titleMapLower) : ''}
|
|
139
|
+
</article>
|
|
140
|
+
</main>
|
|
141
|
+
<footer class="site-footer">
|
|
142
|
+
<p>Built with <a href="https://www.npmjs.com/package/wikimem">wikimem</a></p>
|
|
143
|
+
</footer>
|
|
144
|
+
</body>
|
|
145
|
+
</html>`;
|
|
146
|
+
}
|
|
147
|
+
function renderRelatedLinks(wikilinks, titleMap, titleMapLower) {
|
|
148
|
+
const links = wikilinks.map((link) => {
|
|
149
|
+
const slug = titleMap.get(link) ?? titleMapLower.get(link.toLowerCase());
|
|
150
|
+
if (slug) {
|
|
151
|
+
return `<a href="${slug}.html">${escapeHtml(link)}</a>`;
|
|
152
|
+
}
|
|
153
|
+
return `<span class="broken-link">${escapeHtml(link)}</span>`;
|
|
154
|
+
});
|
|
155
|
+
return `<aside class="related-pages">
|
|
156
|
+
<h3>Related Pages</h3>
|
|
157
|
+
<div class="related-grid">${links.join('')}</div>
|
|
158
|
+
</aside>`;
|
|
159
|
+
}
|
|
160
|
+
function renderIndexHtml(pages, options, config) {
|
|
161
|
+
const stats = getVaultStats(config);
|
|
162
|
+
const grouped = groupByCategory(pages);
|
|
163
|
+
const sections = Object.entries(grouped)
|
|
164
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
165
|
+
.map(([category, categoryPages]) => {
|
|
166
|
+
const label = category.charAt(0).toUpperCase() + category.slice(1);
|
|
167
|
+
const items = categoryPages
|
|
168
|
+
.map((p) => `<li>
|
|
169
|
+
<a href="pages/${p.slug}.html">${escapeHtml(p.title)}</a>
|
|
170
|
+
<span class="item-meta">${p.wordCount} words</span>
|
|
171
|
+
</li>`)
|
|
172
|
+
.join('\n');
|
|
173
|
+
return `<section class="category-section">
|
|
174
|
+
<h2><span class="category-badge category-${category}">${escapeHtml(label)}</span> ${escapeHtml(label)} <span class="count">(${categoryPages.length})</span></h2>
|
|
175
|
+
<ul>${items}</ul>
|
|
176
|
+
</section>`;
|
|
177
|
+
})
|
|
178
|
+
.join('\n');
|
|
179
|
+
return `<!DOCTYPE html>
|
|
180
|
+
<html lang="en">
|
|
181
|
+
<head>
|
|
182
|
+
<meta charset="UTF-8">
|
|
183
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
184
|
+
<title>${escapeHtml(options.title)}</title>
|
|
185
|
+
<meta name="description" content="${escapeHtml(options.description)}">
|
|
186
|
+
<link rel="stylesheet" href="style.css">
|
|
187
|
+
<link rel="alternate" type="application/rss+xml" title="${escapeHtml(options.title)}" href="feed.xml">
|
|
188
|
+
<link rel="alternate" type="application/feed+json" title="${escapeHtml(options.title)}" href="feed.json">
|
|
189
|
+
</head>
|
|
190
|
+
<body>
|
|
191
|
+
<nav class="topbar">
|
|
192
|
+
<a href="index.html" class="site-title">${escapeHtml(options.title)}</a>
|
|
193
|
+
</nav>
|
|
194
|
+
<main class="index-content">
|
|
195
|
+
<header class="hero">
|
|
196
|
+
<h1>${escapeHtml(options.title)}</h1>
|
|
197
|
+
<p class="hero-desc">${escapeHtml(options.description)}</p>
|
|
198
|
+
<div class="stats-bar">
|
|
199
|
+
<span>${stats.pageCount} pages</span>
|
|
200
|
+
<span>${stats.wordCount.toLocaleString()} words</span>
|
|
201
|
+
<span>${stats.sourceCount} sources</span>
|
|
202
|
+
<span>${stats.wikilinks} links</span>
|
|
203
|
+
</div>
|
|
204
|
+
</header>
|
|
205
|
+
${sections}
|
|
206
|
+
</main>
|
|
207
|
+
<footer class="site-footer">
|
|
208
|
+
<p>Built with <a href="https://www.npmjs.com/package/wikimem">wikimem</a></p>
|
|
209
|
+
<p class="feed-links">
|
|
210
|
+
<a href="feed.xml">RSS Feed</a> · <a href="feed.json">JSON Feed</a>
|
|
211
|
+
</p>
|
|
212
|
+
</footer>
|
|
213
|
+
</body>
|
|
214
|
+
</html>`;
|
|
215
|
+
}
|
|
216
|
+
// ── RSS Feed ───────────────────────────────────────────────────────────────
|
|
217
|
+
function generateRssFeed(pages, options) {
|
|
218
|
+
const recent = pages.slice(0, 50);
|
|
219
|
+
const now = new Date().toUTCString();
|
|
220
|
+
const baseUrl = options.baseUrl.replace(/\/$/, '');
|
|
221
|
+
const items = recent
|
|
222
|
+
.map((p) => ` <item>
|
|
223
|
+
<title>${xmlEscape(p.title)}</title>
|
|
224
|
+
<link>${xmlEscape(baseUrl)}/pages/${p.slug}.html</link>
|
|
225
|
+
<guid isPermaLink="true">${xmlEscape(baseUrl)}/pages/${p.slug}.html</guid>
|
|
226
|
+
<description>${xmlEscape(p.summary)}</description>
|
|
227
|
+
<category>${xmlEscape(p.category)}</category>
|
|
228
|
+
<pubDate>${new Date(p.updated).toUTCString()}</pubDate>
|
|
229
|
+
</item>`)
|
|
230
|
+
.join('\n');
|
|
231
|
+
const rss = `<?xml version="1.0" encoding="UTF-8"?>
|
|
232
|
+
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
|
|
233
|
+
<channel>
|
|
234
|
+
<title>${xmlEscape(options.title)}</title>
|
|
235
|
+
<link>${xmlEscape(baseUrl)}</link>
|
|
236
|
+
<description>${xmlEscape(options.description)}</description>
|
|
237
|
+
<language>en</language>
|
|
238
|
+
<lastBuildDate>${now}</lastBuildDate>
|
|
239
|
+
<atom:link href="${xmlEscape(baseUrl)}/feed.xml" rel="self" type="application/rss+xml"/>
|
|
240
|
+
<generator>wikimem</generator>
|
|
241
|
+
${items}
|
|
242
|
+
</channel>
|
|
243
|
+
</rss>`;
|
|
244
|
+
writeFileSync(join(options.outDir, 'feed.xml'), rss, 'utf-8');
|
|
245
|
+
}
|
|
246
|
+
// ── JSON Feed ──────────────────────────────────────────────────────────────
|
|
247
|
+
function generateJsonFeed(pages, options) {
|
|
248
|
+
const recent = pages.slice(0, 50);
|
|
249
|
+
const baseUrl = options.baseUrl.replace(/\/$/, '');
|
|
250
|
+
const feed = {
|
|
251
|
+
version: 'https://jsonfeed.org/version/1.1',
|
|
252
|
+
title: options.title,
|
|
253
|
+
home_page_url: baseUrl,
|
|
254
|
+
feed_url: `${baseUrl}/feed.json`,
|
|
255
|
+
description: options.description,
|
|
256
|
+
authors: [{ name: options.author }],
|
|
257
|
+
items: recent.map((p) => ({
|
|
258
|
+
id: `${baseUrl}/pages/${p.slug}.html`,
|
|
259
|
+
url: `${baseUrl}/pages/${p.slug}.html`,
|
|
260
|
+
title: p.title,
|
|
261
|
+
summary: p.summary,
|
|
262
|
+
date_modified: new Date(p.updated).toISOString(),
|
|
263
|
+
tags: [p.category],
|
|
264
|
+
content_text: p.content.substring(0, 2000),
|
|
265
|
+
})),
|
|
266
|
+
};
|
|
267
|
+
writeFileSync(join(options.outDir, 'feed.json'), JSON.stringify(feed, null, 2), 'utf-8');
|
|
268
|
+
}
|
|
269
|
+
// ── Digest ─────────────────────────────────────────────────────────────────
|
|
270
|
+
function generateDigest(pages, options, config) {
|
|
271
|
+
const stats = getVaultStats(config);
|
|
272
|
+
const now = new Date().toISOString().split('T')[0] ?? '';
|
|
273
|
+
// Group recent changes (last 7 days)
|
|
274
|
+
const weekAgo = new Date();
|
|
275
|
+
weekAgo.setDate(weekAgo.getDate() - 7);
|
|
276
|
+
const recent = pages.filter((p) => new Date(p.updated) >= weekAgo);
|
|
277
|
+
const grouped = groupByCategory(recent);
|
|
278
|
+
let md = `# ${options.title} — Weekly Digest\n\n`;
|
|
279
|
+
md += `**${now}** · ${stats.pageCount} pages · ${stats.wordCount.toLocaleString()} words · ${stats.wikilinks} links\n\n`;
|
|
280
|
+
if (recent.length === 0) {
|
|
281
|
+
md += 'No changes in the last 7 days.\n';
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
md += `## ${recent.length} pages updated this week\n\n`;
|
|
285
|
+
for (const [category, categoryPages] of Object.entries(grouped).sort(([a], [b]) => a.localeCompare(b))) {
|
|
286
|
+
const label = category.charAt(0).toUpperCase() + category.slice(1);
|
|
287
|
+
md += `### ${label}\n\n`;
|
|
288
|
+
for (const p of categoryPages) {
|
|
289
|
+
md += `- **${p.title}** — ${p.summary || `${p.wordCount} words`}\n`;
|
|
290
|
+
}
|
|
291
|
+
md += '\n';
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
// Top connected pages
|
|
295
|
+
const topLinked = [...pages]
|
|
296
|
+
.sort((a, b) => b.wikilinks.length - a.wikilinks.length)
|
|
297
|
+
.slice(0, 10);
|
|
298
|
+
md += `## Most Connected Pages\n\n`;
|
|
299
|
+
for (const p of topLinked) {
|
|
300
|
+
md += `- **${p.title}** — ${p.wikilinks.length} outbound links\n`;
|
|
301
|
+
}
|
|
302
|
+
md += `\n---\n*Generated by [wikimem](https://www.npmjs.com/package/wikimem)*\n`;
|
|
303
|
+
writeFileSync(join(options.outDir, 'digest.md'), md, 'utf-8');
|
|
304
|
+
// Also write HTML version
|
|
305
|
+
const htmlDigest = `<!DOCTYPE html>
|
|
306
|
+
<html lang="en">
|
|
307
|
+
<head>
|
|
308
|
+
<meta charset="UTF-8">
|
|
309
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
310
|
+
<title>${escapeHtml(options.title)} — Digest</title>
|
|
311
|
+
<link rel="stylesheet" href="style.css">
|
|
312
|
+
</head>
|
|
313
|
+
<body>
|
|
314
|
+
<nav class="topbar">
|
|
315
|
+
<a href="index.html" class="site-title">${escapeHtml(options.title)}</a>
|
|
316
|
+
<span class="breadcrumb">Digest</span>
|
|
317
|
+
</nav>
|
|
318
|
+
<main class="page-content">
|
|
319
|
+
<article class="prose">
|
|
320
|
+
${markdownToHtml(md, new Map(), new Map())}
|
|
321
|
+
</article>
|
|
322
|
+
</main>
|
|
323
|
+
<footer class="site-footer">
|
|
324
|
+
<p>Built with <a href="https://www.npmjs.com/package/wikimem">wikimem</a></p>
|
|
325
|
+
</footer>
|
|
326
|
+
</body>
|
|
327
|
+
</html>`;
|
|
328
|
+
writeFileSync(join(options.outDir, 'digest.html'), htmlDigest, 'utf-8');
|
|
329
|
+
}
|
|
330
|
+
// ── Helpers ────────────────────────────────────────────────────────────────
|
|
331
|
+
function inferCategory(pagePath, config) {
|
|
332
|
+
const rel = relative(config.wikiDir, pagePath);
|
|
333
|
+
const dir = rel.split('/')[0] ?? '';
|
|
334
|
+
if (['sources', 'entities', 'concepts', 'syntheses'].includes(dir))
|
|
335
|
+
return dir.replace(/s$/, '');
|
|
336
|
+
return 'page';
|
|
337
|
+
}
|
|
338
|
+
function getUpdatedDate(page, filePath) {
|
|
339
|
+
const fmDate = page.frontmatter['updated']
|
|
340
|
+
?? page.frontmatter['created'];
|
|
341
|
+
if (fmDate)
|
|
342
|
+
return fmDate;
|
|
343
|
+
try {
|
|
344
|
+
return statSync(filePath).mtime.toISOString().split('T')[0] ?? '';
|
|
345
|
+
}
|
|
346
|
+
catch {
|
|
347
|
+
return new Date().toISOString().split('T')[0] ?? '';
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
function groupByCategory(pages) {
|
|
351
|
+
const grouped = {};
|
|
352
|
+
for (const p of pages) {
|
|
353
|
+
const key = p.category;
|
|
354
|
+
if (!grouped[key])
|
|
355
|
+
grouped[key] = [];
|
|
356
|
+
grouped[key].push(p);
|
|
357
|
+
}
|
|
358
|
+
return grouped;
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Minimal markdown → HTML converter. Handles headings, paragraphs, bold,
|
|
362
|
+
* italic, links, code, lists, blockquotes, and wikilinks. No external deps.
|
|
363
|
+
*/
|
|
364
|
+
function markdownToHtml(md, titleMap, titleMapLower) {
|
|
365
|
+
let html = md;
|
|
366
|
+
// Wikilinks → real links
|
|
367
|
+
html = html.replace(/\[\[([^\]]+)\]\]/g, (_, title) => {
|
|
368
|
+
const slug = titleMap.get(title) ?? titleMapLower.get(title.toLowerCase());
|
|
369
|
+
if (slug) {
|
|
370
|
+
return `<a href="${slug}.html" class="wiki-link">${escapeHtml(title)}</a>`;
|
|
371
|
+
}
|
|
372
|
+
return `<span class="wiki-link broken">${escapeHtml(title)}</span>`;
|
|
373
|
+
});
|
|
374
|
+
// Fenced code blocks
|
|
375
|
+
html = html.replace(/```(\w*)\n([\s\S]*?)```/g, (_, lang, code) => {
|
|
376
|
+
return `<pre><code class="language-${lang || 'text'}">${escapeHtml(code.trim())}</code></pre>`;
|
|
377
|
+
});
|
|
378
|
+
// Inline code
|
|
379
|
+
html = html.replace(/`([^`]+)`/g, '<code>$1</code>');
|
|
380
|
+
// Headings
|
|
381
|
+
html = html.replace(/^#### (.+)$/gm, '<h4>$1</h4>');
|
|
382
|
+
html = html.replace(/^### (.+)$/gm, '<h3>$1</h3>');
|
|
383
|
+
html = html.replace(/^## (.+)$/gm, '<h2>$1</h2>');
|
|
384
|
+
html = html.replace(/^# (.+)$/gm, '<h1>$1</h1>');
|
|
385
|
+
// Bold + italic
|
|
386
|
+
html = html.replace(/\*\*\*(.+?)\*\*\*/g, '<strong><em>$1</em></strong>');
|
|
387
|
+
html = html.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
|
|
388
|
+
html = html.replace(/\*(.+?)\*/g, '<em>$1</em>');
|
|
389
|
+
// Links
|
|
390
|
+
html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>');
|
|
391
|
+
// Horizontal rules
|
|
392
|
+
html = html.replace(/^---+$/gm, '<hr>');
|
|
393
|
+
// Blockquotes
|
|
394
|
+
html = html.replace(/^> (.+)$/gm, '<blockquote>$1</blockquote>');
|
|
395
|
+
// Unordered lists
|
|
396
|
+
html = html.replace(/^- (.+)$/gm, '<li>$1</li>');
|
|
397
|
+
html = html.replace(/(<li>[\s\S]*?<\/li>)/g, (match) => {
|
|
398
|
+
if (!match.startsWith('<ul>'))
|
|
399
|
+
return `<ul>${match}</ul>`;
|
|
400
|
+
return match;
|
|
401
|
+
});
|
|
402
|
+
// Wrap remaining lines in paragraphs (skip already-wrapped content)
|
|
403
|
+
const lines = html.split('\n');
|
|
404
|
+
const result = [];
|
|
405
|
+
for (const line of lines) {
|
|
406
|
+
const trimmed = line.trim();
|
|
407
|
+
if (!trimmed) {
|
|
408
|
+
result.push('');
|
|
409
|
+
}
|
|
410
|
+
else if (trimmed.startsWith('<h') ||
|
|
411
|
+
trimmed.startsWith('<pre') ||
|
|
412
|
+
trimmed.startsWith('<ul') ||
|
|
413
|
+
trimmed.startsWith('<li') ||
|
|
414
|
+
trimmed.startsWith('<blockquote') ||
|
|
415
|
+
trimmed.startsWith('<hr') ||
|
|
416
|
+
trimmed.startsWith('<aside') ||
|
|
417
|
+
trimmed.startsWith('<div') ||
|
|
418
|
+
trimmed.startsWith('<article') ||
|
|
419
|
+
trimmed.startsWith('<nav') ||
|
|
420
|
+
trimmed.startsWith('<footer') ||
|
|
421
|
+
trimmed.startsWith('<header') ||
|
|
422
|
+
trimmed.startsWith('<section')) {
|
|
423
|
+
result.push(trimmed);
|
|
424
|
+
}
|
|
425
|
+
else {
|
|
426
|
+
result.push(`<p>${trimmed}</p>`);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
return result.join('\n');
|
|
430
|
+
}
|
|
431
|
+
function escapeHtml(s) {
|
|
432
|
+
return s
|
|
433
|
+
.replace(/&/g, '&')
|
|
434
|
+
.replace(/</g, '<')
|
|
435
|
+
.replace(/>/g, '>')
|
|
436
|
+
.replace(/"/g, '"');
|
|
437
|
+
}
|
|
438
|
+
function xmlEscape(s) {
|
|
439
|
+
return escapeHtml(s).replace(/'/g, ''');
|
|
440
|
+
}
|
|
441
|
+
// ── Stylesheet ─────────────────────────────────────────────────────────────
|
|
442
|
+
function getStylesheet() {
|
|
443
|
+
return `*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
444
|
+
|
|
445
|
+
:root {
|
|
446
|
+
--bg: #1a1a2e;
|
|
447
|
+
--bg-surface: #16213e;
|
|
448
|
+
--bg-card: #1e2a47;
|
|
449
|
+
--bg-hover: #243352;
|
|
450
|
+
--border: #2a3a5c;
|
|
451
|
+
--text: #e0e0e0;
|
|
452
|
+
--text-bright: #f0f0f0;
|
|
453
|
+
--text-secondary: #a0a8c0;
|
|
454
|
+
--text-dim: #6b7394;
|
|
455
|
+
--accent: #4f9eff;
|
|
456
|
+
--accent-hover: #6db3ff;
|
|
457
|
+
--green: #4ec9b0;
|
|
458
|
+
--amber: #d7ba7d;
|
|
459
|
+
--purple: #9c78d8;
|
|
460
|
+
--orange: #ce9178;
|
|
461
|
+
--red: #f14c4c;
|
|
462
|
+
--font: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
|
|
463
|
+
--font-mono: 'JetBrains Mono', 'Fira Code', monospace;
|
|
464
|
+
--font-display: 'Instrument Serif', Georgia, serif;
|
|
465
|
+
--radius: 6px;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
body {
|
|
469
|
+
font-family: var(--font);
|
|
470
|
+
background: var(--bg);
|
|
471
|
+
color: var(--text);
|
|
472
|
+
line-height: 1.7;
|
|
473
|
+
min-height: 100vh;
|
|
474
|
+
display: flex;
|
|
475
|
+
flex-direction: column;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
.topbar {
|
|
479
|
+
background: var(--bg-surface);
|
|
480
|
+
border-bottom: 1px solid var(--border);
|
|
481
|
+
padding: 12px 24px;
|
|
482
|
+
display: flex;
|
|
483
|
+
align-items: center;
|
|
484
|
+
gap: 16px;
|
|
485
|
+
position: sticky;
|
|
486
|
+
top: 0;
|
|
487
|
+
z-index: 10;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
.site-title {
|
|
491
|
+
font-family: var(--font-display);
|
|
492
|
+
font-size: 1.25rem;
|
|
493
|
+
color: var(--text-bright);
|
|
494
|
+
text-decoration: none;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
.breadcrumb {
|
|
498
|
+
color: var(--text-dim);
|
|
499
|
+
font-size: 0.85rem;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
.hero {
|
|
503
|
+
text-align: center;
|
|
504
|
+
padding: 64px 24px 48px;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
.hero h1 {
|
|
508
|
+
font-family: var(--font-display);
|
|
509
|
+
font-size: 2.5rem;
|
|
510
|
+
color: var(--text-bright);
|
|
511
|
+
margin-bottom: 12px;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
.hero-desc {
|
|
515
|
+
color: var(--text-secondary);
|
|
516
|
+
font-size: 1.1rem;
|
|
517
|
+
max-width: 600px;
|
|
518
|
+
margin: 0 auto 24px;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
.stats-bar {
|
|
522
|
+
display: flex;
|
|
523
|
+
justify-content: center;
|
|
524
|
+
gap: 24px;
|
|
525
|
+
font-size: 0.85rem;
|
|
526
|
+
color: var(--text-dim);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
.stats-bar span {
|
|
530
|
+
background: var(--bg-card);
|
|
531
|
+
padding: 4px 12px;
|
|
532
|
+
border-radius: var(--radius);
|
|
533
|
+
border: 1px solid var(--border);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
.index-content, .page-content {
|
|
537
|
+
max-width: 860px;
|
|
538
|
+
margin: 0 auto;
|
|
539
|
+
padding: 32px 24px;
|
|
540
|
+
flex: 1;
|
|
541
|
+
width: 100%;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
.category-section {
|
|
545
|
+
margin-bottom: 32px;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
.category-section h2 {
|
|
549
|
+
font-size: 1.2rem;
|
|
550
|
+
color: var(--text-bright);
|
|
551
|
+
margin-bottom: 12px;
|
|
552
|
+
display: flex;
|
|
553
|
+
align-items: center;
|
|
554
|
+
gap: 8px;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
.count {
|
|
558
|
+
color: var(--text-dim);
|
|
559
|
+
font-weight: 400;
|
|
560
|
+
font-size: 0.85rem;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
.category-section ul {
|
|
564
|
+
list-style: none;
|
|
565
|
+
display: grid;
|
|
566
|
+
gap: 4px;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
.category-section li {
|
|
570
|
+
display: flex;
|
|
571
|
+
justify-content: space-between;
|
|
572
|
+
align-items: center;
|
|
573
|
+
padding: 8px 12px;
|
|
574
|
+
border-radius: var(--radius);
|
|
575
|
+
transition: background 0.15s;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
.category-section li:hover {
|
|
579
|
+
background: var(--bg-hover);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
.category-section li a {
|
|
583
|
+
color: var(--accent);
|
|
584
|
+
text-decoration: none;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
.category-section li a:hover {
|
|
588
|
+
color: var(--accent-hover);
|
|
589
|
+
text-decoration: underline;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
.item-meta {
|
|
593
|
+
color: var(--text-dim);
|
|
594
|
+
font-size: 0.8rem;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
.category-badge {
|
|
598
|
+
display: inline-block;
|
|
599
|
+
font-size: 0.7rem;
|
|
600
|
+
font-weight: 600;
|
|
601
|
+
text-transform: uppercase;
|
|
602
|
+
letter-spacing: 0.05em;
|
|
603
|
+
padding: 2px 8px;
|
|
604
|
+
border-radius: 3px;
|
|
605
|
+
color: var(--text-bright);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
.category-source { background: rgba(79,158,255,0.2); color: var(--accent); }
|
|
609
|
+
.category-entity { background: rgba(78,201,176,0.2); color: var(--green); }
|
|
610
|
+
.category-concept { background: rgba(156,120,216,0.2); color: var(--purple); }
|
|
611
|
+
.category-synthesi, .category-synthesis { background: rgba(206,145,120,0.2); color: var(--orange); }
|
|
612
|
+
.category-page { background: rgba(215,186,125,0.2); color: var(--amber); }
|
|
613
|
+
|
|
614
|
+
.page-header {
|
|
615
|
+
margin-bottom: 32px;
|
|
616
|
+
padding-bottom: 16px;
|
|
617
|
+
border-bottom: 1px solid var(--border);
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
.page-header h1 {
|
|
621
|
+
font-family: var(--font-display);
|
|
622
|
+
font-size: 2rem;
|
|
623
|
+
color: var(--text-bright);
|
|
624
|
+
margin: 8px 0 12px;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
.page-meta {
|
|
628
|
+
display: flex;
|
|
629
|
+
gap: 16px;
|
|
630
|
+
font-size: 0.8rem;
|
|
631
|
+
color: var(--text-dim);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
.prose h1 { font-size: 1.8rem; margin: 32px 0 16px; color: var(--text-bright); }
|
|
635
|
+
.prose h2 { font-size: 1.4rem; margin: 28px 0 12px; color: var(--text-bright); }
|
|
636
|
+
.prose h3 { font-size: 1.15rem; margin: 24px 0 8px; color: var(--text-bright); }
|
|
637
|
+
.prose h4 { font-size: 1rem; margin: 20px 0 8px; color: var(--text-secondary); }
|
|
638
|
+
|
|
639
|
+
.prose p { margin: 0 0 16px; }
|
|
640
|
+
|
|
641
|
+
.prose a { color: var(--accent); text-decoration: none; }
|
|
642
|
+
.prose a:hover { text-decoration: underline; }
|
|
643
|
+
|
|
644
|
+
.prose .wiki-link { color: var(--green); border-bottom: 1px dashed var(--green); }
|
|
645
|
+
.prose .wiki-link.broken { color: var(--red); border-bottom: 1px dashed var(--red); }
|
|
646
|
+
|
|
647
|
+
.prose code {
|
|
648
|
+
background: var(--bg-card);
|
|
649
|
+
padding: 2px 6px;
|
|
650
|
+
border-radius: 3px;
|
|
651
|
+
font-family: var(--font-mono);
|
|
652
|
+
font-size: 0.9em;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
.prose pre {
|
|
656
|
+
background: var(--bg-card);
|
|
657
|
+
padding: 16px;
|
|
658
|
+
border-radius: var(--radius);
|
|
659
|
+
overflow-x: auto;
|
|
660
|
+
margin: 16px 0;
|
|
661
|
+
border: 1px solid var(--border);
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
.prose pre code {
|
|
665
|
+
background: none;
|
|
666
|
+
padding: 0;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
.prose blockquote {
|
|
670
|
+
border-left: 3px solid var(--accent);
|
|
671
|
+
padding: 8px 16px;
|
|
672
|
+
margin: 16px 0;
|
|
673
|
+
color: var(--text-secondary);
|
|
674
|
+
background: rgba(79,158,255,0.05);
|
|
675
|
+
border-radius: 0 var(--radius) var(--radius) 0;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
.prose ul {
|
|
679
|
+
margin: 8px 0 16px 20px;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
.prose li {
|
|
683
|
+
margin: 4px 0;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
.prose hr {
|
|
687
|
+
border: none;
|
|
688
|
+
border-top: 1px solid var(--border);
|
|
689
|
+
margin: 32px 0;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
.related-pages {
|
|
693
|
+
margin-top: 48px;
|
|
694
|
+
padding-top: 24px;
|
|
695
|
+
border-top: 1px solid var(--border);
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
.related-pages h3 {
|
|
699
|
+
font-size: 0.9rem;
|
|
700
|
+
color: var(--text-dim);
|
|
701
|
+
text-transform: uppercase;
|
|
702
|
+
letter-spacing: 0.05em;
|
|
703
|
+
margin-bottom: 12px;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
.related-grid {
|
|
707
|
+
display: flex;
|
|
708
|
+
flex-wrap: wrap;
|
|
709
|
+
gap: 8px;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
.related-grid a, .related-grid .broken-link {
|
|
713
|
+
display: inline-block;
|
|
714
|
+
padding: 4px 12px;
|
|
715
|
+
border-radius: var(--radius);
|
|
716
|
+
font-size: 0.85rem;
|
|
717
|
+
text-decoration: none;
|
|
718
|
+
transition: background 0.15s;
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
.related-grid a {
|
|
722
|
+
background: rgba(78,201,176,0.1);
|
|
723
|
+
color: var(--green);
|
|
724
|
+
border: 1px solid rgba(78,201,176,0.2);
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
.related-grid a:hover {
|
|
728
|
+
background: rgba(78,201,176,0.2);
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
.related-grid .broken-link {
|
|
732
|
+
background: rgba(241,76,76,0.1);
|
|
733
|
+
color: var(--text-dim);
|
|
734
|
+
border: 1px solid rgba(241,76,76,0.15);
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
.site-footer {
|
|
738
|
+
text-align: center;
|
|
739
|
+
padding: 24px;
|
|
740
|
+
border-top: 1px solid var(--border);
|
|
741
|
+
color: var(--text-dim);
|
|
742
|
+
font-size: 0.8rem;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
.site-footer a { color: var(--accent); text-decoration: none; }
|
|
746
|
+
.site-footer a:hover { text-decoration: underline; }
|
|
747
|
+
|
|
748
|
+
.feed-links {
|
|
749
|
+
margin-top: 8px;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
@media (max-width: 640px) {
|
|
753
|
+
.hero h1 { font-size: 1.8rem; }
|
|
754
|
+
.stats-bar { flex-wrap: wrap; }
|
|
755
|
+
.page-header h1 { font-size: 1.5rem; }
|
|
756
|
+
.page-meta { flex-wrap: wrap; }
|
|
757
|
+
}
|
|
758
|
+
`;
|
|
759
|
+
}
|
|
760
|
+
//# sourceMappingURL=publish.js.map
|