vitepress-allyouneed 0.3.6 → 0.3.8

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/CHANGELOG.md CHANGED
@@ -2,6 +2,32 @@
2
2
 
3
3
  本项目遵循 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/);版本号遵循 [SemVer](https://semver.org/lang/zh-CN/)。
4
4
 
5
+ ## [0.3.8] - 2026-05-21
6
+
7
+ ### Fixed
8
+ - **wikilink 锚点匹配过严**:用户表格里大量写 `[[X#7.2]]` 想匹配 `## 7.2 Antike — Vorsokratiker`、`[[X#11.2 Kepler]]` 想匹配 `## 11.2 Die drei Kepler'schen Gesetze`、`[[X#4.2 Cavendish]]` 想匹配 `## 4.2 Cavendish-Experiment (8. Klasse, 1798)` 等。老 resolver 只做 exact text / slug match,以上全部归为 "unmatched-anchor"——页面能加载、锚点不跳。现在加两层 fallback:
9
+ 1. **prefix-with-boundary**:`headingPart` 是 heading text 的前缀,且下一字符是 whitespace 或字符串末尾。处理 `#7.2` → `7.2 Antike...`,且**不**误匹配 `7.21 Andere ...`(下一字符不是空白)。
10
+ 2. **token match**:`headingPart` 按空白拆 token,所有 token 都(忽略大小写)出现在 heading text 中。多个 candidate 取最短 text(最精确那条)。处理 `#11.2 Kepler` → `11.2 Die drei Kepler'schen Gesetze`(同时含 "11.2" 和 "kepler",H3 "1. Kepler'sches Gesetz" 不含 "11.2" 被排除)。
11
+
12
+ 这一改更贴近 Obsidian 用户在表格里"section-number-only"或"number + keyword"的实际书写习惯。
13
+
14
+ ### Tests
15
+ - 新增 `tests/v038-anchor-fuzzy.test.ts`,覆盖 exact / prefix / token / 误匹配防护 / unmatched fallback 等 7 个用例。
16
+
17
+ ## [0.3.6] - 2026-05-20
18
+
19
+ 针对用户反馈"Perspectives 有时不加载 / sidebar 有时错"的间歇性问题做了系统排查,5 个真 bug。
20
+
21
+ ### Fixed
22
+ - **Perspectives 在 `locales.root.themeConfig` 配置下消失**:VitePress 1.6 渲染时浅 merge `themeConfig` 与 `siteData.locales[k]?.themeConfig` —— locale 整个**替换**顶层 nav/sidebar。wrapper 之前 `if (lang === 'root') continue` 跳过 root,导致用户用标准 i18n 写法 `locales: { root: { themeConfig: { nav: [...] }}}` 时,顶层 Perspectives 被 root 的(未注入)nav 覆盖。现在所有 locale(含 root)都过 inject。
23
+ - **dev 模式下新增 / 删除文件后 sidebar/nav 不更新**:wrapper 只在 config time 跑一次 scan + 生成 sidebar,后续 hot-update 只刷新 Vite 插件的 index(用于 wikilink / asset / vault-data.json)。结构变化静默吞掉。现在 `handleHotUpdate` 检测到 `.md` 文件 add / remove 时 `console.warn` 提示"重启 dev 服务器才能反映 sidebar/nav 变化"。
24
+ - **空 `themeConfig.sidebar = {}` 或部分 per-path 配置下 Perspectives 在根路径消失**:`injectViewsSidebar` 老逻辑 `for (Object.keys(sidebar))` 在空 object 上跑 0 次,Perspectives 没塞任何路径;用户在 `/` 或非匹配路径访问时看不到。现在显式给 `/` key 兜底一份。
25
+ - **`themeConfig.nav` 是 function 时被替换为 `[Perspectives]`**:VitePress nav 支持 function(动态 / locale-aware)。老 `Array.isArray(fn) ? [...fn] : []` 把 function 错判成"无 nav",推 Perspectives 后用单元素数组覆盖用户函数。现在 `typeof === 'function'` 时 wrap 一层,运行时调用用户 fn + 追加 Perspectives;用户 fn 抛错时容错只返 Perspectives 并 warn。
26
+ - **perspective fallback sidebar 含标题为 `/` 的死项**:`buildPerspectivesFallbackSidebar` 过滤 `p !== base`,但 base 是 `/sub/` 时永远不等于 `/` → `/` 进 topPaths → seg 为空 → 渲染为 `text: '/'` 的死项。现在显式排除 `'/'` + 双保险 `if (!seg) continue`。
27
+
28
+ ### Tests
29
+ - 新增 `tests/v036-perspective-locale.test.ts`,覆盖以上 5 个 bug。
30
+
5
31
  ## [0.3.5] - 2026-05-20
6
32
 
7
33
  零配置导航更顺手:不写 `index.md` 也能从导航 / sidebar / 用户手写 wikilink 进到一个文件夹;默认 index 模板换成"文件管理器风格"(子文件夹在上、文件在下)。
package/dist/index.cjs CHANGED
@@ -683,9 +683,7 @@ function resolveWikilink(rawTarget, index, options, kind = "page", currentSource
683
683
  let url = entry.url;
684
684
  let hasUnmatchedAnchor = false;
685
685
  if (headingPart) {
686
- const heading = entry.headings.find(
687
- (h) => h.text === headingPart || h.slug === headingPart || h.slug === options.slugify(headingPart)
688
- );
686
+ const heading = matchHeading(entry, headingPart, options.slugify);
689
687
  if (heading) {
690
688
  url = entry.url + "#" + heading.slug;
691
689
  } else {
@@ -765,6 +763,29 @@ function lookupEntry(target, index, options, currentSourcePath, forcePathStyle =
765
763
  return void 0;
766
764
  }
767
765
  }
766
+ function matchHeading(entry, headingPart, slugify) {
767
+ const exact = entry.headings.find(
768
+ (h) => h.text === headingPart || h.slug === headingPart || h.slug === slugify(headingPart)
769
+ );
770
+ if (exact) return exact;
771
+ const lc = headingPart.toLowerCase();
772
+ const prefix = entry.headings.find((h) => {
773
+ const t = h.text.toLowerCase();
774
+ if (!t.startsWith(lc)) return false;
775
+ if (t.length === lc.length) return true;
776
+ return /\s/.test(h.text[lc.length]);
777
+ });
778
+ if (prefix) return prefix;
779
+ const tokens = headingPart.split(/\s+/).map((t) => t.trim()).filter(Boolean);
780
+ if (tokens.length === 0) return void 0;
781
+ const candidates = entry.headings.filter((h) => {
782
+ const lct = h.text.toLowerCase();
783
+ return tokens.every((tok) => lct.includes(tok.toLowerCase()));
784
+ });
785
+ if (candidates.length === 0) return void 0;
786
+ if (candidates.length === 1) return candidates[0];
787
+ return [...candidates].sort((a, b) => a.text.length - b.text.length)[0];
788
+ }
768
789
  function findFirstFileInFolder(folderPath, index) {
769
790
  const prefix = folderPath + "/";
770
791
  const candidates = [];
@@ -2424,15 +2445,28 @@ function viteAllYouNeed(userOptions = {}) {
2424
2445
  },
2425
2446
  handleHotUpdate(ctx) {
2426
2447
  if (!index) return;
2448
+ const isMd = /\.(md|markdown)$/i.test(ctx.file);
2449
+ const wasIndexed = isMd && index.files.has(ctx.file);
2450
+ let structuralChange = null;
2427
2451
  try {
2428
2452
  const stat = import_node_fs7.default.statSync(ctx.file);
2429
2453
  if (stat.isFile()) {
2430
2454
  updateFile(index, ctx.file, resolved);
2455
+ const isNowIndexed = index.files.has(ctx.file);
2456
+ if (!wasIndexed && isNowIndexed) structuralChange = "add";
2431
2457
  } else if (!import_node_fs7.default.existsSync(ctx.file)) {
2432
2458
  removeFile(index, ctx.file, resolved);
2459
+ if (wasIndexed) structuralChange = "remove";
2433
2460
  }
2434
2461
  } catch {
2435
2462
  removeFile(index, ctx.file, resolved);
2463
+ if (wasIndexed) structuralChange = "remove";
2464
+ }
2465
+ if (structuralChange) {
2466
+ const rel = toPosix(ctx.file).replace(toPosix(resolved.srcDir) + "/", "");
2467
+ console.warn(
2468
+ `vitepress-allyouneed: \u68C0\u6D4B\u5230\u7ED3\u6784\u53D8\u5316(${structuralChange === "add" ? "\u65B0\u589E" : "\u5220\u9664"} ${rel}),sidebar/nav \u5DF2\u88AB\u70D8\u7119\u5230 VitePress \u914D\u7F6E\u4E2D,**\u91CD\u542F dev \u670D\u52A1\u5668**\u624D\u80FD\u53CD\u6620;wikilink/asset \u5DF2\u5B9E\u65F6\u5237\u65B0\u3002`
2469
+ );
2436
2470
  }
2437
2471
  if (resolved.modules.views) {
2438
2472
  try {
@@ -2546,6 +2580,11 @@ function injectViewsSidebar(sidebar, options) {
2546
2580
  arr.push(group);
2547
2581
  }
2548
2582
  }
2583
+ if (!sidebar["/"]) {
2584
+ sidebar["/"] = [group];
2585
+ } else if (!sidebar["/"].some((it) => it.text === group.text)) {
2586
+ sidebar["/"].push(group);
2587
+ }
2549
2588
  }
2550
2589
  const prefix = options.views.urlPrefix ? options.views.urlPrefix.replace(/^\/+|\/+$/g, "") : "";
2551
2590
  if (prefix) {
@@ -2567,10 +2606,11 @@ function buildPerspectivesFallbackSidebar(allSidebars, group, base, viewsPrefix)
2567
2606
  const out = [{ text: "Home", link: "/" }];
2568
2607
  const persSuffix = `/${viewsPrefix}/`;
2569
2608
  const topPaths = Object.keys(allSidebars).filter(
2570
- (p) => p !== base && !p.endsWith(persSuffix)
2609
+ (p) => p !== "/" && p !== base && !p.endsWith(persSuffix)
2571
2610
  );
2572
2611
  for (const p of topPaths) {
2573
2612
  const seg = p.replace(/^\/|\/$/g, "").split("/").filter(Boolean).pop() ?? p;
2613
+ if (!seg) continue;
2574
2614
  const text = seg.charAt(0).toUpperCase() + seg.slice(1);
2575
2615
  const b = base.endsWith("/") ? base : base + "/";
2576
2616
  const stripped = b !== "/" && p.startsWith(b) ? "/" + p.slice(b.length) : p;
@@ -2612,6 +2652,26 @@ function injectViewsNav(nav, options) {
2612
2652
  link: it.link
2613
2653
  }))
2614
2654
  };
2655
+ if (typeof nav === "function") {
2656
+ const userFn = nav;
2657
+ return () => {
2658
+ let arr2;
2659
+ try {
2660
+ const r = userFn();
2661
+ arr2 = Array.isArray(r) ? [...r] : [];
2662
+ } catch (e) {
2663
+ console.warn(
2664
+ "vitepress-allyouneed: themeConfig.nav \u51FD\u6570\u6267\u884C\u5931\u8D25,\u4EC5\u8FD4\u56DE Perspectives \u4E0B\u62C9\u3002",
2665
+ e instanceof Error ? e.message : String(e)
2666
+ );
2667
+ arr2 = [];
2668
+ }
2669
+ if (!arr2.some((it) => it && it.text === navItem.text)) {
2670
+ arr2.push(navItem);
2671
+ }
2672
+ return arr2;
2673
+ };
2674
+ }
2615
2675
  const arr = Array.isArray(nav) ? [...nav] : [];
2616
2676
  if (!arr.some((it) => it.text === navItem.text)) {
2617
2677
  arr.push(navItem);
@@ -3675,7 +3735,6 @@ function defineConfigWithAllYouNeed(config, pluginOptions = {}) {
3675
3735
  const localesForViews = config.locales;
3676
3736
  if (localesForViews) {
3677
3737
  for (const lang of Object.keys(localesForViews)) {
3678
- if (lang === "root") continue;
3679
3738
  const lc = localesForViews[lang];
3680
3739
  if (!lc.themeConfig) lc.themeConfig = {};
3681
3740
  if (lc.themeConfig.sidebar !== void 0) {