@silicajs/core 0.6.1 → 0.7.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.
@@ -87,16 +87,26 @@ function createWikiLinkResolutionIndex(allSlugs, options = {}) {
87
87
  )
88
88
  );
89
89
  const uniqueSlugByBasename = /* @__PURE__ */ new Map();
90
+ const slugsByBasename = /* @__PURE__ */ new Map();
90
91
  for (const slug of candidates) {
91
92
  const basename = basenameForNormalizedSlug(slug);
92
93
  if (!basename) continue;
94
+ const basenameCandidates = slugsByBasename.get(basename);
95
+ if (basenameCandidates) {
96
+ basenameCandidates.push(slug);
97
+ } else {
98
+ slugsByBasename.set(basename, [slug]);
99
+ }
93
100
  if (uniqueSlugByBasename.has(basename)) {
94
101
  uniqueSlugByBasename.set(basename, null);
95
102
  continue;
96
103
  }
97
104
  uniqueSlugByBasename.set(basename, slug);
98
105
  }
99
- return { candidates, uniqueSlugByBasename };
106
+ for (const basenameCandidates of slugsByBasename.values()) {
107
+ basenameCandidates.sort(compareSlugs);
108
+ }
109
+ return { candidates, uniqueSlugByBasename, slugsByBasename };
100
110
  }
101
111
  function createAssetResolutionIndex(assets, options = {}) {
102
112
  const assetPathBySourcePath = /* @__PURE__ */ new Map();
@@ -132,7 +142,11 @@ function resolveWikiLink(currentSlug, target, index, strategy = "shortest", opti
132
142
  return asFullSlug(`${normalizedTarget}/index`);
133
143
  const targetBasename = normalizedTarget.split("/").at(-1) ?? "";
134
144
  const byBasename = index.uniqueSlugByBasename.get(targetBasename);
135
- return byBasename ? asFullSlug(byBasename) : void 0;
145
+ if (byBasename) return asFullSlug(byBasename);
146
+ return closestWikiLinkCandidate(
147
+ currentSlug,
148
+ candidatesForBasename(index, targetBasename)
149
+ );
136
150
  }
137
151
  function resolveAssetPath(currentSourcePath, target, index, strategy = "shortest", options = {}) {
138
152
  if (/^(?:https?:|#|\/)/.test(target)) return void 0;
@@ -166,6 +180,49 @@ function basenameForNormalizedSlug(slug) {
166
180
  const simplified = slug === "index" ? "" : slug.replace(/\/index$/, "");
167
181
  return simplified.split("/").at(-1) ?? "";
168
182
  }
183
+ function candidatesForBasename(index, basename) {
184
+ const indexedCandidates = index.slugsByBasename?.get(basename);
185
+ if (indexedCandidates) return indexedCandidates;
186
+ return [...index.candidates].filter((slug) => basenameForNormalizedSlug(slug) === basename).sort(compareSlugs);
187
+ }
188
+ function closestWikiLinkCandidate(currentSlug, candidates) {
189
+ if (candidates.length === 0) return void 0;
190
+ const currentDirectory = normalizeSlug(currentSlug, {
191
+ numericPrefixes: false
192
+ }).split("/").slice(0, -1);
193
+ const [bestCandidate] = [...candidates].sort((left, right) => {
194
+ const leftScore = wikiLinkCandidateScore(currentDirectory, left);
195
+ const rightScore = wikiLinkCandidateScore(currentDirectory, right);
196
+ return rightScore.sharedPrefixLength - leftScore.sharedPrefixLength || leftScore.depth - rightScore.depth || compareSlugs(left, right);
197
+ });
198
+ return bestCandidate ? asFullSlug(bestCandidate) : void 0;
199
+ }
200
+ function wikiLinkCandidateScore(currentDirectory, slug) {
201
+ const simplified = simplifyCandidateSlug(slug);
202
+ const segments = simplified ? simplified.split("/") : [];
203
+ return {
204
+ sharedPrefixLength: sharedPrefixLength(
205
+ currentDirectory,
206
+ segments.slice(0, -1)
207
+ ),
208
+ depth: segments.length
209
+ };
210
+ }
211
+ function simplifyCandidateSlug(slug) {
212
+ return slug === "index" ? "" : slug.replace(/\/index$/, "");
213
+ }
214
+ function sharedPrefixLength(left, right) {
215
+ let length = 0;
216
+ while (left[length] !== void 0 && left[length] === right[length]) {
217
+ length += 1;
218
+ }
219
+ return length;
220
+ }
221
+ function compareSlugs(left, right) {
222
+ if (left < right) return -1;
223
+ if (left > right) return 1;
224
+ return 0;
225
+ }
169
226
  function normalizeAssetSegment(segment, options = {}) {
170
227
  const extension = path.posix.extname(segment);
171
228
  if (!extension) return slugifySegment(segment, options);
@@ -206,6 +263,221 @@ function numericPrefixSortSegment(segment) {
206
263
  return `${order}:${slugifySegment(match[2])}`;
207
264
  }
208
265
 
266
+ // src/pipeline/frontmatter.ts
267
+ import { slug as slugifyHeading } from "github-slugger";
268
+ import { unified } from "unified";
269
+ import remarkParse from "remark-parse";
270
+ import {
271
+ remarkObsidian
272
+ } from "@silicajs/remark-obsidian";
273
+ var RESERVED_FRONTMATTER_KEYS = /* @__PURE__ */ new Set([
274
+ "aliases",
275
+ "alias",
276
+ "created",
277
+ "cssclass",
278
+ "cssclasses",
279
+ "date",
280
+ "description",
281
+ "draft",
282
+ "listed",
283
+ "menu_label",
284
+ "modified",
285
+ "permalink",
286
+ "publish",
287
+ "tag",
288
+ "tags",
289
+ "title"
290
+ ]);
291
+ var ISO_DATE_STRING_PATTERN = /^(\d{4})-(\d{2})-(\d{2})(?:T\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:?\d{2})?)?$/;
292
+ var PAGE_PROPERTY_DATE_FORMATTER = new Intl.DateTimeFormat("en-US", {
293
+ day: "numeric",
294
+ month: "long",
295
+ timeZone: "UTC",
296
+ year: "numeric"
297
+ });
298
+ function getMenuLabel(frontmatter, title) {
299
+ if (typeof frontmatter.menu_label === "string" && frontmatter.menu_label.trim()) {
300
+ return frontmatter.menu_label.trim();
301
+ }
302
+ return title;
303
+ }
304
+ function getPageProperties(frontmatter) {
305
+ return Object.entries(frontmatter).filter(([key]) => !RESERVED_FRONTMATTER_KEYS.has(key.toLowerCase())).map(([key, value]) => {
306
+ const formatted = formatPropertyValue(value);
307
+ if (formatted === void 0) return void 0;
308
+ return {
309
+ key,
310
+ label: formatPropertyLabel(key),
311
+ value: formatted
312
+ };
313
+ }).filter((property) => property !== void 0).sort((a, b) => a.key.localeCompare(b.key));
314
+ }
315
+ function getResolvedPageProperties(frontmatter, context) {
316
+ return getPageProperties(frontmatter).map((property) => ({
317
+ ...property,
318
+ parts: resolvePagePropertyValue(property.value, context).parts
319
+ }));
320
+ }
321
+ function analyzePagePropertyLinks(frontmatter, context) {
322
+ const links = /* @__PURE__ */ new Set();
323
+ const brokenLinks = [];
324
+ for (const property of getPageProperties(frontmatter)) {
325
+ const resolution = resolvePagePropertyValue(property.value, context);
326
+ for (const link of resolution.links) links.add(link);
327
+ brokenLinks.push(...resolution.brokenLinks);
328
+ }
329
+ return { links: [...links], brokenLinks };
330
+ }
331
+ function resolvePagePropertyValue(value, context) {
332
+ const nodes = collectPropertyWikiNodes(value, context);
333
+ if (nodes.length === 0) {
334
+ return {
335
+ parts: [{ type: "text", value }],
336
+ links: [],
337
+ brokenLinks: []
338
+ };
339
+ }
340
+ const parts = [];
341
+ const links = /* @__PURE__ */ new Set();
342
+ const brokenLinks = [];
343
+ let cursor = 0;
344
+ for (const node of nodes) {
345
+ const range = nodeRange(node);
346
+ if (!range || range.start < cursor) continue;
347
+ if (range.start > cursor) {
348
+ parts.push({ type: "text", value: value.slice(cursor, range.start) });
349
+ }
350
+ const label = node.alias || node.target || "";
351
+ const targetPath = node.linkTarget.path || String(context.slug);
352
+ const resolved = resolvePagePropertyWikiTarget(context, targetPath);
353
+ if (resolved) {
354
+ links.add(resolved);
355
+ parts.push({
356
+ type: "link",
357
+ value: label,
358
+ target: node.rawTarget,
359
+ slug: resolved,
360
+ href: `${slugToHref(resolved)}${targetFragment(node)}`
361
+ });
362
+ } else {
363
+ brokenLinks.push({
364
+ source: String(context.slug),
365
+ target: node.rawTarget
366
+ });
367
+ parts.push({
368
+ type: "broken-link",
369
+ value: label,
370
+ target: node.rawTarget
371
+ });
372
+ }
373
+ cursor = range.end;
374
+ }
375
+ if (cursor < value.length) {
376
+ parts.push({ type: "text", value: value.slice(cursor) });
377
+ }
378
+ return { parts, links: [...links], brokenLinks };
379
+ }
380
+ function formatPropertyLabel(key) {
381
+ return key.replace(/[_-]+/g, " ").replace(/([a-z0-9])([A-Z])/g, "$1 $2").toLowerCase();
382
+ }
383
+ function formatPropertyValue(value) {
384
+ if (value === null || value === void 0) return void 0;
385
+ if (value instanceof Date) {
386
+ return formatPagePropertyDate(value);
387
+ }
388
+ if (typeof value === "string") {
389
+ const trimmed = value.trim();
390
+ if (!trimmed) return void 0;
391
+ return formatIsoDateString(trimmed) ?? trimmed;
392
+ }
393
+ if (typeof value === "number" || typeof value === "boolean") {
394
+ return String(value);
395
+ }
396
+ if (Array.isArray(value)) {
397
+ const items = value.map((item) => formatPropertyValue(item)).filter((item) => item !== void 0);
398
+ return items.length ? items.join(", ") : void 0;
399
+ }
400
+ if (typeof value === "object") {
401
+ return JSON.stringify(value);
402
+ }
403
+ return String(value);
404
+ }
405
+ function formatPagePropertyDate(date) {
406
+ if (Number.isNaN(date.getTime())) return void 0;
407
+ return PAGE_PROPERTY_DATE_FORMATTER.format(date);
408
+ }
409
+ function formatIsoDateString(value) {
410
+ const match = ISO_DATE_STRING_PATTERN.exec(value);
411
+ if (!match) return void 0;
412
+ const year = Number(match[1]);
413
+ const month = Number(match[2]);
414
+ const day = Number(match[3]);
415
+ const date = new Date(Date.UTC(year, month - 1, day));
416
+ if (date.getUTCFullYear() !== year || date.getUTCMonth() !== month - 1 || date.getUTCDate() !== day) {
417
+ return void 0;
418
+ }
419
+ return formatPagePropertyDate(date);
420
+ }
421
+ function collectPropertyWikiNodes(value, context) {
422
+ const processor = unified().use(remarkParse).use(remarkObsidian, { inlineTags: context.tags?.inline ?? true });
423
+ const tree = processor.parse(value);
424
+ const nodes = [];
425
+ visitPropertyNodes(tree, (node) => {
426
+ if (!isPropertyWikiNode(node)) return;
427
+ if (node.type === "obsidianWikiEmbed" && isAssetTarget(node.rawTarget)) {
428
+ return;
429
+ }
430
+ nodes.push(node);
431
+ });
432
+ return nodes.sort(
433
+ (a, b) => (nodeRange(a)?.start ?? 0) - (nodeRange(b)?.start ?? 0)
434
+ );
435
+ }
436
+ function visitPropertyNodes(node, visitor) {
437
+ visitor(node);
438
+ for (const child of node.children ?? []) {
439
+ visitPropertyNodes(child, visitor);
440
+ }
441
+ }
442
+ function isPropertyWikiNode(node) {
443
+ return node.type === "obsidianWikilink" || node.type === "obsidianWikiEmbed";
444
+ }
445
+ function resolvePagePropertyWikiTarget(context, targetPath) {
446
+ if (context.resolveWikiLink) {
447
+ return context.resolveWikiLink(context.slug, targetPath);
448
+ }
449
+ if (!context.wikilinkIndex) return void 0;
450
+ return resolveWikiLink(
451
+ context.slug,
452
+ targetPath,
453
+ context.wikilinkIndex,
454
+ context.wikilinkStrategy ?? "shortest",
455
+ context.ordering
456
+ );
457
+ }
458
+ function nodeRange(node) {
459
+ const start = node.position?.start?.offset;
460
+ const end = node.position?.end?.offset;
461
+ if (typeof start !== "number" || typeof end !== "number" || end < start) {
462
+ return void 0;
463
+ }
464
+ return { start, end };
465
+ }
466
+ function targetFragment(node) {
467
+ if (node.linkTarget.blockId) {
468
+ return `#^${encodeURIComponent(node.linkTarget.blockId)}`;
469
+ }
470
+ if (node.linkTarget.heading) {
471
+ return `#${slugifyHeading(node.linkTarget.heading)}`;
472
+ }
473
+ return "";
474
+ }
475
+ function isAssetTarget(target) {
476
+ return /\.(png|jpe?g|gif|webp|svg|pdf|mp4|mov|mp3|wav|ogg|canvas)(?:[?#].*)?$/i.test(
477
+ target
478
+ );
479
+ }
480
+
209
481
  // src/tags.ts
210
482
  import { normalizeTag } from "@silicajs/remark-obsidian";
211
483
  function tagToHref(tag) {
@@ -217,8 +489,8 @@ function tagToHref(tag) {
217
489
 
218
490
  // src/pipeline/index.ts
219
491
  import matter from "gray-matter";
220
- import { unified } from "unified";
221
- import remarkParse from "remark-parse";
492
+ import { unified as unified2 } from "unified";
493
+ import remarkParse2 from "remark-parse";
222
494
  import remarkFrontmatter from "remark-frontmatter";
223
495
  import remarkGfm from "remark-gfm";
224
496
  import remarkMath from "remark-math";
@@ -231,7 +503,7 @@ import rehypeAutolinkHeadings from "rehype-autolink-headings";
231
503
  import rehypeShiki from "@shikijs/rehype";
232
504
  import rehypeReact from "rehype-react";
233
505
  import rehypeStringify from "rehype-stringify";
234
- import { getTags, remarkObsidian } from "@silicajs/remark-obsidian";
506
+ import { getTags, remarkObsidian as remarkObsidian2 } from "@silicajs/remark-obsidian";
235
507
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
236
508
 
237
509
  // src/pipeline/code-block.ts
@@ -340,7 +612,7 @@ function toText(node) {
340
612
 
341
613
  // src/pipeline/obsidian.ts
342
614
  import { visit } from "unist-util-visit";
343
- import { slug as slugifyHeading } from "github-slugger";
615
+ import { slug as slugifyHeading2 } from "github-slugger";
344
616
  function remarkSilicaObsidian(context) {
345
617
  return async (tree, file) => {
346
618
  const links = /* @__PURE__ */ new Set();
@@ -359,11 +631,11 @@ function remarkSilicaObsidian(context) {
359
631
  return;
360
632
  }
361
633
  if (!isWikiNode(node)) return;
362
- if (node.type === "obsidianWikiEmbed" && !isAssetTarget(node.rawTarget)) {
634
+ if (node.type === "obsidianWikiEmbed" && !isAssetTarget2(node.rawTarget)) {
363
635
  embedPromises.push(resolveEmbedNode(node, context));
364
636
  }
365
637
  const targetPath = node.linkTarget.path || String(context.slug);
366
- if (node.type === "obsidianWikiEmbed" && isAssetTarget(node.rawTarget)) {
638
+ if (node.type === "obsidianWikiEmbed" && isAssetTarget2(node.rawTarget)) {
367
639
  const resolvedAsset = resolveAssetTarget(context, node.rawTarget);
368
640
  if (resolvedAsset) {
369
641
  node.data = {
@@ -411,7 +683,7 @@ function createSilicaObsidianHandlers(context) {
411
683
  return wikilinkToHast(state, node);
412
684
  },
413
685
  obsidianWikiEmbed(state, node) {
414
- if (node.rawTarget && isAssetTarget(node.rawTarget)) {
686
+ if (node.rawTarget && isAssetTarget2(node.rawTarget)) {
415
687
  return assetEmbedToHast(context, node);
416
688
  }
417
689
  const embedHtml = getStringData(node, "silicaEmbedHtml");
@@ -522,7 +794,7 @@ function wikilinkToHast(_state, node) {
522
794
  return {
523
795
  type: "element",
524
796
  tagName: "a",
525
- properties: { href: `${slugToHref(resolved)}${targetFragment(node)}` },
797
+ properties: { href: `${slugToHref(resolved)}${targetFragment2(node)}` },
526
798
  children: [{ type: "text", value: label }]
527
799
  };
528
800
  }
@@ -593,12 +865,12 @@ function assetEmbedToHast(context, node) {
593
865
  children: [{ type: "text", value: label }]
594
866
  };
595
867
  }
596
- function targetFragment(node) {
868
+ function targetFragment2(node) {
597
869
  if (node.linkTarget.blockId) {
598
870
  return `#^${encodeURIComponent(node.linkTarget.blockId)}`;
599
871
  }
600
872
  if (node.linkTarget.heading) {
601
- return `#${slugifyHeading(node.linkTarget.heading)}`;
873
+ return `#${slugifyHeading2(node.linkTarget.heading)}`;
602
874
  }
603
875
  return "";
604
876
  }
@@ -626,7 +898,7 @@ function sizeProperties(size) {
626
898
  };
627
899
  }
628
900
  function rewriteAssetUrl(url, assetBaseUrl) {
629
- if (!isAssetTarget(url)) return url;
901
+ if (!isAssetTarget2(url)) return url;
630
902
  if (/^(?:https?:|#|\/)/.test(url)) return url;
631
903
  return `${assetBaseUrl}/${url.replace(/^\.?\//, "")}`;
632
904
  }
@@ -642,7 +914,7 @@ function resolveAssetTarget(context, target) {
642
914
  ) : void 0);
643
915
  return resolved ? `${resolved}${assetReferenceSuffix(target)}` : void 0;
644
916
  }
645
- function isAssetTarget(target) {
917
+ function isAssetTarget2(target) {
646
918
  return /\.(png|jpe?g|gif|webp|svg|pdf|mp4|mov|mp3|wav|ogg|canvas)(?:[?#].*)?$/i.test(
647
919
  target
648
920
  );
@@ -1033,16 +1305,22 @@ async function analyzeMarkdown(raw, context) {
1033
1305
  const file = await runRemarkObsidian(parsed.content, context);
1034
1306
  const plainText = extractPlainText(parsed.content);
1035
1307
  const frontmatter = parsed.data;
1036
- const brokenLinks = getDataArray(
1308
+ const contentBrokenLinks = getDataArray(
1037
1309
  file.data,
1038
1310
  "silicaObsidianBrokenLinks"
1039
1311
  ).map((link) => ({ source: String(context.slug), target: link.target }));
1312
+ const pagePropertyLinks = analyzePagePropertyLinks(frontmatter, context);
1040
1313
  const description = getDescription(frontmatter);
1041
1314
  return {
1042
1315
  frontmatter,
1043
- links: getDataArray(file.data, "silicaObsidianLinks"),
1316
+ links: [
1317
+ .../* @__PURE__ */ new Set([
1318
+ ...getDataArray(file.data, "silicaObsidianLinks"),
1319
+ ...pagePropertyLinks.links
1320
+ ])
1321
+ ],
1044
1322
  embeds: getDataArray(file.data, "silicaObsidianEmbeds"),
1045
- brokenLinks,
1323
+ brokenLinks: [...contentBrokenLinks, ...pagePropertyLinks.brokenLinks],
1046
1324
  plainText,
1047
1325
  title: getTitle(frontmatter),
1048
1326
  description,
@@ -1078,13 +1356,13 @@ function cleanPlainText(text, maxLength, options = {}) {
1078
1356
  return truncated || void 0;
1079
1357
  }
1080
1358
  function baseProcessor(context) {
1081
- return unified().use(remarkParse).use(remarkFrontmatter, ["yaml"]).use(remarkGfm).use(remarkMath).use(remarkObsidian, { inlineTags: context.tags?.inline ?? true }).use(remarkSilicaObsidian, context).use(remarkRehype, {
1359
+ return unified2().use(remarkParse2).use(remarkFrontmatter, ["yaml"]).use(remarkGfm).use(remarkMath).use(remarkObsidian2, { inlineTags: context.tags?.inline ?? true }).use(remarkSilicaObsidian, context).use(remarkRehype, {
1082
1360
  allowDangerousHtml: true,
1083
1361
  handlers: createSilicaObsidianHandlers(context)
1084
1362
  });
1085
1363
  }
1086
1364
  async function runRemarkObsidian(markdown, context) {
1087
- const processor = unified().use(remarkParse).use(remarkFrontmatter, ["yaml"]).use(remarkGfm).use(remarkMath).use(remarkObsidian, { inlineTags: context.tags?.inline ?? true }).use(remarkSilicaObsidian, context);
1365
+ const processor = unified2().use(remarkParse2).use(remarkFrontmatter, ["yaml"]).use(remarkGfm).use(remarkMath).use(remarkObsidian2, { inlineTags: context.tags?.inline ?? true }).use(remarkSilicaObsidian, context);
1088
1366
  const tree = processor.parse(markdown);
1089
1367
  const file = { data: {} };
1090
1368
  await processor.run(tree, file);
@@ -1100,7 +1378,7 @@ function extractPlainText(markdown, options = {}) {
1100
1378
  return normalizePlainText(parts.join(" "));
1101
1379
  }
1102
1380
  function parsePlainTextMarkdown(markdown) {
1103
- return unified().use(remarkParse).use(remarkFrontmatter, ["yaml"]).use(remarkGfm).use(remarkMath).use(remarkObsidian).parse(markdown);
1381
+ return unified2().use(remarkParse2).use(remarkFrontmatter, ["yaml"]).use(remarkGfm).use(remarkMath).use(remarkObsidian2).parse(markdown);
1104
1382
  }
1105
1383
  function collectPlainText(node, parts) {
1106
1384
  if (plainTextSkipTypes.has(node.type)) return;
@@ -1149,6 +1427,13 @@ export {
1149
1427
  resolveAssetPath,
1150
1428
  resolveRelativeAsset,
1151
1429
  joinSegments,
1430
+ getMenuLabel,
1431
+ getPageProperties,
1432
+ getResolvedPageProperties,
1433
+ analyzePagePropertyLinks,
1434
+ resolvePagePropertyValue,
1435
+ formatPropertyLabel,
1436
+ formatPropertyValue,
1152
1437
  tagToHref,
1153
1438
  renderMarkdown,
1154
1439
  renderMarkdownHtml,
@@ -1158,4 +1443,4 @@ export {
1158
1443
  generateDescriptionFromContent,
1159
1444
  getMetaDescription
1160
1445
  };
1161
- //# sourceMappingURL=chunk-2EKH4A4V.js.map
1446
+ //# sourceMappingURL=chunk-7OXC5PEI.js.map