docnest 0.1.1 → 0.1.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.
package/README.md CHANGED
@@ -7,7 +7,7 @@ DocNest 是一个可复用的本地 Markdown 文档中心。文档内容归使
7
7
  在项目根目录执行:
8
8
 
9
9
  ```bash
10
- npm install docnest@0.1.0
10
+ npm install docnest
11
11
  ```
12
12
 
13
13
  项目默认读取 `docs/`,可直接启动:
@@ -22,6 +22,40 @@ npx docnest serve
22
22
 
23
23
  复制 `docnest.config.example.mjs` 为项目根目录的 `docnest.config.mjs`,配置项目自己的标题、目录顺序、存储键前缀和 PDF 水印。
24
24
 
25
+ ### 外观主题
26
+
27
+ DocNest 内置五套独立主题,主题风格与明暗模式分别选择、分别持久化:
28
+
29
+ - `slate-modern`:中性灰阶与克制强调色构成的现代文档风格,也是默认主题
30
+ - `editorial-atlas`:强调长文阅读与技术出版秩序
31
+ - `precision-index`:高信息密度的工程索引
32
+ - `archive-room`:带有档案与阅览室气质的纸张风格
33
+ - `swiss-manual`:黑白网格与信号色构成的工程手册
34
+
35
+ 页面右上角可以选择主题风格;相邻的明暗模式按钮在 `auto → light → dark` 之间循环。偏好使用 `site.storageKeyPrefix` 隔离,因此同一域名下的多个 DocNest 实例不会互相覆盖。
36
+
37
+ 宿主项目可以设置默认外观,并限制用户可选主题:
38
+
39
+ ```js
40
+ import { defineConfig } from 'docnest/config'
41
+
42
+ export default defineConfig({
43
+ appearance: {
44
+ defaultTheme: 'editorial-atlas',
45
+ defaultMode: 'auto',
46
+ enabledThemes: [
47
+ 'slate-modern',
48
+ 'editorial-atlas',
49
+ 'precision-index',
50
+ 'archive-room',
51
+ 'swiss-manual',
52
+ ],
53
+ },
54
+ })
55
+ ```
56
+
57
+ 未配置 `appearance` 时使用 `slate-modern + auto`。旧配置中的 `current-docs` 会自动迁移到 `slate-modern`,正文视觉不会发生变化;旧版保存的 `markdown-theme` 明暗偏好也会自动迁移到新的项目级存储键。
58
+
25
59
  水印默认关闭。开启后,PDF 导出弹窗会读取配置文字,并允许本次导出临时修改;临时修改不会写回文件或浏览器存储。
26
60
 
27
61
  如需显式指定端口、文档目录或监听地址:
package/dist/cli.js CHANGED
@@ -59,6 +59,9 @@ function exposeConfig(config) {
59
59
  process.env.DOCS_LOGO = config.site.logo;
60
60
  process.env.DOCS_STORAGE_KEY_PREFIX = config.site.storageKeyPrefix;
61
61
  process.env.DOCS_ROOT_DIRECTORY_ORDER = JSON.stringify(config.navigation.rootDirectoryOrder);
62
+ process.env.DOCS_THEME_DEFAULT = config.appearance.defaultTheme;
63
+ process.env.DOCS_THEME_MODE_DEFAULT = config.appearance.defaultMode;
64
+ process.env.DOCS_THEME_ENABLED = JSON.stringify(config.appearance.enabledThemes);
62
65
  process.env.DOCS_WATERMARK_ENABLED = String(config.export.watermark.enabled);
63
66
  process.env.DOCS_WATERMARK_TEXT = config.export.watermark.text;
64
67
  process.env.DOCS_HOST = config.server.host;
package/dist/config.d.ts CHANGED
@@ -1,3 +1,8 @@
1
+ export declare const DOCNEST_THEME_NAMES: readonly ["slate-modern", "editorial-atlas", "precision-index", "archive-room", "swiss-manual"];
2
+ declare const DOCNEST_LEGACY_THEME_NAMES: readonly ["current-docs"];
3
+ export declare const DOCNEST_COLOR_MODES: readonly ["auto", "light", "dark"];
4
+ export type DocNestThemeName = (typeof DOCNEST_THEME_NAMES)[number] | (typeof DOCNEST_LEGACY_THEME_NAMES)[number];
5
+ export type DocNestColorMode = (typeof DOCNEST_COLOR_MODES)[number];
1
6
  export type DocNestConfig = {
2
7
  docsDir?: string;
3
8
  site?: {
@@ -9,6 +14,11 @@ export type DocNestConfig = {
9
14
  navigation?: {
10
15
  rootDirectoryOrder?: string[];
11
16
  };
17
+ appearance?: {
18
+ defaultTheme?: DocNestThemeName;
19
+ defaultMode?: DocNestColorMode;
20
+ enabledThemes?: DocNestThemeName[];
21
+ };
12
22
  server?: {
13
23
  host?: string;
14
24
  port?: number;
@@ -28,9 +38,11 @@ export type ResolvedDocNestConfig = Required<DocNestConfig> & {
28
38
  docsDir: string;
29
39
  site: Required<NonNullable<DocNestConfig['site']>>;
30
40
  navigation: Required<NonNullable<DocNestConfig['navigation']>>;
41
+ appearance: Required<NonNullable<DocNestConfig['appearance']>>;
31
42
  server: Required<NonNullable<DocNestConfig['server']>>;
32
43
  export: {
33
44
  watermark: Required<NonNullable<NonNullable<DocNestConfig['export']>['watermark']>>;
34
45
  };
35
46
  };
36
47
  export declare function loadConfig(projectRoot?: string): Promise<ResolvedDocNestConfig>;
48
+ export {};
package/dist/config.js CHANGED
@@ -3,11 +3,21 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.DOCNEST_COLOR_MODES = exports.DOCNEST_THEME_NAMES = void 0;
6
7
  exports.defineConfig = defineConfig;
7
8
  exports.loadConfig = loadConfig;
8
9
  const promises_1 = __importDefault(require("node:fs/promises"));
9
10
  const node_path_1 = __importDefault(require("node:path"));
10
11
  const node_url_1 = require("node:url");
12
+ exports.DOCNEST_THEME_NAMES = [
13
+ 'slate-modern',
14
+ 'editorial-atlas',
15
+ 'precision-index',
16
+ 'archive-room',
17
+ 'swiss-manual',
18
+ ];
19
+ const DOCNEST_LEGACY_THEME_NAMES = ['current-docs'];
20
+ exports.DOCNEST_COLOR_MODES = ['auto', 'light', 'dark'];
11
21
  function defineConfig(config) {
12
22
  return config;
13
23
  }
@@ -31,6 +41,30 @@ function normalizePort(value, fallback) {
31
41
  return fallback;
32
42
  return port;
33
43
  }
44
+ function normalizeThemeName(value) {
45
+ if (value === 'current-docs')
46
+ return 'slate-modern';
47
+ return typeof value === 'string' && exports.DOCNEST_THEME_NAMES.includes(value)
48
+ ? value
49
+ : null;
50
+ }
51
+ function isColorMode(value) {
52
+ return typeof value === 'string' && exports.DOCNEST_COLOR_MODES.includes(value);
53
+ }
54
+ function resolveAppearance(config) {
55
+ const defaultTheme = normalizeThemeName(config.defaultTheme) ?? 'slate-modern';
56
+ const configuredThemes = Array.isArray(config.enabledThemes)
57
+ ? Array.from(new Set(config.enabledThemes.map(normalizeThemeName).filter((theme) => theme !== null)))
58
+ : [...exports.DOCNEST_THEME_NAMES];
59
+ const enabledThemes = configuredThemes.length > 0 ? configuredThemes : [...exports.DOCNEST_THEME_NAMES];
60
+ if (!enabledThemes.includes(defaultTheme))
61
+ enabledThemes.unshift(defaultTheme);
62
+ return {
63
+ defaultTheme,
64
+ defaultMode: isColorMode(config.defaultMode) ? config.defaultMode : 'auto',
65
+ enabledThemes,
66
+ };
67
+ }
34
68
  async function loadConfig(projectRoot = process.cwd()) {
35
69
  const configPath = node_path_1.default.join(projectRoot, DEFAULT_CONFIG_FILE);
36
70
  let userConfig = {};
@@ -46,6 +80,7 @@ async function loadConfig(projectRoot = process.cwd()) {
46
80
  const projectName = await readProjectName(projectRoot);
47
81
  const site = userConfig.site ?? {};
48
82
  const navigation = userConfig.navigation ?? {};
83
+ const appearance = userConfig.appearance ?? {};
49
84
  const server = userConfig.server ?? {};
50
85
  const watermark = userConfig.export?.watermark ?? {};
51
86
  const storageKeyPrefix = site.storageKeyPrefix?.trim() || `docnest:${projectName}`;
@@ -63,6 +98,7 @@ async function loadConfig(projectRoot = process.cwd()) {
63
98
  ? navigation.rootDirectoryOrder.filter((item) => typeof item === 'string')
64
99
  : [],
65
100
  },
101
+ appearance: resolveAppearance(appearance),
66
102
  server: {
67
103
  host: server.host?.trim() || '127.0.0.1',
68
104
  port: normalizePort(server.port, 3000),
package/dist/server.js CHANGED
@@ -32,6 +32,27 @@ const DOCS_TITLE = process.env.DOCS_TITLE || 'DocNest';
32
32
  const DOCS_SUBTITLE = process.env.DOCS_SUBTITLE || '本地 Markdown 文档服务';
33
33
  const DOCS_LOGO = process.env.DOCS_LOGO || '';
34
34
  const DOCS_STORAGE_KEY_PREFIX = process.env.DOCS_STORAGE_KEY_PREFIX || 'docnest';
35
+ const DOCS_THEME_NAMES = ['slate-modern', 'editorial-atlas', 'precision-index', 'archive-room', 'swiss-manual'];
36
+ function normalizeThemeName(value) {
37
+ return value === 'current-docs' ? 'slate-modern' : value;
38
+ }
39
+ const configuredThemeDefault = normalizeThemeName(process.env.DOCS_THEME_DEFAULT || 'slate-modern');
40
+ const DOCS_THEME_DEFAULT = DOCS_THEME_NAMES.includes(configuredThemeDefault)
41
+ ? configuredThemeDefault
42
+ : 'slate-modern';
43
+ const DOCS_THEME_MODE_DEFAULT = process.env.DOCS_THEME_MODE_DEFAULT || 'auto';
44
+ const DOCS_THEME_ENABLED = (() => {
45
+ try {
46
+ const parsed = JSON.parse(process.env.DOCS_THEME_ENABLED || '[]');
47
+ const normalized = Array.isArray(parsed)
48
+ ? [...new Set(parsed.map(normalizeThemeName).filter((item) => DOCS_THEME_NAMES.includes(item)))]
49
+ : [];
50
+ return normalized.length > 0 ? normalized : [...DOCS_THEME_NAMES];
51
+ }
52
+ catch {
53
+ return [...DOCS_THEME_NAMES];
54
+ }
55
+ })();
35
56
  const DOCS_WATERMARK_ENABLED = process.env.DOCS_WATERMARK_ENABLED === 'true';
36
57
  const DOCS_WATERMARK_TEXT = process.env.DOCS_WATERMARK_TEXT || DOCS_TITLE;
37
58
  const DOCS_DIR = path.resolve(process.env.DOCS_ROOT || path.join(process.cwd(), 'docs'));
@@ -74,6 +95,9 @@ app.locals.docsTitle = DOCS_TITLE;
74
95
  app.locals.docsSubtitle = DOCS_SUBTITLE;
75
96
  app.locals.docsLogo = DOCS_LOGO;
76
97
  app.locals.docsStorageKeyPrefix = DOCS_STORAGE_KEY_PREFIX;
98
+ app.locals.docsThemeDefault = DOCS_THEME_DEFAULT;
99
+ app.locals.docsThemeModeDefault = DOCS_THEME_MODE_DEFAULT;
100
+ app.locals.docsThemeEnabled = DOCS_THEME_ENABLED;
77
101
  app.locals.docsWatermarkEnabled = DOCS_WATERMARK_ENABLED;
78
102
  app.locals.docsWatermarkText = DOCS_WATERMARK_TEXT;
79
103
  function normalizeDocRelativePath(value) {
@@ -358,7 +382,7 @@ function renderTree(tree, level = 0) {
358
382
  html += `<li class="directory collapsed"${dataDirPath}>
359
383
  <span class="dir-toggle">
360
384
  <span class="toggle-icon">▼</span>
361
- <span class="dir-name" title="${itemName}">📁 ${itemName}</span>
385
+ <span class="dir-name" title="${itemName}"><span class="tree-kind-icon tree-kind-icon-directory" aria-hidden="true">📁</span>${itemName}</span>
362
386
  </span>
363
387
  <div class="dir-children">${renderTree(item.children, level + 1)}</div>
364
388
  </li>`;
@@ -367,7 +391,7 @@ function renderTree(tree, level = 0) {
367
391
  // 文件路径直接使用,路径重定向在 resolveDocPath 中处理
368
392
  const url = `/doc?path=${encodeURIComponent(item.path)}`;
369
393
  html += `<li class="file">
370
- <a href="${url}" class="file-link" title="${itemName}">📄 ${itemName}</a>
394
+ <a href="${url}" class="file-link" title="${itemName}"><span class="tree-kind-icon tree-kind-icon-file" aria-hidden="true">📄</span>${itemName}</a>
371
395
  </li>`;
372
396
  }
373
397
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "docnest",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "可复用的本地 Markdown 文档中心",
5
5
  "repository": {
6
6
  "type": "git",