@wsxjs/wsx-press 0.0.23 → 0.0.24

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wsxjs/wsx-press",
3
- "version": "0.0.23",
3
+ "version": "0.0.24",
4
4
  "description": "A VitePress-like documentation system built with WSXJS",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -28,10 +28,10 @@
28
28
  "dependencies": {
29
29
  "fuse.js": "^7.0.0",
30
30
  "marked": "^12.0.0",
31
- "@wsxjs/wsx-core": "0.0.23",
32
- "@wsxjs/wsx-logger": "0.0.23",
33
- "@wsxjs/wsx-marked-components": "0.0.23",
34
- "@wsxjs/wsx-router": "0.0.23"
31
+ "@wsxjs/wsx-core": "0.0.24",
32
+ "@wsxjs/wsx-logger": "0.0.24",
33
+ "@wsxjs/wsx-router": "0.0.24",
34
+ "@wsxjs/wsx-marked-components": "0.0.24"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@types/fs-extra": "^11.0.4",
@@ -48,8 +48,8 @@
48
48
  "typescript": "^5.0.0",
49
49
  "vite": "^5.4.19",
50
50
  "vitest": "^2.0.0",
51
- "@wsxjs/eslint-plugin-wsx": "0.0.23",
52
- "@wsxjs/wsx-vite-plugin": "0.0.23"
51
+ "@wsxjs/eslint-plugin-wsx": "0.0.24",
52
+ "@wsxjs/wsx-vite-plugin": "0.0.24"
53
53
  },
54
54
  "peerDependencies": {
55
55
  "typedoc": "^0.26.0",
@@ -252,29 +252,45 @@ export default class DocPage extends LightComponent {
252
252
  // 创建新的 AbortController
253
253
  this.loadingAbortController = new AbortController();
254
254
 
255
- // 从 RouterUtils 获取当前路由信息(包含参数)
255
+ // 从 RouterUtils 获取当前路由信息
256
256
  const routeInfo = RouterUtils.getCurrentRoute();
257
- const params = routeInfo.params as { category?: string; page?: string };
258
-
259
- // 验证参数
260
- if (!params.category || !params.page) {
261
- logger.warn("Missing route parameters:", { params, routeInfo });
262
- // 如果参数为空,可能是路由还未初始化,保持 idle 状态
263
- if (Object.keys(params).length === 0) {
264
- logger.debug("Route params not yet initialized, keeping idle state");
265
- this.loadingState = "idle";
257
+
258
+ // 从路径中提取文档路径(支持多级路径)
259
+ // 例如:/docs/guide/essentials/getting-started -> guide/essentials/getting-started
260
+ let docPath: string;
261
+ if (routeInfo.path.startsWith("/docs/")) {
262
+ // 移除 /docs/ 前缀,得到文档路径
263
+ docPath = routeInfo.path.slice(6); // 移除 "/docs/"
264
+ } else {
265
+ // 尝试从 params 获取(向后兼容)
266
+ const params = routeInfo.params as { category?: string; page?: string };
267
+ if (params.category && params.page) {
268
+ docPath = `${params.category}/${params.page}`;
269
+ } else {
270
+ logger.warn("Invalid document path:", { path: routeInfo.path, params });
271
+ // 如果参数为空,可能是路由还未初始化,保持 idle 状态
272
+ if (Object.keys(params).length === 0) {
273
+ logger.debug("Route params not yet initialized, keeping idle state");
274
+ this.loadingState = "idle";
275
+ return;
276
+ }
277
+ this.loadingState = "error";
278
+ this.error = new DocumentLoadError(
279
+ "Invalid document path: path must start with /docs/",
280
+ "INVALID_PARAMS"
281
+ );
266
282
  return;
267
283
  }
284
+ }
285
+
286
+ // 验证文档路径不为空
287
+ if (!docPath || docPath.trim() === "") {
288
+ logger.warn("Empty document path:", { path: routeInfo.path });
268
289
  this.loadingState = "error";
269
- this.error = new DocumentLoadError(
270
- "Missing route parameters: category and page are required",
271
- "INVALID_PARAMS"
272
- );
290
+ this.error = new DocumentLoadError("Document path is empty", "INVALID_PARAMS");
273
291
  return;
274
292
  }
275
293
 
276
- const docPath = `${params.category}/${params.page}`;
277
-
278
294
  // 防止竞态条件:记录当前加载路径
279
295
  this.currentLoadingPath = docPath;
280
296
  this.isLoading = true;
@@ -12,6 +12,28 @@ wsx-doc-sidebar {
12
12
  max-height: calc(100vh - 70px);
13
13
  }
14
14
 
15
+ /* 浅色模式(通过类名,覆盖媒体查询) */
16
+ .light wsx-doc-sidebar,
17
+ [data-theme="light"] wsx-doc-sidebar {
18
+ background: var(--wsx-press-sidebar-bg, #f9fafb);
19
+ border-right-color: var(--wsx-press-border, #e5e7eb);
20
+ }
21
+
22
+ /* 暗色模式(通过类名) */
23
+ .dark wsx-doc-sidebar,
24
+ [data-theme="dark"] wsx-doc-sidebar {
25
+ background: var(--wsx-press-sidebar-bg, #1f2937);
26
+ border-right-color: var(--wsx-press-border, #374151);
27
+ }
28
+
29
+ /* 暗色模式(通过媒体查询作为后备,仅当没有显式类名时) */
30
+ @media (prefers-color-scheme: dark) {
31
+ html:not(.light):not([data-theme="light"]) wsx-doc-sidebar {
32
+ background: var(--wsx-press-sidebar-bg, #1f2937);
33
+ border-right-color: var(--wsx-press-border, #374151);
34
+ }
35
+ }
36
+
15
37
  .doc-sidebar {
16
38
  display: flex;
17
39
  flex-direction: column;
@@ -48,6 +70,24 @@ wsx-doc-sidebar {
48
70
  margin: 0 0 0.5rem 0;
49
71
  }
50
72
 
73
+ /* 浅色模式分类标题(覆盖媒体查询) */
74
+ .light .doc-sidebar-category-title,
75
+ [data-theme="light"] .doc-sidebar-category-title {
76
+ color: var(--wsx-press-text-secondary, #6b7280);
77
+ }
78
+
79
+ /* 暗色模式分类标题 */
80
+ .dark .doc-sidebar-category-title,
81
+ [data-theme="dark"] .doc-sidebar-category-title {
82
+ color: var(--wsx-press-text-secondary, #9ca3af);
83
+ }
84
+
85
+ @media (prefers-color-scheme: dark) {
86
+ html:not(.light):not([data-theme="light"]) .doc-sidebar-category-title {
87
+ color: var(--wsx-press-text-secondary, #9ca3af);
88
+ }
89
+ }
90
+
51
91
  .doc-sidebar-list {
52
92
  list-style: none;
53
93
  margin: 0;
@@ -75,12 +115,67 @@ wsx-doc-sidebar {
75
115
  }
76
116
 
77
117
  .doc-sidebar-link.active {
78
- background: var(--wsx-press-sidebar-active-bg, #eff6ff);
79
- color: var(--wsx-press-sidebar-active-color, #2563eb);
80
- border-left-color: var(--wsx-press-sidebar-active-color, #2563eb);
118
+ background: var(--wsx-press-sidebar-active-bg, #fef2f2);
119
+ color: var(--wsx-press-sidebar-active-color, #dc2626);
120
+ border-left-color: var(--wsx-press-sidebar-active-color, #dc2626);
81
121
  font-weight: 500;
82
122
  }
83
123
 
124
+ /* 浅色模式链接样式(覆盖媒体查询) */
125
+ .light .doc-sidebar-link,
126
+ [data-theme="light"] .doc-sidebar-link {
127
+ color: var(--wsx-press-text-primary, #111827);
128
+ }
129
+
130
+ .light .doc-sidebar-link:hover,
131
+ [data-theme="light"] .doc-sidebar-link:hover {
132
+ background: var(--wsx-press-sidebar-hover-bg, #f3f4f6);
133
+ color: var(--wsx-press-text-primary, #111827);
134
+ }
135
+
136
+ .light .doc-sidebar-link.active,
137
+ [data-theme="light"] .doc-sidebar-link.active {
138
+ background: var(--wsx-press-sidebar-active-bg, #fef2f2);
139
+ color: var(--wsx-press-sidebar-active-color, #dc2626);
140
+ border-left-color: var(--wsx-press-sidebar-active-color, #dc2626);
141
+ }
142
+
143
+ /* 暗色模式链接样式 */
144
+ .dark .doc-sidebar-link,
145
+ [data-theme="dark"] .doc-sidebar-link {
146
+ color: var(--wsx-press-text-primary, #e5e7eb);
147
+ }
148
+
149
+ .dark .doc-sidebar-link:hover,
150
+ [data-theme="dark"] .doc-sidebar-link:hover {
151
+ background: var(--wsx-press-sidebar-hover-bg, #374151);
152
+ color: var(--wsx-press-text-primary, #f3f4f6);
153
+ }
154
+
155
+ .dark .doc-sidebar-link.active,
156
+ [data-theme="dark"] .doc-sidebar-link.active {
157
+ background: var(--wsx-press-sidebar-active-bg, rgba(220, 38, 38, 0.15));
158
+ color: var(--wsx-press-sidebar-active-color, #f87171);
159
+ border-left-color: var(--wsx-press-sidebar-active-color, #f87171);
160
+ }
161
+
162
+ @media (prefers-color-scheme: dark) {
163
+ html:not(.light):not([data-theme="light"]) .doc-sidebar-link {
164
+ color: var(--wsx-press-text-primary, #e5e7eb);
165
+ }
166
+
167
+ html:not(.light):not([data-theme="light"]) .doc-sidebar-link:hover {
168
+ background: var(--wsx-press-sidebar-hover-bg, #374151);
169
+ color: var(--wsx-press-text-primary, #f3f4f6);
170
+ }
171
+
172
+ html:not(.light):not([data-theme="light"]) .doc-sidebar-link.active {
173
+ background: var(--wsx-press-sidebar-active-bg, rgba(220, 38, 38, 0.15));
174
+ color: var(--wsx-press-sidebar-active-color, #f87171);
175
+ border-left-color: var(--wsx-press-sidebar-active-color, #f87171);
176
+ }
177
+ }
178
+
84
179
  /* 响应式设计 */
85
180
  @media (max-width: 1024px) {
86
181
  wsx-doc-sidebar {
@@ -103,9 +103,24 @@ export default class DocSidebar extends LightComponent {
103
103
  // 按分类名称排序
104
104
  const sortedCategories = Object.keys(organized).sort();
105
105
 
106
- // 对每个分类内的文档排序(按标题)
106
+ // 对每个分类内的文档排序(优先按 order,然后按标题)
107
107
  sortedCategories.forEach((category) => {
108
- organized[category].sort((a, b) => a.title.localeCompare(b.title));
108
+ organized[category].sort((a, b) => {
109
+ // 如果两个文档都有 order,按 order 排序
110
+ if (a.order !== undefined && b.order !== undefined) {
111
+ return a.order - b.order;
112
+ }
113
+ // 如果只有 a 有 order,a 排在前面
114
+ if (a.order !== undefined) {
115
+ return -1;
116
+ }
117
+ // 如果只有 b 有 order,b 排在前面
118
+ if (b.order !== undefined) {
119
+ return 1;
120
+ }
121
+ // 都没有 order,按标题排序
122
+ return a.title.localeCompare(b.title);
123
+ });
109
124
  });
110
125
 
111
126
  return organized;
@@ -105,14 +105,20 @@ export default class DocTOC extends LightComponent {
105
105
  private async loadTOC(): Promise<void> {
106
106
  try {
107
107
  const routeInfo = RouterUtils.getCurrentRoute();
108
- const params = routeInfo.params as { category?: string; page?: string };
108
+ // 从路径中提取 docPath(移除 /docs/ 前缀)
109
+ let docPath: string;
110
+ if (routeInfo.path.startsWith("/docs/")) {
111
+ docPath = routeInfo.path.slice(6); // 移除 "/docs/" 前缀
112
+ } else {
113
+ this.items = [];
114
+ return;
115
+ }
109
116
 
110
- if (!params.category || !params.page) {
117
+ if (!docPath || docPath.trim() === "") {
111
118
  this.items = [];
112
119
  return;
113
120
  }
114
121
 
115
- const docPath = `${params.category}/${params.page}`;
116
122
  const tocData = await loadTOCData();
117
123
  this.items = tocData[docPath] || [];
118
124
 
@@ -27,7 +27,8 @@ export async function scanDocsMetadata(docsRoot: string): Promise<DocsMetaCollec
27
27
  const dirname = path.dirname(relativePath);
28
28
  metadata[key] = {
29
29
  title: frontmatter.title || path.basename(file, ".md"),
30
- category: dirname === "." ? "." : dirname,
30
+ // 优先使用 frontmatter 中的 category,如果没有则使用 dirname
31
+ category: frontmatter.category || (dirname === "." ? "." : dirname),
31
32
  route: `/docs/${key}`,
32
33
  ...frontmatter,
33
34
  };
@@ -64,7 +65,25 @@ export function extractFrontmatter(markdown: string): Partial<DocMetadata> {
64
65
  if (key && value) {
65
66
  // 类型安全的属性赋值
66
67
  if (key === "title" || key === "description" || key === "category") {
67
- (meta as Record<string, string>)[key] = value;
68
+ // 移除值两端的引号(如果存在)
69
+ let cleanValue = value.replace(/^["']|["']$/g, "");
70
+ // 解码 HTML 实体(如 &quot; -> ")
71
+ // 使用 Node.js 兼容的方法
72
+ if (cleanValue.includes("&")) {
73
+ cleanValue = cleanValue
74
+ .replace(/&quot;/g, '"')
75
+ .replace(/&apos;/g, "'")
76
+ .replace(/&lt;/g, "<")
77
+ .replace(/&gt;/g, ">")
78
+ .replace(/&amp;/g, "&");
79
+ }
80
+ (meta as Record<string, string>)[key] = cleanValue;
81
+ } else if (key === "order") {
82
+ // 解析 order 字段为数字
83
+ const orderNum = Number.parseInt(value, 10);
84
+ if (!Number.isNaN(orderNum)) {
85
+ meta.order = orderNum;
86
+ }
68
87
  } else if (key === "tags") {
69
88
  // 简单的数组解析(实际应使用 YAML 解析器)
70
89
  meta.tags = value
@@ -101,7 +120,15 @@ export function addPrevNextLinks(metadata: DocsMetaCollection): DocsMetaCollecti
101
120
 
102
121
  // 为每个类别添加 prev/next
103
122
  for (const [_category, keys] of categories) {
104
- keys.sort();
123
+ // 按 order 字段排序(如果存在),然后按 key 排序
124
+ keys.sort((a, b) => {
125
+ const orderA = metadata[a].order ?? Number.MAX_SAFE_INTEGER;
126
+ const orderB = metadata[b].order ?? Number.MAX_SAFE_INTEGER;
127
+ if (orderA !== orderB) {
128
+ return orderA - orderB;
129
+ }
130
+ return a.localeCompare(b);
131
+ });
105
132
  for (let i = 0; i < keys.length; i++) {
106
133
  const key = keys[i];
107
134
  metadata[key].prev = i > 0 ? `/docs/${keys[i - 1]}` : null;
package/src/types.ts CHANGED
@@ -13,6 +13,8 @@ export interface DocMetadata {
13
13
  category: string;
14
14
  /** 文档路由 */
15
15
  route: string;
16
+ /** 文档顺序(用于控制侧边栏和导航中的显示顺序,数字越小越靠前) */
17
+ order?: number;
16
18
  /** 上一篇文档路由 */
17
19
  prev?: string | null;
18
20
  /** 下一篇文档路由 */