@silicajs/core 0.6.1 → 0.7.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.
@@ -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,197 @@ 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
+ function getMenuLabel(frontmatter, title) {
292
+ if (typeof frontmatter.menu_label === "string" && frontmatter.menu_label.trim()) {
293
+ return frontmatter.menu_label.trim();
294
+ }
295
+ return title;
296
+ }
297
+ function getPageProperties(frontmatter) {
298
+ return Object.entries(frontmatter).filter(([key]) => !RESERVED_FRONTMATTER_KEYS.has(key.toLowerCase())).map(([key, value]) => {
299
+ const formatted = formatPropertyValue(value);
300
+ if (formatted === void 0) return void 0;
301
+ return {
302
+ key,
303
+ label: formatPropertyLabel(key),
304
+ value: formatted
305
+ };
306
+ }).filter((property) => property !== void 0).sort((a, b) => a.key.localeCompare(b.key));
307
+ }
308
+ function getResolvedPageProperties(frontmatter, context) {
309
+ return getPageProperties(frontmatter).map((property) => ({
310
+ ...property,
311
+ parts: resolvePagePropertyValue(property.value, context).parts
312
+ }));
313
+ }
314
+ function analyzePagePropertyLinks(frontmatter, context) {
315
+ const links = /* @__PURE__ */ new Set();
316
+ const brokenLinks = [];
317
+ for (const property of getPageProperties(frontmatter)) {
318
+ const resolution = resolvePagePropertyValue(property.value, context);
319
+ for (const link of resolution.links) links.add(link);
320
+ brokenLinks.push(...resolution.brokenLinks);
321
+ }
322
+ return { links: [...links], brokenLinks };
323
+ }
324
+ function resolvePagePropertyValue(value, context) {
325
+ const nodes = collectPropertyWikiNodes(value, context);
326
+ if (nodes.length === 0) {
327
+ return {
328
+ parts: [{ type: "text", value }],
329
+ links: [],
330
+ brokenLinks: []
331
+ };
332
+ }
333
+ const parts = [];
334
+ const links = /* @__PURE__ */ new Set();
335
+ const brokenLinks = [];
336
+ let cursor = 0;
337
+ for (const node of nodes) {
338
+ const range = nodeRange(node);
339
+ if (!range || range.start < cursor) continue;
340
+ if (range.start > cursor) {
341
+ parts.push({ type: "text", value: value.slice(cursor, range.start) });
342
+ }
343
+ const label = node.alias || node.target || "";
344
+ const targetPath = node.linkTarget.path || String(context.slug);
345
+ const resolved = resolvePagePropertyWikiTarget(context, targetPath);
346
+ if (resolved) {
347
+ links.add(resolved);
348
+ parts.push({
349
+ type: "link",
350
+ value: label,
351
+ target: node.rawTarget,
352
+ slug: resolved,
353
+ href: `${slugToHref(resolved)}${targetFragment(node)}`
354
+ });
355
+ } else {
356
+ brokenLinks.push({
357
+ source: String(context.slug),
358
+ target: node.rawTarget
359
+ });
360
+ parts.push({
361
+ type: "broken-link",
362
+ value: label,
363
+ target: node.rawTarget
364
+ });
365
+ }
366
+ cursor = range.end;
367
+ }
368
+ if (cursor < value.length) {
369
+ parts.push({ type: "text", value: value.slice(cursor) });
370
+ }
371
+ return { parts, links: [...links], brokenLinks };
372
+ }
373
+ function formatPropertyLabel(key) {
374
+ return key.replace(/[_-]+/g, " ").replace(/([a-z0-9])([A-Z])/g, "$1 $2").toLowerCase();
375
+ }
376
+ function formatPropertyValue(value) {
377
+ if (value === null || value === void 0) return void 0;
378
+ if (value instanceof Date) {
379
+ return value.toISOString().slice(0, 10);
380
+ }
381
+ if (typeof value === "string") {
382
+ const trimmed = value.trim();
383
+ return trimmed || void 0;
384
+ }
385
+ if (typeof value === "number" || typeof value === "boolean") {
386
+ return String(value);
387
+ }
388
+ if (Array.isArray(value)) {
389
+ const items = value.map((item) => formatPropertyValue(item)).filter((item) => item !== void 0);
390
+ return items.length ? items.join(", ") : void 0;
391
+ }
392
+ if (typeof value === "object") {
393
+ return JSON.stringify(value);
394
+ }
395
+ return String(value);
396
+ }
397
+ function collectPropertyWikiNodes(value, context) {
398
+ const processor = unified().use(remarkParse).use(remarkObsidian, { inlineTags: context.tags?.inline ?? true });
399
+ const tree = processor.parse(value);
400
+ const nodes = [];
401
+ visitPropertyNodes(tree, (node) => {
402
+ if (!isPropertyWikiNode(node)) return;
403
+ if (node.type === "obsidianWikiEmbed" && isAssetTarget(node.rawTarget)) {
404
+ return;
405
+ }
406
+ nodes.push(node);
407
+ });
408
+ return nodes.sort(
409
+ (a, b) => (nodeRange(a)?.start ?? 0) - (nodeRange(b)?.start ?? 0)
410
+ );
411
+ }
412
+ function visitPropertyNodes(node, visitor) {
413
+ visitor(node);
414
+ for (const child of node.children ?? []) {
415
+ visitPropertyNodes(child, visitor);
416
+ }
417
+ }
418
+ function isPropertyWikiNode(node) {
419
+ return node.type === "obsidianWikilink" || node.type === "obsidianWikiEmbed";
420
+ }
421
+ function resolvePagePropertyWikiTarget(context, targetPath) {
422
+ if (context.resolveWikiLink) {
423
+ return context.resolveWikiLink(context.slug, targetPath);
424
+ }
425
+ if (!context.wikilinkIndex) return void 0;
426
+ return resolveWikiLink(
427
+ context.slug,
428
+ targetPath,
429
+ context.wikilinkIndex,
430
+ context.wikilinkStrategy ?? "shortest",
431
+ context.ordering
432
+ );
433
+ }
434
+ function nodeRange(node) {
435
+ const start = node.position?.start?.offset;
436
+ const end = node.position?.end?.offset;
437
+ if (typeof start !== "number" || typeof end !== "number" || end < start) {
438
+ return void 0;
439
+ }
440
+ return { start, end };
441
+ }
442
+ function targetFragment(node) {
443
+ if (node.linkTarget.blockId) {
444
+ return `#^${encodeURIComponent(node.linkTarget.blockId)}`;
445
+ }
446
+ if (node.linkTarget.heading) {
447
+ return `#${slugifyHeading(node.linkTarget.heading)}`;
448
+ }
449
+ return "";
450
+ }
451
+ function isAssetTarget(target) {
452
+ return /\.(png|jpe?g|gif|webp|svg|pdf|mp4|mov|mp3|wav|ogg|canvas)(?:[?#].*)?$/i.test(
453
+ target
454
+ );
455
+ }
456
+
209
457
  // src/tags.ts
210
458
  import { normalizeTag } from "@silicajs/remark-obsidian";
211
459
  function tagToHref(tag) {
@@ -217,8 +465,8 @@ function tagToHref(tag) {
217
465
 
218
466
  // src/pipeline/index.ts
219
467
  import matter from "gray-matter";
220
- import { unified } from "unified";
221
- import remarkParse from "remark-parse";
468
+ import { unified as unified2 } from "unified";
469
+ import remarkParse2 from "remark-parse";
222
470
  import remarkFrontmatter from "remark-frontmatter";
223
471
  import remarkGfm from "remark-gfm";
224
472
  import remarkMath from "remark-math";
@@ -231,7 +479,7 @@ import rehypeAutolinkHeadings from "rehype-autolink-headings";
231
479
  import rehypeShiki from "@shikijs/rehype";
232
480
  import rehypeReact from "rehype-react";
233
481
  import rehypeStringify from "rehype-stringify";
234
- import { getTags, remarkObsidian } from "@silicajs/remark-obsidian";
482
+ import { getTags, remarkObsidian as remarkObsidian2 } from "@silicajs/remark-obsidian";
235
483
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
236
484
 
237
485
  // src/pipeline/code-block.ts
@@ -340,7 +588,7 @@ function toText(node) {
340
588
 
341
589
  // src/pipeline/obsidian.ts
342
590
  import { visit } from "unist-util-visit";
343
- import { slug as slugifyHeading } from "github-slugger";
591
+ import { slug as slugifyHeading2 } from "github-slugger";
344
592
  function remarkSilicaObsidian(context) {
345
593
  return async (tree, file) => {
346
594
  const links = /* @__PURE__ */ new Set();
@@ -359,11 +607,11 @@ function remarkSilicaObsidian(context) {
359
607
  return;
360
608
  }
361
609
  if (!isWikiNode(node)) return;
362
- if (node.type === "obsidianWikiEmbed" && !isAssetTarget(node.rawTarget)) {
610
+ if (node.type === "obsidianWikiEmbed" && !isAssetTarget2(node.rawTarget)) {
363
611
  embedPromises.push(resolveEmbedNode(node, context));
364
612
  }
365
613
  const targetPath = node.linkTarget.path || String(context.slug);
366
- if (node.type === "obsidianWikiEmbed" && isAssetTarget(node.rawTarget)) {
614
+ if (node.type === "obsidianWikiEmbed" && isAssetTarget2(node.rawTarget)) {
367
615
  const resolvedAsset = resolveAssetTarget(context, node.rawTarget);
368
616
  if (resolvedAsset) {
369
617
  node.data = {
@@ -411,7 +659,7 @@ function createSilicaObsidianHandlers(context) {
411
659
  return wikilinkToHast(state, node);
412
660
  },
413
661
  obsidianWikiEmbed(state, node) {
414
- if (node.rawTarget && isAssetTarget(node.rawTarget)) {
662
+ if (node.rawTarget && isAssetTarget2(node.rawTarget)) {
415
663
  return assetEmbedToHast(context, node);
416
664
  }
417
665
  const embedHtml = getStringData(node, "silicaEmbedHtml");
@@ -522,7 +770,7 @@ function wikilinkToHast(_state, node) {
522
770
  return {
523
771
  type: "element",
524
772
  tagName: "a",
525
- properties: { href: `${slugToHref(resolved)}${targetFragment(node)}` },
773
+ properties: { href: `${slugToHref(resolved)}${targetFragment2(node)}` },
526
774
  children: [{ type: "text", value: label }]
527
775
  };
528
776
  }
@@ -593,12 +841,12 @@ function assetEmbedToHast(context, node) {
593
841
  children: [{ type: "text", value: label }]
594
842
  };
595
843
  }
596
- function targetFragment(node) {
844
+ function targetFragment2(node) {
597
845
  if (node.linkTarget.blockId) {
598
846
  return `#^${encodeURIComponent(node.linkTarget.blockId)}`;
599
847
  }
600
848
  if (node.linkTarget.heading) {
601
- return `#${slugifyHeading(node.linkTarget.heading)}`;
849
+ return `#${slugifyHeading2(node.linkTarget.heading)}`;
602
850
  }
603
851
  return "";
604
852
  }
@@ -626,7 +874,7 @@ function sizeProperties(size) {
626
874
  };
627
875
  }
628
876
  function rewriteAssetUrl(url, assetBaseUrl) {
629
- if (!isAssetTarget(url)) return url;
877
+ if (!isAssetTarget2(url)) return url;
630
878
  if (/^(?:https?:|#|\/)/.test(url)) return url;
631
879
  return `${assetBaseUrl}/${url.replace(/^\.?\//, "")}`;
632
880
  }
@@ -642,7 +890,7 @@ function resolveAssetTarget(context, target) {
642
890
  ) : void 0);
643
891
  return resolved ? `${resolved}${assetReferenceSuffix(target)}` : void 0;
644
892
  }
645
- function isAssetTarget(target) {
893
+ function isAssetTarget2(target) {
646
894
  return /\.(png|jpe?g|gif|webp|svg|pdf|mp4|mov|mp3|wav|ogg|canvas)(?:[?#].*)?$/i.test(
647
895
  target
648
896
  );
@@ -1033,16 +1281,22 @@ async function analyzeMarkdown(raw, context) {
1033
1281
  const file = await runRemarkObsidian(parsed.content, context);
1034
1282
  const plainText = extractPlainText(parsed.content);
1035
1283
  const frontmatter = parsed.data;
1036
- const brokenLinks = getDataArray(
1284
+ const contentBrokenLinks = getDataArray(
1037
1285
  file.data,
1038
1286
  "silicaObsidianBrokenLinks"
1039
1287
  ).map((link) => ({ source: String(context.slug), target: link.target }));
1288
+ const pagePropertyLinks = analyzePagePropertyLinks(frontmatter, context);
1040
1289
  const description = getDescription(frontmatter);
1041
1290
  return {
1042
1291
  frontmatter,
1043
- links: getDataArray(file.data, "silicaObsidianLinks"),
1292
+ links: [
1293
+ .../* @__PURE__ */ new Set([
1294
+ ...getDataArray(file.data, "silicaObsidianLinks"),
1295
+ ...pagePropertyLinks.links
1296
+ ])
1297
+ ],
1044
1298
  embeds: getDataArray(file.data, "silicaObsidianEmbeds"),
1045
- brokenLinks,
1299
+ brokenLinks: [...contentBrokenLinks, ...pagePropertyLinks.brokenLinks],
1046
1300
  plainText,
1047
1301
  title: getTitle(frontmatter),
1048
1302
  description,
@@ -1078,13 +1332,13 @@ function cleanPlainText(text, maxLength, options = {}) {
1078
1332
  return truncated || void 0;
1079
1333
  }
1080
1334
  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, {
1335
+ return unified2().use(remarkParse2).use(remarkFrontmatter, ["yaml"]).use(remarkGfm).use(remarkMath).use(remarkObsidian2, { inlineTags: context.tags?.inline ?? true }).use(remarkSilicaObsidian, context).use(remarkRehype, {
1082
1336
  allowDangerousHtml: true,
1083
1337
  handlers: createSilicaObsidianHandlers(context)
1084
1338
  });
1085
1339
  }
1086
1340
  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);
1341
+ const processor = unified2().use(remarkParse2).use(remarkFrontmatter, ["yaml"]).use(remarkGfm).use(remarkMath).use(remarkObsidian2, { inlineTags: context.tags?.inline ?? true }).use(remarkSilicaObsidian, context);
1088
1342
  const tree = processor.parse(markdown);
1089
1343
  const file = { data: {} };
1090
1344
  await processor.run(tree, file);
@@ -1100,7 +1354,7 @@ function extractPlainText(markdown, options = {}) {
1100
1354
  return normalizePlainText(parts.join(" "));
1101
1355
  }
1102
1356
  function parsePlainTextMarkdown(markdown) {
1103
- return unified().use(remarkParse).use(remarkFrontmatter, ["yaml"]).use(remarkGfm).use(remarkMath).use(remarkObsidian).parse(markdown);
1357
+ return unified2().use(remarkParse2).use(remarkFrontmatter, ["yaml"]).use(remarkGfm).use(remarkMath).use(remarkObsidian2).parse(markdown);
1104
1358
  }
1105
1359
  function collectPlainText(node, parts) {
1106
1360
  if (plainTextSkipTypes.has(node.type)) return;
@@ -1149,6 +1403,13 @@ export {
1149
1403
  resolveAssetPath,
1150
1404
  resolveRelativeAsset,
1151
1405
  joinSegments,
1406
+ getMenuLabel,
1407
+ getPageProperties,
1408
+ getResolvedPageProperties,
1409
+ analyzePagePropertyLinks,
1410
+ resolvePagePropertyValue,
1411
+ formatPropertyLabel,
1412
+ formatPropertyValue,
1152
1413
  tagToHref,
1153
1414
  renderMarkdown,
1154
1415
  renderMarkdownHtml,
@@ -1158,4 +1419,4 @@ export {
1158
1419
  generateDescriptionFromContent,
1159
1420
  getMetaDescription
1160
1421
  };
1161
- //# sourceMappingURL=chunk-2EKH4A4V.js.map
1422
+ //# sourceMappingURL=chunk-4A6TRY75.js.map