vitepress-allyouneed 0.3.4 → 0.3.6

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,50 @@
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.5] - 2026-05-20
6
+
7
+ 零配置导航更顺手:不写 `index.md` 也能从导航 / sidebar / 用户手写 wikilink 进到一个文件夹;默认 index 模板换成"文件管理器风格"(子文件夹在上、文件在下)。
8
+
9
+ ### Added
10
+
11
+ - **`sidebarAuto.folderLinkFallback: 'first-file' | 'none'`(默认 `'first-file'`)**
12
+ 控制"文件夹链接缺 index 时怎么办"。三处效果一致:
13
+ - 自动生成的 sidebar group:无 dirIndex 时,group title 链到该文件夹第一个文件;
14
+ - 自动生成的 nav tab:无 dirIndex 时,tab 链到第一个文件(不再 silently skip);
15
+ - 用户手写的 `[[folder/]]` wikilink:同样兜底到第一个文件,label 用文件夹名(不用 first file 的 basename)。
16
+ 设为 `'none'` 退回老行为(无 link / 死链)。最常见用法:`autoFolderIndex: 'off'` + `folderLinkFallback: 'first-file'`,完全不在 vault 写 `index.md` 也能从导航走通。
17
+ > **注意 sidebar vs nav 的差异**:
18
+ > - **Sidebar group**:**空 frontmatter-only dirIndex** 是用户显式 opt-out 信号(读 frontmatter 中的 `sidebarTitle` / `sidebarCollapsed`,但 group **无 link**,只展开/折叠)。这种文件**不会**触发 first-file 兜底 —— 兜底只在**完全没有** dirIndex 文件时生效。
19
+ > - **Nav tab**:nav tab 没"展开/折叠"状态,空 dirIndex 不兜底就成了点不动的死 tab。所以 nav 一视同仁:空或无 dirIndex **都**走 first-file 兜底(若 `folderLinkFallback === 'first-file'`);兜底也找不到才 skip。
20
+ > - **`[[folder/]]` wikilink**:用户写 `[[folder/]]` 时本就期望"跳转",和 nav 同语义 —— 空 dirIndex 兜底到第一个文件。
21
+ - **`resolveWikilink` 加同名 dirIndex 路径变体** —— `[[Themen/]]` 现在会查 `Themen/Themen.md` 当作索引(对应 `pickDirIndexes` 同名优先策略)。
22
+ - **`[[folder/]]` 写法支持完整化**:之前 `[[Themen/]]` 由于 `target + '/index.md'` 拼出 `Themen//index.md` 双斜杠 bug,实际是死链。现在 normalize 时剥尾 `/` 并强制走 path-style 分支,变体查找正确。
23
+
24
+ ### Changed
25
+
26
+ - **默认 `autoFolderIndex` 模板 = "Folders 在上、Files 在下"**(像文件管理器 / Tags 视图)。
27
+ - 段标题改成 `## Folders` + `## Files`(原 `## Sections` + `## Pages`)。
28
+ - 子文件夹位置:**上面**(老模板在下面)。
29
+ - 子文件夹标题是否可点接 `sidebarAuto.groupLink`(等同 sidebar 配置语义):
30
+ - `'all'`(默认):所有子文件夹标题都用 wikilink 可点
31
+ - `'top-level'`:仅根 `index.md` 内可点;深层 `index.md` 内为纯文字
32
+ - `'off'`:全部纯文字
33
+ - 子文件夹的"可点目的地"再走 `folderLinkFallback` 兜底,所以即使子文件夹无 index,点了也能进。
34
+ - **`FolderIndexOptions` / `TemplateContext` 扩展**:新增 `groupLink` 字段;`TemplateContext` 新增 `isRoot` / `groupLink`。
35
+ - **`resolveWikilink` 的 `defaultLabel`**:wasFolderForm 时(`[[folder/]]`),label 用文件夹名,而不是兜底文件的 basename。
36
+
37
+ ### Migration
38
+
39
+ 老用户若觉得新默认行为"不对路":
40
+ ```ts
41
+ sidebarAuto: {
42
+ folderLinkFallback: 'none', // 回到 v0.3.4 行为
43
+ autoFolderIndex: {
44
+ template: oldTemplate, // 自己写模板覆盖
45
+ },
46
+ }
47
+ ```
48
+
5
49
  ## [0.3.4] - 2026-05-20
6
50
 
7
51
  真实 Obsidian 物理笔记 vault 测试暴露的 9 个 bug + 3 个隐性问题的集中修复。
package/dist/index.cjs CHANGED
@@ -661,12 +661,20 @@ function resolveWikilink(rawTarget, index, options, kind = "page", currentSource
661
661
  headingPart = target.slice(hashIdx + 1).trim();
662
662
  target = target.slice(0, hashIdx).trim();
663
663
  }
664
+ const wasFolderForm = /\/$/.test(target);
665
+ if (wasFolderForm) target = target.replace(/\/+$/, "");
664
666
  target = stripMarkdownExt(target);
665
- const entry = lookupEntry(target, index, options, currentSourcePath);
667
+ const entry = lookupEntry(
668
+ target,
669
+ index,
670
+ options,
671
+ currentSourcePath,
672
+ wasFolderForm
673
+ );
666
674
  if (!entry) {
667
675
  return {
668
676
  url: buildDeadUrl(rawTarget, options),
669
- defaultLabel: defaultLabel(target, headingPart, void 0, options),
677
+ defaultLabel: defaultLabel(target, headingPart, void 0, options, wasFolderForm),
670
678
  isDead: true,
671
679
  hasUnmatchedAnchor: false,
672
680
  kind
@@ -687,22 +695,25 @@ function resolveWikilink(rawTarget, index, options, kind = "page", currentSource
687
695
  }
688
696
  return {
689
697
  url,
690
- defaultLabel: defaultLabel(target, headingPart, entry, options),
698
+ defaultLabel: defaultLabel(target, headingPart, entry, options, wasFolderForm),
691
699
  isDead: false,
692
700
  hasUnmatchedAnchor,
693
701
  target: entry,
694
702
  kind
695
703
  };
696
704
  }
697
- function lookupEntry(target, index, options, currentSourcePath) {
705
+ function lookupEntry(target, index, options, currentSourcePath, forcePathStyle = false) {
698
706
  if (!target) return void 0;
699
- if (target.includes("/")) {
707
+ if (target.includes("/") || forcePathStyle) {
708
+ const lastSeg = target.split("/").pop() ?? "";
700
709
  const variants = [
701
710
  target,
702
711
  target + ".md",
703
712
  target + ".markdown",
704
713
  target + "/index.md",
705
- target + "/index.markdown"
714
+ target + "/index.markdown",
715
+ // 同名文件夹索引(和 pickDirIndexes 优先级 1 对齐)
716
+ ...lastSeg ? [target + "/" + lastSeg + ".md", target + "/" + lastSeg + ".markdown"] : []
706
717
  ];
707
718
  for (const v of variants) {
708
719
  const e = index.byRelativePath.get(v);
@@ -728,6 +739,11 @@ function lookupEntry(target, index, options, currentSourcePath) {
728
739
  }
729
740
  }
730
741
  }
742
+ const folderFallback = options.sidebarAuto?.folderLinkFallback ?? "first-file";
743
+ if (folderFallback === "first-file") {
744
+ const first = findFirstFileInFolder(target, index);
745
+ if (first) return first;
746
+ }
731
747
  return void 0;
732
748
  }
733
749
  const aliasKey = options.caseSensitive ? target : target.toLowerCase();
@@ -749,14 +765,31 @@ function lookupEntry(target, index, options, currentSourcePath) {
749
765
  return void 0;
750
766
  }
751
767
  }
768
+ function findFirstFileInFolder(folderPath, index) {
769
+ const prefix = folderPath + "/";
770
+ const candidates = [];
771
+ for (const f of index.files.values()) {
772
+ if (f.relativePath.startsWith(prefix)) candidates.push(f);
773
+ }
774
+ if (candidates.length === 0) return void 0;
775
+ candidates.sort((a, b) => {
776
+ const da = a.relativePath.split("/").length;
777
+ const db = b.relativePath.split("/").length;
778
+ if (da !== db) return da - db;
779
+ return a.relativePath.localeCompare(b.relativePath);
780
+ });
781
+ return candidates[0];
782
+ }
752
783
  function buildDeadUrl(rawTarget, options) {
753
784
  const safe = encodeURIComponent(stripMarkdownExt(rawTarget).split("#")[0]);
754
785
  return options.base + safe;
755
786
  }
756
- function defaultLabel(target, headingPart, entry, options) {
787
+ function defaultLabel(target, headingPart, entry, options, wasFolderForm = false) {
757
788
  const lt = options.wikilinks.linkText;
758
789
  let base;
759
- if (typeof lt === "function") {
790
+ if (wasFolderForm) {
791
+ base = basename(target) || target;
792
+ } else if (typeof lt === "function") {
760
793
  if (entry) {
761
794
  base = lt(entry, target);
762
795
  } else {
@@ -2761,7 +2794,8 @@ function resolveSidebarAutoOptions(user = {}) {
2761
2794
  groupLink: user.groupLink ?? "all",
2762
2795
  includePrefix: user.includePrefix,
2763
2796
  excludePrefixes: user.excludePrefixes ?? [],
2764
- foldersFirst: user.foldersFirst ?? false
2797
+ foldersFirst: user.foldersFirst ?? false,
2798
+ folderLinkFallback: user.folderLinkFallback ?? "first-file"
2765
2799
  };
2766
2800
  }
2767
2801
  function defaultItemTitle(entry, strip) {
@@ -2923,8 +2957,13 @@ function renderNode(node, opts, depth, isRoot, index, options) {
2923
2957
  collapsed: resolveGroupCollapsed(child.dirIndex, opts),
2924
2958
  items: childItems
2925
2959
  };
2926
- if (child.dirIndex && !child.dirIndexEmpty && shouldLinkGroup(opts, isRoot)) {
2927
- group.link = child.dirIndex.url;
2960
+ if (shouldLinkGroup(opts, isRoot)) {
2961
+ if (child.dirIndex && !child.dirIndexEmpty) {
2962
+ group.link = child.dirIndex.url;
2963
+ } else if (!child.dirIndex && opts.folderLinkFallback === "first-file") {
2964
+ const first = findFirstPageUrl(child, opts);
2965
+ if (first) group.link = first;
2966
+ }
2928
2967
  }
2929
2968
  folderItems.push(group);
2930
2969
  }
@@ -3008,8 +3047,13 @@ function toFlatSidebar(root, opts) {
3008
3047
  collapsed: resolveGroupCollapsed(d.dirIndex, opts),
3009
3048
  items
3010
3049
  };
3011
- if (d.dirIndex && !d.dirIndexEmpty && shouldLinkGroup(opts, true)) {
3012
- group.link = d.dirIndex.url;
3050
+ if (shouldLinkGroup(opts, true)) {
3051
+ if (d.dirIndex && !d.dirIndexEmpty) {
3052
+ group.link = d.dirIndex.url;
3053
+ } else if (!d.dirIndex && opts.folderLinkFallback === "first-file") {
3054
+ const first = findFirstPageUrl(d, opts);
3055
+ if (first) group.link = first;
3056
+ }
3013
3057
  }
3014
3058
  out.push(group);
3015
3059
  }
@@ -3042,7 +3086,12 @@ function toPerFolderSidebar(root, opts, options, index) {
3042
3086
  /* isTopLevel */
3043
3087
  true
3044
3088
  )) {
3045
- const firstUrl = child.dirIndex && !child.dirIndexEmpty ? child.dirIndex.url : findFirstPageUrl(child, opts);
3089
+ let firstUrl = null;
3090
+ if (child.dirIndex && !child.dirIndexEmpty) {
3091
+ firstUrl = child.dirIndex.url;
3092
+ } else if (!child.dirIndex && opts.folderLinkFallback === "first-file") {
3093
+ firstUrl = findFirstPageUrl(child, opts);
3094
+ }
3046
3095
  if (firstUrl) {
3047
3096
  rootItems.push({ text: labelText, link: firstUrl });
3048
3097
  } else {
@@ -3067,16 +3116,23 @@ function toPerFolderSidebar(root, opts, options, index) {
3067
3116
  );
3068
3117
  if (items.length === 0 && !child.dirIndex) continue;
3069
3118
  const sidebar = [];
3070
- const canLink = child.dirIndex && !child.dirIndexEmpty && shouldLinkGroup(
3119
+ if (shouldLinkGroup(
3071
3120
  opts,
3072
3121
  /* isTopLevel */
3073
3122
  true
3074
- );
3075
- if (canLink) {
3076
- sidebar.push({
3077
- text: computeGroupText(child.path, child.dirIndex, opts),
3078
- link: child.dirIndex.url
3079
- });
3123
+ )) {
3124
+ let selfUrl = null;
3125
+ if (child.dirIndex && !child.dirIndexEmpty) {
3126
+ selfUrl = child.dirIndex.url;
3127
+ } else if (!child.dirIndex && opts.folderLinkFallback === "first-file") {
3128
+ selfUrl = findFirstPageUrl(child, opts);
3129
+ }
3130
+ if (selfUrl) {
3131
+ sidebar.push({
3132
+ text: computeGroupText(child.path, child.dirIndex, opts),
3133
+ link: selfUrl
3134
+ });
3135
+ }
3080
3136
  }
3081
3137
  sidebar.push(...items);
3082
3138
  out[`/${key}/`] = sidebar;
@@ -3104,10 +3160,12 @@ function generateNav(index, options, autoOptions = {}) {
3104
3160
  let link;
3105
3161
  if (child.dirIndex && !child.dirIndexEmpty) {
3106
3162
  link = stripBase(child.dirIndex.url, base);
3107
- } else {
3163
+ } else if (opts.folderLinkFallback === "first-file") {
3108
3164
  const first = findFirstPageUrl(child, opts);
3109
3165
  if (!first) continue;
3110
3166
  link = first;
3167
+ } else {
3168
+ continue;
3111
3169
  }
3112
3170
  const escapedPrefix = `/${key}/`.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3113
3171
  out.push({ text, link, activeMatch: "^" + escapedPrefix });
@@ -3277,7 +3335,9 @@ function generateFolderIndexes(options, folderOpts = {}) {
3277
3335
  dirRelPath: dirRel,
3278
3336
  title: humanize2(lastSeg, strip) || "Home",
3279
3337
  files,
3280
- subDirs
3338
+ subDirs,
3339
+ isRoot: dirRel === "",
3340
+ groupLink: folderOpts.groupLink ?? "all"
3281
3341
  };
3282
3342
  try {
3283
3343
  import_node_fs8.default.writeFileSync(target, template(ctx), "utf8");
@@ -3358,6 +3418,7 @@ function humanize2(name, stripNumeric) {
3358
3418
  }
3359
3419
  function defaultTemplate(ctx) {
3360
3420
  const prefix = ctx.dirRelPath === "" ? "" : `${ctx.dirRelPath}/`;
3421
+ const subDirLinkable = subFolderClickable(ctx.groupLink, ctx.isRoot);
3361
3422
  const lines = [];
3362
3423
  lines.push("---");
3363
3424
  lines.push(`title: ${ctx.title}`);
@@ -3368,15 +3429,19 @@ function defaultTemplate(ctx) {
3368
3429
  lines.push(`# ${ctx.title}`);
3369
3430
  lines.push("");
3370
3431
  if (ctx.subDirs.length > 0) {
3371
- lines.push("## Sections");
3432
+ lines.push("## Folders");
3372
3433
  lines.push("");
3373
3434
  for (const d of ctx.subDirs) {
3374
- lines.push(`- [[${prefix}${d.name}/|${d.title}]]`);
3435
+ if (subDirLinkable) {
3436
+ lines.push(`- [[${prefix}${d.name}/|${d.title}]]`);
3437
+ } else {
3438
+ lines.push(`- ${d.title}`);
3439
+ }
3375
3440
  }
3376
3441
  lines.push("");
3377
3442
  }
3378
3443
  if (ctx.files.length > 0) {
3379
- lines.push("## Pages");
3444
+ lines.push("## Files");
3380
3445
  lines.push("");
3381
3446
  for (const f of ctx.files) {
3382
3447
  lines.push(`- [[${prefix}${f.relPath}|${f.title}]]`);
@@ -3385,6 +3450,11 @@ function defaultTemplate(ctx) {
3385
3450
  }
3386
3451
  return lines.join("\n");
3387
3452
  }
3453
+ function subFolderClickable(groupLink, isRoot) {
3454
+ if (groupLink === "off") return false;
3455
+ if (groupLink === "top-level") return isRoot;
3456
+ return true;
3457
+ }
3388
3458
 
3389
3459
  // src/core/scan-wikilinks.ts
3390
3460
  var WIKILINK_RE3 = /(!?)\[\[([^\]\n]+)\]\]/g;
@@ -3534,6 +3604,7 @@ function defineConfigWithAllYouNeed(config, pluginOptions = {}) {
3534
3604
  sidebarAuto.autoFolderIndex,
3535
3605
  sidebarAuto.stripNumericPrefix
3536
3606
  );
3607
+ folderOpts.groupLink = sidebarAuto.groupLink ?? "all";
3537
3608
  if (folderOpts.mode !== "off") {
3538
3609
  try {
3539
3610
  generateFolderIndexes(resolvedForWrapper, folderOpts);