@silicajs/core 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.
@@ -0,0 +1,1045 @@
1
+ // src/path.ts
2
+ import path from "path";
3
+ var NUMERIC_PREFIX_RE = /^(\d+)[\s._-]+(.+)$/;
4
+ var UNORDERED_SEGMENT_SORT_PREFIX = "~~~~~~~~~~";
5
+ function asFilePath(value) {
6
+ return normalizePath(value);
7
+ }
8
+ function asFullSlug(value) {
9
+ return normalizeSlug(value);
10
+ }
11
+ function asSimpleSlug(value) {
12
+ return simplifySlug(asFullSlug(value));
13
+ }
14
+ function asRelativeURL(value) {
15
+ return value;
16
+ }
17
+ function normalizePath(value) {
18
+ return value.replace(/\\/g, "/").replace(/^\/+/, "").replace(/\/+$/, "");
19
+ }
20
+ function stripNumericPrefix(segment) {
21
+ return segment.replace(NUMERIC_PREFIX_RE, "$2");
22
+ }
23
+ function hasNumericPrefix(segment) {
24
+ return NUMERIC_PREFIX_RE.test(segment);
25
+ }
26
+ function slugifySegment(segment, options = {}) {
27
+ const stem = segment.replace(/\.[^.]+$/, "");
28
+ const displaySegment = options.numericPrefixes ? stripNumericPrefix(stem) : stem;
29
+ return displaySegment.normalize("NFKD").replace(/[\u0300-\u036f]/g, "").trim().toLowerCase().replace(/['"]/g, "").replace(/[^a-z0-9/_-]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
30
+ }
31
+ function normalizeSlug(value, options = {}) {
32
+ const cleaned = normalizePath(value).replace(/^\.\//, "").replace(/\.(md|markdown|mdx)$/i, "");
33
+ const parts = cleaned.split("/").filter(Boolean).map((part) => slugifySegment(part, options));
34
+ return (parts.join("/") || "index").replace(/\/index\/index$/, "/index");
35
+ }
36
+ function slugifyFilePath(filePath, contentDir = "content", options = {}) {
37
+ const normalizedFile = normalizePath(filePath);
38
+ const normalizedContent = normalizePath(contentDir);
39
+ const relative = normalizedFile.startsWith(`${normalizedContent}/`) ? normalizedFile.slice(normalizedContent.length + 1) : normalizedFile;
40
+ return asFullSlug(normalizeSlug(relative, options));
41
+ }
42
+ function hasNumericPrefixInPath(filePath) {
43
+ return normalizePath(filePath).split("/").some((segment) => hasNumericPrefix(segment.replace(/\.[^.]+$/, "")));
44
+ }
45
+ function numericPrefixSortKey(filePath) {
46
+ return normalizePath(filePath).replace(/\.(md|markdown|mdx)$/i, "").split("/").filter(Boolean).map(numericPrefixSortSegment).join("/");
47
+ }
48
+ function simplifySlug(slug) {
49
+ const normalized = normalizeSlug(slug);
50
+ if (normalized === "index") return "";
51
+ return normalized.replace(/\/index$/, "");
52
+ }
53
+ function slugToHref(slug) {
54
+ const simple = simplifySlug(slug).toString();
55
+ return simple ? `/${simple}` : "/";
56
+ }
57
+ function hrefToSlug(href) {
58
+ const normalized = normalizePath(href.split("#")[0] ?? "");
59
+ return asFullSlug(normalized === "" ? "index" : normalized);
60
+ }
61
+ function pathToRoot(slug) {
62
+ const simple = simplifySlug(slug).toString();
63
+ const depth = simple ? simple.split("/").length - 1 : 0;
64
+ return asRelativeURL(depth === 0 ? "." : Array(depth).fill("..").join("/"));
65
+ }
66
+ function resolveRelative(currentSlug, target, options = {}) {
67
+ const currentDir = normalizePath(currentSlug).split("/").slice(0, -1).join("/");
68
+ return asFullSlug(
69
+ normalizeSlug(path.posix.join(currentDir, target), options)
70
+ );
71
+ }
72
+ function resolveWikiLink(currentSlug, target, allSlugs, strategy = "shortest", options = {}) {
73
+ const [rawPath] = target.split("#");
74
+ const normalizedTarget = normalizeSlug(rawPath ?? target, options);
75
+ const candidates = new Set(allSlugs.map((slug) => normalizeSlug(slug)));
76
+ if (strategy === "absolute" && candidates.has(normalizedTarget))
77
+ return asFullSlug(normalizedTarget);
78
+ if (strategy === "relative") {
79
+ const relative = resolveRelative(currentSlug, normalizedTarget, options);
80
+ if (candidates.has(relative)) return relative;
81
+ }
82
+ if (candidates.has(normalizedTarget)) return asFullSlug(normalizedTarget);
83
+ if (candidates.has(`${normalizedTarget}/index`))
84
+ return asFullSlug(`${normalizedTarget}/index`);
85
+ const byBasename = [...candidates].filter((slug) => {
86
+ const simplified = simplifySlug(slug).toString();
87
+ return simplified.split("/").at(-1) === normalizedTarget.split("/").at(-1);
88
+ });
89
+ return byBasename.length === 1 ? asFullSlug(byBasename[0]) : void 0;
90
+ }
91
+ function joinSegments(...segments) {
92
+ return normalizePath(segments.filter(Boolean).join("/"));
93
+ }
94
+ function numericPrefixSortSegment(segment) {
95
+ const stem = segment.replace(/\.[^.]+$/, "");
96
+ const match = NUMERIC_PREFIX_RE.exec(stem);
97
+ if (!match) {
98
+ return `${UNORDERED_SEGMENT_SORT_PREFIX}:${slugifySegment(stem)}`;
99
+ }
100
+ const order = match[1].padStart(10, "0");
101
+ return `${order}:${slugifySegment(match[2])}`;
102
+ }
103
+
104
+ // src/pipeline/frontmatter.ts
105
+ var RESERVED_FRONTMATTER_KEYS = /* @__PURE__ */ new Set([
106
+ "aliases",
107
+ "alias",
108
+ "created",
109
+ "cssclass",
110
+ "cssclasses",
111
+ "date",
112
+ "description",
113
+ "draft",
114
+ "listed",
115
+ "menu_label",
116
+ "modified",
117
+ "permalink",
118
+ "publish",
119
+ "tag",
120
+ "tags",
121
+ "title"
122
+ ]);
123
+ function getMenuLabel(frontmatter, title) {
124
+ if (typeof frontmatter.menu_label === "string" && frontmatter.menu_label.trim()) {
125
+ return frontmatter.menu_label.trim();
126
+ }
127
+ return title;
128
+ }
129
+ function getPageProperties(frontmatter) {
130
+ return Object.entries(frontmatter).filter(([key]) => !RESERVED_FRONTMATTER_KEYS.has(key.toLowerCase())).map(([key, value]) => {
131
+ const formatted = formatPropertyValue(value);
132
+ if (formatted === void 0) return void 0;
133
+ return {
134
+ key,
135
+ label: formatPropertyLabel(key),
136
+ value: formatted
137
+ };
138
+ }).filter((property) => property !== void 0).sort((a, b) => a.key.localeCompare(b.key));
139
+ }
140
+ function formatPropertyLabel(key) {
141
+ return key.replace(/[_-]+/g, " ").replace(/([a-z0-9])([A-Z])/g, "$1 $2").toLowerCase();
142
+ }
143
+ function formatPropertyValue(value) {
144
+ if (value === null || value === void 0) return void 0;
145
+ if (value instanceof Date) {
146
+ return value.toISOString().slice(0, 10);
147
+ }
148
+ if (typeof value === "string") {
149
+ const trimmed = value.trim();
150
+ return trimmed || void 0;
151
+ }
152
+ if (typeof value === "number" || typeof value === "boolean") {
153
+ return String(value);
154
+ }
155
+ if (Array.isArray(value)) {
156
+ const items = value.map((item) => formatPropertyValue(item)).filter((item) => item !== void 0);
157
+ return items.length ? items.join(", ") : void 0;
158
+ }
159
+ if (typeof value === "object") {
160
+ return JSON.stringify(value);
161
+ }
162
+ return String(value);
163
+ }
164
+
165
+ // src/tags.ts
166
+ import { normalizeTag } from "@silicajs/remark-obsidian";
167
+ function tagToHref(tag) {
168
+ const normalized = normalizeTag(tag);
169
+ if (!normalized) return "/tags";
170
+ const encoded = normalized.split("/").map((segment) => encodeURIComponent(segment)).join("/");
171
+ return `/tags/${encoded}`;
172
+ }
173
+
174
+ // src/pipeline/index.ts
175
+ import matter from "gray-matter";
176
+ import { unified } from "unified";
177
+ import remarkParse from "remark-parse";
178
+ import remarkFrontmatter from "remark-frontmatter";
179
+ import remarkGfm from "remark-gfm";
180
+ import remarkMath from "remark-math";
181
+ import remarkRehype from "remark-rehype";
182
+ import rehypeRaw from "rehype-raw";
183
+ import rehypeSanitize, { defaultSchema } from "rehype-sanitize";
184
+ import rehypeKatex from "rehype-katex";
185
+ import rehypeSlug from "rehype-slug";
186
+ import rehypeAutolinkHeadings from "rehype-autolink-headings";
187
+ import rehypeShiki from "@shikijs/rehype";
188
+ import rehypeReact from "rehype-react";
189
+ import rehypeStringify from "rehype-stringify";
190
+ import { getTags, remarkObsidian } from "@silicajs/remark-obsidian";
191
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
192
+ import { visit as visit3 } from "unist-util-visit";
193
+
194
+ // src/pipeline/code-block.ts
195
+ var LANGUAGE_LABELS = {
196
+ bash: "Bash",
197
+ c: "C",
198
+ cpp: "C++",
199
+ cs: "C#",
200
+ css: "CSS",
201
+ diff: "Diff",
202
+ docker: "Dockerfile",
203
+ dockerfile: "Dockerfile",
204
+ go: "Go",
205
+ graphql: "GraphQL",
206
+ html: "HTML",
207
+ ini: "INI",
208
+ java: "Java",
209
+ javascript: "JavaScript",
210
+ js: "JavaScript",
211
+ json: "JSON",
212
+ jsonc: "JSON",
213
+ jsx: "JSX",
214
+ kotlin: "Kotlin",
215
+ less: "Less",
216
+ lua: "Lua",
217
+ markdown: "Markdown",
218
+ md: "Markdown",
219
+ mdx: "MDX",
220
+ objc: "Objective-C",
221
+ php: "PHP",
222
+ ps: "PowerShell",
223
+ ps1: "PowerShell",
224
+ powershell: "PowerShell",
225
+ py: "Python",
226
+ python: "Python",
227
+ r: "R",
228
+ rb: "Ruby",
229
+ ruby: "Ruby",
230
+ rs: "Rust",
231
+ rust: "Rust",
232
+ scala: "Scala",
233
+ scss: "SCSS",
234
+ sh: "Shell",
235
+ shell: "Shell",
236
+ sql: "SQL",
237
+ svelte: "Svelte",
238
+ swift: "Swift",
239
+ toml: "TOML",
240
+ ts: "TypeScript",
241
+ tsx: "TSX",
242
+ typescript: "TypeScript",
243
+ vue: "Vue",
244
+ xml: "XML",
245
+ yaml: "YAML",
246
+ yml: "YAML",
247
+ zig: "Zig",
248
+ zsh: "Zsh"
249
+ };
250
+ var HIDDEN_LANGUAGES = /* @__PURE__ */ new Set([
251
+ "",
252
+ "text",
253
+ "plain",
254
+ "plaintext",
255
+ "txt",
256
+ "ansi"
257
+ ]);
258
+ function formatLanguage(lang) {
259
+ if (!lang) return null;
260
+ const lower = lang.toLowerCase();
261
+ if (HIDDEN_LANGUAGES.has(lower)) return null;
262
+ return LANGUAGE_LABELS[lower] ?? lang;
263
+ }
264
+ function rehypeShikiCodeBlockWrapper() {
265
+ return {
266
+ name: "silica:code-block-wrapper",
267
+ root(hast) {
268
+ const pre = hast.children.find(
269
+ (child) => child.type === "element" && child.tagName === "pre"
270
+ );
271
+ if (!pre) return hast;
272
+ const rawLang = typeof this.options.lang === "string" ? this.options.lang : void 0;
273
+ const label = formatLanguage(rawLang);
274
+ const isMermaid = rawLang?.toLowerCase() === "mermaid";
275
+ const wrapper = {
276
+ type: "element",
277
+ tagName: isMermaid ? "silica-mermaid" : "silica-code-block",
278
+ properties: {
279
+ ...rawLang ? { "data-language": rawLang } : {},
280
+ ...isMermaid ? { "data-source": toText(pre) } : {},
281
+ ...label ? { "data-language-label": label } : {}
282
+ },
283
+ children: [pre]
284
+ };
285
+ hast.children = [wrapper];
286
+ return hast;
287
+ }
288
+ };
289
+ }
290
+ function toText(node) {
291
+ return node.children.map((child) => {
292
+ if (child.type === "text") return child.value;
293
+ if (child.type === "element") return toText(child);
294
+ return "";
295
+ }).join("");
296
+ }
297
+
298
+ // src/pipeline/obsidian.ts
299
+ import { visit } from "unist-util-visit";
300
+ import { slug as slugifyHeading } from "github-slugger";
301
+ function remarkSilicaObsidian(context) {
302
+ return async (tree, file) => {
303
+ const links = /* @__PURE__ */ new Set();
304
+ const brokenLinks = [];
305
+ const assetBaseUrl = context.assetBaseUrl ?? "/silica";
306
+ const embedPromises = [];
307
+ transformInlineFootnotes(tree);
308
+ visit(tree, (node) => {
309
+ if (node.type === "image" && typeof node.url === "string") {
310
+ node.url = rewriteAssetUrl(node.url, assetBaseUrl);
311
+ applyImageSize(node);
312
+ return;
313
+ }
314
+ if (!isWikiNode(node)) return;
315
+ if (node.type === "obsidianWikiEmbed" && !isAssetTarget(node.rawTarget)) {
316
+ embedPromises.push(resolveEmbedNode(node, context));
317
+ }
318
+ const targetPath = node.linkTarget.path || String(context.slug);
319
+ if (node.type === "obsidianWikiEmbed" && isAssetTarget(node.rawTarget)) {
320
+ return;
321
+ }
322
+ const resolved = resolveWikiLink(
323
+ context.slug,
324
+ targetPath,
325
+ context.allSlugs,
326
+ context.wikilinkStrategy ?? "shortest",
327
+ context.ordering
328
+ );
329
+ node.data = { ...node.data };
330
+ if (!resolved) {
331
+ node.data.silicaBroken = true;
332
+ brokenLinks.push({ target: node.rawTarget });
333
+ return;
334
+ }
335
+ node.data.silicaResolvedSlug = resolved;
336
+ links.add(resolved);
337
+ });
338
+ await Promise.all(embedPromises);
339
+ file.data.silicaObsidianLinks = [...links];
340
+ file.data.silicaObsidianBrokenLinks = brokenLinks;
341
+ };
342
+ }
343
+ function createSilicaObsidianHandlers(context) {
344
+ return {
345
+ obsidianWikilink(state, node) {
346
+ return wikilinkToHast(state, node);
347
+ },
348
+ obsidianWikiEmbed(state, node) {
349
+ if (node.rawTarget && isAssetTarget(node.rawTarget)) {
350
+ return assetEmbedToHast(context, node);
351
+ }
352
+ const embedHtml = getStringData(node, "silicaEmbedHtml");
353
+ if (embedHtml) {
354
+ return {
355
+ type: "element",
356
+ tagName: "figure",
357
+ properties: {
358
+ className: ["silica-embed", "silica-note-embed"],
359
+ "data-embed-kind": "note",
360
+ "data-embed-target": node.rawTarget
361
+ },
362
+ children: [{ type: "raw", value: embedHtml }]
363
+ };
364
+ }
365
+ return wikilinkToHast(state, node);
366
+ },
367
+ obsidianHighlight(state, node) {
368
+ return {
369
+ type: "element",
370
+ tagName: "mark",
371
+ properties: {},
372
+ children: state.all(node)
373
+ };
374
+ },
375
+ obsidianCallout(state, node) {
376
+ return {
377
+ type: "element",
378
+ tagName: "silica-callout",
379
+ properties: {
380
+ className: ["silica-callout"],
381
+ "data-callout": node.kind ?? "note",
382
+ "data-callout-title": node.title ?? titleCase(node.kind ?? "note"),
383
+ ...node.fold ? {
384
+ "data-callout-foldable": "true",
385
+ "data-callout-open": node.fold === "open" ? "true" : "false"
386
+ } : {}
387
+ },
388
+ children: state.all(node)
389
+ };
390
+ },
391
+ obsidianTag(state, node) {
392
+ return {
393
+ type: "element",
394
+ tagName: "a",
395
+ properties: { href: tagToHref(node.tag ?? "") },
396
+ children: state.all(node)
397
+ };
398
+ },
399
+ obsidianComment(_state, _node) {
400
+ return { type: "text", value: "" };
401
+ },
402
+ obsidianBlockId(_state, node) {
403
+ return {
404
+ type: "element",
405
+ tagName: "span",
406
+ properties: {
407
+ id: `^${node.id}`,
408
+ className: ["silica-block-id"],
409
+ "data-silica-block-id": node.id,
410
+ ariaHidden: "true"
411
+ },
412
+ children: []
413
+ };
414
+ }
415
+ };
416
+ }
417
+ function transformInlineFootnotes(tree) {
418
+ const definitions = [];
419
+ let nextIndex = 1;
420
+ visit(
421
+ tree,
422
+ "obsidianInlineFootnote",
423
+ (node, index, parent) => {
424
+ if (index === void 0 || !parent) return;
425
+ const identifier = `obsidian-inline-${nextIndex++}`;
426
+ parent.children[index] = {
427
+ type: "footnoteReference",
428
+ identifier,
429
+ label: identifier
430
+ };
431
+ definitions.push({
432
+ type: "footnoteDefinition",
433
+ identifier,
434
+ label: identifier,
435
+ children: [
436
+ {
437
+ type: "paragraph",
438
+ children: node.children.length ? node.children : [{ type: "text", value: node.value }]
439
+ }
440
+ ]
441
+ });
442
+ }
443
+ );
444
+ tree.children.push(...definitions);
445
+ }
446
+ function wikilinkToHast(_state, node) {
447
+ const label = node.alias || node.target || "";
448
+ const resolved = getResolvedSlug(node);
449
+ if (!resolved) {
450
+ return {
451
+ type: "element",
452
+ tagName: "span",
453
+ properties: { className: ["silica-broken-link"] },
454
+ children: [{ type: "text", value: label }]
455
+ };
456
+ }
457
+ return {
458
+ type: "element",
459
+ tagName: "a",
460
+ properties: { href: `${slugToHref(resolved)}${targetFragment(node)}` },
461
+ children: [{ type: "text", value: label }]
462
+ };
463
+ }
464
+ function isWikiNode(node) {
465
+ return (node.type === "obsidianWikilink" || node.type === "obsidianWikiEmbed") && typeof node.rawTarget === "string";
466
+ }
467
+ function getResolvedSlug(node) {
468
+ const value = node.data?.silicaResolvedSlug;
469
+ return typeof value === "string" ? value : void 0;
470
+ }
471
+ function getStringData(node, key) {
472
+ const value = node.data?.[key];
473
+ return typeof value === "string" ? value : void 0;
474
+ }
475
+ async function resolveEmbedNode(node, context) {
476
+ if (!context.resolveEmbed) return;
477
+ const maxDepth = context.maxEmbedDepth ?? 3;
478
+ const depth = context.embedDepth ?? 0;
479
+ if (depth >= maxDepth) return;
480
+ const html = await context.resolveEmbed(node.linkTarget);
481
+ if (!html) return;
482
+ node.data = {
483
+ ...node.data,
484
+ silicaEmbedHtml: html
485
+ };
486
+ }
487
+ function assetEmbedToHast(context, node) {
488
+ const kind = assetKind(node.rawTarget);
489
+ const src = assetUrl(context.assetBaseUrl ?? "/silica", node.rawTarget);
490
+ const label = node.alias || node.rawTarget.split("/").at(-1) || node.rawTarget;
491
+ const dimensions = sizeProperties(
492
+ node.embedSize ?? sizeFromParams(node.linkTarget.params)
493
+ );
494
+ if (kind === "image") {
495
+ return {
496
+ type: "element",
497
+ tagName: "img",
498
+ properties: {
499
+ src,
500
+ alt: label,
501
+ ...dimensions
502
+ },
503
+ children: []
504
+ };
505
+ }
506
+ if (kind === "audio" || kind === "video") {
507
+ return {
508
+ type: "element",
509
+ tagName: kind,
510
+ properties: {
511
+ src,
512
+ controls: true,
513
+ ...dimensions
514
+ },
515
+ children: []
516
+ };
517
+ }
518
+ return {
519
+ type: "element",
520
+ tagName: "silica-embed",
521
+ properties: {
522
+ src,
523
+ "data-embed-kind": kind,
524
+ "data-embed-target": node.rawTarget,
525
+ ...dimensions
526
+ },
527
+ children: [{ type: "text", value: label }]
528
+ };
529
+ }
530
+ function targetFragment(node) {
531
+ if (node.linkTarget.blockId) {
532
+ return `#^${encodeURIComponent(node.linkTarget.blockId)}`;
533
+ }
534
+ if (node.linkTarget.heading) {
535
+ return `#${slugifyHeading(node.linkTarget.heading)}`;
536
+ }
537
+ return "";
538
+ }
539
+ function applyImageSize(node) {
540
+ const size = node.data?.obsidianEmbedSize;
541
+ if (!size) return;
542
+ node.data = {
543
+ ...node.data,
544
+ hProperties: {
545
+ ...node.data?.hProperties ?? {},
546
+ ...sizeProperties(size)
547
+ }
548
+ };
549
+ }
550
+ function sizeFromParams(params) {
551
+ const height = Number(params?.height);
552
+ if (!Number.isFinite(height) || height <= 0) return;
553
+ return { width: 0, height };
554
+ }
555
+ function sizeProperties(size) {
556
+ if (!size) return {};
557
+ return {
558
+ ...size.width > 0 ? { width: size.width } : {},
559
+ ...size.height ? { height: size.height } : {}
560
+ };
561
+ }
562
+ function rewriteAssetUrl(url, assetBaseUrl) {
563
+ if (!isAssetTarget(url)) return url;
564
+ if (/^(?:https?:|#|\/)/.test(url)) return url;
565
+ return `${assetBaseUrl}/${url.replace(/^\.?\//, "")}`;
566
+ }
567
+ function isAssetTarget(target) {
568
+ return /\.(png|jpe?g|gif|webp|svg|pdf|mp4|mov|mp3|wav|ogg|canvas)(?:[?#].*)?$/i.test(
569
+ target
570
+ );
571
+ }
572
+ function isImageTarget(target) {
573
+ return /\.(png|jpe?g|gif|webp|svg)(?:[?#].*)?$/i.test(target);
574
+ }
575
+ function assetKind(target) {
576
+ if (isImageTarget(target)) return "image";
577
+ if (/\.(mp3|wav|ogg)(?:[?#].*)?$/i.test(target)) return "audio";
578
+ if (/\.(mp4|mov)(?:[?#].*)?$/i.test(target)) return "video";
579
+ if (/\.pdf(?:[?#].*)?$/i.test(target)) return "pdf";
580
+ if (/\.canvas(?:[?#].*)?$/i.test(target)) return "canvas";
581
+ return "file";
582
+ }
583
+ function assetUrl(assetBaseUrl, target) {
584
+ const cleaned = stripEmbedOnlyParams(target);
585
+ if (/^(?:https?:|#|\/)/.test(cleaned)) return cleaned;
586
+ return `${assetBaseUrl}/${cleaned.replace(/^\/+/, "")}`;
587
+ }
588
+ function stripEmbedOnlyParams(target) {
589
+ const hashIndex = target.indexOf("#");
590
+ if (hashIndex === -1) return target;
591
+ const before = target.slice(0, hashIndex);
592
+ const fragment = target.slice(hashIndex + 1);
593
+ if (!fragment.includes("=")) return target;
594
+ const params = new URLSearchParams(fragment);
595
+ params.delete("height");
596
+ const remaining = params.toString();
597
+ return remaining ? `${before}#${remaining}` : before;
598
+ }
599
+ function titleCase(value) {
600
+ return value.split(/[-_\s]+/).filter(Boolean).map((part) => part[0].toUpperCase() + part.slice(1)).join(" ");
601
+ }
602
+
603
+ // src/pipeline/plugins.ts
604
+ import Slugger from "github-slugger";
605
+ import { toString } from "hast-util-to-string";
606
+ import { visit as visit2 } from "unist-util-visit";
607
+ function rehypeCollectTocAndLinks() {
608
+ return (tree, file) => {
609
+ const toc = [];
610
+ const links = /* @__PURE__ */ new Set();
611
+ const slugger = new Slugger();
612
+ visit2(tree, "element", (node) => {
613
+ if (isHeading(node)) {
614
+ const text = toString(node);
615
+ const id = String(node.properties?.id ?? slugger.slug(text));
616
+ node.properties = { ...node.properties, id };
617
+ if (id !== "footnote-label") {
618
+ toc.push({ id, text, depth: Number(node.tagName?.slice(1) ?? 2) });
619
+ }
620
+ }
621
+ if (node.tagName === "a" && typeof node.properties?.href === "string") {
622
+ const href = node.properties.href;
623
+ if (href.startsWith("/") && !href.startsWith("/silica/")) {
624
+ links.add(hrefToSlug(href));
625
+ }
626
+ }
627
+ });
628
+ file.data.toc = toc;
629
+ file.data.links = [...links];
630
+ };
631
+ }
632
+ function rehypeCleanFootnoteHeadings() {
633
+ return (tree) => {
634
+ visit2(tree, "element", (node) => {
635
+ if (node.properties?.id !== "footnote-label") return;
636
+ const child = node.children?.[0];
637
+ if (child?.tagName !== "a") return;
638
+ node.children = child.children ?? [];
639
+ });
640
+ };
641
+ }
642
+ function rehypeExternalLinks() {
643
+ return (tree) => {
644
+ visit2(tree, "element", (node) => {
645
+ if (node.tagName !== "a" || typeof node.properties?.href !== "string")
646
+ return;
647
+ if (!/^https?:\/\//.test(node.properties.href)) return;
648
+ node.properties = {
649
+ ...node.properties,
650
+ rel: "noreferrer noopener",
651
+ target: "_blank"
652
+ };
653
+ });
654
+ };
655
+ }
656
+ function rehypeUnwrapSilicaEmbeds() {
657
+ return (tree) => {
658
+ unwrapStandaloneEmbeds(tree);
659
+ };
660
+ }
661
+ function rehypeRestoreObsidianBlockIds() {
662
+ return (tree) => {
663
+ restoreGeneratedFootnoteIds(tree);
664
+ visit2(tree, "element", (node) => {
665
+ if (node.tagName !== "span") return;
666
+ const blockId = getStringProperty(
667
+ node,
668
+ "dataSilicaBlockId",
669
+ "data-silica-block-id"
670
+ );
671
+ if (!blockId) return;
672
+ node.properties = { ...node.properties, id: `^${blockId}` };
673
+ });
674
+ };
675
+ }
676
+ function getDataArray(data, key) {
677
+ const value = data[key];
678
+ return Array.isArray(value) ? value : [];
679
+ }
680
+ function mergeBrokenLinks(a, b) {
681
+ const seen = /* @__PURE__ */ new Set();
682
+ const merged = [];
683
+ for (const item of [...a, ...b]) {
684
+ const key = `${item.source}\0${item.target}`;
685
+ if (seen.has(key)) continue;
686
+ seen.add(key);
687
+ merged.push(item);
688
+ }
689
+ return merged;
690
+ }
691
+ function isHeading(node) {
692
+ return node.type === "element" && /^h[1-6]$/.test(node.tagName ?? "");
693
+ }
694
+ function getStringProperty(node, camelCaseKey, kebabCaseKey) {
695
+ const value = node.properties?.[camelCaseKey] ?? node.properties?.[kebabCaseKey];
696
+ return typeof value === "string" ? value : void 0;
697
+ }
698
+ function restoreGeneratedFootnoteIds(node, inFootnotes = false) {
699
+ const isFootnotes = inFootnotes || isFootnotesSection(node);
700
+ if (isFootnotes || isFootnoteReference(node)) {
701
+ normalizeGeneratedFootnoteProperties(node);
702
+ }
703
+ for (const child of node.children ?? []) {
704
+ restoreGeneratedFootnoteIds(child, isFootnotes);
705
+ }
706
+ }
707
+ function normalizeGeneratedFootnoteProperties(node) {
708
+ if (!node.properties) return;
709
+ normalizeProperty(node, "id");
710
+ normalizeProperty(node, "href");
711
+ normalizeProperty(node, "ariaDescribedBy");
712
+ normalizeProperty(node, "aria-describedby");
713
+ }
714
+ function normalizeProperty(node, key) {
715
+ const value = node.properties?.[key];
716
+ if (typeof value !== "string") return;
717
+ node.properties = {
718
+ ...node.properties,
719
+ [key]: normalizeGeneratedFootnoteReference(value)
720
+ };
721
+ }
722
+ function normalizeGeneratedFootnoteReference(value) {
723
+ const prefix = value.startsWith("#") ? "#" : "";
724
+ const id = prefix ? value.slice(1) : value;
725
+ if (id === "user-content-footnote-label") return `${prefix}footnote-label`;
726
+ if (id.startsWith("user-content-user-content-fn")) {
727
+ return `${prefix}${id.replace(/^user-content-/, "")}`;
728
+ }
729
+ return value;
730
+ }
731
+ function isFootnotesSection(node) {
732
+ return node.tagName === "section" && hasProperty(node, "dataFootnotes", "data-footnotes");
733
+ }
734
+ function isFootnoteReference(node) {
735
+ return node.tagName === "a" && hasProperty(node, "dataFootnoteRef", "data-footnote-ref");
736
+ }
737
+ function hasProperty(node, camelCaseKey, kebabCaseKey) {
738
+ return node.properties?.[camelCaseKey] !== void 0 || node.properties?.[kebabCaseKey] !== void 0;
739
+ }
740
+ function unwrapStandaloneEmbeds(parent) {
741
+ if (!parent.children) return;
742
+ parent.children = parent.children.map((child) => {
743
+ unwrapStandaloneEmbeds(child);
744
+ if (child.tagName !== "p") return child;
745
+ const renderedChildren = child.children?.filter(
746
+ (candidate) => !isWhitespaceText(candidate)
747
+ );
748
+ const onlyChild = renderedChildren?.[0];
749
+ if (renderedChildren?.length === 1 && isStandaloneEmbed(onlyChild)) {
750
+ return onlyChild;
751
+ }
752
+ return child;
753
+ });
754
+ }
755
+ function isStandaloneEmbed(node) {
756
+ if (node?.tagName === "silica-embed") return true;
757
+ if (node?.tagName !== "figure") return false;
758
+ const kind = node.properties?.dataEmbedKind ?? node.properties?.["data-embed-kind"];
759
+ return kind === "note";
760
+ }
761
+ function isWhitespaceText(node) {
762
+ return node.type === "text" && /^\s*$/.test(node.value ?? "");
763
+ }
764
+
765
+ // src/pipeline/index.ts
766
+ var headingLinkIcon = {
767
+ type: "element",
768
+ tagName: "svg",
769
+ properties: {
770
+ ariaHidden: "true",
771
+ className: ["silica-heading-link-icon"],
772
+ fill: "none",
773
+ stroke: "currentColor",
774
+ strokeLinecap: "round",
775
+ strokeLinejoin: "round",
776
+ strokeWidth: 2,
777
+ viewBox: "0 0 24 24"
778
+ },
779
+ children: [
780
+ {
781
+ type: "element",
782
+ tagName: "path",
783
+ properties: {
784
+ d: "M9 17H7A5 5 0 0 1 7 7h2"
785
+ },
786
+ children: []
787
+ },
788
+ {
789
+ type: "element",
790
+ tagName: "path",
791
+ properties: {
792
+ d: "M15 7h2a5 5 0 1 1 0 10h-2"
793
+ },
794
+ children: []
795
+ },
796
+ {
797
+ type: "element",
798
+ tagName: "line",
799
+ properties: {
800
+ x1: 8,
801
+ x2: 16,
802
+ y1: 12,
803
+ y2: 12
804
+ },
805
+ children: []
806
+ }
807
+ ]
808
+ };
809
+ var sanitizeSchema = {
810
+ ...defaultSchema,
811
+ attributes: {
812
+ ...defaultSchema.attributes,
813
+ span: [
814
+ ...defaultSchema.attributes?.span ?? [],
815
+ ["className", "silica-broken-link"],
816
+ ["className", "silica-block-id"],
817
+ ["dataSilicaBlockId"],
818
+ ["data-silica-block-id"],
819
+ ["ariaHidden"],
820
+ ["aria-hidden"]
821
+ ],
822
+ sup: [
823
+ ...defaultSchema.attributes?.sup ?? [],
824
+ ["className", "silica-inline-footnote"]
825
+ ],
826
+ img: [...defaultSchema.attributes?.img ?? [], ["width"], ["height"]],
827
+ audio: [["src"], ["controls"], ["width"], ["height"]],
828
+ video: [["src"], ["controls"], ["width"], ["height"]],
829
+ source: [["src"], ["type"]],
830
+ figure: [
831
+ ...defaultSchema.attributes?.figure ?? [],
832
+ ["className", "silica-embed", "silica-note-embed"],
833
+ ["dataEmbedKind"],
834
+ ["data-embed-kind"],
835
+ ["dataEmbedTarget"],
836
+ ["data-embed-target"]
837
+ ],
838
+ strong: [
839
+ ...defaultSchema.attributes?.strong ?? [],
840
+ ["className", "silica-callout-title"],
841
+ ["dataCallout"],
842
+ ["data-callout"],
843
+ ["dataCalloutFold"],
844
+ ["data-callout-fold"]
845
+ ],
846
+ "silica-callout": [
847
+ ["className", "silica-callout"],
848
+ ["dataCallout"],
849
+ ["data-callout"],
850
+ ["dataCalloutTitle"],
851
+ ["data-callout-title"],
852
+ ["dataCalloutFoldable"],
853
+ ["data-callout-foldable"],
854
+ ["dataCalloutOpen"],
855
+ ["data-callout-open"]
856
+ ],
857
+ "silica-embed": [
858
+ ["src"],
859
+ ["width"],
860
+ ["height"],
861
+ ["dataEmbedKind"],
862
+ ["data-embed-kind"],
863
+ ["dataEmbedTarget"],
864
+ ["data-embed-target"]
865
+ ],
866
+ "silica-mermaid": [
867
+ ["dataSource"],
868
+ ["data-source"],
869
+ ["dataLanguage"],
870
+ ["data-language"],
871
+ ["dataLanguageLabel"],
872
+ ["data-language-label"]
873
+ ],
874
+ mark: defaultSchema.attributes?.mark ?? []
875
+ },
876
+ tagNames: [
877
+ ...defaultSchema.tagNames ?? [],
878
+ "mark",
879
+ "audio",
880
+ "video",
881
+ "source",
882
+ "figure",
883
+ "silica-callout",
884
+ "silica-embed",
885
+ "silica-mermaid"
886
+ ]
887
+ };
888
+ async function renderMarkdown(raw, context) {
889
+ const parsed = matter(raw);
890
+ const inlineTags = context.tags?.inline ?? true;
891
+ const processor = baseProcessor(context).use(rehypeUnwrapSilicaEmbeds).use(rehypeRaw).use(rehypeSanitize, sanitizeSchema).use(rehypeRestoreObsidianBlockIds).use(rehypeUnwrapSilicaEmbeds).use(rehypeKatex);
892
+ if (hasCodeFence(parsed.content)) {
893
+ processor.use(rehypeShiki, {
894
+ themes: {
895
+ light: "github-light",
896
+ dark: "github-dark"
897
+ },
898
+ defaultColor: "light-dark()",
899
+ // Ensure unlabeled fences still flow through Shiki so they pick up the
900
+ // wrapper transformer below (just without a language header).
901
+ defaultLanguage: "text",
902
+ rootStyle: false,
903
+ transformers: [rehypeShikiCodeBlockWrapper()]
904
+ });
905
+ }
906
+ processor.use(rehypeSlug).use(rehypeAutolinkHeadings, {
907
+ behavior: "wrap",
908
+ content: headingLinkIcon,
909
+ properties: { className: ["silica-heading-link"] }
910
+ }).use(rehypeCleanFootnoteHeadings).use(rehypeCollectTocAndLinks).use(rehypeExternalLinks).use(rehypeReact, {
911
+ Fragment,
912
+ jsx,
913
+ jsxs,
914
+ components: context.components
915
+ });
916
+ const file = await processor.process(parsed.content);
917
+ const frontmatter = parsed.data;
918
+ const obsidianBrokenLinks = getDataArray(
919
+ file.data,
920
+ "silicaObsidianBrokenLinks"
921
+ ).map((link) => ({ source: String(context.slug), target: link.target }));
922
+ const links = unique([
923
+ ...getDataArray(file.data, "silicaObsidianLinks"),
924
+ ...getDataArray(file.data, "links")
925
+ ]);
926
+ const toc = getDataArray(file.data, "toc");
927
+ const plainText = extractPlainText(parsed.content);
928
+ return {
929
+ content: file.result,
930
+ frontmatter,
931
+ toc,
932
+ links,
933
+ brokenLinks: mergeBrokenLinks(obsidianBrokenLinks, []),
934
+ plainText,
935
+ title: getTitle(frontmatter, plainText),
936
+ description: getDescription(frontmatter, plainText),
937
+ tags: getTags(frontmatter, parsed.content, { inline: inlineTags })
938
+ };
939
+ }
940
+ async function renderMarkdownHtml(raw, context) {
941
+ const parsed = matter(raw);
942
+ const processor = baseProcessor(context).use(rehypeUnwrapSilicaEmbeds).use(rehypeRaw).use(rehypeSanitize, sanitizeSchema).use(rehypeRestoreObsidianBlockIds).use(rehypeUnwrapSilicaEmbeds).use(rehypeKatex);
943
+ if (hasCodeFence(parsed.content)) {
944
+ processor.use(rehypeShiki, {
945
+ themes: {
946
+ light: "github-light",
947
+ dark: "github-dark"
948
+ },
949
+ defaultColor: "light-dark()",
950
+ defaultLanguage: "text",
951
+ rootStyle: false,
952
+ transformers: [rehypeShikiCodeBlockWrapper()]
953
+ });
954
+ }
955
+ processor.use(rehypeExternalLinks).use(rehypeStringify);
956
+ const file = await processor.process(parsed.content);
957
+ return String(file);
958
+ }
959
+ async function analyzeMarkdown(raw, context) {
960
+ const parsed = matter(raw);
961
+ const inlineTags = context.tags?.inline ?? true;
962
+ const file = await runRemarkObsidian(parsed.content, context);
963
+ const plainText = extractPlainText(parsed.content);
964
+ const frontmatter = parsed.data;
965
+ const brokenLinks = getDataArray(
966
+ file.data,
967
+ "silicaObsidianBrokenLinks"
968
+ ).map((link) => ({ source: String(context.slug), target: link.target }));
969
+ return {
970
+ frontmatter,
971
+ toc: [],
972
+ links: getDataArray(file.data, "silicaObsidianLinks"),
973
+ brokenLinks,
974
+ plainText,
975
+ title: getTitle(frontmatter, plainText),
976
+ description: getDescription(frontmatter, plainText),
977
+ tags: getTags(frontmatter, parsed.content, { inline: inlineTags })
978
+ };
979
+ }
980
+ function getTitle(frontmatter, plainText) {
981
+ if (typeof frontmatter.title === "string" && frontmatter.title.trim())
982
+ return frontmatter.title.trim();
983
+ const heading = plainText.split("\n").map((line) => line.trim()).find(Boolean);
984
+ return heading?.replace(/^#+\s*/, "");
985
+ }
986
+ function getDescription(frontmatter, plainText) {
987
+ if (typeof frontmatter.description === "string" && frontmatter.description.trim()) {
988
+ return frontmatter.description.trim();
989
+ }
990
+ const sentence = extractPlainText(plainText).slice(0, 180).trim();
991
+ return sentence || void 0;
992
+ }
993
+ function baseProcessor(context) {
994
+ return unified().use(remarkParse).use(remarkFrontmatter, ["yaml"]).use(remarkGfm).use(remarkMath).use(remarkObsidian, { inlineTags: context.tags?.inline ?? true }).use(remarkSilicaObsidian, context).use(remarkRehype, {
995
+ allowDangerousHtml: true,
996
+ handlers: createSilicaObsidianHandlers(context)
997
+ });
998
+ }
999
+ async function runRemarkObsidian(markdown, context) {
1000
+ const processor = unified().use(remarkParse).use(remarkFrontmatter, ["yaml"]).use(remarkGfm).use(remarkMath).use(remarkObsidian, { inlineTags: context.tags?.inline ?? true }).use(remarkSilicaObsidian, context);
1001
+ const tree = processor.parse(markdown);
1002
+ const file = { data: {} };
1003
+ await processor.run(tree, file);
1004
+ return file;
1005
+ }
1006
+ function extractPlainText(markdown) {
1007
+ return markdown.replace(/```[\s\S]*?```/g, "").replace(/%%[\s\S]*?%%/g, "").replace(/\^\[[^\]]+]/g, "").replace(/(?:^|\s)\^[A-Za-z0-9-]+/g, " ").replace(/`([^`]+)`/g, "$1").replace(/!\[[^\]]*]\([^)]+\)/g, "").replace(/\[([^\]]+)]\([^)]+\)/g, "$1").replace(/[#>*_\-~`]+/g, " ").replace(/\s+/g, " ").trim();
1008
+ }
1009
+ function hasCodeFence(markdown) {
1010
+ return /(^|\n)(```|~~~)/.test(markdown);
1011
+ }
1012
+ function unique(values) {
1013
+ return [...new Set(values.filter(Boolean))];
1014
+ }
1015
+
1016
+ export {
1017
+ asFilePath,
1018
+ asFullSlug,
1019
+ asSimpleSlug,
1020
+ asRelativeURL,
1021
+ normalizePath,
1022
+ slugifySegment,
1023
+ normalizeSlug,
1024
+ slugifyFilePath,
1025
+ hasNumericPrefixInPath,
1026
+ numericPrefixSortKey,
1027
+ simplifySlug,
1028
+ slugToHref,
1029
+ hrefToSlug,
1030
+ pathToRoot,
1031
+ resolveRelative,
1032
+ resolveWikiLink,
1033
+ joinSegments,
1034
+ getMenuLabel,
1035
+ getPageProperties,
1036
+ formatPropertyLabel,
1037
+ formatPropertyValue,
1038
+ tagToHref,
1039
+ renderMarkdown,
1040
+ renderMarkdownHtml,
1041
+ analyzeMarkdown,
1042
+ getTitle,
1043
+ getDescription
1044
+ };
1045
+ //# sourceMappingURL=chunk-L565TMI5.js.map