vitepress-allyouneed 0.4.0 → 0.5.0-beta.3

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.
Files changed (39) hide show
  1. package/CHANGELOG.md +98 -0
  2. package/README.md +59 -0
  3. package/dist/index.cjs +115 -47
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.js +115 -47
  6. package/dist/index.js.map +1 -1
  7. package/dist/theme/components/DocHeader.vue +55 -15
  8. package/dist/theme/components/Tags.vue +24 -5
  9. package/dist/theme/components/VaultGraph.vue +228 -45
  10. package/dist/theme/components/VaultStats.vue +34 -10
  11. package/dist/theme/composables/useVaultData.cjs +21 -6
  12. package/dist/theme/composables/useVaultData.cjs.map +1 -1
  13. package/dist/theme/composables/useVaultData.d.cts +12 -1
  14. package/dist/theme/composables/useVaultData.d.ts +12 -1
  15. package/dist/theme/composables/useVaultData.js +19 -5
  16. package/dist/theme/composables/useVaultData.js.map +1 -1
  17. package/dist/theme/index.cjs +37 -14
  18. package/dist/theme/index.cjs.map +1 -1
  19. package/dist/theme/index.d.cts +8 -0
  20. package/dist/theme/index.d.ts +8 -0
  21. package/dist/theme/index.js +36 -14
  22. package/dist/theme/index.js.map +1 -1
  23. package/dist/theme/styles/callouts.css +9 -3
  24. package/dist/theme/styles/doc-header.css +10 -1
  25. package/dist/theme/styles/graph.css +90 -13
  26. package/dist/theme/styles/index.css +24 -1
  27. package/dist/theme/styles/shared.css +12 -2
  28. package/dist/theme/styles/stats.css +11 -0
  29. package/dist/theme/styles/tags.css +17 -3
  30. package/dist/vite.cjs +46 -36
  31. package/dist/vite.cjs.map +1 -1
  32. package/dist/vite.js +46 -36
  33. package/dist/vite.js.map +1 -1
  34. package/dist/vitepress.cjs +115 -47
  35. package/dist/vitepress.cjs.map +1 -1
  36. package/dist/vitepress.js +115 -47
  37. package/dist/vitepress.js.map +1 -1
  38. package/package.json +12 -3
  39. package/style.css +5 -0
package/CHANGELOG.md CHANGED
@@ -2,6 +2,104 @@
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.5.0-beta.3] - 2026-05-22
6
+
7
+ ### Fixed
8
+ - **Tags 视图(可能也包括其它视图)整片失样**:0.5.0-beta.0/.1/.2 的 `@layer` 声明用了 `@import url('./x.css') layer(vitepress-allyouneed)` 这种"在 @import 子句上挂 layer"语法。CSS 标准支持,但 **Vite + postcss-import / lightningcss 不同链路对它支持参差**:某些组合下 `layer()` 子句被吞,resolve 后的规则**没进 layer**;另一些组合下规则进了 layer 但没正确链入 layer order,被埋在最底层 specificity 一概输掉。Tags chip 失去 pill 样式就是这个症状(`.ayn-tag-chip` 的 padding/border/radius/bg 全输给 user-agent + VP)。
9
+ - **改法**:不再用 `@import ... layer()`。每个 .css 文件**自己头尾**包 `@layer vitepress-allyouneed { ... }`,这是浏览器原生 cascade layer 语法,不经过 bundler 黑盒,100% 可靠。`styles/index.css` 退回最朴素的 `@import './x.css'`。
10
+ - 行为不变:7 个 .css 全 layered,主题作者 unlayered CSS 自动赢。
11
+
12
+ ### Files touched
13
+ - `src/theme/styles/index.css`(去 layer() 子句)
14
+ - `src/theme/styles/shared.css`、`stats.css`、`tags.css`、`graph.css`、`callouts.css`、`doc-header.css`(各自头尾包 @layer block)
15
+ - `style.css`(同上)
16
+
17
+ ## [0.5.0-beta.2] - 2026-05-22
18
+
19
+ 更彻底的修法:把 DocHeader 的 banner title 改成 `<div role="heading" aria-level="1">`,把 doc-header.css 放回 @layer,所有 7 个 .css 终于统一 layered。
20
+
21
+ ### Changed
22
+ - **DocHeader banner 元素 `<h1>` → `<div role="heading" aria-level="1">`**:`.vp-doc h1` 选择器不再匹配我们,@layer 内 layered CSS 不再被 VitePress 默认 unlayered 32px 字号覆盖。
23
+ - 视觉:无差(`.ayn-doc-banner-title` 字号/字重等都生效)
24
+ - 辅助技术:WAI-ARIA 标准,屏幕阅读器按 h1 念出,跟原生 `<h1>` 等同
25
+ - SEO:Google 早期声明 `role="heading" aria-level="1"` 跟 `<h1>` 等价对待
26
+ - 文档大纲算法:99% 工具支持;少数 reader-mode 工具可能少识别一个标题
27
+ - **doc-header.css 放回 `@layer vitepress-allyouneed`**:0.5.0-beta.1 的"豁免"特例不再需要。**所有 7 个 .css 都 layered**,主题作者覆盖故事彻底干净
28
+ - `styles/index.css` 不再 split "layered / unlayered",注释简化
29
+
30
+ ### Why
31
+ 0.5.0-beta.0 引入 @layer 后,banner title 是 `<h1>` 在 `.vp-doc` 内,VP 默认 `.vp-doc h1 { font-size: 32px }` 是 unlayered,我们 layered CSS 永远输 → banner title 字号塌缩。0.5.0-beta.1 临时方案:doc-header.css 不 layer。0.5.0-beta.2 釜底抽薪:banner 不用 `<h1>` 元素了,VP 选择器抓不到我们。
32
+
33
+ ## [0.5.0-beta.1] - 2026-05-22
34
+
35
+ ### Fixed
36
+ - **DocHeader banner title 字号丢失**(0.5.0-beta.0 回归):banner title 是 `<h1>` 在 `.vp-doc` 内,VitePress 默认 `.vp-doc h1 { font-size: 32px }` 是 unlayered;0.5.0-beta.0 把 `doc-header.css` 包进 `@layer vitepress-allyouneed` 后,**layered 永远输 unlayered**,我们的 `clamp(2rem, 4.5vw, 3.25rem)` 大字号被 VP 32px 覆盖 → "大标题"看起来变小。
37
+ - **修法**:`doc-header.css` 不再 layer。原则上"必须压过 VP unlayered defaults 的 CSS"不能 layer;"用户可覆盖的装饰"才 layer。文档 `styles/index.css` 顶部添加注释说明这条边界
38
+ - 其它 6 个 CSS 文件继续 layer(它们的 selector 不跟 VP 默认强冲突)
39
+
40
+ ### Note for theme authors
41
+ 想覆盖 banner title:写 `.ayn-doc-banner-title { ... }` —— 跟 0.4.x 行为一致(unlayered 源顺序赢)。注意我们文件在 `defineTheme` import 时加载,你 CSS 必须在它之后 import 才赢。
42
+
43
+ ## [0.5.0-beta.0] - 2026-05-21
44
+
45
+ 主题集成大改 —— **目标:让 3rd-party VitePress 主题作者完全不需要知道本插件**。
46
+
47
+ ### Theme: CSS 用 `@layer` 包裹(底层化)
48
+ - 全部 7 个 .css 文件通过 `styles/index.css` 用 `@import ... layer(vitepress-allyouneed)` 加载,所有规则进 `vitepress-allyouneed` cascade layer
49
+ - **后果**:用户(或第三方主题)写的任何 unlayered CSS **自动赢**我们,无论 specificity 或 import 顺序。`.wikilink { color: red }` 真就 red,不用知道我们有个 `.wikilink`
50
+ - 删除全部 3 处 `!important`(`callouts.css` callout-title 色、`shared.css` mark 内 mathjax bg、`tags.css` tag link 下划线 reset)—— 它们都不需要 `!important`,specificity 已够,且 `!important` 会在 layer 内反向打败用户 unlayered。删了用户 100% 能盖
51
+ - 浏览器支持:`@layer` Chrome/Edge 99 / Firefox 97 / Safari 15.4+(2022 春)
52
+
53
+ ### Theme: 新 `defineTheme()` 工厂(替代之前的 `withAllYouNeed` 设想)
54
+ - `vitepress-allyouneed/theme` 默认导出仍是开箱即用的 Theme 对象,**额外**导出 `defineTheme(userTheme?)` 工厂
55
+ - 三种用户场景:
56
+ 1. **零配置**:`export { default } from 'vitepress-allyouneed/theme'` — 一行完事(DefaultTheme + 我们组件 + 我们 CSS)
57
+ 2. **自定义 Layout / 加自己组件**:`defineTheme({ Layout, enhanceApp })`
58
+ 3. **嵌 3rd-party 主题**:`defineTheme({ extends: SomeAwesomeTheme })`
59
+ - 内部:用户 `enhanceApp` 在我们之后跑 → 同名 `app.component()` 注册**自动赢**(Vue last-registration-wins);用户 `Layout`、`setup` 直接替换
60
+ - 主题包作者的工作流跟有没有本插件**完全无关**:他们写正常 VitePress 主题,published,最终用户负责 `defineTheme({ extends: ... })` 一行接入
61
+
62
+ ### Theme: 可被替换的 Vue 组件清单(API 契约)
63
+ | 组件名 | 干什么 | 用户怎么换 |
64
+ |---|---|---|
65
+ | `Layout` | DefaultTheme.Layout + 自动 `<DocHeader />` + `frontmatter.cssclasses` → body | `defineTheme({ Layout: MyLayout })` |
66
+ | `DocHeader` | 文档头:banner / 标题 / 时间 / tags | `app.component('DocHeader', MyDocHeader)` |
67
+ | `VaultGraph` | d3 力导向关系图 | 同上 |
68
+ | `VaultStats` | 统计卡片 | 同上 |
69
+ | `Tags` | 标签云 + 搜索 + 列表 | 同上 |
70
+
71
+ ### Composables(显式契约)
72
+ `useVaultData()` 早已暴露;0.5.0 起明确这是"给写自己 UI 的人"的公共 API。
73
+
74
+ ### Docs
75
+ - `src/theme/index.ts` 文件头三段示例覆盖三种场景
76
+ - README "当前版本" 章节同步
77
+ - DOCS.md 新增 "Theming" 章节(下个 beta 补全)
78
+
79
+ ### Why beta
80
+ - @layer 在 Vite CSS pipeline 的实际行为没在我这边 prod build 跑过,需要真实项目验证
81
+ - `defineTheme()` 是新 API,等用户回声再 stabilize
82
+ - 老 0.4.x 用户 `import AllYouNeedTheme from 'vitepress-allyouneed/theme'` 仍兼容(default export 没变),只是多了 `defineTheme` 命名导出
83
+
84
+ ### Migration from 0.4.x
85
+ **完全无 breaking** —— 老代码继续跑。新代码推荐用 `defineTheme()`,见 README。
86
+
87
+ ## [0.4.1] - 2026-05-21
88
+
89
+ 修 0.4.0 几个回归 + 小幅 UX 优化。
90
+
91
+ ### Fixed
92
+ - **数字排序坑**:`1. Masse / 10. Energie / 11. Impuls / 2. Dichte / 3. ...` —— title 比较改成 `localeCompare(b, undefined, { numeric: true })`(natural sort)。现在 `1, 2, 3, ..., 10, 11` 按数值排,而不是字典序的 `1, 10, 11, 2, 3`。影响 `sidebarAuto.sortBy` 的 `'title'` 与 `'order-then-title'`(后者在 order 缺失时 fallback 到 title)。
93
+ - **`autoFolderIndex` 警告刷屏**:dev 模式 `server.restart()` 会反复触发,加 module-level once-flag 只报一次;同时把 message 写得更可操作("delete this field from your .vitepress/config.ts")。
94
+
95
+ ### Added
96
+ - **`markdown-it-mathjax3` 友好提示**:用户在 vitepress config 写 `markdown: { math: true }` 但没装 `markdown-it-mathjax3` 时,wrapper 自动报一条带安装命令的提示。包加为 optional peer dependency,装与不装都不阻塞。
97
+ - **VaultGraph 视觉/性能**:节点默认无边框(更 Obsidian-like,hover/active 才描边)。大图(>200 节点)启用 `alphaDecay` 加速 + 每 N 帧才更新一次 DOM,显著降卡。500 节点可用,1000+ 建议拆 vault。
98
+
99
+ ### Docs
100
+ - README 加 "当前版本 0.4.1" + 0.3.x → 0.4.x 升级速览。
101
+ - DOCS.md 内 `v0.3.10` 提法统一改成 `v0.4.0`(autoFolderIndex 删除事件实际发布在 0.4.0)。
102
+
5
103
  ## [0.4.0] - 2026-05-21
6
104
 
7
105
  **0.x.0 = breaking-ish minor bump**(按 0.x SemVer 约定大改 → 二位号 bump)。
package/README.md CHANGED
@@ -4,6 +4,65 @@
4
4
 
5
5
  Forked from [`actuallysomecat/markdown-it-wikilinks-plus`](https://github.com/actuallysomecat/markdown-it-wikilinks-plus) (MIT) 并针对 VitePress 大幅重写。
6
6
 
7
+ ## 当前版本:0.5.0-beta.0(2026-05-21)
8
+
9
+ **主题集成大改 —— 目标:让第三方 VitePress 主题作者不需要知道本插件存在**
10
+
11
+ ### CSS 自动让用户赢(`@layer`)
12
+
13
+ 我们所有 CSS 包在 `@layer vitepress-allyouneed` 里。CSS 标准规则:**任何 unlayered CSS 永远赢 layered CSS**,无关顺序、specificity。所以:
14
+
15
+ ```css
16
+ /* 第三方主题随便写,自动赢 */
17
+ .wikilink { color: red; }
18
+ .callout { border-radius: 12px; }
19
+ ```
20
+
21
+ 零知识负担。
22
+
23
+ ### Vue 组件:`defineTheme()` 工厂
24
+
25
+ 三种场景,递进式:
26
+
27
+ ```ts
28
+ // 1. 零配置(默认主题 + 我们)
29
+ export { default } from 'vitepress-allyouneed/theme'
30
+
31
+ // 2. 自定义 Layout / 覆盖某个视图
32
+ import { defineTheme } from 'vitepress-allyouneed/theme'
33
+ import MyLayout from './MyLayout.vue'
34
+ import MyVaultGraph from './MyVaultGraph.vue'
35
+ export default defineTheme({
36
+ Layout: MyLayout,
37
+ enhanceApp({ app }) {
38
+ app.component('VaultGraph', MyVaultGraph) // 同名注册自动覆盖
39
+ },
40
+ })
41
+
42
+ // 3. 嵌别人写的 VitePress 主题(主题包作者无需感知本插件)
43
+ import { defineTheme } from 'vitepress-allyouneed/theme'
44
+ import SomeTheme from 'some-vitepress-theme'
45
+ export default defineTheme({ extends: SomeTheme })
46
+ ```
47
+
48
+ 可被替换的全局组件:`Layout` / `DocHeader` / `VaultGraph` / `VaultStats` / `Tags`。
49
+
50
+ ### 4.x 兼容
51
+
52
+ 老代码 `import theme from 'vitepress-allyouneed/theme'; export default theme` 继续跑,没有 breaking。
53
+
54
+ ---
55
+
56
+ ### 0.4.x 重要变更(从 0.3.x 升级看这里)
57
+
58
+ - **`sidebarAuto.autoFolderIndex` 已删除**。文件夹链接由 `sidebarAuto.folderLinkOrder`(默认 `['same-name', 'index', 'readme', 'first-file']`)在 sidebar/nav/wikilink 解析时直接处理。
59
+ - **数字排序修正(0.4.1)**:`1, 2, 10, 11` 真按数字大小排。
60
+ - **`markdown.math: true`** 自动检测 — 没装 `markdown-it-mathjax3` 时给清晰提示。
61
+ - **dev HMR**:`.md` add/remove 或 `_sidebar.md` 改动自动 server restart。
62
+ - **leading-number 锚点**:`#13` 能匹 `## 13) Optik`、`13: ...` 等。
63
+
64
+ 完整 changelog 见 [`CHANGELOG.md`](./CHANGELOG.md);配置参考见 [`DOCS.md`](./DOCS.md)。
65
+
7
66
  ## 这个包做什么
8
67
 
9
68
  - 让 VitePress 直接把你的 **Obsidian vault 当作 srcDir 用**,你照常写笔记,剩下交给插件:
package/dist/index.cjs CHANGED
@@ -2102,11 +2102,10 @@ var markdown_it_default = allYouNeedMarkdownIt;
2102
2102
 
2103
2103
  // src/vite.ts
2104
2104
  var import_node_fs7 = __toESM(require("fs"), 1);
2105
- var import_node_path8 = __toESM(require("path"), 1);
2105
+ var import_node_path7 = __toESM(require("path"), 1);
2106
2106
 
2107
2107
  // src/core/asset-pipeline/dev-middleware.ts
2108
2108
  var import_node_fs4 = __toESM(require("fs"), 1);
2109
- var import_node_path5 = __toESM(require("path"), 1);
2110
2109
  function createDevMiddleware(index, options) {
2111
2110
  return (req, res, next) => {
2112
2111
  if (!req.url) return next();
@@ -2124,25 +2123,38 @@ function createDevMiddleware(index, options) {
2124
2123
  if (!/\.[a-zA-Z0-9]+$/.test(decoded)) return next();
2125
2124
  const relCandidate = decoded.replace(/^\/+/, "");
2126
2125
  let asset = index.assetsByRelativePath.get(relCandidate);
2127
- if (!asset) {
2126
+ if (!asset && !relCandidate.includes("/")) {
2128
2127
  const bn = basename(decoded);
2129
2128
  const map = options.caseSensitive ? index.assetsByBasename : index.assetsByBasenameLower;
2130
2129
  const key = options.caseSensitive ? bn : bn.toLowerCase();
2131
2130
  const candidates = map.get(key);
2132
- if (candidates && candidates.length > 0) asset = candidates[0];
2131
+ if (candidates && candidates.length > 0) {
2132
+ asset = [...candidates].sort(
2133
+ (a, b) => a.relativePath.length - b.relativePath.length
2134
+ )[0];
2135
+ }
2133
2136
  }
2134
2137
  if (!asset) return next();
2135
- let stat;
2136
- try {
2137
- stat = import_node_fs4.default.statSync(asset.absolutePath);
2138
- } catch {
2139
- return next();
2138
+ const etag = `W/"${asset.size.toString(16)}-${Math.floor(asset.mtime).toString(16)}"`;
2139
+ const lastModified = new Date(asset.mtime).toUTCString();
2140
+ const ifNoneMatch = req.headers["if-none-match"];
2141
+ const ifModifiedSince = req.headers["if-modified-since"];
2142
+ if (ifNoneMatch === etag || ifModifiedSince && Date.parse(ifModifiedSince) >= Math.floor(asset.mtime)) {
2143
+ res.statusCode = 304;
2144
+ res.setHeader("ETag", etag);
2145
+ res.setHeader("Last-Modified", lastModified);
2146
+ return res.end();
2140
2147
  }
2141
2148
  res.statusCode = 200;
2142
2149
  res.setHeader("Content-Type", guessMime(asset.extension));
2143
- res.setHeader("Content-Length", String(stat.size));
2150
+ res.setHeader("Content-Length", String(asset.size));
2144
2151
  res.setHeader("Cache-Control", "no-cache");
2145
- import_node_fs4.default.createReadStream(asset.absolutePath).on("error", () => next()).pipe(res);
2152
+ res.setHeader("ETag", etag);
2153
+ res.setHeader("Last-Modified", lastModified);
2154
+ import_node_fs4.default.createReadStream(asset.absolutePath).on("error", () => {
2155
+ if (!res.headersSent) next();
2156
+ else res.destroy();
2157
+ }).pipe(res);
2146
2158
  };
2147
2159
  }
2148
2160
  var MIME = {
@@ -2174,14 +2186,14 @@ function guessMime(ext) {
2174
2186
 
2175
2187
  // src/core/views/generate-md.ts
2176
2188
  var import_node_fs5 = __toESM(require("fs"), 1);
2177
- var import_node_path6 = __toESM(require("path"), 1);
2189
+ var import_node_path5 = __toESM(require("path"), 1);
2178
2190
  var VIEW_SENTINEL = "<!-- generated by vitepress-allyouneed (do not edit; will be regenerated) -->";
2179
2191
  function generateViewMarkdown(options, index) {
2180
2192
  const report = { written: [], skipped: [] };
2181
2193
  if (!options.modules.views) return report;
2182
- const srcDir = import_node_path6.default.resolve(options.srcDir);
2194
+ const srcDir = import_node_path5.default.resolve(options.srcDir);
2183
2195
  const prefix = options.views.urlPrefix;
2184
- const viewDir = prefix ? import_node_path6.default.join(srcDir, prefix) : srcDir;
2196
+ const viewDir = prefix ? import_node_path5.default.join(srcDir, prefix) : srcDir;
2185
2197
  if (prefix) {
2186
2198
  try {
2187
2199
  import_node_fs5.default.mkdirSync(viewDir, { recursive: true });
@@ -2190,7 +2202,7 @@ function generateViewMarkdown(options, index) {
2190
2202
  }
2191
2203
  const views = buildViewList(options);
2192
2204
  for (const v of views) {
2193
- const target = import_node_path6.default.join(viewDir, v.fileName);
2205
+ const target = import_node_path5.default.join(viewDir, v.fileName);
2194
2206
  if (import_node_fs5.default.existsSync(target)) {
2195
2207
  let existing;
2196
2208
  try {
@@ -2271,8 +2283,8 @@ function renderTemplate(v) {
2271
2283
 
2272
2284
  // src/core/views/generate-data.ts
2273
2285
  var import_node_fs6 = __toESM(require("fs"), 1);
2274
- var import_node_path7 = __toESM(require("path"), 1);
2275
- var PLUGIN_VERSION = "0.2.0-beta.0";
2286
+ var import_node_path6 = __toESM(require("path"), 1);
2287
+ var PLUGIN_VERSION = true ? "0.5.0-beta.3" : "0.0.0-dev";
2276
2288
  var WIKILINK_RE = /(!?)\[\[([^\]\n]+)\]\]/g;
2277
2289
  var DEFAULT_BODY_TAG_RE = /(?:^|[\s([{,;。,;])#([\p{L}_][\p{L}\p{N}_/-]*)/gu;
2278
2290
  function buildBodyTagRe(userPattern) {
@@ -2329,19 +2341,6 @@ function buildVaultData(index, options) {
2329
2341
  const edges = [...edgeMap.values()];
2330
2342
  const bodyTagRe = buildBodyTagRe(options.views.inlineTagPattern);
2331
2343
  const tagsMap = /* @__PURE__ */ new Map();
2332
- for (const f of userFiles) {
2333
- const allTags = new Set(f.tags);
2334
- if (options.views.parseInlineTags) {
2335
- for (const tm of f.content.matchAll(bodyTagRe)) {
2336
- allTags.add(tm[1]);
2337
- }
2338
- }
2339
- for (const tag of allTags) {
2340
- const arr = tagsMap.get(tag) ?? [];
2341
- arr.push(f);
2342
- tagsMap.set(tag, arr);
2343
- }
2344
- }
2345
2344
  const allTagsByFile = /* @__PURE__ */ new Map();
2346
2345
  for (const f of userFiles) {
2347
2346
  const set = new Set(f.tags);
@@ -2349,6 +2348,11 @@ function buildVaultData(index, options) {
2349
2348
  for (const tm of f.content.matchAll(bodyTagRe)) set.add(tm[1]);
2350
2349
  }
2351
2350
  allTagsByFile.set(f.relativePath, [...set]);
2351
+ for (const tag of set) {
2352
+ const arr = tagsMap.get(tag) ?? [];
2353
+ arr.push(f);
2354
+ tagsMap.set(tag, arr);
2355
+ }
2352
2356
  }
2353
2357
  const tags = {};
2354
2358
  for (const [tag, files] of tagsMap) {
@@ -2391,9 +2395,9 @@ function buildVaultData(index, options) {
2391
2395
  function writeVaultData(index, options) {
2392
2396
  const data = buildVaultData(index, options);
2393
2397
  const json = JSON.stringify(data);
2394
- const publicDir = import_node_path7.default.join(import_node_path7.default.resolve(options.srcDir), "public");
2398
+ const publicDir = import_node_path6.default.join(import_node_path6.default.resolve(options.srcDir), "public");
2395
2399
  import_node_fs6.default.mkdirSync(publicDir, { recursive: true });
2396
- const out = import_node_path7.default.join(publicDir, options.views.dataFileName);
2400
+ const out = import_node_path6.default.join(publicDir, options.views.dataFileName);
2397
2401
  import_node_fs6.default.writeFileSync(out, json, "utf8");
2398
2402
  return { path: out, bytes: json.length };
2399
2403
  }
@@ -2458,7 +2462,7 @@ function viteAllYouNeed(userOptions = {}) {
2458
2462
  config(_userViteConfig, _envCtx) {
2459
2463
  const srcDirOpt = userOptions.srcDir;
2460
2464
  if (!srcDirOpt) return void 0;
2461
- const abs = toPosix(import_node_path8.default.resolve(srcDirOpt));
2465
+ const abs = toPosix(import_node_path7.default.resolve(srcDirOpt));
2462
2466
  return {
2463
2467
  server: {
2464
2468
  fs: {
@@ -2540,9 +2544,15 @@ function viteAllYouNeed(userOptions = {}) {
2540
2544
  removeFile(index, ctx.file, resolved);
2541
2545
  if (wasIndexed) structuralChange = "remove";
2542
2546
  }
2543
- } catch {
2544
- removeFile(index, ctx.file, resolved);
2545
- if (wasIndexed) structuralChange = "remove";
2547
+ } catch (err) {
2548
+ if (err.code === "ENOENT") {
2549
+ removeFile(index, ctx.file, resolved);
2550
+ if (wasIndexed) structuralChange = "remove";
2551
+ } else {
2552
+ console.warn(
2553
+ `vitepress-allyouneed: stat failed for ${ctx.file} (${err.code ?? "unknown"}); keeping existing index entry.`
2554
+ );
2555
+ }
2546
2556
  }
2547
2557
  const baseName = ctx.file.split(/[\\/]/).pop() ?? "";
2548
2558
  const isSidebarFile = /^_sidebar\.(md|markdown)$/i.test(baseName);
@@ -3520,8 +3530,9 @@ function stripBase(url, base) {
3520
3530
  return "/" + url.slice(b.length);
3521
3531
  }
3522
3532
  function compareEntries(a, b, opts) {
3533
+ const collateOpts = { numeric: true, sensitivity: "base" };
3523
3534
  if (opts.sortBy === "title") {
3524
- return opts.formatItemTitle(a).localeCompare(opts.formatItemTitle(b));
3535
+ return opts.formatItemTitle(a).localeCompare(opts.formatItemTitle(b), void 0, collateOpts);
3525
3536
  }
3526
3537
  if (opts.sortBy === "mtime-desc") {
3527
3538
  return b.mtime - a.mtime;
@@ -3529,7 +3540,7 @@ function compareEntries(a, b, opts) {
3529
3540
  const oa = readOrder(a, opts.orderKey);
3530
3541
  const ob = readOrder(b, opts.orderKey);
3531
3542
  if (oa !== ob) return oa - ob;
3532
- return opts.formatItemTitle(a).localeCompare(opts.formatItemTitle(b));
3543
+ return opts.formatItemTitle(a).localeCompare(opts.formatItemTitle(b), void 0, collateOpts);
3533
3544
  }
3534
3545
  function readOrder(entry, key) {
3535
3546
  const v = entry.frontmatter[key];
@@ -3593,7 +3604,7 @@ function computeGroupText(dir, dirIndex, opts) {
3593
3604
 
3594
3605
  // src/core/sidebar-auto/generate-sidebar-materialize.ts
3595
3606
  var import_node_fs8 = __toESM(require("fs"), 1);
3596
- var import_node_path9 = __toESM(require("path"), 1);
3607
+ var import_node_path8 = __toESM(require("path"), 1);
3597
3608
  var SIDEBAR_MATERIALIZE_SENTINEL = "<!-- generated by vitepress-allyouneed/sidebar-materialize (do not edit if you want auto-refresh; remove this comment to take ownership) -->";
3598
3609
  var MD_RE = /\.(md|markdown)$/i;
3599
3610
  var SIDEBAR_RE = /^_sidebar\.(md|markdown)$/i;
@@ -3601,12 +3612,12 @@ function generateSidebarMaterializations(options, matOpts = {}) {
3601
3612
  const report = { written: [], skipped: [] };
3602
3613
  const mode = matOpts.mode ?? "off";
3603
3614
  if (mode === "off") return report;
3604
- const srcDir = import_node_path9.default.resolve(options.srcDir);
3615
+ const srcDir = import_node_path8.default.resolve(options.srcDir);
3605
3616
  const viewsPrefix = options.views.urlPrefix ? options.views.urlPrefix.replace(/^\/+|\/+$/g, "") : "";
3606
3617
  const exclude = matOpts.exclude ?? [];
3607
3618
  const dirs = [srcDir, ...collectDirs(srcDir, srcDir, viewsPrefix, exclude, mode)];
3608
3619
  for (const dirAbs of dirs) {
3609
- const target = import_node_path9.default.join(dirAbs, "_sidebar.md");
3620
+ const target = import_node_path8.default.join(dirAbs, "_sidebar.md");
3610
3621
  if (import_node_fs8.default.existsSync(target)) {
3611
3622
  let content;
3612
3623
  try {
@@ -3679,8 +3690,8 @@ function collectDirs(root, cur, viewsPrefix, exclude, mode) {
3679
3690
  if (!e.isDirectory()) continue;
3680
3691
  if (e.name.startsWith(".") || e.name.startsWith("_")) continue;
3681
3692
  if (e.name === "node_modules" || e.name === "public") continue;
3682
- const full = import_node_path9.default.join(dir, e.name);
3683
- const rel = import_node_path9.default.relative(root, full).split(import_node_path9.default.sep).join("/");
3693
+ const full = import_node_path8.default.join(dir, e.name);
3694
+ const rel = import_node_path8.default.relative(root, full).split(import_node_path8.default.sep).join("/");
3684
3695
  if (viewsPrefix && (rel === viewsPrefix || rel.startsWith(viewsPrefix + "/"))) continue;
3685
3696
  if (exclude.some((pat) => matchGlob(rel, pat))) continue;
3686
3697
  out.push(full);
@@ -3694,7 +3705,7 @@ function hasAnyMd(dir) {
3694
3705
  for (const e of entries) {
3695
3706
  if (e.isFile() && MD_RE.test(e.name) && !SIDEBAR_RE.test(e.name)) return true;
3696
3707
  if (e.isDirectory() && !e.name.startsWith("_") && !e.name.startsWith(".")) {
3697
- if (hasAnyMd(import_node_path9.default.join(dir, e.name))) return true;
3708
+ if (hasAnyMd(import_node_path8.default.join(dir, e.name))) return true;
3698
3709
  }
3699
3710
  }
3700
3711
  } catch {
@@ -3712,9 +3723,47 @@ var WIKILINK_RE3 = /(!?)\[\[([^\]\n]+)\]\]/g;
3712
3723
  function stripCodeForScan(src) {
3713
3724
  let r = src.replace(/```[\s\S]*?```/g, (m) => " ".repeat(m.length));
3714
3725
  r = r.replace(/~~~[\s\S]*?~~~/g, (m) => " ".repeat(m.length));
3715
- r = r.replace(/(`+)(?:(?!\1)[\s\S])*?\1/g, (m) => " ".repeat(m.length));
3726
+ r = stripInlineCodeLinear(r);
3716
3727
  return r;
3717
3728
  }
3729
+ function stripInlineCodeLinear(src) {
3730
+ const out = src.split("");
3731
+ let i = 0;
3732
+ const n = src.length;
3733
+ while (i < n) {
3734
+ if (src.charCodeAt(i) !== 96) {
3735
+ i += 1;
3736
+ continue;
3737
+ }
3738
+ let openLen = 0;
3739
+ while (i + openLen < n && src.charCodeAt(i + openLen) === 96) openLen += 1;
3740
+ const contentStart = i + openLen;
3741
+ let j = contentStart;
3742
+ let closeAt = -1;
3743
+ while (j < n) {
3744
+ if (src.charCodeAt(j) === 96) {
3745
+ let runLen = 0;
3746
+ while (j + runLen < n && src.charCodeAt(j + runLen) === 96) runLen += 1;
3747
+ if (runLen === openLen) {
3748
+ closeAt = j;
3749
+ break;
3750
+ }
3751
+ j += runLen;
3752
+ } else {
3753
+ j += 1;
3754
+ }
3755
+ }
3756
+ if (closeAt === -1) {
3757
+ i = contentStart;
3758
+ continue;
3759
+ }
3760
+ for (let k = i; k < closeAt + openLen; k++) {
3761
+ if (out[k] !== "\n") out[k] = " ";
3762
+ }
3763
+ i = closeAt + openLen;
3764
+ }
3765
+ return out.join("");
3766
+ }
3718
3767
  function scanWikilinks(index, options) {
3719
3768
  const dead = [];
3720
3769
  const ambiguous = [];
@@ -3864,6 +3913,23 @@ function resolveSimple(raw, index, options, currentSourceRel) {
3864
3913
  }
3865
3914
 
3866
3915
  // src/vitepress.ts
3916
+ var autoFolderIndexWarned = false;
3917
+ var mathHintShown = false;
3918
+ async function maybeHintMathPackage(config) {
3919
+ if (mathHintShown) return;
3920
+ const md = config.markdown;
3921
+ const wantsMath = md?.math === true || md?.math !== null && !Array.isArray(md?.math) && typeof md?.math === "object";
3922
+ if (!wantsMath) return;
3923
+ mathHintShown = true;
3924
+ const modName = "markdown-it-mathjax3";
3925
+ try {
3926
+ await import(modName);
3927
+ } catch {
3928
+ console.warn(
3929
+ "[vitepress-allyouneed] `markdown.math: true` set but `markdown-it-mathjax3` is missing.\n Install it once: npm i -D markdown-it-mathjax3\n (then no further setup \u2014 VitePress + this plugin will pick it up automatically.)"
3930
+ );
3931
+ }
3932
+ }
3867
3933
  function defineConfigWithAllYouNeed(config, pluginOptions = {}) {
3868
3934
  const vpExclude = Array.isArray(config.srcExclude) ? config.srcExclude : [];
3869
3935
  const mergedOptions = {
@@ -3879,6 +3945,7 @@ function defineConfigWithAllYouNeed(config, pluginOptions = {}) {
3879
3945
  ]
3880
3946
  }
3881
3947
  };
3948
+ void maybeHintMathPackage(config);
3882
3949
  const vitePlugin = viteAllYouNeed(mergedOptions);
3883
3950
  const existingVite = typeof config.vite === "object" && config.vite !== null ? config.vite : {};
3884
3951
  const existingPlugins = Array.isArray(existingVite.plugins) ? existingVite.plugins : [];
@@ -3915,9 +3982,10 @@ function defineConfigWithAllYouNeed(config, pluginOptions = {}) {
3915
3982
  const shouldFill = sidebarMode === "force" || !userProvided;
3916
3983
  if (shouldFill) {
3917
3984
  try {
3918
- if (sidebarAuto.autoFolderIndex !== void 0) {
3985
+ if (sidebarAuto.autoFolderIndex !== void 0 && !autoFolderIndexWarned) {
3986
+ autoFolderIndexWarned = true;
3919
3987
  console.warn(
3920
- "vitepress-allyouneed: `sidebarAuto.autoFolderIndex` was removed in v0.3.10. Folder URLs are now resolved via `folderLinkOrder` (default: same-name \u2192 index \u2192 README \u2192 first-file). Previously generated index.md files (with our sentinel) are safe to delete manually."
3988
+ "[vitepress-allyouneed] `sidebarAuto.autoFolderIndex` was removed in v0.4.0. No-op now \u2014 please delete this field from your .vitepress/config.ts. Folder URLs are resolved via `folderLinkOrder` (default: same-name \u2192 index \u2192 README \u2192 first-file). Old index.md files with our sentinel can be deleted by hand."
3921
3989
  );
3922
3990
  }
3923
3991
  const materializeMode = sidebarAuto.materialize ?? "off";