diffwiki-core 0.6.1-rc.202607242058.7b3501c → 0.6.1
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/dist/index.cjs +6 -163
- package/dist/index.d.cts +12 -100
- package/dist/index.d.ts +12 -100
- package/dist/index.js +6 -158
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -577,7 +577,6 @@ __export(index_exports, {
|
|
|
577
577
|
addTags: () => addTags,
|
|
578
578
|
buildArticleTree: () => buildArticleTree,
|
|
579
579
|
buildCollections: () => buildCollections,
|
|
580
|
-
buildLinkGraph: () => buildLinkGraph,
|
|
581
580
|
buildNavTree: () => buildNavTree,
|
|
582
581
|
buildSearchIndex: () => buildSearchIndex,
|
|
583
582
|
collectionDir: () => collectionDir,
|
|
@@ -602,7 +601,6 @@ __export(index_exports, {
|
|
|
602
601
|
gitToplevel: () => gitToplevel,
|
|
603
602
|
initRepoWiki: () => initRepoWiki,
|
|
604
603
|
installPlugin: () => installPlugin,
|
|
605
|
-
invalidateLinkGraph: () => invalidateLinkGraph,
|
|
606
604
|
isLinkedWorktree: () => isLinkedWorktree,
|
|
607
605
|
listArticleTree: () => listArticleTree,
|
|
608
606
|
listAvailablePlugins: () => listAvailablePlugins,
|
|
@@ -611,7 +609,6 @@ __export(index_exports, {
|
|
|
611
609
|
listPlugins: () => listPlugins,
|
|
612
610
|
listSearchModes: () => listSearchModes,
|
|
613
611
|
loadSearchProvider: () => loadSearchProvider,
|
|
614
|
-
localGraph: () => localGraph,
|
|
615
612
|
nativeSearch: () => nativeSearch,
|
|
616
613
|
parseAddTarget: () => parseAddTarget,
|
|
617
614
|
parseArticle: () => parseArticle,
|
|
@@ -946,8 +943,8 @@ function isEpochish(d) {
|
|
|
946
943
|
}
|
|
947
944
|
var timestampCache = /* @__PURE__ */ new Map();
|
|
948
945
|
async function fileTimestamps(absPath) {
|
|
949
|
-
const
|
|
950
|
-
if (
|
|
946
|
+
const cached = timestampCache.get(absPath);
|
|
947
|
+
if (cached) return cached;
|
|
951
948
|
const dir = import_node_path4.default.dirname(absPath);
|
|
952
949
|
const base = import_node_path4.default.basename(absPath);
|
|
953
950
|
const [addLog, lastLog] = await Promise.all([
|
|
@@ -1307,56 +1304,6 @@ function rehypeCollectToc(toc) {
|
|
|
1307
1304
|
});
|
|
1308
1305
|
};
|
|
1309
1306
|
}
|
|
1310
|
-
function isExternalHref(href) {
|
|
1311
|
-
if (!href) return true;
|
|
1312
|
-
if (href.startsWith("#")) return true;
|
|
1313
|
-
if (href.startsWith("//")) return true;
|
|
1314
|
-
return /^[a-z][a-z0-9+.-]*:/i.test(href);
|
|
1315
|
-
}
|
|
1316
|
-
function resolveHref(href, ctx) {
|
|
1317
|
-
if (isExternalHref(href)) return null;
|
|
1318
|
-
const clean = (p) => p.replace(/[?#].*$/, "").replace(/\.(mdx?|html)$/i, "").replace(/\/+$/, "");
|
|
1319
|
-
const target = clean(href);
|
|
1320
|
-
if (!target) return null;
|
|
1321
|
-
if (target.startsWith("/")) {
|
|
1322
|
-
const base = ctx.base ?? process.env.DIFFWIKI_BASE ?? "";
|
|
1323
|
-
const baseSegs2 = base.split("/").filter(Boolean);
|
|
1324
|
-
let segs = target.split("/").filter(Boolean);
|
|
1325
|
-
if (baseSegs2.length > 0 && baseSegs2.every((b, i) => segs[i] === b)) {
|
|
1326
|
-
segs = segs.slice(baseSegs2.length);
|
|
1327
|
-
}
|
|
1328
|
-
if (segs.length < 2) return null;
|
|
1329
|
-
return segs.join("/");
|
|
1330
|
-
}
|
|
1331
|
-
const dir = ctx.isIndex ? ctx.slug : ctx.slug.includes("/") ? ctx.slug.slice(0, ctx.slug.lastIndexOf("/")) : "";
|
|
1332
|
-
const baseSegs = dir ? dir.split("/") : [];
|
|
1333
|
-
const out = [...baseSegs];
|
|
1334
|
-
for (const seg of target.split("/")) {
|
|
1335
|
-
if (seg === "" || seg === ".") continue;
|
|
1336
|
-
if (seg === "..") {
|
|
1337
|
-
if (out.length === 0) return null;
|
|
1338
|
-
out.pop();
|
|
1339
|
-
continue;
|
|
1340
|
-
}
|
|
1341
|
-
out.push(seg);
|
|
1342
|
-
}
|
|
1343
|
-
if (out.length === 0) return null;
|
|
1344
|
-
return `${ctx.coll}/${out.join("/")}`;
|
|
1345
|
-
}
|
|
1346
|
-
function rehypeCollectLinks(links, ctx) {
|
|
1347
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1348
|
-
return (tree) => {
|
|
1349
|
-
(0, import_unist_util_visit.visit)(tree, "element", (node) => {
|
|
1350
|
-
if (node.tagName !== "a") return;
|
|
1351
|
-
const href = node.properties?.href;
|
|
1352
|
-
if (typeof href !== "string") return;
|
|
1353
|
-
const id = resolveHref(href, ctx);
|
|
1354
|
-
if (!id || seen.has(id)) return;
|
|
1355
|
-
seen.add(id);
|
|
1356
|
-
links.push(id);
|
|
1357
|
-
});
|
|
1358
|
-
};
|
|
1359
|
-
}
|
|
1360
1307
|
function rehypeStripLeadingH1() {
|
|
1361
1308
|
return (tree) => {
|
|
1362
1309
|
let removed = false;
|
|
@@ -1384,112 +1331,13 @@ function rehypeMarkMermaid() {
|
|
|
1384
1331
|
}
|
|
1385
1332
|
async function renderArticle(body, ctx) {
|
|
1386
1333
|
const toc = [];
|
|
1387
|
-
const
|
|
1388
|
-
const pipeline = (0, import_unified.unified)().use(import_remark_parse.default).use(import_remark_gfm.default).use(import_remark_rehype.default).use(import_rehype_slug.default).use(rehypeCollectToc, toc).use(rehypeCollectLinks, links, ctx);
|
|
1389
|
-
const file = await pipeline.use(rehypeStripLeadingH1).use(rehypeMarkMermaid).use(rehypeRewriteLinks, ctx).use(import_rehype.default, {
|
|
1334
|
+
const file = await (0, import_unified.unified)().use(import_remark_parse.default).use(import_remark_gfm.default).use(import_remark_rehype.default).use(import_rehype_slug.default).use(rehypeCollectToc, toc).use(rehypeStripLeadingH1).use(rehypeMarkMermaid).use(rehypeRewriteLinks, ctx).use(import_rehype.default, {
|
|
1390
1335
|
themes: { light: "github-light", dark: "github-dark" },
|
|
1391
1336
|
defaultColor: false,
|
|
1392
1337
|
langAlias: LANG_ALIASES,
|
|
1393
1338
|
fallbackLanguage: "plaintext"
|
|
1394
1339
|
}).use(import_rehype_stringify.default).process(body);
|
|
1395
|
-
return { html: String(file), toc
|
|
1396
|
-
}
|
|
1397
|
-
|
|
1398
|
-
// src/graph.ts
|
|
1399
|
-
init_collections();
|
|
1400
|
-
init_errors();
|
|
1401
|
-
function tagId(tag) {
|
|
1402
|
-
return `tag:${tag}`;
|
|
1403
|
-
}
|
|
1404
|
-
async function collectArticle(coll, slug, fallbackTitle, outNodes, outEdges, outTags) {
|
|
1405
|
-
let art;
|
|
1406
|
-
try {
|
|
1407
|
-
art = await readArticle(coll, slug);
|
|
1408
|
-
} catch (err) {
|
|
1409
|
-
if (err instanceof ArticleNotFoundError) return;
|
|
1410
|
-
throw err;
|
|
1411
|
-
}
|
|
1412
|
-
const id = `${coll}/${slug}`;
|
|
1413
|
-
outNodes.push({ id, kind: "article", coll, slug, title: art.title ?? fallbackTitle ?? slug });
|
|
1414
|
-
const { links } = await renderArticle(art.body, { coll, slug, isIndex: art.isIndex });
|
|
1415
|
-
for (const target of links) outEdges.push({ source: id, target, kind: "link" });
|
|
1416
|
-
for (const tag of art.tags) outTags.push({ source: id, tag });
|
|
1417
|
-
}
|
|
1418
|
-
async function collectCollection(coll, nodes, outNodes, outEdges, outTags) {
|
|
1419
|
-
for (const n of nodes) {
|
|
1420
|
-
if (n.slug) await collectArticle(coll, n.slug, n.title ?? n.name, outNodes, outEdges, outTags);
|
|
1421
|
-
if (n.children) await collectCollection(coll, n.children, outNodes, outEdges, outTags);
|
|
1422
|
-
}
|
|
1423
|
-
}
|
|
1424
|
-
var cached;
|
|
1425
|
-
function invalidateLinkGraph() {
|
|
1426
|
-
cached = void 0;
|
|
1427
|
-
}
|
|
1428
|
-
async function buildLinkGraph() {
|
|
1429
|
-
if (cached) return cached;
|
|
1430
|
-
const nodes = [];
|
|
1431
|
-
const rawEdges = [];
|
|
1432
|
-
const rawTags = [];
|
|
1433
|
-
for (const e of await listCollections()) {
|
|
1434
|
-
try {
|
|
1435
|
-
await collectArticle(e.name, "", e.name, nodes, rawEdges, rawTags);
|
|
1436
|
-
await collectCollection(e.name, await listArticleTree(e.name), nodes, rawEdges, rawTags);
|
|
1437
|
-
} catch {
|
|
1438
|
-
}
|
|
1439
|
-
}
|
|
1440
|
-
const nodeIds = new Set(nodes.map((n) => n.id));
|
|
1441
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1442
|
-
const edges = [];
|
|
1443
|
-
for (const edge of rawEdges) {
|
|
1444
|
-
if (edge.source === edge.target) continue;
|
|
1445
|
-
if (!nodeIds.has(edge.target)) continue;
|
|
1446
|
-
const key = `${edge.source} ${edge.target}`;
|
|
1447
|
-
if (seen.has(key)) continue;
|
|
1448
|
-
seen.add(key);
|
|
1449
|
-
edges.push(edge);
|
|
1450
|
-
}
|
|
1451
|
-
const tagSeen = /* @__PURE__ */ new Set();
|
|
1452
|
-
for (const { source, tag } of rawTags) {
|
|
1453
|
-
if (!nodeIds.has(source)) continue;
|
|
1454
|
-
const id = tagId(tag);
|
|
1455
|
-
if (!tagSeen.has(id)) {
|
|
1456
|
-
tagSeen.add(id);
|
|
1457
|
-
nodes.push({ id, kind: "tag", coll: "", slug: "", title: `#${tag}` });
|
|
1458
|
-
}
|
|
1459
|
-
const key = `${source} ${id}`;
|
|
1460
|
-
if (seen.has(key)) continue;
|
|
1461
|
-
seen.add(key);
|
|
1462
|
-
edges.push({ source, target: id, kind: "tag" });
|
|
1463
|
-
}
|
|
1464
|
-
cached = { nodes, edges };
|
|
1465
|
-
return cached;
|
|
1466
|
-
}
|
|
1467
|
-
function localGraph(graph, focusId, include, siblingCap = 12) {
|
|
1468
|
-
const scoped = include ? {
|
|
1469
|
-
// Tag hubs survive the filter — never a page, so they can't 404.
|
|
1470
|
-
nodes: graph.nodes.filter((n) => n.kind === "tag" || include.has(n.coll)),
|
|
1471
|
-
edges: graph.edges
|
|
1472
|
-
} : graph;
|
|
1473
|
-
const byId = new Map(scoped.nodes.map((n) => [n.id, n]));
|
|
1474
|
-
const focus = byId.get(focusId);
|
|
1475
|
-
if (!focus) return { focus: focusId, nodes: [], edges: [] };
|
|
1476
|
-
const keep = /* @__PURE__ */ new Set([focusId]);
|
|
1477
|
-
for (const e of scoped.edges) {
|
|
1478
|
-
if (e.source === focusId && byId.has(e.target)) keep.add(e.target);
|
|
1479
|
-
if (e.target === focusId && byId.has(e.source)) keep.add(e.source);
|
|
1480
|
-
}
|
|
1481
|
-
const focusTagHubs = new Set([...keep].filter((id) => byId.get(id)?.kind === "tag"));
|
|
1482
|
-
let siblings = 0;
|
|
1483
|
-
for (const e of scoped.edges) {
|
|
1484
|
-
if (siblings >= siblingCap) break;
|
|
1485
|
-
if (e.kind === "tag" && focusTagHubs.has(e.target) && byId.has(e.source) && !keep.has(e.source)) {
|
|
1486
|
-
keep.add(e.source);
|
|
1487
|
-
siblings++;
|
|
1488
|
-
}
|
|
1489
|
-
}
|
|
1490
|
-
const nodes = scoped.nodes.filter((n) => keep.has(n.id));
|
|
1491
|
-
const edges = scoped.edges.filter((e) => keep.has(e.source) && keep.has(e.target));
|
|
1492
|
-
return { focus: focusId, nodes, edges };
|
|
1340
|
+
return { html: String(file), toc };
|
|
1493
1341
|
}
|
|
1494
1342
|
|
|
1495
1343
|
// src/site.ts
|
|
@@ -1543,7 +1391,7 @@ function findNode(nodes, slug) {
|
|
|
1543
1391
|
}
|
|
1544
1392
|
return void 0;
|
|
1545
1393
|
}
|
|
1546
|
-
async function resolvePage(coll, slug
|
|
1394
|
+
async function resolvePage(coll, slug) {
|
|
1547
1395
|
try {
|
|
1548
1396
|
const art = await readArticle(coll, slug);
|
|
1549
1397
|
const { html, toc } = await renderArticle(art.body, {
|
|
@@ -1552,7 +1400,6 @@ async function resolvePage(coll, slug, opts) {
|
|
|
1552
1400
|
isIndex: art.isIndex
|
|
1553
1401
|
});
|
|
1554
1402
|
const { created, updated } = await fileTimestamps(art.path);
|
|
1555
|
-
const graph = localGraph(await buildLinkGraph(), `${art.coll}/${art.slug}`, opts?.include);
|
|
1556
1403
|
return {
|
|
1557
1404
|
kind: "doc",
|
|
1558
1405
|
coll: art.coll,
|
|
@@ -1563,8 +1410,7 @@ async function resolvePage(coll, slug, opts) {
|
|
|
1563
1410
|
toc,
|
|
1564
1411
|
path: `/${art.coll}/${art.slug}`,
|
|
1565
1412
|
created,
|
|
1566
|
-
updated
|
|
1567
|
-
graph
|
|
1413
|
+
updated
|
|
1568
1414
|
};
|
|
1569
1415
|
} catch (err) {
|
|
1570
1416
|
if (err instanceof CollectionNotFoundError) return null;
|
|
@@ -2251,7 +2097,6 @@ init_registry2();
|
|
|
2251
2097
|
addTags,
|
|
2252
2098
|
buildArticleTree,
|
|
2253
2099
|
buildCollections,
|
|
2254
|
-
buildLinkGraph,
|
|
2255
2100
|
buildNavTree,
|
|
2256
2101
|
buildSearchIndex,
|
|
2257
2102
|
collectionDir,
|
|
@@ -2276,7 +2121,6 @@ init_registry2();
|
|
|
2276
2121
|
gitToplevel,
|
|
2277
2122
|
initRepoWiki,
|
|
2278
2123
|
installPlugin,
|
|
2279
|
-
invalidateLinkGraph,
|
|
2280
2124
|
isLinkedWorktree,
|
|
2281
2125
|
listArticleTree,
|
|
2282
2126
|
listAvailablePlugins,
|
|
@@ -2285,7 +2129,6 @@ init_registry2();
|
|
|
2285
2129
|
listPlugins,
|
|
2286
2130
|
listSearchModes,
|
|
2287
2131
|
loadSearchProvider,
|
|
2288
|
-
localGraph,
|
|
2289
2132
|
nativeSearch,
|
|
2290
2133
|
parseAddTarget,
|
|
2291
2134
|
parseArticle,
|
package/dist/index.d.cts
CHANGED
|
@@ -192,8 +192,6 @@ interface DocData {
|
|
|
192
192
|
created?: string;
|
|
193
193
|
/** ISO-8601 last-update date, sourced from git history / filesystem (not frontmatter). */
|
|
194
194
|
updated?: string;
|
|
195
|
-
/** Local knowledge-graph neighbourhood for this page (this node + its direct links). */
|
|
196
|
-
graph: LocalGraph;
|
|
197
195
|
}
|
|
198
196
|
interface ListingEntry {
|
|
199
197
|
name: string;
|
|
@@ -210,44 +208,6 @@ type PageData = ({
|
|
|
210
208
|
title: string;
|
|
211
209
|
entries: ListingEntry[];
|
|
212
210
|
};
|
|
213
|
-
/**
|
|
214
|
-
* One node in the knowledge graph. Two kinds:
|
|
215
|
-
* - `article` — a wiki page. `id` is `"<coll>/<slug>"`, with `coll`/`slug` set.
|
|
216
|
-
* - `tag` — an Obsidian-style tag hub. `id` is `"tag:<name>"`; `coll`/`slug` are
|
|
217
|
-
* empty (a tag is not a page, so clicking it never navigates), `title` is `#name`.
|
|
218
|
-
*/
|
|
219
|
-
interface GraphNode {
|
|
220
|
-
id: string;
|
|
221
|
-
kind: 'article' | 'tag';
|
|
222
|
-
coll: string;
|
|
223
|
-
slug: string;
|
|
224
|
-
title: string;
|
|
225
|
-
}
|
|
226
|
-
/**
|
|
227
|
-
* A graph edge. `kind` distinguishes an explicit hyperlink between two articles
|
|
228
|
-
* (`link`) from an article↔tag-hub membership edge (`tag`). For `link` edges the
|
|
229
|
-
* direction (source→target) is the link direction; `tag` edges go article→tag.
|
|
230
|
-
*/
|
|
231
|
-
interface GraphEdge {
|
|
232
|
-
source: string;
|
|
233
|
-
target: string;
|
|
234
|
-
kind: 'link' | 'tag';
|
|
235
|
-
}
|
|
236
|
-
/** The whole-wiki knowledge graph. */
|
|
237
|
-
interface LinkGraph {
|
|
238
|
-
nodes: GraphNode[];
|
|
239
|
-
edges: GraphEdge[];
|
|
240
|
-
}
|
|
241
|
-
/**
|
|
242
|
-
* The local neighbourhood of one node: the node itself plus its directly linked
|
|
243
|
-
* (outgoing) and linking (incoming) neighbours, and the edges among them. `focus`
|
|
244
|
-
* is the id of the page this neighbourhood is centred on.
|
|
245
|
-
*/
|
|
246
|
-
interface LocalGraph {
|
|
247
|
-
focus: string;
|
|
248
|
-
nodes: GraphNode[];
|
|
249
|
-
edges: GraphEdge[];
|
|
250
|
-
}
|
|
251
211
|
|
|
252
212
|
/** Base class for all expected, user-facing diffwiki failures. */
|
|
253
213
|
declare class DiffwikiError extends Error {
|
|
@@ -440,18 +400,8 @@ declare function fileTimestamps(absPath: string): Promise<FileTimestamps>;
|
|
|
440
400
|
declare function toTreeNodes(coll: string, nodes: ArticleNode[]): TreeNode[];
|
|
441
401
|
declare function buildCollections(): Promise<CollectionInfo[]>;
|
|
442
402
|
declare function buildNavTree(): Promise<CollectionTree[]>;
|
|
443
|
-
/**
|
|
444
|
-
|
|
445
|
-
*
|
|
446
|
-
* `opts.include` restricts the doc's sidebar `graph` preview to the given set of
|
|
447
|
-
* collection names — supplied by the static exporter for subset exports so the
|
|
448
|
-
* per-page local graph matches the filtered overlay `graph.json` and never links
|
|
449
|
-
* to a page that was excluded from the build. Omitting it (the dynamic server
|
|
450
|
-
* path, which serves every collection) keeps the whole-wiki neighbourhood.
|
|
451
|
-
*/
|
|
452
|
-
declare function resolvePage(coll: string, slug: string, opts?: {
|
|
453
|
-
include?: ReadonlySet<string>;
|
|
454
|
-
}): Promise<PageData | null>;
|
|
403
|
+
/** Resolve a route to a rendered doc or a generated directory listing (or null). */
|
|
404
|
+
declare function resolvePage(coll: string, slug: string): Promise<PageData | null>;
|
|
455
405
|
|
|
456
406
|
/**
|
|
457
407
|
* Collection selection for `diffwiki export` — resolve which collection names to
|
|
@@ -653,26 +603,18 @@ declare function listArticleTree(coll: string): Promise<ArticleNode[]>;
|
|
|
653
603
|
*/
|
|
654
604
|
declare function readArticle(coll: string, slug: string): Promise<ArticleContent>;
|
|
655
605
|
|
|
656
|
-
/**
|
|
657
|
-
* Route context for an article being rendered — used both to rewrite relative
|
|
658
|
-
* inter-page links into in-app routes and to resolve links to `coll/slug` graph
|
|
659
|
-
* node ids.
|
|
660
|
-
*/
|
|
606
|
+
/** Route context for an article being rendered — used to resolve relative links. */
|
|
661
607
|
interface RenderContext {
|
|
662
608
|
/** Collection name (first route segment). */
|
|
663
609
|
coll: string;
|
|
664
610
|
/** Article route slug (path within the collection, no extension), e.g. "a/b/deep". */
|
|
665
611
|
slug: string;
|
|
666
612
|
/**
|
|
667
|
-
* True
|
|
668
|
-
*
|
|
613
|
+
* True when the source file is a folder-index page (`<slug>/index.md`). Such a
|
|
614
|
+
* page shares its route slug with the folder, so relative links must resolve
|
|
615
|
+
* against the folder itself, not its parent. Defaults to false (leaf page).
|
|
669
616
|
*/
|
|
670
617
|
isIndex?: boolean;
|
|
671
|
-
/**
|
|
672
|
-
* Deployment base path (e.g. `/my-repo/`) stripped from absolute route links so
|
|
673
|
-
* the collection segment resolves cleanly. Defaults to `DIFFWIKI_BASE`.
|
|
674
|
-
*/
|
|
675
|
-
base?: string;
|
|
676
618
|
}
|
|
677
619
|
/** The result of rendering an article body to HTML. */
|
|
678
620
|
interface RenderedArticle {
|
|
@@ -680,47 +622,17 @@ interface RenderedArticle {
|
|
|
680
622
|
html: string;
|
|
681
623
|
/** h2/h3 headings collected during render, in document order. */
|
|
682
624
|
toc: TocItem[];
|
|
683
|
-
/**
|
|
684
|
-
* Internal outgoing links as `coll/slug` node ids, de-duplicated, in document
|
|
685
|
-
* order. Only populated when a {@link RenderContext} is supplied; external
|
|
686
|
-
* links, in-page anchors, and unresolvable targets are dropped.
|
|
687
|
-
*/
|
|
688
|
-
links: string[];
|
|
689
625
|
}
|
|
690
626
|
/**
|
|
691
627
|
* Render an article body (markdown) to Shiki-highlighted HTML plus its table of
|
|
692
|
-
* contents
|
|
693
|
-
*
|
|
694
|
-
*
|
|
695
|
-
*
|
|
696
|
-
*
|
|
697
|
-
* client rendering.
|
|
628
|
+
* contents, rewriting relative links to in-app routes via `ctx`.
|
|
629
|
+
* Runs entirely server-side; the client mounts the HTML directly with
|
|
630
|
+
* no `eval`/`new Function`, so it works under a strict CSP. Code is highlighted
|
|
631
|
+
* here (dual github-light/github-dark via CSS variables); mermaid fences are
|
|
632
|
+
* marked for client rendering.
|
|
698
633
|
*/
|
|
699
634
|
declare function renderArticle(body: string, ctx: RenderContext): Promise<RenderedArticle>;
|
|
700
635
|
|
|
701
|
-
/** Clear the memoised whole-wiki graph (tests; future live-reload invalidation). */
|
|
702
|
-
declare function invalidateLinkGraph(): void;
|
|
703
|
-
/**
|
|
704
|
-
* Build the whole-wiki graph: one node per article, one directed edge per resolved
|
|
705
|
-
* internal link (self/dead/external dropped, duplicates collapsed), plus tag hubs.
|
|
706
|
-
* Memoised per process; {@link invalidateLinkGraph} clears it.
|
|
707
|
-
*
|
|
708
|
-
* TODO(live-reload): the memo is process-keyed and the dynamic server fn never
|
|
709
|
-
* invalidates, so a long-lived server freezes the graph until restart.
|
|
710
|
-
*/
|
|
711
|
-
declare function buildLinkGraph(): Promise<LinkGraph>;
|
|
712
|
-
/**
|
|
713
|
-
* Slice the local neighbourhood of `focusId`: the focus, its direct link neighbours,
|
|
714
|
-
* its tag hubs, and its co-tagged sibling articles (2 hops through a shared hub, so
|
|
715
|
-
* the tag-proximity view has neighbours rather than a lone dot). All edges among the
|
|
716
|
-
* kept nodes are retained; an unknown focus yields an empty-but-focused graph.
|
|
717
|
-
* `siblingCap` bounds the co-tagged siblings pulled in.
|
|
718
|
-
*
|
|
719
|
-
* `include` prunes to the given collection set before slicing (tag hubs kept), so a
|
|
720
|
-
* subset export's preview never links to an excluded page.
|
|
721
|
-
*/
|
|
722
|
-
declare function localGraph(graph: LinkGraph, focusId: string, include?: ReadonlySet<string>, siblingCap?: number): LocalGraph;
|
|
723
|
-
|
|
724
636
|
/**
|
|
725
637
|
* Host-side plugin invocation and search-provider loading.
|
|
726
638
|
*
|
|
@@ -890,4 +802,4 @@ declare function findEnabledPlugin(kind: PluginKind, name?: string): Promise<Plu
|
|
|
890
802
|
/** Find a plugin record by name (regardless of enabled state). */
|
|
891
803
|
declare function findPluginRecord(name: string): Promise<PluginRecord | undefined>;
|
|
892
804
|
|
|
893
|
-
export { type AddExternalOptions, type Article, type ArticleContent, type ArticleNode, ArticleNotFoundError, CollectionAlreadyAddedError, type CollectionEntry, CollectionExistsError, type CollectionGitStatus, type CollectionInfo, CollectionNotFoundError, type CollectionSelection, type CollectionSelectionResult, type CollectionTree, type CollectionType, type Config, type Diagnostic, type DiagnosticLevel, DiffwikiError, type DiffwikiLogLevel, type DocData, type FileTimestamps, type
|
|
805
|
+
export { type AddExternalOptions, type Article, type ArticleContent, type ArticleNode, ArticleNotFoundError, CollectionAlreadyAddedError, type CollectionEntry, CollectionExistsError, type CollectionGitStatus, type CollectionInfo, CollectionNotFoundError, type CollectionSelection, type CollectionSelectionResult, type CollectionTree, type CollectionType, type Config, type Diagnostic, type DiagnosticLevel, DiffwikiError, type DiffwikiLogLevel, type DocData, type FileTimestamps, type InitOptions, type InstallOutcome, InvalidExportSelectionError, InvalidTargetError, type KnownPlugin, type ListingEntry, NATIVE_ENGINE, NoDefaultCollectionError, type NodeMeta, type PageData, type ParsedArticle, type PluginCapabilities, PluginError, type PluginEvent, type PluginHealth, type PluginKind, type PluginReadiness, type PluginRecord, type PluginRegistryFile, type QueryHit, type QueryOptions, REPO_CONFIG_FILE, type Registry, type RenderContext, type RenderedArticle, type RepoConfig, SearchDoc, type SearchMode, type SearchOutcome, type SearchProvider, type SerializableArticle, type SetupOutcome, type SetupStep, type TocItem, type TreeNode, type UpstreamStatus, WorktreeNotAllowedError, addExternalCollection, addTags, buildArticleTree, buildCollections, buildNavTree, buildSearchIndex, collectionDir, collectionGitStatus, collectionsDir, configPath, createArticle, createCollection, currentBranch, deregisterPlugin, doctor, emitPluginEvent, ensureGitRepo, ensurePluginRoot, fileTimestamps, findCollection, findCollectionById, findEnabledPlugin, findPluginRecord, getConfigValue, getLogger, gitToplevel, initRepoWiki, installPlugin, isLinkedWorktree, listArticleTree, listAvailablePlugins, listCollections, listEnabledPlugins, listPlugins, listSearchModes, loadSearchProvider, nativeSearch, parseAddTarget, parseArticle, parsePathTarget, pluginsDir, pluginsRegistryPath, query, readArticle, readConfig, readPluginRegistry, readRegistry, readRepoConfig, registerCollection, registerPlugin, registryPath, removeArticle, removeCollection, removePlugin, removeTags, renderArticle, repoId, repoRemoteName, resolveCollectionSelection, resolveCwdCollection, resolveDefaultSearchMode, resolveHome, resolveLogLevel, resolvePage, search, serializeArticle, setConfigValue, setTags, slugify, toTreeNodes, unregisterCollection, updateArticleBody, upsertPlugin, upstreamStatus, writeConfig, writePluginRegistry, writeRegistry };
|
package/dist/index.d.ts
CHANGED
|
@@ -192,8 +192,6 @@ interface DocData {
|
|
|
192
192
|
created?: string;
|
|
193
193
|
/** ISO-8601 last-update date, sourced from git history / filesystem (not frontmatter). */
|
|
194
194
|
updated?: string;
|
|
195
|
-
/** Local knowledge-graph neighbourhood for this page (this node + its direct links). */
|
|
196
|
-
graph: LocalGraph;
|
|
197
195
|
}
|
|
198
196
|
interface ListingEntry {
|
|
199
197
|
name: string;
|
|
@@ -210,44 +208,6 @@ type PageData = ({
|
|
|
210
208
|
title: string;
|
|
211
209
|
entries: ListingEntry[];
|
|
212
210
|
};
|
|
213
|
-
/**
|
|
214
|
-
* One node in the knowledge graph. Two kinds:
|
|
215
|
-
* - `article` — a wiki page. `id` is `"<coll>/<slug>"`, with `coll`/`slug` set.
|
|
216
|
-
* - `tag` — an Obsidian-style tag hub. `id` is `"tag:<name>"`; `coll`/`slug` are
|
|
217
|
-
* empty (a tag is not a page, so clicking it never navigates), `title` is `#name`.
|
|
218
|
-
*/
|
|
219
|
-
interface GraphNode {
|
|
220
|
-
id: string;
|
|
221
|
-
kind: 'article' | 'tag';
|
|
222
|
-
coll: string;
|
|
223
|
-
slug: string;
|
|
224
|
-
title: string;
|
|
225
|
-
}
|
|
226
|
-
/**
|
|
227
|
-
* A graph edge. `kind` distinguishes an explicit hyperlink between two articles
|
|
228
|
-
* (`link`) from an article↔tag-hub membership edge (`tag`). For `link` edges the
|
|
229
|
-
* direction (source→target) is the link direction; `tag` edges go article→tag.
|
|
230
|
-
*/
|
|
231
|
-
interface GraphEdge {
|
|
232
|
-
source: string;
|
|
233
|
-
target: string;
|
|
234
|
-
kind: 'link' | 'tag';
|
|
235
|
-
}
|
|
236
|
-
/** The whole-wiki knowledge graph. */
|
|
237
|
-
interface LinkGraph {
|
|
238
|
-
nodes: GraphNode[];
|
|
239
|
-
edges: GraphEdge[];
|
|
240
|
-
}
|
|
241
|
-
/**
|
|
242
|
-
* The local neighbourhood of one node: the node itself plus its directly linked
|
|
243
|
-
* (outgoing) and linking (incoming) neighbours, and the edges among them. `focus`
|
|
244
|
-
* is the id of the page this neighbourhood is centred on.
|
|
245
|
-
*/
|
|
246
|
-
interface LocalGraph {
|
|
247
|
-
focus: string;
|
|
248
|
-
nodes: GraphNode[];
|
|
249
|
-
edges: GraphEdge[];
|
|
250
|
-
}
|
|
251
211
|
|
|
252
212
|
/** Base class for all expected, user-facing diffwiki failures. */
|
|
253
213
|
declare class DiffwikiError extends Error {
|
|
@@ -440,18 +400,8 @@ declare function fileTimestamps(absPath: string): Promise<FileTimestamps>;
|
|
|
440
400
|
declare function toTreeNodes(coll: string, nodes: ArticleNode[]): TreeNode[];
|
|
441
401
|
declare function buildCollections(): Promise<CollectionInfo[]>;
|
|
442
402
|
declare function buildNavTree(): Promise<CollectionTree[]>;
|
|
443
|
-
/**
|
|
444
|
-
|
|
445
|
-
*
|
|
446
|
-
* `opts.include` restricts the doc's sidebar `graph` preview to the given set of
|
|
447
|
-
* collection names — supplied by the static exporter for subset exports so the
|
|
448
|
-
* per-page local graph matches the filtered overlay `graph.json` and never links
|
|
449
|
-
* to a page that was excluded from the build. Omitting it (the dynamic server
|
|
450
|
-
* path, which serves every collection) keeps the whole-wiki neighbourhood.
|
|
451
|
-
*/
|
|
452
|
-
declare function resolvePage(coll: string, slug: string, opts?: {
|
|
453
|
-
include?: ReadonlySet<string>;
|
|
454
|
-
}): Promise<PageData | null>;
|
|
403
|
+
/** Resolve a route to a rendered doc or a generated directory listing (or null). */
|
|
404
|
+
declare function resolvePage(coll: string, slug: string): Promise<PageData | null>;
|
|
455
405
|
|
|
456
406
|
/**
|
|
457
407
|
* Collection selection for `diffwiki export` — resolve which collection names to
|
|
@@ -653,26 +603,18 @@ declare function listArticleTree(coll: string): Promise<ArticleNode[]>;
|
|
|
653
603
|
*/
|
|
654
604
|
declare function readArticle(coll: string, slug: string): Promise<ArticleContent>;
|
|
655
605
|
|
|
656
|
-
/**
|
|
657
|
-
* Route context for an article being rendered — used both to rewrite relative
|
|
658
|
-
* inter-page links into in-app routes and to resolve links to `coll/slug` graph
|
|
659
|
-
* node ids.
|
|
660
|
-
*/
|
|
606
|
+
/** Route context for an article being rendered — used to resolve relative links. */
|
|
661
607
|
interface RenderContext {
|
|
662
608
|
/** Collection name (first route segment). */
|
|
663
609
|
coll: string;
|
|
664
610
|
/** Article route slug (path within the collection, no extension), e.g. "a/b/deep". */
|
|
665
611
|
slug: string;
|
|
666
612
|
/**
|
|
667
|
-
* True
|
|
668
|
-
*
|
|
613
|
+
* True when the source file is a folder-index page (`<slug>/index.md`). Such a
|
|
614
|
+
* page shares its route slug with the folder, so relative links must resolve
|
|
615
|
+
* against the folder itself, not its parent. Defaults to false (leaf page).
|
|
669
616
|
*/
|
|
670
617
|
isIndex?: boolean;
|
|
671
|
-
/**
|
|
672
|
-
* Deployment base path (e.g. `/my-repo/`) stripped from absolute route links so
|
|
673
|
-
* the collection segment resolves cleanly. Defaults to `DIFFWIKI_BASE`.
|
|
674
|
-
*/
|
|
675
|
-
base?: string;
|
|
676
618
|
}
|
|
677
619
|
/** The result of rendering an article body to HTML. */
|
|
678
620
|
interface RenderedArticle {
|
|
@@ -680,47 +622,17 @@ interface RenderedArticle {
|
|
|
680
622
|
html: string;
|
|
681
623
|
/** h2/h3 headings collected during render, in document order. */
|
|
682
624
|
toc: TocItem[];
|
|
683
|
-
/**
|
|
684
|
-
* Internal outgoing links as `coll/slug` node ids, de-duplicated, in document
|
|
685
|
-
* order. Only populated when a {@link RenderContext} is supplied; external
|
|
686
|
-
* links, in-page anchors, and unresolvable targets are dropped.
|
|
687
|
-
*/
|
|
688
|
-
links: string[];
|
|
689
625
|
}
|
|
690
626
|
/**
|
|
691
627
|
* Render an article body (markdown) to Shiki-highlighted HTML plus its table of
|
|
692
|
-
* contents
|
|
693
|
-
*
|
|
694
|
-
*
|
|
695
|
-
*
|
|
696
|
-
*
|
|
697
|
-
* client rendering.
|
|
628
|
+
* contents, rewriting relative links to in-app routes via `ctx`.
|
|
629
|
+
* Runs entirely server-side; the client mounts the HTML directly with
|
|
630
|
+
* no `eval`/`new Function`, so it works under a strict CSP. Code is highlighted
|
|
631
|
+
* here (dual github-light/github-dark via CSS variables); mermaid fences are
|
|
632
|
+
* marked for client rendering.
|
|
698
633
|
*/
|
|
699
634
|
declare function renderArticle(body: string, ctx: RenderContext): Promise<RenderedArticle>;
|
|
700
635
|
|
|
701
|
-
/** Clear the memoised whole-wiki graph (tests; future live-reload invalidation). */
|
|
702
|
-
declare function invalidateLinkGraph(): void;
|
|
703
|
-
/**
|
|
704
|
-
* Build the whole-wiki graph: one node per article, one directed edge per resolved
|
|
705
|
-
* internal link (self/dead/external dropped, duplicates collapsed), plus tag hubs.
|
|
706
|
-
* Memoised per process; {@link invalidateLinkGraph} clears it.
|
|
707
|
-
*
|
|
708
|
-
* TODO(live-reload): the memo is process-keyed and the dynamic server fn never
|
|
709
|
-
* invalidates, so a long-lived server freezes the graph until restart.
|
|
710
|
-
*/
|
|
711
|
-
declare function buildLinkGraph(): Promise<LinkGraph>;
|
|
712
|
-
/**
|
|
713
|
-
* Slice the local neighbourhood of `focusId`: the focus, its direct link neighbours,
|
|
714
|
-
* its tag hubs, and its co-tagged sibling articles (2 hops through a shared hub, so
|
|
715
|
-
* the tag-proximity view has neighbours rather than a lone dot). All edges among the
|
|
716
|
-
* kept nodes are retained; an unknown focus yields an empty-but-focused graph.
|
|
717
|
-
* `siblingCap` bounds the co-tagged siblings pulled in.
|
|
718
|
-
*
|
|
719
|
-
* `include` prunes to the given collection set before slicing (tag hubs kept), so a
|
|
720
|
-
* subset export's preview never links to an excluded page.
|
|
721
|
-
*/
|
|
722
|
-
declare function localGraph(graph: LinkGraph, focusId: string, include?: ReadonlySet<string>, siblingCap?: number): LocalGraph;
|
|
723
|
-
|
|
724
636
|
/**
|
|
725
637
|
* Host-side plugin invocation and search-provider loading.
|
|
726
638
|
*
|
|
@@ -890,4 +802,4 @@ declare function findEnabledPlugin(kind: PluginKind, name?: string): Promise<Plu
|
|
|
890
802
|
/** Find a plugin record by name (regardless of enabled state). */
|
|
891
803
|
declare function findPluginRecord(name: string): Promise<PluginRecord | undefined>;
|
|
892
804
|
|
|
893
|
-
export { type AddExternalOptions, type Article, type ArticleContent, type ArticleNode, ArticleNotFoundError, CollectionAlreadyAddedError, type CollectionEntry, CollectionExistsError, type CollectionGitStatus, type CollectionInfo, CollectionNotFoundError, type CollectionSelection, type CollectionSelectionResult, type CollectionTree, type CollectionType, type Config, type Diagnostic, type DiagnosticLevel, DiffwikiError, type DiffwikiLogLevel, type DocData, type FileTimestamps, type
|
|
805
|
+
export { type AddExternalOptions, type Article, type ArticleContent, type ArticleNode, ArticleNotFoundError, CollectionAlreadyAddedError, type CollectionEntry, CollectionExistsError, type CollectionGitStatus, type CollectionInfo, CollectionNotFoundError, type CollectionSelection, type CollectionSelectionResult, type CollectionTree, type CollectionType, type Config, type Diagnostic, type DiagnosticLevel, DiffwikiError, type DiffwikiLogLevel, type DocData, type FileTimestamps, type InitOptions, type InstallOutcome, InvalidExportSelectionError, InvalidTargetError, type KnownPlugin, type ListingEntry, NATIVE_ENGINE, NoDefaultCollectionError, type NodeMeta, type PageData, type ParsedArticle, type PluginCapabilities, PluginError, type PluginEvent, type PluginHealth, type PluginKind, type PluginReadiness, type PluginRecord, type PluginRegistryFile, type QueryHit, type QueryOptions, REPO_CONFIG_FILE, type Registry, type RenderContext, type RenderedArticle, type RepoConfig, SearchDoc, type SearchMode, type SearchOutcome, type SearchProvider, type SerializableArticle, type SetupOutcome, type SetupStep, type TocItem, type TreeNode, type UpstreamStatus, WorktreeNotAllowedError, addExternalCollection, addTags, buildArticleTree, buildCollections, buildNavTree, buildSearchIndex, collectionDir, collectionGitStatus, collectionsDir, configPath, createArticle, createCollection, currentBranch, deregisterPlugin, doctor, emitPluginEvent, ensureGitRepo, ensurePluginRoot, fileTimestamps, findCollection, findCollectionById, findEnabledPlugin, findPluginRecord, getConfigValue, getLogger, gitToplevel, initRepoWiki, installPlugin, isLinkedWorktree, listArticleTree, listAvailablePlugins, listCollections, listEnabledPlugins, listPlugins, listSearchModes, loadSearchProvider, nativeSearch, parseAddTarget, parseArticle, parsePathTarget, pluginsDir, pluginsRegistryPath, query, readArticle, readConfig, readPluginRegistry, readRegistry, readRepoConfig, registerCollection, registerPlugin, registryPath, removeArticle, removeCollection, removePlugin, removeTags, renderArticle, repoId, repoRemoteName, resolveCollectionSelection, resolveCwdCollection, resolveDefaultSearchMode, resolveHome, resolveLogLevel, resolvePage, search, serializeArticle, setConfigValue, setTags, slugify, toTreeNodes, unregisterCollection, updateArticleBody, upsertPlugin, upstreamStatus, writeConfig, writePluginRegistry, writeRegistry };
|
package/dist/index.js
CHANGED
|
@@ -318,8 +318,8 @@ function isEpochish(d) {
|
|
|
318
318
|
}
|
|
319
319
|
var timestampCache = /* @__PURE__ */ new Map();
|
|
320
320
|
async function fileTimestamps(absPath) {
|
|
321
|
-
const
|
|
322
|
-
if (
|
|
321
|
+
const cached = timestampCache.get(absPath);
|
|
322
|
+
if (cached) return cached;
|
|
323
323
|
const dir = path2.dirname(absPath);
|
|
324
324
|
const base = path2.basename(absPath);
|
|
325
325
|
const [addLog, lastLog] = await Promise.all([
|
|
@@ -673,56 +673,6 @@ function rehypeCollectToc(toc) {
|
|
|
673
673
|
});
|
|
674
674
|
};
|
|
675
675
|
}
|
|
676
|
-
function isExternalHref(href) {
|
|
677
|
-
if (!href) return true;
|
|
678
|
-
if (href.startsWith("#")) return true;
|
|
679
|
-
if (href.startsWith("//")) return true;
|
|
680
|
-
return /^[a-z][a-z0-9+.-]*:/i.test(href);
|
|
681
|
-
}
|
|
682
|
-
function resolveHref(href, ctx) {
|
|
683
|
-
if (isExternalHref(href)) return null;
|
|
684
|
-
const clean = (p) => p.replace(/[?#].*$/, "").replace(/\.(mdx?|html)$/i, "").replace(/\/+$/, "");
|
|
685
|
-
const target = clean(href);
|
|
686
|
-
if (!target) return null;
|
|
687
|
-
if (target.startsWith("/")) {
|
|
688
|
-
const base = ctx.base ?? process.env.DIFFWIKI_BASE ?? "";
|
|
689
|
-
const baseSegs2 = base.split("/").filter(Boolean);
|
|
690
|
-
let segs = target.split("/").filter(Boolean);
|
|
691
|
-
if (baseSegs2.length > 0 && baseSegs2.every((b, i) => segs[i] === b)) {
|
|
692
|
-
segs = segs.slice(baseSegs2.length);
|
|
693
|
-
}
|
|
694
|
-
if (segs.length < 2) return null;
|
|
695
|
-
return segs.join("/");
|
|
696
|
-
}
|
|
697
|
-
const dir = ctx.isIndex ? ctx.slug : ctx.slug.includes("/") ? ctx.slug.slice(0, ctx.slug.lastIndexOf("/")) : "";
|
|
698
|
-
const baseSegs = dir ? dir.split("/") : [];
|
|
699
|
-
const out = [...baseSegs];
|
|
700
|
-
for (const seg of target.split("/")) {
|
|
701
|
-
if (seg === "" || seg === ".") continue;
|
|
702
|
-
if (seg === "..") {
|
|
703
|
-
if (out.length === 0) return null;
|
|
704
|
-
out.pop();
|
|
705
|
-
continue;
|
|
706
|
-
}
|
|
707
|
-
out.push(seg);
|
|
708
|
-
}
|
|
709
|
-
if (out.length === 0) return null;
|
|
710
|
-
return `${ctx.coll}/${out.join("/")}`;
|
|
711
|
-
}
|
|
712
|
-
function rehypeCollectLinks(links, ctx) {
|
|
713
|
-
const seen = /* @__PURE__ */ new Set();
|
|
714
|
-
return (tree) => {
|
|
715
|
-
visit(tree, "element", (node) => {
|
|
716
|
-
if (node.tagName !== "a") return;
|
|
717
|
-
const href = node.properties?.href;
|
|
718
|
-
if (typeof href !== "string") return;
|
|
719
|
-
const id = resolveHref(href, ctx);
|
|
720
|
-
if (!id || seen.has(id)) return;
|
|
721
|
-
seen.add(id);
|
|
722
|
-
links.push(id);
|
|
723
|
-
});
|
|
724
|
-
};
|
|
725
|
-
}
|
|
726
676
|
function rehypeStripLeadingH1() {
|
|
727
677
|
return (tree) => {
|
|
728
678
|
let removed = false;
|
|
@@ -750,110 +700,13 @@ function rehypeMarkMermaid() {
|
|
|
750
700
|
}
|
|
751
701
|
async function renderArticle(body, ctx) {
|
|
752
702
|
const toc = [];
|
|
753
|
-
const
|
|
754
|
-
const pipeline = unified().use(remarkParse).use(remarkGfm).use(remarkRehype).use(rehypeSlug).use(rehypeCollectToc, toc).use(rehypeCollectLinks, links, ctx);
|
|
755
|
-
const file = await pipeline.use(rehypeStripLeadingH1).use(rehypeMarkMermaid).use(rehypeRewriteLinks, ctx).use(rehypeShiki, {
|
|
703
|
+
const file = await unified().use(remarkParse).use(remarkGfm).use(remarkRehype).use(rehypeSlug).use(rehypeCollectToc, toc).use(rehypeStripLeadingH1).use(rehypeMarkMermaid).use(rehypeRewriteLinks, ctx).use(rehypeShiki, {
|
|
756
704
|
themes: { light: "github-light", dark: "github-dark" },
|
|
757
705
|
defaultColor: false,
|
|
758
706
|
langAlias: LANG_ALIASES,
|
|
759
707
|
fallbackLanguage: "plaintext"
|
|
760
708
|
}).use(rehypeStringify).process(body);
|
|
761
|
-
return { html: String(file), toc
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
// src/graph.ts
|
|
765
|
-
function tagId(tag) {
|
|
766
|
-
return `tag:${tag}`;
|
|
767
|
-
}
|
|
768
|
-
async function collectArticle(coll, slug, fallbackTitle, outNodes, outEdges, outTags) {
|
|
769
|
-
let art;
|
|
770
|
-
try {
|
|
771
|
-
art = await readArticle(coll, slug);
|
|
772
|
-
} catch (err) {
|
|
773
|
-
if (err instanceof ArticleNotFoundError) return;
|
|
774
|
-
throw err;
|
|
775
|
-
}
|
|
776
|
-
const id = `${coll}/${slug}`;
|
|
777
|
-
outNodes.push({ id, kind: "article", coll, slug, title: art.title ?? fallbackTitle ?? slug });
|
|
778
|
-
const { links } = await renderArticle(art.body, { coll, slug, isIndex: art.isIndex });
|
|
779
|
-
for (const target of links) outEdges.push({ source: id, target, kind: "link" });
|
|
780
|
-
for (const tag of art.tags) outTags.push({ source: id, tag });
|
|
781
|
-
}
|
|
782
|
-
async function collectCollection(coll, nodes, outNodes, outEdges, outTags) {
|
|
783
|
-
for (const n of nodes) {
|
|
784
|
-
if (n.slug) await collectArticle(coll, n.slug, n.title ?? n.name, outNodes, outEdges, outTags);
|
|
785
|
-
if (n.children) await collectCollection(coll, n.children, outNodes, outEdges, outTags);
|
|
786
|
-
}
|
|
787
|
-
}
|
|
788
|
-
var cached;
|
|
789
|
-
function invalidateLinkGraph() {
|
|
790
|
-
cached = void 0;
|
|
791
|
-
}
|
|
792
|
-
async function buildLinkGraph() {
|
|
793
|
-
if (cached) return cached;
|
|
794
|
-
const nodes = [];
|
|
795
|
-
const rawEdges = [];
|
|
796
|
-
const rawTags = [];
|
|
797
|
-
for (const e of await listCollections()) {
|
|
798
|
-
try {
|
|
799
|
-
await collectArticle(e.name, "", e.name, nodes, rawEdges, rawTags);
|
|
800
|
-
await collectCollection(e.name, await listArticleTree(e.name), nodes, rawEdges, rawTags);
|
|
801
|
-
} catch {
|
|
802
|
-
}
|
|
803
|
-
}
|
|
804
|
-
const nodeIds = new Set(nodes.map((n) => n.id));
|
|
805
|
-
const seen = /* @__PURE__ */ new Set();
|
|
806
|
-
const edges = [];
|
|
807
|
-
for (const edge of rawEdges) {
|
|
808
|
-
if (edge.source === edge.target) continue;
|
|
809
|
-
if (!nodeIds.has(edge.target)) continue;
|
|
810
|
-
const key = `${edge.source} ${edge.target}`;
|
|
811
|
-
if (seen.has(key)) continue;
|
|
812
|
-
seen.add(key);
|
|
813
|
-
edges.push(edge);
|
|
814
|
-
}
|
|
815
|
-
const tagSeen = /* @__PURE__ */ new Set();
|
|
816
|
-
for (const { source, tag } of rawTags) {
|
|
817
|
-
if (!nodeIds.has(source)) continue;
|
|
818
|
-
const id = tagId(tag);
|
|
819
|
-
if (!tagSeen.has(id)) {
|
|
820
|
-
tagSeen.add(id);
|
|
821
|
-
nodes.push({ id, kind: "tag", coll: "", slug: "", title: `#${tag}` });
|
|
822
|
-
}
|
|
823
|
-
const key = `${source} ${id}`;
|
|
824
|
-
if (seen.has(key)) continue;
|
|
825
|
-
seen.add(key);
|
|
826
|
-
edges.push({ source, target: id, kind: "tag" });
|
|
827
|
-
}
|
|
828
|
-
cached = { nodes, edges };
|
|
829
|
-
return cached;
|
|
830
|
-
}
|
|
831
|
-
function localGraph(graph, focusId, include, siblingCap = 12) {
|
|
832
|
-
const scoped = include ? {
|
|
833
|
-
// Tag hubs survive the filter — never a page, so they can't 404.
|
|
834
|
-
nodes: graph.nodes.filter((n) => n.kind === "tag" || include.has(n.coll)),
|
|
835
|
-
edges: graph.edges
|
|
836
|
-
} : graph;
|
|
837
|
-
const byId = new Map(scoped.nodes.map((n) => [n.id, n]));
|
|
838
|
-
const focus = byId.get(focusId);
|
|
839
|
-
if (!focus) return { focus: focusId, nodes: [], edges: [] };
|
|
840
|
-
const keep = /* @__PURE__ */ new Set([focusId]);
|
|
841
|
-
for (const e of scoped.edges) {
|
|
842
|
-
if (e.source === focusId && byId.has(e.target)) keep.add(e.target);
|
|
843
|
-
if (e.target === focusId && byId.has(e.source)) keep.add(e.source);
|
|
844
|
-
}
|
|
845
|
-
const focusTagHubs = new Set([...keep].filter((id) => byId.get(id)?.kind === "tag"));
|
|
846
|
-
let siblings = 0;
|
|
847
|
-
for (const e of scoped.edges) {
|
|
848
|
-
if (siblings >= siblingCap) break;
|
|
849
|
-
if (e.kind === "tag" && focusTagHubs.has(e.target) && byId.has(e.source) && !keep.has(e.source)) {
|
|
850
|
-
keep.add(e.source);
|
|
851
|
-
siblings++;
|
|
852
|
-
}
|
|
853
|
-
}
|
|
854
|
-
const nodes = scoped.nodes.filter((n) => keep.has(n.id));
|
|
855
|
-
const edges = scoped.edges.filter((e) => keep.has(e.source) && keep.has(e.target));
|
|
856
|
-
return { focus: focusId, nodes, edges };
|
|
709
|
+
return { html: String(file), toc };
|
|
857
710
|
}
|
|
858
711
|
|
|
859
712
|
// src/site.ts
|
|
@@ -906,7 +759,7 @@ function findNode(nodes, slug) {
|
|
|
906
759
|
}
|
|
907
760
|
return void 0;
|
|
908
761
|
}
|
|
909
|
-
async function resolvePage(coll, slug
|
|
762
|
+
async function resolvePage(coll, slug) {
|
|
910
763
|
try {
|
|
911
764
|
const art = await readArticle(coll, slug);
|
|
912
765
|
const { html, toc } = await renderArticle(art.body, {
|
|
@@ -915,7 +768,6 @@ async function resolvePage(coll, slug, opts) {
|
|
|
915
768
|
isIndex: art.isIndex
|
|
916
769
|
});
|
|
917
770
|
const { created, updated } = await fileTimestamps(art.path);
|
|
918
|
-
const graph = localGraph(await buildLinkGraph(), `${art.coll}/${art.slug}`, opts?.include);
|
|
919
771
|
return {
|
|
920
772
|
kind: "doc",
|
|
921
773
|
coll: art.coll,
|
|
@@ -926,8 +778,7 @@ async function resolvePage(coll, slug, opts) {
|
|
|
926
778
|
toc,
|
|
927
779
|
path: `/${art.coll}/${art.slug}`,
|
|
928
780
|
created,
|
|
929
|
-
updated
|
|
930
|
-
graph
|
|
781
|
+
updated
|
|
931
782
|
};
|
|
932
783
|
} catch (err) {
|
|
933
784
|
if (err instanceof CollectionNotFoundError) return null;
|
|
@@ -1509,7 +1360,6 @@ export {
|
|
|
1509
1360
|
addTags,
|
|
1510
1361
|
buildArticleTree,
|
|
1511
1362
|
buildCollections,
|
|
1512
|
-
buildLinkGraph,
|
|
1513
1363
|
buildNavTree,
|
|
1514
1364
|
buildSearchIndex,
|
|
1515
1365
|
collectionDir,
|
|
@@ -1534,7 +1384,6 @@ export {
|
|
|
1534
1384
|
gitToplevel,
|
|
1535
1385
|
initRepoWiki,
|
|
1536
1386
|
installPlugin,
|
|
1537
|
-
invalidateLinkGraph,
|
|
1538
1387
|
isLinkedWorktree,
|
|
1539
1388
|
listArticleTree,
|
|
1540
1389
|
listAvailablePlugins,
|
|
@@ -1543,7 +1392,6 @@ export {
|
|
|
1543
1392
|
listPlugins,
|
|
1544
1393
|
listSearchModes,
|
|
1545
1394
|
loadSearchProvider,
|
|
1546
|
-
localGraph,
|
|
1547
1395
|
nativeSearch,
|
|
1548
1396
|
parseAddTarget,
|
|
1549
1397
|
parseArticle,
|