oceanpress 1.0.3 → 1.0.9

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/dist-cli/cli.js CHANGED
@@ -4,101 +4,175 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
4
4
 
5
5
  // src/cli/deploy.ts
6
6
  import { readFile } from "fs/promises";
7
- import { createRPC as createRPC2 } from "oceanpress-rpc";
8
- import { stringify as stringify2 } from "superjson";
9
-
10
- // src/core/dependency.ts
11
- var nullDep = () => {
12
- throw new Error("\u4E0D\u53EF\u8C03\u7528\u672A\u586B\u5145\u7684\u4F9D\u8D56");
13
- };
14
- var storeDep = {
15
- // 读写配置文件所依赖的副作用
16
- setItem: nullDep,
17
- getItem: nullDep,
18
- // render功能依赖的副作用
19
- getNodeByID: nullDep,
20
- getDocPathBySY: nullDep,
21
- getDocByChildID: nullDep,
22
- getHPathByID_Node: nullDep
23
- };
24
7
 
25
- // src/util/deep_assign.ts
26
- function deepAssign(target, source, config = { add: true, update: true }) {
27
- for (let key in source) {
28
- if (source.hasOwnProperty(key)) {
29
- if (source[key] instanceof Object && !Array.isArray(source[key])) {
30
- if (!target.hasOwnProperty(key)) {
31
- target[key] = {};
32
- }
33
- deepAssign(target[key], source[key], config);
8
+ // ../rpc/dist/index.js
9
+ async function createRPC(...[type, options]) {
10
+ const apiModule = type === "apiProvider" ? await options.genApiModule() : void 0;
11
+ const remoteCall = type === "apiConsumer" ? options.remoteCall : void 0;
12
+ async function RC(method, data) {
13
+ async function executeMiddleware(index) {
14
+ if (options.middleware && index < options.middleware.length) {
15
+ return options.middleware[index](method, data, () => executeMiddleware(index + 1));
34
16
  } else {
35
- if (!target.hasOwnProperty(key) && config.add) {
36
- target[key] = source[key];
37
- } else if (config.update) {
38
- target[key] = source[key];
17
+ return executeCall();
18
+ }
19
+ }
20
+ async function executeCall() {
21
+ try {
22
+ if (type === "apiProvider") {
23
+ const methodParts = method.split(".");
24
+ let currentObj = apiModule;
25
+ for (const part of methodParts) {
26
+ if (currentObj && typeof currentObj === "object" && part in currentObj) {
27
+ currentObj = currentObj[part];
28
+ } else {
29
+ throw new Error(`Method ${method} not found`);
30
+ }
31
+ }
32
+ if (typeof currentObj === "function") {
33
+ return await currentObj(...data);
34
+ } else {
35
+ throw new Error(`${method} is not a function`);
36
+ }
37
+ } else {
38
+ return await remoteCall(method, data);
39
39
  }
40
+ } catch (error) {
41
+ console.error("API call failed:", error);
42
+ throw error;
40
43
  }
41
44
  }
45
+ return await executeMiddleware(0);
42
46
  }
43
- return target;
47
+ function createNestedProxy(path = []) {
48
+ return {
49
+ get(target, prop) {
50
+ if (prop === "then") {
51
+ return void 0;
52
+ }
53
+ const newPath = [...path, prop];
54
+ return new Proxy(function(...args) {
55
+ const method = newPath.join(".");
56
+ return RC(method, args);
57
+ }, createNestedProxy(newPath));
58
+ },
59
+ apply(target, thisArg, args) {
60
+ const method = path.join(".");
61
+ return RC(method, args);
62
+ }
63
+ };
64
+ }
65
+ const API2 = new Proxy(function() {
66
+ }, createNestedProxy());
67
+ return { API: API2, RC };
44
68
  }
45
69
 
70
+ // src/cli/deploy.ts
71
+ import { stringify as stringify2 } from "superjson";
72
+
46
73
  // src/core/config.ts
74
+ import { Effect } from "effect";
47
75
  import { computed, reactive, watch } from "vue";
48
76
 
49
77
  // package.json
50
78
  var package_default = {
51
79
  name: "oceanpress",
52
- private: true,
53
- version: "1.0.2",
80
+ version: "1.0.9",
54
81
  type: "module",
55
82
  scripts: {
56
83
  dev: "vite",
57
84
  cli: "tsx ./src/cli.ts",
58
- cli_watch: "tsx watch ./src/cli.ts",
85
+ "cli-w": "tsx watch ./src/cli.ts",
59
86
  build: "vite build && npm run build_lib",
60
87
  build_lib: "vite build --config vite.sw.config.ts",
61
- build_npmlib: "vite build --mode library",
88
+ build_app: "vite build --mode library",
89
+ build_cli: "tsup",
62
90
  build_plugin_ui: "vite build --config vite.plugin.config.ts",
63
91
  dev_plugin_ui: "vite build --watch --config vite.plugin.config.ts",
64
- generate_dependency_graph: "depcruise src --include-only '^src' --output-type dot > ./assets/dep.dot",
65
92
  preview: "vite preview"
66
93
  },
67
- bin: "./dist/cli.js",
94
+ bin: {
95
+ oceanpress: "./dist-cli/cli.js"
96
+ },
97
+ files: [
98
+ "dist/assets",
99
+ "dist/dev",
100
+ "dist/index.html",
101
+ "dist/ocean_press-log.png",
102
+ "dist-app/assets",
103
+ "dist-app/dev",
104
+ "dist-app/app.*",
105
+ "dist-cli",
106
+ "*.md"
107
+ ],
68
108
  dependencies: {
69
- "@aws-sdk/client-s3": "^3.758.0",
70
- "@hono/node-server": "^1.13.8",
71
- cheerio: "1.0.0",
72
- commander: "^13.1.0",
109
+ "@aws-sdk/client-s3": "^3.864.0",
110
+ "@hono/node-server": "^1.18.1",
111
+ cheerio: "1.1.2",
112
+ commander: "^14.0.0",
113
+ effect: "^3.17.7",
73
114
  fzstd: "^0.1.1",
74
- hono: "^4.7.4",
115
+ hono: "^4.9.0",
75
116
  jszip: "^3.10.1",
76
- meilisearch: "^0.49.0",
77
- "naive-ui": "^2.41.0",
78
- "oceanpress-rpc": "workspace:*",
79
- "oceanpress-server": "workspace:*",
80
- octokit: "^4.1.2",
117
+ meilisearch: "^0.51.0",
118
+ "naive-ui": "^2.42.0",
119
+ octokit: "^5.0.3",
81
120
  superjson: "^2.2.2",
82
- tsx: "^4.19.3",
83
- vditor: "^3.10.9",
84
- vue: "^3.5.13",
85
- "vue-router": "^4.5.0",
121
+ tsx: "^4.20.3",
122
+ vditor: "^3.11.1",
123
+ vue: "^3.5.18",
124
+ "vue-router": "^4.5.1",
86
125
  "zstd-codec": "^0.1.5"
87
126
  },
88
127
  devDependencies: {
89
- "@vitejs/plugin-vue": "^5.2.1",
90
- "@vitejs/plugin-vue-jsx": "^4.1.1",
91
- "dependency-cruiser": "^16.10.0",
92
- tsup: "^8.4.0",
93
- typescript: "^5.8.2",
94
- vite: "^6.2.2",
95
- "vite-plugin-vue-devtools": "^7.7.2",
96
- "vue-tsc": "^2.2.8"
128
+ "@vitejs/plugin-vue": "^6.0.1",
129
+ "@vitejs/plugin-vue-jsx": "^5.0.1",
130
+ "dependency-cruiser": "^17.0.1",
131
+ "oceanpress-rpc": "workspace:*",
132
+ "oceanpress-server": "workspace:*",
133
+ tsup: "^8.5.0",
134
+ typescript: "^5.9.2",
135
+ vite: "^7.1.2",
136
+ "vite-plugin-vue-devtools": "^8.0.0",
137
+ "vue-tsc": "^3.0.5"
138
+ }
139
+ };
140
+
141
+ // src/util/deep_assign.ts
142
+ function deepAssign(target, source, config = { add: true, update: true }) {
143
+ for (let key in source) {
144
+ if (source.hasOwnProperty(key)) {
145
+ if (source[key] instanceof Object && !Array.isArray(source[key])) {
146
+ if (!target.hasOwnProperty(key)) {
147
+ target[key] = {};
148
+ }
149
+ deepAssign(target[key], source[key], config);
150
+ } else {
151
+ if (!target.hasOwnProperty(key) && config.add) {
152
+ target[key] = source[key];
153
+ } else if (config.update) {
154
+ target[key] = source[key];
155
+ }
156
+ }
157
+ }
97
158
  }
159
+ return target;
160
+ }
161
+
162
+ // src/core/EffectDep.ts
163
+ import { Context } from "effect";
164
+ var EffectRender = class extends Context.Tag("EffectDep")() {
165
+ };
166
+ var EffectLocalStorageDep = class extends Context.Tag("EffectLocalStorageDep")() {
167
+ };
168
+ var EffectLogDep = class extends Context.Tag("EffectLogDep")() {
169
+ };
170
+ var EffectConfigDep = class extends Context.Tag("EffectConfigDep")() {
98
171
  };
99
172
 
100
173
  // src/core/config.ts
101
174
  var version = package_default.version;
175
+ console.log("[version]", version);
102
176
  var defaultConfig = {
103
177
  name: "default",
104
178
  /** 需要编译的笔记本 */
@@ -150,7 +224,7 @@ var defaultConfig = {
150
224
  // publicZip:
151
225
  // 'https://fastly.jsdelivr.net/gh/siyuan-note/oceanpress@v0.0.7/apps/frontend/public/public.zip',
152
226
  // },
153
- /** s3 上传配置
227
+ /** 部署到 s3 上传配置
154
228
  * https://help.aliyun.com/zh/oss/developer-reference/use-amazon-s3-sdks-to-access-oss#section-2ri-suq-pb3
155
229
  */
156
230
  s3: {
@@ -187,6 +261,15 @@ var defaultConfig = {
187
261
  </p>
188
262
  </footer>`
189
263
  },
264
+ /** 侧边栏配置 */
265
+ sidebarCode: {
266
+ /** 启用文档树,则 leftCode 将插入在文档树上方 */
267
+ enableDocTree: true,
268
+ /** 将插入主文档的左侧div中的html代码 */
269
+ leftCode: "",
270
+ /** 将插入主文档的右侧div中的html代码 */
271
+ rightCode: ""
272
+ },
190
273
  OceanPress: {
191
274
  /** 此配置文件编译时的版本 */
192
275
  version
@@ -200,47 +283,49 @@ var configs = reactive({
200
283
  default: deepAssign({}, defaultConfig)
201
284
  });
202
285
  var loadConfigFile = (c) => {
203
- if (c) {
204
- deepAssign(configs, c);
205
- } else {
206
- const localConfig = storeDep.getItem("configs");
207
- if (localConfig) {
208
- deepAssign(configs, JSON.parse(localConfig));
286
+ return Effect.gen(function* () {
287
+ const effectDep = yield* EffectLocalStorageDep;
288
+ if (c) {
289
+ deepAssign(configs, c);
290
+ } else {
291
+ const localConfig = effectDep.getItem("configs");
292
+ if (localConfig) {
293
+ deepAssign(configs, JSON.parse(localConfig));
294
+ }
209
295
  }
210
- }
211
- Object.entries(configs).filter(([key]) => key.startsWith("__") === false).forEach(([_key, obj]) => {
212
- deepAssign(obj, defaultConfig, { update: false, add: true });
296
+ Object.entries(configs).filter(([key]) => key.startsWith("__") === false).forEach(([_key, obj]) => {
297
+ deepAssign(obj, defaultConfig, { update: false, add: true });
298
+ });
299
+ const saveConfig = () => {
300
+ if (configs.__init__ === false)
301
+ effectDep.setItem("configs", JSON.stringify(configs, null, 2));
302
+ };
303
+ let timer = null;
304
+ const debounceSaveConfig = () => {
305
+ if (timer) {
306
+ clearTimeout(timer);
307
+ }
308
+ timer = setTimeout(() => {
309
+ saveConfig();
310
+ timer = null;
311
+ }, 700);
312
+ };
313
+ watch(configs, debounceSaveConfig, { deep: true });
314
+ return configs;
213
315
  });
214
316
  };
215
317
  var currentConfig = computed(() => configs[configs.__current__]);
216
318
  var tempConfig = {
217
319
  cdn: {
218
320
  /** 思源 js、css等文件的前缀 */
219
- siyuanPrefix: "https://fastly.jsdelivr.net/gh/siyuan-note/oceanpress@latest/apps/frontend/public/notebook/",
321
+ siyuanPrefix: `https://fastly.jsdelivr.net/gh/siyuan-note/oceanpress@${version}/apps/frontend/public/notebook/`,
322
+ // 'https://fastly.jsdelivr.net/gh/siyuan-note/oceanpress@latest/apps/frontend/public/notebook/',
220
323
  /** 思源 js、css等文件zip包地址 */
221
324
  publicZip: "https://fastly.jsdelivr.net/gh/siyuan-note/oceanpress@v0.0.7/apps/frontend/public/public.zip"
222
325
  },
223
326
  withoutPublicZip: true
224
327
  };
225
- var saveConfig = () => {
226
- if (configs.__init__ === false)
227
- storeDep.setItem("configs", JSON.stringify(configs, null, 2));
228
- };
229
- var timer = null;
230
- var debounceSaveConfig = () => {
231
- if (timer) {
232
- clearTimeout(timer);
233
- }
234
- timer = setTimeout(() => {
235
- saveConfig();
236
- timer = null;
237
- }, 700);
238
- };
239
- watch(configs, debounceSaveConfig, { deep: true });
240
328
  configs.__init__ = false;
241
- if (globalThis.document) {
242
- loadConfigFile();
243
- }
244
329
 
245
330
  // src/core/genZip.ts
246
331
  import JSZip from "jszip";
@@ -306,7 +391,7 @@ var MeilisearchPlugin = class {
306
391
  if (path.endsWith("index.html")) break;
307
392
  }
308
393
  this.updateDocument();
309
- return next(tree);
394
+ return next(...c);
310
395
  });
311
396
  this.host = option.host;
312
397
  this.apiKey = option.apiKey;
@@ -339,91 +424,72 @@ var MeilisearchPlugin = class {
339
424
  }
340
425
  };
341
426
 
342
- // src/core/plugin.ts
343
- var PluginCenter = class {
344
- constructor(_funMap) {
345
- this._funMap = _funMap;
346
- __publicField(this, "plugins", []);
347
- /** 辅助类型,不可调用! */
348
- __publicField(this, "pluginType", 0);
349
- /** 对需要调用的函数进行代理,完成插件hook介入。 */
350
- __publicField(this, "fun");
351
- const that = this;
352
- this.fun = new Proxy({}, {
353
- get(_target, propertyKey, receiver) {
354
- const method = Reflect.get(that._funMap, propertyKey, receiver);
355
- if (typeof method === "function") {
356
- return (...args) => {
357
- return that.callFn(
358
- propertyKey,
359
- //@ts-ignore 懒得推类型了。属于内部实现,就直接忽略掉吧
360
- method
361
- )(...args);
362
- };
363
- }
364
- return method;
365
- }
366
- });
367
- }
368
- registerPlugin(plugin) {
369
- this.plugins.push(plugin);
370
- }
371
- removePlugin(plugin) {
372
- this.plugins = this.plugins.filter((p) => p !== plugin);
373
- }
374
- /** 洋葱hook调用机制的实现 */
375
- callFn(name, fn) {
376
- return (...arg) => {
377
- const m = new middlewareRunner(fn);
378
- for (const plugin of this.plugins) {
379
- const middleware = plugin[name];
380
- if (middleware) {
381
- m.use(middleware);
427
+ // src/plugins/publish/OceanPressServer.ts
428
+ import { stringify } from "superjson";
429
+ function deployOceanPressServer_plugin(config) {
430
+ const plugin = {
431
+ async build_onFileTree([tree, effectApi], next) {
432
+ next(tree, effectApi);
433
+ const client = await createRPC("apiConsumer", {
434
+ async remoteCall(method, data) {
435
+ let body;
436
+ let content_type;
437
+ if (data[0] instanceof ReadableStream) {
438
+ body = await new Response(data[0]).blob();
439
+ content_type = "application/octet-stream";
440
+ } else {
441
+ body = stringify(data);
442
+ console.log("[body]", body);
443
+ content_type = "application/json";
444
+ }
445
+ return fetch(`${config.oceanPressServer.apiBase}/api/${method}`, {
446
+ method: "POST",
447
+ body,
448
+ headers: {
449
+ "x-api-key": config.oceanPressServer.apiKey,
450
+ "Content-Type": content_type
451
+ },
452
+ // @ts-expect-error 在 node 运行的时候需要声明双工模式才能正确发送 ReadableStream,TODO 需要验证浏览器端可以这样运行吗
453
+ duplex: "half"
454
+ // 关键:显式声明半双工模式
455
+ }).then((res2) => res2.json()).then((r) => {
456
+ if (r.error) {
457
+ console.log("[r]", r);
458
+ throw new Error();
459
+ }
460
+ return r.result;
461
+ });
382
462
  }
383
- }
384
- return m.runMiddlewareHandel(...arg);
385
- };
386
- }
387
- };
388
- var middlewareRunner = class {
389
- constructor(handel) {
390
- this.handel = handel;
391
- __publicField(this, "middlewares", []);
392
- }
393
- use(middleware) {
394
- this.middlewares.push(middleware);
395
- }
396
- runMiddlewareHandel(...ctx) {
397
- let index = 0;
398
- const next = (...ctx2) => {
399
- const middleware = this.middlewares[index];
400
- index++;
401
- if (middleware === void 0) {
402
- return this.handel.call(this, ...ctx2);
403
- }
404
- return middleware(ctx2, next);
405
- };
406
- return next.call(this, ...ctx);
407
- }
408
- };
463
+ });
464
+ const zip = await genZIP(tree, { withoutZip: true });
465
+ const sizeInMB = zip.size / (1024 * 1024);
466
+ console.log("[zip.size in MB]", sizeInMB.toFixed(2));
467
+ const readableStream = zip.stream();
468
+ const { chunkCount, fileId } = await client.API.upload(readableStream);
469
+ console.log("[res]", { chunkCount, fileId });
470
+ const res = await client.API.deploy({ zipFileId: fileId });
471
+ console.log("[deploy res]", res);
472
+ }
473
+ };
474
+ return plugin;
475
+ }
409
476
 
410
477
  // src/plugins/publish/s3.ts
411
478
  import { S3 } from "@aws-sdk/client-s3";
412
479
  var s3Upload_plugin = {
413
- build: async function([config, effect, other], next) {
414
- const res = await next(config, effect, {
415
- ...other,
416
- onFileTree: async (tree) => {
417
- if (other?.onFileTree) {
418
- await other.onFileTree(tree);
480
+ build: function([config, otherConfig], next) {
481
+ return next(config, {
482
+ ...otherConfig,
483
+ onFileTree: async (tree, effectApi) => {
484
+ if (otherConfig?.onFileTree) {
485
+ await otherConfig.onFileTree(tree, effectApi);
419
486
  }
420
487
  for await (const [fileName, ETag] of s3_uploads(tree, config)) {
421
- effect.log(`\u4E0A\u4F20\uFF1A ${fileName} ${ETag}`);
488
+ effectApi.log(`\u4E0A\u4F20\uFF1A ${fileName} ${ETag}`);
422
489
  }
490
+ effectApi.log(`s3 \u4E0A\u4F20\u5B8C\u6BD5`);
423
491
  }
424
492
  });
425
- effect.log(`s3 \u4E0A\u4F20\u5B8C\u6BD5`);
426
- return res;
427
493
  }
428
494
  };
429
495
  var s3_uploads = async function* (tree, config) {
@@ -452,115 +518,22 @@ var s3_uploads = async function* (tree, config) {
452
518
  }
453
519
  };
454
520
 
455
- // src/core/htmlTemplate.ts
456
- async function htmlTemplate(p, config) {
457
- let prePath = "";
458
- if (config?.siyuanPrefix) {
459
- prePath = config.siyuanPrefix;
460
- } else {
461
- for (let i = 0; i < p.level; i++) {
462
- prePath += "../";
463
- }
521
+ // src/core/build.ts
522
+ import { Effect as Effect4 } from "effect";
523
+
524
+ // src/components/data_promise/index.ts
525
+ import { customRef, nextTick, watch as watch2, watchEffect } from "vue";
526
+ var PromiseObj = class {
527
+ constructor() {
528
+ __publicField(this, "pending", false);
529
+ __publicField(this, "fulfilled", false);
530
+ __publicField(this, "rejected", false);
531
+ __publicField(this, "data", {});
532
+ __publicField(this, "error", {});
533
+ __publicField(this, "_p", Promise.resolve());
464
534
  }
465
- const version2 = "2.10.5";
466
- return `<!DOCTYPE html>
467
- <html lang="zh_CN" data-theme-mode="light" data-light-theme="daylight" data-dark-theme="midnight">
468
- <head>
469
- ${config?.embedCode?.head ?? ""}
470
- <meta charset="utf-8" />
471
- <meta http-equiv="X-UA-Compatible" content="IE=edge" />
472
- <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"/>
473
- <meta name="apple-mobile-web-app-capable" content="yes" />
474
- <meta name="mobile-web-app-capable" content="yes" />
475
- <meta name="apple-mobile-web-app-status-bar-style" content="black" />
476
- <link rel="stylesheet" type="text/css" id="baseStyle" href="${prePath}stage/build/export/base.css?${version2}"/>
477
- <script>
478
- function isNightTime() {
479
- const currentHour = new Date().getHours();
480
- return currentHour >= 18 || currentHour < 6;
481
- }
482
- document.write('<link rel="stylesheet" type="text/css" id="themeDefaultStyle" href="${prePath}appearance/themes/'+(isNightTime()?'midnight':'daylight')+'/theme.css?${version2}"/>');
483
- </script>
484
- <link rel="stylesheet" type="text/css" href="${prePath}appearance/oceanpress.css"/>
485
- <title>${p.title}</title>
486
- </head>
487
- <body>
488
- ${config?.embedCode?.beforeBody ?? ""}
489
- <div class="protyle-wysiwyg protyle-wysiwyg--attr" id="preview">
490
- ${p.htmlContent}
491
- </div>
492
- <script src="${prePath}appearance/icons/material/icon.js?${version2}"></script>
493
- <script src="${prePath}stage/build/export/protyle-method.js?${version2}"></script>
494
- <script src="${prePath}stage/protyle/js/lute/lute.min.js?${version2}"></script>
495
- <script>
496
- window.siyuan = {
497
- config: {
498
- appearance: {
499
- mode: isNightTime()?1:0,//\u4E3B\u9898 \u660E\u4EAE=0 \u6697\u9ED1=1
500
- codeBlockThemeDark: "base16/dracula",
501
- codeBlockThemeLight: "github",
502
- },
503
- editor: {
504
- codeLineWrap: true,
505
- codeLigatures: false,
506
- plantUMLServePath: "https://www.plantuml.com/plantuml/svg/~1",
507
- codeSyntaxHighlightLineNum: true,
508
- katexMacros: JSON.stringify({}),
509
- },
510
- },
511
- languages: { copy: "\u590D\u5236" },
512
- };
513
- const cdn = "${prePath}stage/protyle";
514
- const previewElement = document.getElementById("preview");
515
-
516
- Protyle.highlightRender(previewElement, cdn);
517
- Protyle.mathRender(previewElement, cdn, false);
518
- Protyle.mermaidRender(previewElement, cdn);
519
- Protyle.flowchartRender(previewElement, cdn);
520
- Protyle.graphvizRender(previewElement, cdn);
521
- Protyle.chartRender(previewElement, cdn);
522
- Protyle.mindmapRender(previewElement, cdn);
523
- Protyle.abcRender(previewElement, cdn);
524
- Protyle.htmlRender(previewElement);
525
- Protyle.plantumlRender(previewElement, cdn);
526
- document.querySelectorAll(".protyle-action__copy").forEach((item) => {
527
- item.addEventListener("click", (event) => {
528
- navigator.clipboard.writeText(
529
- item.parentElement.nextElementSibling.textContent.trimEnd(),
530
- );
531
- event.preventDefault();
532
- event.stopPropagation();
533
- });
534
- });
535
- </script>
536
- ${config?.embedCode?.afterBody ?? ""}
537
- </body>
538
- </html>`;
539
- }
540
-
541
- // src/util/escaping.ts
542
- function escaping(s) {
543
- return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos");
544
- }
545
- function unescaping(s) {
546
- return s.replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&apos;/g, "'").replace(/&#(\d+);/g, (_sub, code) => {
547
- return String.fromCharCode(Number(code));
548
- });
549
- }
550
-
551
- // src/components/data_promise/index.ts
552
- import { customRef, nextTick, watch as watch2, watchEffect } from "vue";
553
- var PromiseObj = class {
554
- constructor() {
555
- __publicField(this, "pending", false);
556
- __publicField(this, "fulfilled", false);
557
- __publicField(this, "rejected", false);
558
- __publicField(this, "data", {});
559
- __publicField(this, "error", {});
560
- __publicField(this, "_p", Promise.resolve());
561
- }
562
- setP(p) {
563
- this._p = p;
535
+ setP(p) {
536
+ this._p = p;
564
537
  }
565
538
  equalP(p) {
566
539
  return this._p === p;
@@ -727,606 +700,100 @@ var vApi = new Proxy(
727
700
  }
728
701
  );
729
702
 
730
- // src/core/render.ts
731
- async function renderHTML(sy, renderInstance = getRender()) {
732
- if (sy === void 0) return "";
733
- const renderObj = {
734
- ...renderInstance,
735
- nodeStack: [
736
- /** 避免让所有的 renderInstance.nodeStack 是同一个对象 ,所以这里复制一个新的 */
737
- ...renderInstance.nodeStack
738
- ]
739
- };
740
- if (renderInstance.nodeStack.find(
741
- (node) => node.ID && sy.ID && node.ID === sy.ID
742
- )) {
743
- return warnDiv(
744
- "\u5FAA\u73AF\u5F15\u7528",
745
- [...renderInstance.nodeStack, sy].map((el) => el.ID)
746
- );
703
+ // src/core/siyuan_type.ts
704
+ function DB_block_path(p) {
705
+ return `data/${p.box}${p.path}`;
706
+ }
707
+
708
+ // src/core/cache.ts
709
+ var cache = true;
710
+ function setCache(b) {
711
+ cache = b;
712
+ }
713
+ var sqlCacheMap = /* @__PURE__ */ new Map();
714
+ var hpathCacheMap = /* @__PURE__ */ new Map();
715
+ var idCacheMap = /* @__PURE__ */ new Map();
716
+ var blockCacheMap = /* @__PURE__ */ new Map();
717
+ async function query_sql(stmt) {
718
+ if (cache && sqlCacheMap.has(stmt)) {
719
+ return sqlCacheMap.get(stmt);
747
720
  }
748
- if (renderObj[sy.Type] === void 0) {
749
- return warnDiv(
750
- `\u6CA1\u6709\u627E\u5230\u5BF9\u5E94\u7684\u6E32\u67D3\u65B9\u6CD5 ${sy.Type} ${renderObj.nodeStack[0].Properties?.title}`
751
- );
752
- } else {
753
- renderObj.nodeStack.push(sy);
754
- if (sy.ID && renderInstance.nodeStack[0]?.ID) {
755
- const targetDoc = await storeDep.getDocByChildID(sy.ID);
756
- const currentDoc = renderInstance.nodeStack[0];
757
- if (targetDoc?.ID !== void 0 && targetDoc.ID !== currentDoc.ID && currentDoc.ID) {
758
- renderObj.refs.add(targetDoc.ID);
759
- }
760
- }
761
- const r = await renderObj[sy.Type](sy);
762
- renderObj.nodeStack.pop();
763
- return r;
721
+ const res = await API.query_sql({
722
+ stmt
723
+ });
724
+ if (cache) {
725
+ sqlCacheMap.set(stmt, res);
764
726
  }
727
+ return res;
765
728
  }
766
- function warnDiv(msg, ...args) {
767
- warn(msg, ...args);
768
- return `<div class="ft__smaller ft__secondary b3-form__space--small">${msg}</div>`;
729
+ async function get_doc_by_hpath(hpath) {
730
+ if (cache) {
731
+ const c = hpathCacheMap.get(hpath);
732
+ if (c) return c;
733
+ }
734
+ const docBlock = (await query_sql(
735
+ `SELECT * FROM blocks WHERE hpath = '${hpath}'`
736
+ ))[0];
737
+ if (docBlock === void 0) throw new Error(`not doc by:${hpath}`);
738
+ const res = await get_doc_by_SyPath(DB_block_path(docBlock));
739
+ if (cache) {
740
+ hpathCacheMap.set(hpath, res);
741
+ }
742
+ return res;
769
743
  }
770
- function isRenderCode(sy) {
771
- const mark = atob(
772
- sy.CodeBlockInfo ?? sy.Children?.find((el) => el.Type === "NodeCodeBlockFenceInfoMarker")?.CodeBlockInfo ?? ""
744
+ async function get_doc_by_SyPath(path) {
745
+ const res = parentRef(
746
+ await API.file_getFile({
747
+ path
748
+ })
773
749
  );
774
- return [
775
- [
776
- "mindmap",
777
- "mermaid",
778
- "echarts",
779
- "abc",
780
- "graphviz",
781
- "flowchart",
782
- "plantuml"
783
- ].includes(mark),
784
- mark
785
- ];
786
- }
787
- var html = String.raw;
788
- async function childRender(sy, renderInstance) {
789
- let h = "";
790
- for await (const el of sy?.Children ?? []) {
791
- h += await renderHTML(el, renderInstance);
750
+ if (cache) {
751
+ idCache(res);
792
752
  }
793
- return h;
753
+ return res;
794
754
  }
795
- function strAttr(sy, config = {}) {
796
- if (config?.subtype_class === void 0) {
797
- config.subtype_class = (() => {
798
- const typ_subtype = sy.ListData?.Typ === 1 ? (
799
- /** 有序列表 */
800
- "o"
801
- ) : sy.ListData?.Typ === 3 ? (
802
- /** 任务列表 */
803
- "t"
804
- ) : (
805
- /** 无序列表 */
806
- "u"
807
- );
808
- if (sy.Type === "NodeDocument") return "h1";
809
- else if (sy.Type === "NodeHeading") return `h${sy.HeadingLevel}`;
810
- else if (sy.Type === "NodeList") return [typ_subtype, "list"];
811
- else if (sy.Type === "NodeListItem") return [typ_subtype, "li"];
812
- else if (sy.Type === "NodeParagraph") return ["", "p"];
813
- else if (sy.Type === "NodeImage") return ["", "img"];
814
- else if (sy.Type === "NodeBlockquote") return ["", "bq"];
815
- else if (sy.Type === "NodeSuperBlock") return ["", "sb"];
816
- else if (sy.Type === "NodeCodeBlock") {
817
- const [yes, mark] = isRenderCode(sy);
818
- if (yes) {
819
- return [mark, "render-node"];
820
- } else {
821
- return ["", "code-block"];
822
- }
823
- } else if (sy.Type === "NodeTable") return ["", "table"];
824
- else if (sy.Type === "NodeThematicBreak") return ["", "hr"];
825
- else if (sy.Type === "NodeMathBlock") return ["math", "render-node"];
826
- else if (sy.Type === "NodeIFrame") return ["", "iframe"];
827
- else if (sy.Type === "NodeVideo") return ["", "iframe"];
828
- else return "";
829
- })();
830
- }
831
- const attrObj = {};
832
- function addAttr(key, value) {
833
- attrObj[key] = value;
755
+ async function get_block_by_id(id) {
756
+ if (cache) {
757
+ const block = blockCacheMap.get(id);
758
+ if (block) return block;
834
759
  }
835
- if (sy.ID) {
836
- addAttr("id", sy.ID);
837
- addAttr("data-node-id", sy.ID);
760
+ const blocks = await query_sql(`
761
+ SELECT * from blocks
762
+ WHERE id = '${id}'
763
+ `);
764
+ if (blocks.length === 0) {
765
+ return;
838
766
  }
839
- if (sy?.TextMarkType === "tag") {
840
- addAttr(`data-type`, sy.TextMarkType ?? "");
841
- } else {
842
- addAttr(`data-type`, config?.data_type ?? sy.Type);
767
+ if (cache) blockCacheMap.set(id, blocks[0]);
768
+ return blocks[0];
769
+ }
770
+ async function get_node_by_id(id) {
771
+ if (id === void 0) return;
772
+ if (cache) {
773
+ const node = idCacheMap.get(id);
774
+ if (node) return node;
843
775
  }
844
- if (sy.Properties?.updated) addAttr("updated", sy.Properties.updated);
845
- if (config?.subtype_class) {
846
- if (typeof config.subtype_class === "string") {
847
- addAttr("data-subtype", config.subtype_class);
848
- addAttr("class", config.subtype_class);
849
- } else {
850
- if (config.subtype_class[0] !== "")
851
- addAttr("data-subtype", config.subtype_class[0]);
852
- if (config.subtype_class[1] !== "")
853
- addAttr("class", config.subtype_class[1]);
776
+ const doc = await get_doc_by_child_id(id);
777
+ if (doc === void 0) return;
778
+ return findNode(doc);
779
+ function findNode(node) {
780
+ if (node.ID === id) return node;
781
+ if (node.Children) {
782
+ return node.Children.find((child) => findNode(child));
854
783
  }
855
784
  }
856
- if (sy.Properties) {
857
- Object.entries(sy.Properties).forEach(([k, v]) => addAttr(k, v));
858
- }
859
- if (sy.ListData?.Marker) addAttr("data-marker", atob(sy.ListData.Marker));
860
- if (
861
- /** 任务列表 */
862
- sy.ListData?.Typ === 3 && /** 该项被选中 */
863
- sy.Children?.find(
864
- (el) => el.Type === "NodeTaskListItemMarker"
865
- )?.TaskListItemChecked
866
- ) {
867
- attrObj["class"] = (attrObj["class"] ?? "") + " protyle-task--done ";
785
+ }
786
+ async function allDocBlock_by_bookId(id) {
787
+ const res = await query_sql(`
788
+ SELECT * from blocks
789
+ WHERE box = '${id}'
790
+ AND type = 'd'
791
+ limit 150000 OFFSET 0
792
+ `);
793
+ if (cache) {
794
+ res.forEach((block) => blockCacheMap.set(block.id, block));
868
795
  }
869
- delete attrObj["fold"];
870
- if (sy.Type === "NodeDocument") delete attrObj["title"];
871
- return Object.entries(attrObj).map(([k, v]) => `${k}="${v}"`).join(" ");
872
- }
873
- var _emptyString = async (_sy) => "";
874
- var _dataString = async (sy) => sy.Data ?? "";
875
- var getRender = () => {
876
- return {
877
- ...render,
878
- nodeStack: [],
879
- refs: /* @__PURE__ */ new Set()
880
- };
881
- };
882
- var render = {
883
- nodeStack: [],
884
- refs: /* @__PURE__ */ new Set(),
885
- async getTopPathPrefix() {
886
- const sy = this.nodeStack[0];
887
- let prefix = ".";
888
- if (sy.Type === "NodeDocument" && sy.ID) {
889
- const path = await storeDep.getDocPathBySY(sy);
890
- if (path) {
891
- const level = path.split("/").length - 3;
892
- for (let i = 0; i < level; i++) {
893
- prefix += "/..";
894
- }
895
- }
896
- return prefix;
897
- } else {
898
- console.log("\u672A\u5B9A\u4E49\u9876\u5C42\u5143\u7D20\u975E NodeDocument \u65F6\u7684\u5904\u7406\u65B9\u5F0F", sy);
899
- return "";
900
- }
901
- },
902
- async NodeDocument(sy) {
903
- let html2 = "";
904
- if (
905
- /** 只有顶层的文档块才渲染题图 */
906
- this.nodeStack.length === 1
907
- ) {
908
- html2 += `<div style="min-height: 150px;" ${strAttr(sy)}>`;
909
- if (sy.Properties?.["title-img"]) {
910
- html2 += `<div class="protyle-background__img" style="margin-bottom: 30px;position: relative;height: 16vh;${sy.Properties?.["title-img"].replace(
911
- /assets/,
912
- // 修改为相对路径
913
- await this.getTopPathPrefix() + "/assets"
914
- )}"/>${sy.Properties?.["icon"] ? `<div style="position: absolute;bottom:-10px;left:15px;height: 80px;width: 80px;transition: var(--b3-transition);cursor: pointer;font-size: 68px;line-height: 80px;text-align: center;font-family: var(--b3-font-family-emoji);margin-right: 16px;"> &#x${sy.Properties?.["icon"]} </div>` : ""}</div>`;
915
- }
916
- html2 += "</div>";
917
- html2 += `<h1 ${strAttr(sy)} data-type="NodeHeading" class="h1">${sy.Properties?.title}</h1>`;
918
- }
919
- html2 += await childRender(sy, this);
920
- return html2;
921
- },
922
- async NodeHeading(sy) {
923
- const tagName = `h${sy.HeadingLevel}`;
924
- let html2 = `<${tagName} ${strAttr(sy)}>${await childRender(
925
- sy,
926
- this
927
- )}</${tagName}>`;
928
- const parentNode = this.nodeStack[
929
- this.nodeStack.length - 2
930
- /** 最后一个元素是 sy本身(NodeHeading)还得要往前一个,所以是2 */
931
- ];
932
- if (parentNode?.Type === "NodeBlockQueryEmbedScript") {
933
- let afterFlag = false;
934
- for (const node of sy.Parent.Children ?? []) {
935
- if (node === sy) {
936
- afterFlag = true;
937
- } else if (node !== sy && node.Type === "NodeHeading") {
938
- afterFlag = false;
939
- } else if (afterFlag) {
940
- html2 += "\n" + await renderHTML(node, this);
941
- }
942
- }
943
- }
944
- return html2;
945
- },
946
- NodeText: _dataString,
947
- async NodeList(sy) {
948
- return html`<div ${strAttr(sy)}>${await childRender(sy, this)}</div>`;
949
- },
950
- async NodeListItem(sy) {
951
- return html`<div ${await strAttr(sy)}>
952
- <div class="protyle-action">
953
- ${sy.ListData?.Typ === 1 ? (
954
- /** 有序列表 */
955
- atob(sy.ListData?.Marker ?? "")
956
- ) : sy.ListData?.Typ === 3 ? (
957
- /** 任务列表 */
958
- `<svg><use xlink:href="#${sy.Children?.find((el) => el.Type === "NodeTaskListItemMarker")?.TaskListItemChecked ? "iconCheck" : "iconUncheck"}"></use></svg>`
959
- ) : (
960
- /** 无序列表 */
961
- `<svg><use xlink:href="#iconDot"></use></svg>`
962
- )}
963
- </div>
964
- ${await childRender(sy, this)}
965
- </div>`;
966
- },
967
- NodeTaskListItemMarker: _emptyString,
968
- async NodeParagraph(sy) {
969
- return `<div ${strAttr(sy)}><div spellcheck="false">${await childRender(
970
- sy,
971
- this
972
- )}</div></div>`;
973
- },
974
- async NodeTextMark(sy) {
975
- const that = this;
976
- let r = "";
977
- for (const type of (sy.TextMarkType?.split(" ") ?? []).reverse()) {
978
- if (r === "") {
979
- r = await TextMarkRender(sy, type, sy.TextMarkTextContent ?? "");
980
- } else {
981
- r = await TextMarkRender(sy, type, r);
982
- }
983
- }
984
- return r;
985
- async function TextMarkRender(sy2, type, content) {
986
- if (type === "inline-math") {
987
- return `<span data-type="inline-math" data-subtype="math" data-content="${sy2.TextMarkInlineMathContent}" class="render-node"></span>`;
988
- } else if (type === "inline-memo") {
989
- return `${content}<sup>\uFF08${sy2.TextMarkInlineMemoContent}\uFF09</sup>`;
990
- } else if (type === "block-ref") {
991
- let href = "";
992
- if (sy2.TextMarkBlockRefID) {
993
- const doc = await storeDep.getDocByChildID(sy2.TextMarkBlockRefID);
994
- if (doc?.ID) {
995
- href = `${await that.getTopPathPrefix()}${await storeDep.getHPathByID_Node(
996
- doc
997
- )}.html#${sy2.TextMarkBlockRefID}`;
998
- that.refs.add(doc.ID);
999
- } else {
1000
- warn(`\u672A\u67E5\u627E\u5230${sy2.ID}\u6240\u6307\u5411\u7684\u6587\u6863\u8282\u70B9 ${sy2.TextMarkBlockRefID}`);
1001
- }
1002
- } else {
1003
- warn(`${sy2.ID} \u5757\u5F15\u7528\u6CA1\u6709\u8BBE\u5B9A ref id`);
1004
- }
1005
- return `<span data-type="${sy2.TextMarkType}" data-subtype="${/** "s" */
1006
- sy2.TextMarkBlockRefSubtype}" data-id="${/** 被引用块的id */
1007
- sy2.TextMarkBlockRefID}"><a href="${href}">${content}</a></span>`;
1008
- } else if (type === "a") {
1009
- let href = sy2.TextMarkAHref;
1010
- if (href?.startsWith("assets/")) {
1011
- href = `${await that.getTopPathPrefix()}/${href}`;
1012
- }
1013
- return `<a href="${href}">${content}</a>`;
1014
- } else if (`strong em u s mark sup sub kbd tag code strong code text`.includes(
1015
- type ?? ""
1016
- )) {
1017
- return `<span ${strAttr(sy2, { data_type: type })}>${content}</span>`;
1018
- } else {
1019
- return warnDiv(
1020
- `\u6CA1\u6709\u627E\u5230\u5BF9\u5E94\u7684\u6E32\u67D3\u5668 ${sy2.TextMarkType} ${that.nodeStack[0].Properties?.title}`
1021
- );
1022
- }
1023
- }
1024
- },
1025
- async NodeImage(sy) {
1026
- let link = "";
1027
- const LinkDest = sy.Children?.filter((c) => c.Type === "NodeLinkDest");
1028
- if (LinkDest?.length === 1) {
1029
- link = await renderHTML(LinkDest[0], this);
1030
- } else if (LinkDest?.length && LinkDest.length > 1) {
1031
- warn("NodeImage \u5B58\u5728\u591A\u4E2A LinkDest", sy);
1032
- }
1033
- let title = "";
1034
- const LinkTitle = sy.Children?.filter((c) => c.Type === "NodeLinkTitle");
1035
- if (LinkTitle?.length === 1) {
1036
- title = await renderHTML(LinkTitle[0], this);
1037
- } else if (LinkTitle?.length && LinkTitle.length > 1) {
1038
- warn("NodeImage \u5B58\u5728\u591A\u4E2A LinkTitle", sy);
1039
- }
1040
- return `<span ${await strAttr(sy)} style="${sy.Properties?.["parent-style"] ?? ""}">
1041
- <img
1042
- src="${link}"
1043
- data-src="${link}"
1044
- title="${title}"
1045
- style="${sy.Properties?.style ?? ""}"
1046
- loading="lazy"
1047
- />
1048
- <span class="protyle-action__title">${title}</span></span>`;
1049
- },
1050
- async NodeLinkDest(sy) {
1051
- if (/^(?:[a-z]+:)?\/\/|^(?:\/)/.test(sy.Data ?? "")) {
1052
- return sy.Data ?? "";
1053
- }
1054
- return `${await this.getTopPathPrefix()}/${sy.Data}`;
1055
- },
1056
- NodeLinkTitle: _dataString,
1057
- NodeKramdownSpanIAL: _emptyString,
1058
- async NodeSuperBlock(sy) {
1059
- return `<div ${strAttr(sy)} data-sb-layout="${childDateByType(
1060
- sy,
1061
- "NodeSuperBlockLayoutMarker"
1062
- )}">${await childRender(sy, this)}</div>`;
1063
- },
1064
- NodeSuperBlockOpenMarker: _emptyString,
1065
- NodeSuperBlockCloseMarker: _emptyString,
1066
- NodeSuperBlockLayoutMarker: _emptyString,
1067
- async NodeBlockQueryEmbed(sy) {
1068
- return `<div ${strAttr(sy)} data-type="NodeBlockquote" class="bq">${await childRender(sy, this)}</div>`;
1069
- },
1070
- NodeOpenBrace: _emptyString,
1071
- NodeCloseBrace: _emptyString,
1072
- async NodeBlockQueryEmbedScript(sy) {
1073
- const sql = sy.Data;
1074
- if (!sql) {
1075
- console.log("no sql", sy);
1076
- return html`<pre>${sql}</pre>`;
1077
- }
1078
- let htmlStr = "";
1079
- const blocks = await API.query_sql({
1080
- stmt: (
1081
- /** sql 被思源转义了,类似 :SELECT * FROM blocks WHERE id = &#39;20201227174241-nxny1tq&#39;
1082
- 所以这里将它转义回来
1083
- TODO 当用户确实使用了包含转义的字符串时,这个实现是错误的 */
1084
- unescaping(
1085
- sql
1086
- ).replace(
1087
- /** 我不理解lute为什么这样实现 https://github.com/88250/lute/blob/HEAD/editor/const.go#L38
1088
- * https://ld246.com/article/1696750832289
1089
- */
1090
- /_esc_newline_/g,
1091
- "\n"
1092
- )
1093
- )
1094
- }).catch((err) => {
1095
- throw new Error(
1096
- `sql error: ${err.message}
1097
- rawSql:${sql}
1098
- unescapingSql:${unescaping(
1099
- sql
1100
- )}`
1101
- );
1102
- });
1103
- for (const block of blocks) {
1104
- const node = await storeDep.getNodeByID(block.id);
1105
- if (node === void 0) {
1106
- return warnDiv("\u672A\u627E\u5230\u6B64\u5757\uFF0C\u53EF\u80FD\u4E3A\u8DE8\u7B14\u8BB0\u5F15\u7528", block.id, sql);
1107
- }
1108
- htmlStr += await renderHTML(node, this);
1109
- }
1110
- return htmlStr;
1111
- },
1112
- async NodeBlockquote(sy) {
1113
- return html`<div ${strAttr(sy)}>${await childRender(sy, this)}</div>`;
1114
- },
1115
- NodeBlockquoteMarker: _emptyString,
1116
- NodeCodeBlock: async (sy) => {
1117
- const [yes, _] = isRenderCode(sy);
1118
- if (yes) {
1119
- return `<div ${strAttr(sy)} data-content="${escaping(
1120
- sy.Children?.find((el) => el.Type === "NodeCodeBlockCode")?.Data ?? ""
1121
- )}">
1122
- <div spin="1"></div>
1123
- <div class="protyle-attr" contenteditable="false"></div>
1124
- </div>`;
1125
- }
1126
- return `<div ${strAttr(sy)}>
1127
- <div class="protyle-action">
1128
- <span class="protyle-action--first protyle-action__language">${await renderHTML(
1129
- sy.Children?.find(
1130
- (el) => el.Type === "NodeCodeBlockFenceInfoMarker"
1131
- ),
1132
- void 0
1133
- )}</span>
1134
- <span class="fn__flex-1"></span><span class="protyle-icon protyle-icon--only protyle-action__copy"><svg><use xlink:href="#iconCopy"></use></svg></span>
1135
- </div>
1136
- ${await renderHTML(
1137
- sy.Children?.find((el) => el.Type === "NodeCodeBlockCode"),
1138
- void 0
1139
- )}
1140
- </div>`;
1141
- },
1142
- NodeCodeBlockFenceInfoMarker: async (sy) => atob(sy.CodeBlockInfo ?? ""),
1143
- NodeCodeBlockCode: async (sy) => `<div class="hljs" spellcheck="false">${sy.Data}</div>`,
1144
- NodeCodeBlockFenceOpenMarker: _emptyString,
1145
- NodeCodeBlockFenceCloseMarker: _emptyString,
1146
- async NodeTable(sy) {
1147
- return `<div ${strAttr(sy)}>
1148
- <div>
1149
- <table spellcheck="false">
1150
- <colgroup>
1151
- ${sy.TableAligns?.map(() => "<col />").join("")}
1152
- </colgroup>
1153
- ${await renderHTML(
1154
- sy.Children?.find((el) => el.Type === "NodeTableHead"),
1155
- this
1156
- )}
1157
- <tbody>
1158
- ${(await Promise.all(
1159
- sy.Children?.filter((el) => el.Type === "NodeTableRow").map(
1160
- (el) => renderHTML(el, this)
1161
- ) ?? []
1162
- )).join("\n")}
1163
- </tbody>
1164
- </table>
1165
- </div>
1166
- </div>`;
1167
- },
1168
- async NodeTableHead(sy) {
1169
- return `<${sy.Data}>${await childRender(sy, this)}</${sy.Data}>`;
1170
- },
1171
- async NodeTableRow(sy) {
1172
- return `<tr>${await childRender(sy, this)}</tr>`;
1173
- },
1174
- async NodeTableCell(sy) {
1175
- return `<td>${await childRender(sy, this)}</td>`;
1176
- },
1177
- NodeHTMLBlock: async (sy) => `<div ${strAttr(sy)}>${sy.Data}</div>`,
1178
- NodeThematicBreak: async (sy) => `<div ${strAttr(sy)}><div></div></div>`,
1179
- NodeMathBlock: async (sy) => `<div ${strAttr(
1180
- sy
1181
- )} data-content="${childDateByType(sy, "NodeMathBlockContent")}">
1182
- <div spin="1"></div>
1183
- </div>`,
1184
- NodeMathBlockOpenMarker: _emptyString,
1185
- NodeMathBlockCloseMarker: _emptyString,
1186
- async NodeIFrame(sy) {
1187
- return ` <div ${strAttr(sy)}>
1188
- <div class="iframe-content">
1189
- ${/** 资源总是被复制到顶层目录,所以直接跳到顶层即可 */
1190
- /** TODO 应该有一个统一处理资源的方案 */
1191
- sy.Data?.replace(
1192
- /src="assets\//,
1193
- `src="${await this.getTopPathPrefix()}/assets/`
1194
- )}
1195
- </div>
1196
- </div>`;
1197
- },
1198
- async NodeVideo(sy) {
1199
- return await this.NodeIFrame(sy);
1200
- },
1201
- async NodeAudio(sy) {
1202
- return await this.NodeIFrame(sy);
1203
- },
1204
- /** 虚拟链接 */
1205
- NodeHeadingC8hMarker: _emptyString,
1206
- async NodeSoftBreak(_sy) {
1207
- return "\u200B";
1208
- },
1209
- async NodeBr(sy) {
1210
- return `<${sy.Data}>`;
1211
- },
1212
- async NodeWidget(sy) {
1213
- return `<div ${strAttr(
1214
- sy
1215
- )}><img src="${await this.getTopPathPrefix()}/assets/widget/${sy.ID}.jpg"/></div>`;
1216
- },
1217
- async NodeBackslash(sy) {
1218
- if (sy.Data === void 0 || sy.Data === "span") {
1219
- return `${await childRender(sy, this)}`;
1220
- } else {
1221
- return warnDiv(
1222
- `\u672A\u5B9A\u4E49\u7684 NodeBackslash \u5904\u7406 ${sy.Data}`,
1223
- this.nodeStack[0].Properties?.title
1224
- );
1225
- }
1226
- },
1227
- NodeBackslashContent: _dataString
1228
- };
1229
- function childDateByType(sy, type) {
1230
- return sy.Children?.find((el) => el.Type === type)?.Data;
1231
- }
1232
- function warn(...arg) {
1233
- console.warn("\n", ...arg);
1234
- }
1235
-
1236
- // src/core/siyuan_type.ts
1237
- function DB_block_path(p) {
1238
- return `data/${p.box}${p.path}`;
1239
- }
1240
-
1241
- // src/core/cache.ts
1242
- var cache = true;
1243
- function setCache(b) {
1244
- cache = b;
1245
- }
1246
- var sqlCacheMap = /* @__PURE__ */ new Map();
1247
- var hpathCacheMap = /* @__PURE__ */ new Map();
1248
- var idCacheMap = /* @__PURE__ */ new Map();
1249
- var blockCacheMap = /* @__PURE__ */ new Map();
1250
- async function query_sql(stmt) {
1251
- if (cache && sqlCacheMap.has(stmt)) {
1252
- return sqlCacheMap.get(stmt);
1253
- }
1254
- const res = await API.query_sql({
1255
- stmt
1256
- });
1257
- if (cache) {
1258
- sqlCacheMap.set(stmt, res);
1259
- }
1260
- return res;
1261
- }
1262
- async function get_doc_by_hpath(hpath) {
1263
- if (cache) {
1264
- const c = hpathCacheMap.get(hpath);
1265
- if (c) return c;
1266
- }
1267
- const docBlock = (await query_sql(
1268
- `SELECT * FROM blocks WHERE hpath = '${hpath}'`
1269
- ))[0];
1270
- if (docBlock === void 0) throw new Error(`not doc by:${hpath}`);
1271
- const res = await get_doc_by_SyPath(DB_block_path(docBlock));
1272
- if (cache) {
1273
- hpathCacheMap.set(hpath, res);
1274
- }
1275
- return res;
1276
- }
1277
- async function get_doc_by_SyPath(path) {
1278
- const res = parentRef(
1279
- await API.file_getFile({
1280
- path
1281
- })
1282
- );
1283
- if (cache) {
1284
- idCache(res);
1285
- }
1286
- return res;
1287
- }
1288
- async function get_block_by_id(id) {
1289
- if (cache) {
1290
- const block = blockCacheMap.get(id);
1291
- if (block) return block;
1292
- }
1293
- const blocks = await query_sql(`
1294
- SELECT * from blocks
1295
- WHERE id = '${id}'
1296
- `);
1297
- if (blocks.length === 0) {
1298
- return;
1299
- }
1300
- if (cache) blockCacheMap.set(id, blocks[0]);
1301
- return blocks[0];
1302
- }
1303
- async function get_node_by_id(id) {
1304
- if (id === void 0) return;
1305
- if (cache) {
1306
- const node = idCacheMap.get(id);
1307
- if (node) return node;
1308
- }
1309
- const doc = await get_doc_by_child_id(id);
1310
- if (doc === void 0) return;
1311
- return findNode(doc);
1312
- function findNode(node) {
1313
- if (node.ID === id) return node;
1314
- if (node.Children) {
1315
- return node.Children.find((child) => findNode(child));
1316
- }
1317
- }
1318
- }
1319
- async function allDocBlock_by_bookId(id) {
1320
- const res = await query_sql(`
1321
- SELECT * from blocks
1322
- WHERE box = '${id}'
1323
- AND type = 'd'
1324
- limit 150000 OFFSET 0
1325
- `);
1326
- if (cache) {
1327
- res.forEach((block) => blockCacheMap.set(block.id, block));
1328
- }
1329
- return res;
796
+ return res;
1330
797
  }
1331
798
  async function get_doc_by_child_id(id) {
1332
799
  if (cache) {
@@ -1364,7 +831,7 @@ function parentRef(sy) {
1364
831
  }
1365
832
 
1366
833
  // src/core/genRssXml.ts
1367
- async function generateRSSXML(path, renderInstance, config) {
834
+ async function generateRSSXML(path, renderInstance, config, getHPathByID_Node) {
1368
835
  const refNode = (await Promise.all([...renderInstance.refs.values()].map(get_node_by_id))).filter((el) => el);
1369
836
  return `<?xml version="1.0" encoding="UTF-8"?>
1370
837
  <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
@@ -1378,7 +845,7 @@ async function generateRSSXML(path, renderInstance, config) {
1378
845
  refNode.map(
1379
846
  async (node) => `<item>
1380
847
  <title>${node?.Properties?.title}</title>
1381
- <link>${config.sitemap.sitePrefix}${node?.ID ? await storeDep.getHPathByID_Node(node?.ID) + ".html" : ""}</link>
848
+ <link>${config.sitemap.sitePrefix}${node?.ID ? await getHPathByID_Node(node?.ID) + ".html" : ""}</link>
1382
849
  <description>${""}</description>
1383
850
  <pubDate>${node?.Properties?.updated ? new Date(
1384
851
  node.Properties.updated.replace(
@@ -1414,181 +881,1790 @@ function sitemap_xml(docArr, config) {
1414
881
  </urlset>`;
1415
882
  }
1416
883
 
1417
- // src/core/build.ts
1418
- async function build(config, effect, otherConfig) {
1419
- const _renderHTML = otherConfig?.renderHtmlFn ?? renderHTML;
1420
- const book = config.notebook;
1421
- const docTree = {};
1422
- const skipBuilds = useSkipBuilds();
1423
- let oldPercentage = 0;
1424
- let total = 0;
1425
- function processPercentage(percentage) {
1426
- total += oldPercentage;
1427
- return (process3) => {
1428
- oldPercentage = process3 * percentage;
1429
- effect.percentage((total + oldPercentage) * 100);
1430
- };
884
+ // src/core/seo.ts
885
+ function extractDateFromId(id) {
886
+ if (!id || id.length < 14) return (/* @__PURE__ */ new Date()).toISOString();
887
+ try {
888
+ const datePart = id.slice(0, 14);
889
+ if (datePart.length === 14) {
890
+ const year = datePart.slice(0, 4);
891
+ const month = datePart.slice(4, 6);
892
+ const day = datePart.slice(6, 8);
893
+ const hour = datePart.slice(8, 10);
894
+ const minute = datePart.slice(10, 12);
895
+ const second = datePart.slice(12, 14);
896
+ return `${year}-${month}-${day}T${hour}:${minute}:${second}Z`;
897
+ }
898
+ } catch {
899
+ }
900
+ return (/* @__PURE__ */ new Date()).toISOString();
901
+ }
902
+ function formatDate(dateString) {
903
+ if (!dateString) return (/* @__PURE__ */ new Date()).toISOString();
904
+ try {
905
+ if (dateString.length === 14) {
906
+ const year = dateString.slice(0, 4);
907
+ const month = dateString.slice(4, 6);
908
+ const day = dateString.slice(6, 8);
909
+ const hour = dateString.slice(8, 10);
910
+ const minute = dateString.slice(10, 12);
911
+ const second = dateString.slice(12, 14);
912
+ return `${year}-${month}-${day}T${hour}:${minute}:${second}Z`;
913
+ }
914
+ const timestamp = parseInt(dateString);
915
+ if (!isNaN(timestamp)) {
916
+ return new Date(timestamp).toISOString();
917
+ }
918
+ return new Date(dateString).toISOString();
919
+ } catch {
920
+ return (/* @__PURE__ */ new Date()).toISOString();
921
+ }
922
+ }
923
+ var TFIDFKeywordExtractor = class {
924
+ constructor() {
925
+ __publicField(this, "stopWords", /* @__PURE__ */ new Set([
926
+ // 中文停用词
927
+ "\u7684",
928
+ "\u4E86",
929
+ "\u5728",
930
+ "\u662F",
931
+ "\u6211",
932
+ "\u6709",
933
+ "\u548C",
934
+ "\u5C31",
935
+ "\u4E0D",
936
+ "\u4EBA",
937
+ "\u90FD",
938
+ "\u4E00",
939
+ "\u4E2A",
940
+ "\u4E0A",
941
+ "\u4E5F",
942
+ "\u5F88",
943
+ "\u5230",
944
+ "\u8BF4",
945
+ "\u8981",
946
+ "\u53BB",
947
+ "\u4F60",
948
+ "\u4F1A",
949
+ "\u7740",
950
+ "\u6CA1\u6709",
951
+ "\u770B",
952
+ "\u597D",
953
+ "\u81EA\u5DF1",
954
+ "\u8FD9",
955
+ "\u90A3",
956
+ "\u73B0\u5728",
957
+ "\u53EF\u4EE5",
958
+ "\u4F46\u662F",
959
+ "\u8FD8\u662F",
960
+ "\u56E0\u4E3A",
961
+ "\u4EC0\u4E48",
962
+ "\u5982\u679C",
963
+ "\u6240\u4EE5",
964
+ "\u5BF9\u4E8E",
965
+ "\u5173\u4E8E",
966
+ "\u901A\u8FC7",
967
+ "\u8FDB\u884C",
968
+ "\u57FA\u4E8E",
969
+ "\u4EE5\u53CA",
970
+ "\u6216\u8005",
971
+ "\u800C\u4E14",
972
+ "\u7136\u540E",
973
+ "\u53EA\u662F",
974
+ "\u5DF2\u7ECF",
975
+ "\u6B63\u5728",
976
+ "\u5E94\u8BE5",
977
+ "\u80FD\u591F",
978
+ "\u9700\u8981",
979
+ "\u53EF\u80FD",
980
+ "\u4E00\u5B9A",
981
+ "\u8FD9\u6837",
982
+ "\u90A3\u6837",
983
+ "\u600E\u4E48",
984
+ "\u4E3A\u4EC0\u4E48",
985
+ "\u54EA\u91CC",
986
+ "\u54EA\u4E2A",
987
+ "\u591A\u5C11",
988
+ "\u51E0\u4E2A",
989
+ "\u4EC0\u4E48",
990
+ "\u600E\u4E48",
991
+ "\u5982\u4F55",
992
+ "\u4E3A\u4EC0\u4E48",
993
+ // 英文停用词
994
+ "the",
995
+ "a",
996
+ "an",
997
+ "and",
998
+ "or",
999
+ "but",
1000
+ "in",
1001
+ "on",
1002
+ "at",
1003
+ "to",
1004
+ "for",
1005
+ "of",
1006
+ "with",
1007
+ "by",
1008
+ "is",
1009
+ "are",
1010
+ "was",
1011
+ "were",
1012
+ "be",
1013
+ "been",
1014
+ "being",
1015
+ "have",
1016
+ "has",
1017
+ "had",
1018
+ "do",
1019
+ "does",
1020
+ "did",
1021
+ "will",
1022
+ "would",
1023
+ "could",
1024
+ "should",
1025
+ "may",
1026
+ "might",
1027
+ "must",
1028
+ "can",
1029
+ "this",
1030
+ "that",
1031
+ "these",
1032
+ "those",
1033
+ "i",
1034
+ "you",
1035
+ "he",
1036
+ "she",
1037
+ "it",
1038
+ "we",
1039
+ "they",
1040
+ "me",
1041
+ "him",
1042
+ "her",
1043
+ "us",
1044
+ "them",
1045
+ "my",
1046
+ "your",
1047
+ "his",
1048
+ "its",
1049
+ "our",
1050
+ "their",
1051
+ "not",
1052
+ "no",
1053
+ "yes",
1054
+ "so",
1055
+ "if",
1056
+ "when",
1057
+ "where",
1058
+ "how",
1059
+ "why",
1060
+ "what",
1061
+ "which",
1062
+ "who",
1063
+ "whom",
1064
+ "there",
1065
+ "here"
1066
+ ]));
1431
1067
  }
1432
- effect.log(`=== \u5F00\u59CB\u7F16\u8BD1 ${book.name} ===`);
1433
- let process2 = processPercentage(0.4);
1434
- const Doc_blocks = await allDocBlock_by_bookId(book.id);
1435
- function refsNotUpdated(docBlock) {
1436
- const refs = config.__skipBuilds__[docBlock.id]?.refs ?? [];
1437
- for (const ref_id of refs) {
1438
- const new_doc_hash = Doc_blocks.find(
1439
- (docBlock2) => docBlock2.id === ref_id
1440
- )?.hash;
1441
- const old_doc_hash = config.__skipBuilds__[ref_id]?.hash;
1442
- if (new_doc_hash === void 0 || old_doc_hash === void 0) {
1068
+ /**
1069
+ * 计算词频 (TF)
1070
+ */
1071
+ calculateTermFrequency(text) {
1072
+ const words = this.tokenize(text);
1073
+ const tf = /* @__PURE__ */ new Map();
1074
+ const totalWords = words.length;
1075
+ for (const word of words) {
1076
+ tf.set(word, (tf.get(word) || 0) + 1);
1077
+ }
1078
+ for (const [word, count] of tf) {
1079
+ tf.set(word, count / totalWords);
1080
+ }
1081
+ return tf;
1082
+ }
1083
+ /**
1084
+ * 分词处理(针对中文和英文优化)
1085
+ */
1086
+ tokenize(text) {
1087
+ const tokens = [];
1088
+ const plainText = text.replace(/<[^>]*>/g, " ").replace(/&[^;]+;/g, " ").replace(/\s+/g, " ").trim();
1089
+ const englishWords = plainText.match(/[a-zA-Z]{3,}/g) || [];
1090
+ tokens.push(...englishWords.map((word) => word.toLowerCase()).filter((word) => !this.stopWords.has(word)));
1091
+ const camelCaseWords = plainText.match(/[a-z]+[A-Z][a-zA-Z0-9]*|[A-Z][a-z0-9]+[A-Z][a-zA-Z0-9]*/g) || [];
1092
+ tokens.push(...camelCaseWords.map((word) => word.toLowerCase()));
1093
+ const chineseTokens = this.extractChineseTokens(plainText);
1094
+ tokens.push(...chineseTokens);
1095
+ const filteredTokens = tokens.filter((token) => {
1096
+ if (token.length < 2) return false;
1097
+ const htmlRelatedWords = ["div", "span", "class", "id", "type", "data", "href", "src", "alt", "title", "style", "width", "height", "rootid", "endid", "nodedocument", "ezcqbj", "cktc", "quot"];
1098
+ if (htmlRelatedWords.includes(token.toLowerCase())) {
1443
1099
  return false;
1444
- } else if (new_doc_hash === old_doc_hash) {
1445
- continue;
1446
- } else {
1100
+ }
1101
+ if (token.match(/^[a-f0-9]{6,}$/)) return false;
1102
+ if (/[\u4e00-\u9fa5]/.test(token) && token.length < 2) {
1447
1103
  return false;
1448
1104
  }
1105
+ return true;
1106
+ });
1107
+ return filteredTokens;
1108
+ }
1109
+ /**
1110
+ * 智能中文分词 - 基于文本特征分析
1111
+ */
1112
+ extractChineseTokens(text) {
1113
+ const tokens = [];
1114
+ const chineseText = text.replace(/[^\u4e00-\u9fa5\s]/g, " ");
1115
+ const wordPatterns = [
1116
+ /[\u4e00-\u9fa5]{4}/g,
1117
+ // 4字词汇
1118
+ /[\u4e00-\u9fa5]{3}/g,
1119
+ // 3字词汇
1120
+ /[\u4e00-\u9fa5]{2}/g
1121
+ // 2字词汇
1122
+ ];
1123
+ for (const pattern of wordPatterns) {
1124
+ const matches = chineseText.match(pattern) || [];
1125
+ for (const word of matches) {
1126
+ if (!this.stopWords.has(word) && this.isMeaningfulChineseWord(word)) {
1127
+ tokens.push(word.toLowerCase());
1128
+ }
1129
+ }
1449
1130
  }
1450
- return true;
1131
+ return tokens;
1451
1132
  }
1452
- effect.log(`=== \u67E5\u8BE2\u6587\u6863\u7EA7block\u5B8C\u6210 ===`);
1453
- let i = 0;
1454
- await Promise.all(
1455
- Doc_blocks.map(async (docBlock) => {
1456
- const sy = await get_doc_by_SyPath(DB_block_path(docBlock));
1457
- docTree[docBlock.hpath] = { sy, docBlock };
1458
- i++;
1459
- process2(i / Doc_blocks.length);
1460
- })
1461
- );
1462
- const fileTree = {};
1463
- process2 = processPercentage(0.4);
1464
- const enableIncrementalCompilation_doc = (() => {
1465
- if (package_default.version !== config.OceanPress.version) {
1466
- effect.log(
1467
- `\u914D\u7F6E\u6587\u4EF6\u7248\u672C\u53F7[${config.OceanPress.version}]\u4E0EOceanPress\u7248\u672C[${package_default.version}]\u4E0D\u4E00\u81F4\uFF0C\u5C06\u8FDB\u884C\u6587\u6863\u5168\u91CF\u7F16\u8BD1`
1468
- );
1133
+ /**
1134
+ * 判断是否为有意义的中文词汇
1135
+ */
1136
+ isMeaningfulChineseWord(word) {
1137
+ const meaninglessPatterns = [
1138
+ /^的.*$/,
1139
+ /^.*的$/,
1140
+ /^了.*$/,
1141
+ /^.*了$/,
1142
+ /^在.*$/,
1143
+ /^.*在$/,
1144
+ /^是.*$/,
1145
+ /^.*是$/,
1146
+ /^我.*$/,
1147
+ /^.*我$/,
1148
+ /^有.*$/,
1149
+ /^.*有$/,
1150
+ /^和.*$/,
1151
+ /^.*和$/,
1152
+ /^就.*$/,
1153
+ /^.*就$/,
1154
+ /^不.*$/,
1155
+ /^.*不$/,
1156
+ /^人.*$/,
1157
+ /^.*人$/
1158
+ ];
1159
+ for (const pattern of meaninglessPatterns) {
1160
+ if (pattern.test(word)) {
1161
+ return false;
1162
+ }
1163
+ }
1164
+ if (/(.)\1{2,}/.test(word)) {
1469
1165
  return false;
1470
1166
  }
1471
- return config.enableIncrementalCompilation_doc;
1472
- })();
1473
- effect.log(`=== \u5F00\u59CB\u6E32\u67D3\u6587\u6863 ===`);
1474
- await Promise.all(
1475
- Object.entries(docTree).map(async ([path, { sy, docBlock }]) => {
1476
- if (config.enableIncrementalCompilation && enableIncrementalCompilation_doc && /** 文档本身没有发生变化 */
1477
- config.__skipBuilds__[docBlock.id]?.hash === docBlock.hash && /** docBlock所引用的文档也没有更新 */
1478
- refsNotUpdated(docBlock))
1479
- return;
1480
- try {
1481
- const rootLevel = path.split("/").length - 2;
1482
- const renderInstance = getRender();
1483
- fileTree[path + ".html"] = await htmlTemplate(
1484
- {
1485
- title: sy.Properties?.title || "",
1486
- htmlContent: await _renderHTML(sy, renderInstance),
1487
- level: rootLevel
1488
- },
1489
- {
1490
- ...tempConfig.cdn,
1491
- embedCode: config.embedCode
1492
- }
1493
- );
1494
- if (config.sitemap.rss && path.endsWith(".rss.xml")) {
1495
- const rssPath = path;
1496
- fileTree[rssPath] = await generateRSSXML(rssPath, renderInstance, config);
1497
- effect.log(`\u6E32\u67D3 rss.xml:${rssPath} \u5B8C\u6BD5`);
1167
+ return true;
1168
+ }
1169
+ /**
1170
+ * 计算逆文档频率 (IDF) - 简化版本
1171
+ */
1172
+ calculateInverseDocumentFrequency(term) {
1173
+ const commonTerms = /* @__PURE__ */ new Set([
1174
+ "\u6280\u672F",
1175
+ "\u5F00\u53D1",
1176
+ "\u4EE3\u7801",
1177
+ "\u7CFB\u7EDF",
1178
+ "\u6570\u636E",
1179
+ "\u529F\u80FD",
1180
+ "\u5E94\u7528",
1181
+ "\u5B9E\u73B0",
1182
+ "\u65B9\u6CD5",
1183
+ "\u95EE\u9898",
1184
+ "time",
1185
+ "data",
1186
+ "system",
1187
+ "code",
1188
+ "development",
1189
+ "application",
1190
+ "function",
1191
+ "method",
1192
+ "problem",
1193
+ "solution"
1194
+ ]);
1195
+ if (commonTerms.has(term.toLowerCase())) {
1196
+ return Math.log(1e3 / 500);
1197
+ }
1198
+ return Math.log(1e3 / 10);
1199
+ }
1200
+ /**
1201
+ * 提取关键词
1202
+ */
1203
+ extractKeywords(content, maxKeywords = 10) {
1204
+ const tf = this.calculateTermFrequency(content);
1205
+ const keywordScores = /* @__PURE__ */ new Map();
1206
+ for (const [term, frequency] of tf) {
1207
+ const idf = this.calculateInverseDocumentFrequency(term);
1208
+ const tfidf = frequency * idf;
1209
+ let bonus = 1;
1210
+ if (content.toLowerCase().includes(term.toLowerCase()) && (content.match(new RegExp(`^${term}`, "mi")) || content.match(new RegExp(`${term}$`, "mi")))) {
1211
+ bonus *= 1.5;
1212
+ }
1213
+ if (term.length >= 2 && term.length <= 6) {
1214
+ bonus *= 1.2;
1215
+ }
1216
+ keywordScores.set(term, tfidf * bonus);
1217
+ }
1218
+ return Array.from(keywordScores.entries()).sort((a, b) => b[1] - a[1]).slice(0, maxKeywords).map(([term]) => term);
1219
+ }
1220
+ };
1221
+ function extractDescription(content, maxLength = 160) {
1222
+ const plainText = content.replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim();
1223
+ if (plainText.length <= maxLength) return plainText;
1224
+ const truncated = plainText.substring(0, maxLength);
1225
+ const lastSentenceEnd = Math.max(
1226
+ truncated.lastIndexOf("\u3002"),
1227
+ truncated.lastIndexOf("\uFF01"),
1228
+ truncated.lastIndexOf("\uFF1F"),
1229
+ truncated.lastIndexOf("."),
1230
+ truncated.lastIndexOf("!"),
1231
+ truncated.lastIndexOf("?")
1232
+ );
1233
+ if (lastSentenceEnd > maxLength * 0.7) {
1234
+ return truncated.substring(0, lastSentenceEnd + 1);
1235
+ }
1236
+ return truncated + "...";
1237
+ }
1238
+ function extractKeywords(content) {
1239
+ const tfidfExtractor = new TFIDFKeywordExtractor();
1240
+ return tfidfExtractor.extractKeywords(content, 8);
1241
+ }
1242
+ function generateArticleJsonLd(doc, config, pageUrl, content) {
1243
+ const title = doc.Properties?.title || "\u672A\u547D\u540D\u6587\u6863";
1244
+ const description = extractDescription(content);
1245
+ const keywords = extractKeywords(content);
1246
+ const datePublished = extractDateFromId(doc.ID);
1247
+ const dateModified = formatDate(doc.Properties?.updated);
1248
+ const jsonLd = {
1249
+ "@context": "https://schema.org",
1250
+ "@type": "Article",
1251
+ headline: title,
1252
+ description,
1253
+ keywords: keywords.join(", "),
1254
+ datePublished,
1255
+ dateModified,
1256
+ author: {
1257
+ "@type": "Person",
1258
+ name: config.sitemap?.title || "\u5D2E\u751F",
1259
+ url: config.sitemap?.siteLink || ""
1260
+ },
1261
+ publisher: {
1262
+ "@type": "Organization",
1263
+ name: config.sitemap?.title || "OceanPress",
1264
+ logo: {
1265
+ "@type": "ImageObject",
1266
+ url: `${config.sitemap?.siteLink || ""}/assets/logo.png`
1267
+ }
1268
+ },
1269
+ mainEntityOfPage: {
1270
+ "@type": "WebPage",
1271
+ "@id": pageUrl
1272
+ },
1273
+ image: doc.Properties?.["title-img"] ? {
1274
+ "@type": "ImageObject",
1275
+ url: `${config.sitemap?.siteLink || ""}${doc.Properties["title-img"].replace("assets", "/assets")}`,
1276
+ width: 1200,
1277
+ height: 630
1278
+ } : void 0,
1279
+ wordCount: content.replace(/<[^>]*>/g, "").length,
1280
+ articleSection: "\u6280\u672F\u6587\u6863",
1281
+ inLanguage: "zh-CN"
1282
+ };
1283
+ return `<script type="application/ld+json">
1284
+ ${JSON.stringify(jsonLd, null, 2)}
1285
+ </script>`;
1286
+ }
1287
+ function generateBreadcrumbJsonLd(breadcrumbs) {
1288
+ const itemListElement = breadcrumbs.map((crumb, index) => ({
1289
+ "@type": "ListItem",
1290
+ position: index + 1,
1291
+ name: crumb.name,
1292
+ item: crumb.url
1293
+ }));
1294
+ const jsonLd = {
1295
+ "@context": "https://schema.org",
1296
+ "@type": "BreadcrumbList",
1297
+ itemListElement
1298
+ };
1299
+ return `<script type="application/ld+json">
1300
+ ${JSON.stringify(jsonLd, null, 2)}
1301
+ </script>`;
1302
+ }
1303
+ function generateSeoMetaTags(doc, config, pageUrl, content) {
1304
+ const title = doc.Properties?.title || "\u672A\u547D\u540D\u6587\u6863";
1305
+ const description = extractDescription(content);
1306
+ const keywords = extractKeywords(content);
1307
+ let metaTags = "";
1308
+ metaTags += ` <meta name="description" content="${description.replace(/"/g, "&quot;")}" />
1309
+ `;
1310
+ metaTags += ` <meta name="keywords" content="${keywords.join(", ")}" />
1311
+ `;
1312
+ metaTags += ` <meta name="author" content="${config.sitemap?.title || "\u5D2E\u751F"}" />
1313
+ `;
1314
+ metaTags += ` <meta property="og:title" content="${title.replace(/"/g, "&quot;")}" />
1315
+ `;
1316
+ metaTags += ` <meta property="og:description" content="${description.replace(/"/g, "&quot;")}" />
1317
+ `;
1318
+ metaTags += ` <meta property="og:type" content="article" />
1319
+ `;
1320
+ metaTags += ` <meta property="og:url" content="${pageUrl}" />
1321
+ `;
1322
+ metaTags += ` <meta property="og:site_name" content="${config.sitemap?.title || "OceanPress"}" />
1323
+ `;
1324
+ if (doc.Properties?.["title-img"]) {
1325
+ const imageUrl = `${config.sitemap?.siteLink || ""}${doc.Properties["title-img"].replace("assets", "/assets")}`;
1326
+ metaTags += ` <meta property="og:image" content="${imageUrl}" />
1327
+ `;
1328
+ metaTags += ` <meta property="og:image:width" content="1200" />
1329
+ `;
1330
+ metaTags += ` <meta property="og:image:height" content="630" />
1331
+ `;
1332
+ }
1333
+ const datePublished = extractDateFromId(doc.ID);
1334
+ const dateModified = formatDate(doc.Properties?.updated);
1335
+ metaTags += ` <meta property="article:published_time" content="${datePublished}" />
1336
+ `;
1337
+ metaTags += ` <meta property="article:modified_time" content="${dateModified}" />
1338
+ `;
1339
+ metaTags += ` <meta name="twitter:card" content="summary_large_image" />
1340
+ `;
1341
+ metaTags += ` <meta name="twitter:title" content="${title.replace(/"/g, "&quot;")}" />
1342
+ `;
1343
+ metaTags += ` <meta name="twitter:description" content="${description.replace(/"/g, "&quot;")}" />
1344
+ `;
1345
+ if (doc.Properties?.["title-img"]) {
1346
+ const imageUrl = `${config.sitemap?.siteLink || ""}${doc.Properties["title-img"].replace("assets", "/assets")}`;
1347
+ metaTags += ` <meta name="twitter:image" content="${imageUrl}" />
1348
+ `;
1349
+ }
1350
+ metaTags += ` <meta name="robots" content="index, follow" />
1351
+ `;
1352
+ metaTags += ` <link rel="canonical" href="${pageUrl}" />
1353
+ `;
1354
+ return metaTags;
1355
+ }
1356
+ function generateSeoContent(data) {
1357
+ const metaTags = generateSeoMetaTags(data.doc, data.config, data.pageUrl, data.content);
1358
+ let jsonLd = generateArticleJsonLd(data.doc, data.config, data.pageUrl, data.content);
1359
+ if (data.breadcrumbs && data.breadcrumbs.length > 0) {
1360
+ jsonLd += "\n" + generateBreadcrumbJsonLd(data.breadcrumbs);
1361
+ }
1362
+ return {
1363
+ metaTags,
1364
+ jsonLd
1365
+ };
1366
+ }
1367
+
1368
+ // src/core/htmlTemplate.ts
1369
+ async function htmlTemplate(p, config) {
1370
+ let prePath = "";
1371
+ if (config?.siyuanPrefix) {
1372
+ prePath = config.siyuanPrefix;
1373
+ } else {
1374
+ for (let i = 0; i < p.level; i++) {
1375
+ prePath += "../";
1376
+ }
1377
+ }
1378
+ const version2 = "2.10.5";
1379
+ return `<!DOCTYPE html>
1380
+ <html lang="zh_CN" data-theme-mode="light" data-light-theme="daylight" data-dark-theme="midnight">
1381
+ <head>
1382
+ ${config?.embedCode?.head ?? ""}
1383
+ <meta charset="utf-8" />
1384
+ <meta http-equiv="X-UA-Compatible" content="IE=edge" />
1385
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"/>
1386
+ <meta name="apple-mobile-web-app-capable" content="yes" />
1387
+ <meta name="mobile-web-app-capable" content="yes" />
1388
+ <meta name="apple-mobile-web-app-status-bar-style" content="black" />
1389
+ ${p.seoData ? generateSeoContent({
1390
+ doc: p.seoData.doc,
1391
+ config: p.seoData.config,
1392
+ pageUrl: p.seoData.pageUrl,
1393
+ content: p.htmlContent,
1394
+ breadcrumbs: p.seoData.breadcrumbs
1395
+ }).metaTags : ""}
1396
+ <link rel="stylesheet" type="text/css" id="baseStyle" href="${prePath}stage/build/export/base.css?${version2}"/>
1397
+ <script>
1398
+ // \u66F4\u597D\u7684\u4E3B\u9898\u5207\u6362\u65B9\u6848
1399
+ (function() {
1400
+ // \u4E3B\u9898\u914D\u7F6E
1401
+ const themes = {
1402
+ light: {
1403
+ name: 'daylight',
1404
+ mode: 'light',
1405
+ icon: '\u2600\uFE0F'
1406
+ },
1407
+ dark: {
1408
+ name: 'midnight',
1409
+ mode: 'dark',
1410
+ icon: '\u{1F319}'
1411
+ },
1412
+ auto: {
1413
+ name: 'auto',
1414
+ mode: 'auto',
1415
+ icon: '\u{1F317}',
1416
+ getTheme: function() {
1417
+ const currentHour = new Date().getHours();
1418
+ return currentHour >= 18 || currentHour < 6 ? 'dark' : 'light';
1419
+ }
1420
+ }
1421
+ };
1422
+
1423
+ // \u83B7\u53D6\u5F53\u524D\u4E3B\u9898\u8BBE\u7F6E
1424
+ function getCurrentTheme() {
1425
+ // \u4F18\u5148\u7EA7\uFF1AlocalStorage > \u7CFB\u7EDF\u504F\u597D > auto
1426
+ const savedTheme = localStorage.getItem('oceanpress-theme');
1427
+ if (savedTheme && themes[savedTheme]) {
1428
+ return savedTheme;
1429
+ }
1430
+
1431
+ // \u68C0\u6D4B\u7CFB\u7EDF\u504F\u597D
1432
+ if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
1433
+ return 'dark';
1434
+ }
1435
+ if (window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches) {
1436
+ return 'light';
1437
+ }
1438
+
1439
+ return 'auto';
1440
+ }
1441
+
1442
+ // \u5E94\u7528\u4E3B\u9898
1443
+ function applyTheme(themeName) {
1444
+ const theme = themes[themeName] || themes.auto;
1445
+ const actualTheme = themeName === 'auto' ? theme.getTheme() : themeName;
1446
+ const themeConfig = themes[actualTheme];
1447
+
1448
+ // \u8BBE\u7F6E CSS \u53D8\u91CF
1449
+ document.documentElement.setAttribute('data-theme-mode', themeConfig.mode);
1450
+ document.documentElement.setAttribute('data-light-theme', 'daylight');
1451
+ document.documentElement.setAttribute('data-dark-theme', 'midnight');
1452
+
1453
+ // \u52A0\u8F7D\u5BF9\u5E94\u7684 CSS
1454
+ const themeStyle = document.getElementById('themeDefaultStyle');
1455
+ if (themeStyle) {
1456
+ themeStyle.href = '${prePath}appearance/themes/' + themeConfig.name + '/theme.css?${version2}';
1457
+ } else {
1458
+ // \u5982\u679C\u5143\u7D20\u4E0D\u5B58\u5728\uFF0C\u521B\u5EFA\u5B83
1459
+ const link = document.createElement('link');
1460
+ link.id = 'themeDefaultStyle';
1461
+ link.rel = 'stylesheet';
1462
+ link.type = 'text/css';
1463
+ link.href = '${prePath}appearance/themes/' + themeConfig.name + '/theme.css?${version2}';
1464
+ document.head.appendChild(link);
1465
+ }
1466
+
1467
+ // \u66F4\u65B0\u4E3B\u9898\u5207\u6362\u6309\u94AE
1468
+ updateThemeToggle(themeName);
1469
+
1470
+ // \u4FDD\u5B58\u5230 localStorage
1471
+ localStorage.setItem('oceanpress-theme', themeName);
1472
+
1473
+ // \u89E6\u53D1\u81EA\u5B9A\u4E49\u4E8B\u4EF6
1474
+ window.dispatchEvent(new CustomEvent('oceanpress-theme-changed', {
1475
+ detail: { theme: themeName, actualTheme: actualTheme }
1476
+ }));
1477
+ }
1478
+
1479
+ // \u521B\u5EFA\u4E3B\u9898\u5207\u6362\u6309\u94AE
1480
+ function createThemeToggle() {
1481
+ const toggle = document.createElement('div');
1482
+ toggle.id = 'oceanpress-theme-toggle';
1483
+ toggle.innerHTML = '<span class="theme-icon">\u{1F317}</span><span class="theme-text">\u81EA\u52A8</span>';
1484
+ toggle.addEventListener('click', toggleTheme);
1485
+ (document.querySelector('[data-type="NodeDocument"]')||document.body).appendChild(toggle);
1486
+ }
1487
+
1488
+ // \u66F4\u65B0\u4E3B\u9898\u5207\u6362\u6309\u94AE
1489
+ function updateThemeToggle(themeName) {
1490
+ const toggle = document.getElementById('oceanpress-theme-toggle');
1491
+ if (!toggle) return;
1492
+
1493
+ const theme = themes[themeName] || themes.auto;
1494
+ toggle.innerHTML = '<span class="theme-icon">' + theme.icon + '</span><span class="theme-text">' +
1495
+ (themeName === 'auto' ? '\u81EA\u52A8' : (themeName === 'dark' ? '\u6DF1\u8272' : '\u6D45\u8272')) + '</span>';
1496
+ }
1497
+
1498
+ // \u5207\u6362\u4E3B\u9898
1499
+ function toggleTheme() {
1500
+ const currentTheme = getCurrentTheme();
1501
+ const themeOrder = ['auto', 'light', 'dark'];
1502
+ const currentIndex = themeOrder.indexOf(currentTheme);
1503
+ const nextIndex = (currentIndex + 1) % themeOrder.length;
1504
+ const nextTheme = themeOrder[nextIndex];
1505
+
1506
+ applyTheme(nextTheme);
1507
+ }
1508
+
1509
+ // \u76D1\u542C\u7CFB\u7EDF\u4E3B\u9898\u53D8\u5316
1510
+ if (window.matchMedia) {
1511
+ window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
1512
+ const currentTheme = getCurrentTheme();
1513
+ if (currentTheme === 'auto') {
1514
+ applyTheme('auto');
1515
+ }
1516
+ });
1517
+ }
1518
+
1519
+ // \u76D1\u542C\u81EA\u5B9A\u4E49\u4E3B\u9898\u53D8\u5316\u4E8B\u4EF6\uFF08\u7528\u4E8E\u4FA7\u8FB9\u680F\u7B49\u7EC4\u4EF6\uFF09
1520
+ window.addEventListener('oceanpress-theme-changed', function(e) {
1521
+ // \u8FD9\u91CC\u53EF\u4EE5\u6DFB\u52A0\u5176\u4ED6\u7EC4\u4EF6\u9700\u8981\u54CD\u5E94\u4E3B\u9898\u53D8\u5316\u7684\u903B\u8F91
1522
+ console.log('Theme changed to:', e.detail);
1523
+ });
1524
+
1525
+ // \u521D\u59CB\u5316\u4E3B\u9898
1526
+ function initTheme() {
1527
+ const themeName = getCurrentTheme();
1528
+ const theme = themes[themeName] || themes.auto;
1529
+ const actualTheme = themeName === 'auto' ? theme.getTheme() : themeName;
1530
+ const themeConfig = themes[actualTheme];
1531
+
1532
+ // \u7ACB\u5373\u8BBE\u7F6E\u57FA\u7840\u4E3B\u9898\uFF0C\u907F\u514D\u95EA\u70C1
1533
+ document.documentElement.setAttribute('data-theme-mode', themeConfig.mode);
1534
+ document.documentElement.setAttribute('data-light-theme', 'daylight');
1535
+ document.documentElement.setAttribute('data-dark-theme', 'midnight');
1536
+
1537
+ // \u521B\u5EFA\u4E3B\u9898\u5207\u6362\u6309\u94AE
1538
+ createThemeToggle();
1539
+
1540
+ // \u5E94\u7528\u5B8C\u6574\u4E3B\u9898
1541
+ applyTheme(themeName);
1542
+ }
1543
+
1544
+ // \u9875\u9762\u52A0\u8F7D\u5B8C\u6210\u540E\u521D\u59CB\u5316
1545
+ if (document.readyState === 'loading') {
1546
+ document.addEventListener('DOMContentLoaded', initTheme);
1547
+ } else {
1548
+ initTheme();
1549
+ }
1550
+ })();
1551
+ </script>
1552
+ <link rel="stylesheet" type="text/css" href="${prePath}appearance/oceanpress.css"/>
1553
+ <title>${p.title}</title>
1554
+ ${p.seoData ? generateSeoContent({
1555
+ doc: p.seoData.doc,
1556
+ config: p.seoData.config,
1557
+ pageUrl: p.seoData.pageUrl,
1558
+ content: p.htmlContent,
1559
+ breadcrumbs: p.seoData.breadcrumbs
1560
+ }).jsonLd : ""}
1561
+ </head>
1562
+ <body>
1563
+ ${config?.embedCode?.beforeBody ?? ""}
1564
+ ${p.htmlContent}
1565
+ <script src="${prePath}appearance/icons/material/icon.js?${version2}"></script>
1566
+ <script src="${prePath}stage/build/export/protyle-method.js?${version2}"></script>
1567
+ <script src="${prePath}stage/protyle/js/lute/lute.min.js?${version2}"></script>
1568
+ <script>
1569
+ window.siyuan = {
1570
+ config: {
1571
+ appearance: {
1572
+ mode: document.documentElement.getAttribute('data-theme-mode') === 'dark' ? 1 : 0,//\u4E3B\u9898 \u660E\u4EAE=0 \u6697\u9ED1=1
1573
+ codeBlockThemeDark: "base16/dracula",
1574
+ codeBlockThemeLight: "github",
1575
+ },
1576
+ editor: {
1577
+ codeLineWrap: true,
1578
+ codeLigatures: false,
1579
+ plantUMLServePath: "https://www.plantuml.com/plantuml/svg/~1",
1580
+ codeSyntaxHighlightLineNum: true,
1581
+ katexMacros: JSON.stringify({}),
1582
+ },
1583
+ },
1584
+ languages: { copy: "\u590D\u5236" },
1585
+ };
1586
+ const cdn = "${prePath}stage/protyle";
1587
+ const previewElement = document.getElementById("preview");
1588
+
1589
+ Protyle.highlightRender(previewElement, cdn);
1590
+ Protyle.mathRender(previewElement, cdn, false);
1591
+ Protyle.mermaidRender(previewElement, cdn);
1592
+ Protyle.flowchartRender(previewElement, cdn);
1593
+ Protyle.graphvizRender(previewElement, cdn);
1594
+ Protyle.chartRender(previewElement, cdn);
1595
+ Protyle.mindmapRender(previewElement, cdn);
1596
+ Protyle.abcRender(previewElement, cdn);
1597
+ Protyle.htmlRender(previewElement);
1598
+ Protyle.plantumlRender(previewElement, cdn);
1599
+ document.querySelectorAll(".protyle-action__copy").forEach((item) => {
1600
+ item.addEventListener("click", (event) => {
1601
+ navigator.clipboard.writeText(
1602
+ item.parentElement.nextElementSibling.textContent.trimEnd(),
1603
+ );
1604
+ event.preventDefault();
1605
+ event.stopPropagation();
1606
+ });
1607
+ });
1608
+ </script>
1609
+ ${config?.embedCode?.afterBody ?? ""}
1610
+ </body>
1611
+ </html>`;
1612
+ }
1613
+
1614
+ // src/core/render.ts
1615
+ import { Context as Context2, Effect as Effect3 } from "effect";
1616
+
1617
+ // src/util/escaping.ts
1618
+ function escaping(s) {
1619
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos");
1620
+ }
1621
+ function unescaping(s) {
1622
+ return s.replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&apos;/g, "'").replace(/&#(\d+);/g, (_sub, code) => {
1623
+ return String.fromCharCode(Number(code));
1624
+ });
1625
+ }
1626
+
1627
+ // src/core/renderDocTree.ts
1628
+ import { Effect as Effect2 } from "effect";
1629
+ var renderDocTreeJsPath = `/__oceanpress/docTree.js`;
1630
+ function renderDocTree() {
1631
+ return Effect2.gen(function* () {
1632
+ const config = yield* EffectConfigDep;
1633
+ const Doc_blocks = yield* Effect2.tryPromise(
1634
+ () => allDocBlock_by_bookId(config.notebook.id)
1635
+ );
1636
+ const sortJSON = yield* Effect2.tryPromise(
1637
+ () => API.file_getFile({
1638
+ path: `/data/${config.notebook.id}/.siyuan/sort.json`
1639
+ }).then((r) => {
1640
+ const decoder = new TextDecoder("utf-8");
1641
+ const jsonString = decoder.decode(r);
1642
+ return JSON.parse(jsonString);
1643
+ })
1644
+ );
1645
+ const docs = Doc_blocks.map((el) => ({
1646
+ id: el.id,
1647
+ /** 类似 '/record/cssFlex' */
1648
+ hpath: el.hpath,
1649
+ title: el.content,
1650
+ sort: sortJSON[el.id]
1651
+ }));
1652
+ const tree = buildTree(docs);
1653
+ const jsCode = generateJSTree(tree);
1654
+ return `
1655
+ // OceanPress DocTree - \u52A8\u6001\u52A0\u8F7D\u7684\u6587\u6863\u6811
1656
+ (function() {
1657
+ 'use strict';
1658
+
1659
+ // \u6587\u6863\u6811\u6570\u636E
1660
+ const docTreeData = ${jsCode};
1661
+
1662
+ // \u6E32\u67D3\u51FD\u6570
1663
+ function renderDocTree(containerId, options = {}) {
1664
+ const container = document.getElementById(containerId);
1665
+ if (!container) {
1666
+ console.error('Container not found:', containerId);
1667
+ return;
1668
+ }
1669
+
1670
+ const currentPath = options.currentPath || window.location.pathname.replace(/\\.html$/, '');
1671
+
1672
+ // \u751F\u6210 HTML
1673
+ const html = generateHTMLTree(docTreeData, currentPath);
1674
+ container.innerHTML = html;
1675
+
1676
+ // \u52A0\u8F7D\u6837\u5F0F
1677
+ loadStyles();
1678
+
1679
+ // \u521D\u59CB\u5316\u4EA4\u4E92
1680
+ initInteractions(container, currentPath);
1681
+ }
1682
+
1683
+ // \u68C0\u67E5\u662F\u5426\u4E3A\u5355\u4E00\u8DEF\u5F84\uFF08\u53EA\u6709\u5355\u4E2A\u5B50\u8282\u70B9\u7684\u6587\u4EF6\u5939\uFF09
1684
+ function isSinglePath(node) {
1685
+ if (!node.children || node.children.length !== 1) {
1686
+ return false;
1687
+ }
1688
+
1689
+ let current = node;
1690
+ while (current.children && current.children.length === 1) {
1691
+ current = current.children[0];
1692
+ }
1693
+
1694
+ // \u5982\u679C\u6700\u7EC8\u8282\u70B9\u6CA1\u6709\u5B50\u8282\u70B9\uFF0C\u8BF4\u660E\u662F\u5355\u4E00\u8DEF\u5F84
1695
+ return !current.children || current.children.length === 0;
1696
+ }
1697
+
1698
+ // \u83B7\u53D6\u5355\u4E00\u8DEF\u5F84\u7684\u6240\u6709\u8282\u70B9
1699
+ function getSinglePathNodes(node) {
1700
+ const pathNodes = [node];
1701
+ let current = node;
1702
+
1703
+ while (current.children && current.children.length === 1) {
1704
+ current = current.children[0];
1705
+ pathNodes.push(current);
1706
+ }
1707
+
1708
+ return pathNodes;
1709
+ }
1710
+
1711
+ // \u751F\u6210\u9762\u5305\u5C51\u8DEF\u5F84
1712
+ function generateBreadcrumb(pathNodes, currentPath) {
1713
+ const lastNode = pathNodes[pathNodes.length - 1];
1714
+ const isCurrent = lastNode.hpath === currentPath;
1715
+
1716
+ let breadcrumbHtml = '<div class="breadcrumb-path">';
1717
+
1718
+ pathNodes.forEach((node, index) => {
1719
+ if (index > 0) {
1720
+ breadcrumbHtml += '<span class="breadcrumb-separator">/</span>';
1721
+ }
1722
+
1723
+ const isLast = index === pathNodes.length - 1;
1724
+ const nodeCurrent = node.hpath === currentPath;
1725
+
1726
+ breadcrumbHtml += \`
1727
+ <a href="\${node.hpath}.html" class="breadcrumb-part \${(isLast && isCurrent) ? 'current' : ''}" target="_top">\${node.title}</a>
1728
+ \`;
1729
+ });
1730
+
1731
+ breadcrumbHtml += '</div>';
1732
+ return breadcrumbHtml;
1733
+ }
1734
+
1735
+ // \u751F\u6210 HTML \u6811
1736
+ function generateHTMLTree(nodes, currentPath, level = 0) {
1737
+ let html = '';
1738
+ for (const node of nodes) {
1739
+ const isCurrent = node.hpath === currentPath;
1740
+ const isActive = isCurrent || (currentPath && node.hpath && currentPath.startsWith(node.hpath));
1741
+
1742
+ // \u68C0\u67E5\u662F\u5426\u4E3A\u5355\u4E00\u8DEF\u5F84\uFF0C\u5982\u679C\u662F\u5219\u663E\u793A\u4E3A\u9762\u5305\u5C51
1743
+ if (isSinglePath(node)) {
1744
+ const pathNodes = getSinglePathNodes(node);
1745
+ html += generateBreadcrumb(pathNodes, currentPath);
1746
+ continue;
1747
+ }
1748
+
1749
+ if (node.children && node.children.length > 0) {
1750
+ // \u6709\u5B50\u8282\u70B9\u65F6\u4F7F\u7528 details/summary
1751
+ const isExpanded = isActive ? 'open' : '';
1752
+ html += \`
1753
+ <details class="folder" \${isExpanded}>
1754
+ <summary class="folder-summary">
1755
+ <a href="\${node.hpath}.html" class="folder-link \${isCurrent ? 'current' : ''}" target="_top">\${node.title}</a>
1756
+ </summary>
1757
+ <div class="folder-children" style="padding:0 0 0 10px;">
1758
+ \${generateHTMLTree(node.children, currentPath, level + 1)}
1759
+ </div>
1760
+ </details>
1761
+ \`;
1762
+ } else {
1763
+ // \u6CA1\u6709\u5B50\u8282\u70B9\u7684\u666E\u901A\u9879\u76EE
1764
+ html += \`
1765
+ <div class="file \${isCurrent ? 'current' : ''}">
1766
+ <a href="\${node.hpath}.html" class="file-link" target="_top">\${node.title}</a>
1767
+ </div>
1768
+ \`;
1769
+ }
1770
+ }
1771
+ return html;
1772
+ }
1773
+
1774
+ // \u52A0\u8F7D\u6837\u5F0F
1775
+ function loadStyles() {
1776
+ if (document.getElementById('oceanpress-doctree-styles')) return;
1777
+
1778
+ const link = document.createElement('link');
1779
+ link.id = 'oceanpress-doctree-styles';
1780
+ link.rel = 'stylesheet';
1781
+ link.type = 'text/css';
1782
+ link.href = '${tempConfig.cdn.siyuanPrefix}appearance/docTree.css';
1783
+ document.head.appendChild(link);
1784
+ }
1785
+
1786
+ // \u521D\u59CB\u5316\u4EA4\u4E92
1787
+ function initInteractions(container, currentPath) {
1788
+ // \u4E3A\u5F53\u524D\u9875\u9762\u9879\u6DFB\u52A0\u9AD8\u4EAE\u6837\u5F0F
1789
+ const currentItems = container.querySelectorAll('.current');
1790
+ currentItems.forEach(item => {
1791
+ // \u5982\u679C\u662F\u9762\u5305\u5C51\u8DEF\u5F84\u4E2D\u7684\u5F53\u524D\u9879\uFF0C\u9700\u8981\u7279\u6B8A\u5904\u7406
1792
+ if (item.classList.contains('breadcrumb-part')) {
1793
+ item.style.fontWeight = 'bold';
1794
+ item.style.color = 'var(--oceanpress-sidebar-current-border)';
1795
+ } else {
1796
+ // \u4F7F\u7528 CSS \u53D8\u91CF\u800C\u4E0D\u662F\u786C\u7F16\u7801\u989C\u8272
1797
+ item.style.backgroundColor = 'var(--oceanpress-sidebar-current-bg)';
1798
+ item.style.borderLeft = '3px solid var(--oceanpress-sidebar-current-border)';
1799
+ item.style.paddingLeft = '7px';
1800
+ }
1801
+ });
1802
+
1803
+ // \u786E\u4FDD\u5F53\u524D\u9875\u9762\u7684\u6240\u6709\u7236\u6587\u4EF6\u5939\u90FD\u5C55\u5F00
1804
+ const currentElements = container.querySelectorAll('.current');
1805
+ currentElements.forEach(currentElement => {
1806
+ // \u5411\u4E0A\u904D\u5386\u6240\u6709\u7236\u7EA7 details \u5143\u7D20\u5E76\u5C55\u5F00
1807
+ let parent = currentElement.parentElement;
1808
+ while (parent) {
1809
+ if (parent.tagName === 'DETAILS') {
1810
+ parent.setAttribute('open', 'open');
1811
+ }
1812
+ parent = parent.parentElement;
1813
+ }
1814
+ });
1815
+
1816
+ // \u81EA\u52A8\u6EDA\u52A8\u5230\u5F53\u524D\u9875\u9762
1817
+ const firstCurrent = container.querySelector('.current');
1818
+ if (firstCurrent) {
1819
+ setTimeout(() => {
1820
+ firstCurrent.scrollIntoView({ behavior: 'smooth', block: 'center' });
1821
+ }, 100);
1822
+ }
1823
+
1824
+ // \u76D1\u542C\u4E3B\u9898\u53D8\u5316\u4E8B\u4EF6\uFF0C\u66F4\u65B0\u9AD8\u4EAE\u6837\u5F0F
1825
+ window.addEventListener('oceanpress-theme-changed', function(e) {
1826
+ currentItems.forEach(item => {
1827
+ if (item.classList.contains('breadcrumb-part')) {
1828
+ item.style.fontWeight = 'bold';
1829
+ item.style.color = 'var(--oceanpress-sidebar-current-border)';
1830
+ } else {
1831
+ item.style.backgroundColor = 'var(--oceanpress-sidebar-current-bg)';
1832
+ item.style.borderLeft = '3px solid var(--oceanpress-sidebar-current-border)';
1833
+ }
1834
+ });
1835
+ });
1836
+ }
1837
+
1838
+ // \u66B4\u9732\u5230\u5168\u5C40
1839
+ window.OceanPressDocTree = {
1840
+ render: renderDocTree,
1841
+ data: docTreeData
1842
+ };
1843
+
1844
+ // \u81EA\u52A8\u6E32\u67D3\uFF08\u5982\u679C\u5BB9\u5668\u5B58\u5728\uFF09
1845
+ if (document.readyState === 'loading') {
1846
+ document.addEventListener('DOMContentLoaded', () => {
1847
+ if (document.getElementById('oceanpress-doctree')) {
1848
+ renderDocTree('oceanpress-doctree');
1849
+ }
1850
+ });
1851
+ } else {
1852
+ if (document.getElementById('oceanpress-doctree')) {
1853
+ renderDocTree('oceanpress-doctree');
1854
+ }
1855
+ }
1856
+ })();
1857
+ `;
1858
+ });
1859
+ }
1860
+ function generateJSTree(nodes) {
1861
+ return JSON.stringify(nodes, null, 2);
1862
+ }
1863
+ function buildTree(docs) {
1864
+ const root = [];
1865
+ const pathMap = {};
1866
+ docs.sort((a, b) => a.hpath.localeCompare(b.hpath));
1867
+ for (const doc of docs) {
1868
+ const pathParts = doc.hpath.split("/").filter((part) => part !== "");
1869
+ let currentPath = "";
1870
+ let parentNode = void 0;
1871
+ for (let i = 0; i < pathParts.length - 1; i++) {
1872
+ currentPath += "/" + pathParts[i];
1873
+ if (!pathMap[currentPath]) {
1874
+ pathMap[currentPath] = {
1875
+ id: "virtual_" + currentPath,
1876
+ hpath: currentPath,
1877
+ title: pathParts[i],
1878
+ sort: void 0,
1879
+ children: []
1880
+ };
1881
+ if (parentNode) {
1882
+ parentNode.children = parentNode.children || [];
1883
+ parentNode.children.push(pathMap[currentPath]);
1884
+ } else {
1885
+ root.push(pathMap[currentPath]);
1886
+ }
1887
+ }
1888
+ parentNode = pathMap[currentPath];
1889
+ }
1890
+ if (parentNode) {
1891
+ parentNode.children = parentNode.children || [];
1892
+ parentNode.children.push(doc);
1893
+ } else {
1894
+ root.push(doc);
1895
+ }
1896
+ pathMap[doc.hpath] = doc;
1897
+ }
1898
+ function sortNodes(nodes) {
1899
+ return nodes.map((node) => {
1900
+ if (node.children) {
1901
+ node.children = sortNodes(node.children);
1902
+ }
1903
+ return node;
1904
+ }).sort((a, b) => {
1905
+ if (a.sort !== void 0 && b.sort !== void 0) {
1906
+ return a.sort - b.sort;
1907
+ } else if (a.sort !== void 0) {
1908
+ return -1;
1909
+ } else if (b.sort !== void 0) {
1910
+ return 1;
1911
+ } else {
1912
+ return (a.title || "").localeCompare(b.title || "");
1913
+ }
1914
+ });
1915
+ }
1916
+ return sortNodes(root);
1917
+ }
1918
+
1919
+ // src/core/render.ts
1920
+ var renderHTML = (sy, render) => Effect3.gen(function* () {
1921
+ if (sy === void 0) return "";
1922
+ const defaultRender = yield* getRender;
1923
+ const renderInstance = render ?? defaultRender;
1924
+ const renderObj = {
1925
+ ...renderInstance,
1926
+ nodeStack: [
1927
+ /** 避免让所有的 renderInstance.nodeStack 是同一个对象 ,所以这里复制一个新的 */
1928
+ ...renderInstance.nodeStack
1929
+ ]
1930
+ };
1931
+ if (renderInstance.nodeStack.find(
1932
+ (node) => node.ID && sy.ID && node.ID === sy.ID
1933
+ )) {
1934
+ return warnDiv(
1935
+ "\u5FAA\u73AF\u5F15\u7528",
1936
+ [...renderInstance.nodeStack, sy].map((el) => el.ID)
1937
+ );
1938
+ }
1939
+ if (renderObj[sy.Type] === void 0) {
1940
+ return warnDiv(
1941
+ `\u6CA1\u6709\u627E\u5230\u5BF9\u5E94\u7684\u6E32\u67D3\u65B9\u6CD5 ${sy.Type} ${renderObj.nodeStack[0].Properties?.title}`
1942
+ );
1943
+ } else {
1944
+ renderObj.nodeStack.push(sy);
1945
+ if (sy.ID && renderInstance.nodeStack[0]?.ID) {
1946
+ const storeDep = yield* EffectRender;
1947
+ const id = sy.ID;
1948
+ const targetDoc = yield* Effect3.tryPromise(
1949
+ () => storeDep.getDocByChildID(id)
1950
+ );
1951
+ const currentDoc = renderInstance.nodeStack[0];
1952
+ if (targetDoc?.ID !== void 0 && targetDoc.ID !== currentDoc.ID && currentDoc.ID) {
1953
+ renderObj.refs.add(targetDoc.ID);
1954
+ }
1955
+ }
1956
+ const r = yield* Effect3.tryPromise(() => renderObj[sy.Type](sy));
1957
+ renderObj.nodeStack.pop();
1958
+ return r;
1959
+ }
1960
+ });
1961
+ function warnDiv(msg, ...args) {
1962
+ warn(msg, ...args);
1963
+ return `<div class="ft__smaller ft__secondary b3-form__space--small">${msg}</div>`;
1964
+ }
1965
+ function isRenderCode(sy) {
1966
+ const mark = atob(
1967
+ sy.CodeBlockInfo ?? sy.Children?.find((el) => el.Type === "NodeCodeBlockFenceInfoMarker")?.CodeBlockInfo ?? ""
1968
+ );
1969
+ return [
1970
+ [
1971
+ "mindmap",
1972
+ "mermaid",
1973
+ "echarts",
1974
+ "abc",
1975
+ "graphviz",
1976
+ "flowchart",
1977
+ "plantuml"
1978
+ ].includes(mark),
1979
+ mark
1980
+ ];
1981
+ }
1982
+ var html = String.raw;
1983
+ function strAttr(sy, config = {}) {
1984
+ if (config?.subtype_class === void 0) {
1985
+ config.subtype_class = (() => {
1986
+ const typ_subtype = sy.ListData?.Typ === 1 ? (
1987
+ /** 有序列表 */
1988
+ "o"
1989
+ ) : sy.ListData?.Typ === 3 ? (
1990
+ /** 任务列表 */
1991
+ "t"
1992
+ ) : (
1993
+ /** 无序列表 */
1994
+ "u"
1995
+ );
1996
+ if (sy.Type === "NodeDocument") return "";
1997
+ else if (sy.Type === "NodeHeading") return `h${sy.HeadingLevel}`;
1998
+ else if (sy.Type === "NodeList") return [typ_subtype, "list"];
1999
+ else if (sy.Type === "NodeListItem") return [typ_subtype, "li"];
2000
+ else if (sy.Type === "NodeParagraph") return ["", "p"];
2001
+ else if (sy.Type === "NodeImage") return ["", "img"];
2002
+ else if (sy.Type === "NodeBlockquote") return ["", "bq"];
2003
+ else if (sy.Type === "NodeSuperBlock") return ["", "sb"];
2004
+ else if (sy.Type === "NodeCodeBlock") {
2005
+ const [yes, mark] = isRenderCode(sy);
2006
+ if (yes) {
2007
+ return [mark, "render-node"];
2008
+ } else {
2009
+ return ["", "code-block"];
2010
+ }
2011
+ } else if (sy.Type === "NodeTable") return ["", "table"];
2012
+ else if (sy.Type === "NodeThematicBreak") return ["", "hr"];
2013
+ else if (sy.Type === "NodeMathBlock") return ["math", "render-node"];
2014
+ else if (sy.Type === "NodeIFrame") return ["", "iframe"];
2015
+ else if (sy.Type === "NodeVideo") return ["", "iframe"];
2016
+ else return "";
2017
+ })();
2018
+ }
2019
+ const attrObj = {};
2020
+ function addAttr(key, value) {
2021
+ attrObj[key] = value;
2022
+ }
2023
+ if (sy.ID) {
2024
+ addAttr("id", sy.ID);
2025
+ addAttr("data-node-id", sy.ID);
2026
+ }
2027
+ if (sy?.TextMarkType === "tag") {
2028
+ addAttr(`data-type`, sy.TextMarkType ?? "");
2029
+ } else {
2030
+ addAttr(`data-type`, config?.data_type ?? sy.Type);
2031
+ }
2032
+ if (sy.Properties?.updated) addAttr("updated", sy.Properties.updated);
2033
+ if (config?.subtype_class) {
2034
+ if (typeof config.subtype_class === "string") {
2035
+ addAttr("data-subtype", config.subtype_class);
2036
+ addAttr("class", config.subtype_class);
2037
+ } else {
2038
+ if (config.subtype_class[0] !== "")
2039
+ addAttr("data-subtype", config.subtype_class[0]);
2040
+ if (config.subtype_class[1] !== "")
2041
+ addAttr("class", config.subtype_class[1]);
2042
+ }
2043
+ }
2044
+ if (sy.Properties) {
2045
+ Object.entries(sy.Properties).forEach(([k, v]) => addAttr(k, v));
2046
+ }
2047
+ if (sy.ListData?.Marker) addAttr("data-marker", atob(sy.ListData.Marker));
2048
+ if (
2049
+ /** 任务列表 */
2050
+ sy.ListData?.Typ === 3 && /** 该项被选中 */
2051
+ sy.Children?.find(
2052
+ (el) => el.Type === "NodeTaskListItemMarker"
2053
+ )?.TaskListItemChecked
2054
+ ) {
2055
+ attrObj["class"] = (attrObj["class"] ?? "") + " protyle-task--done ";
2056
+ }
2057
+ delete attrObj["fold"];
2058
+ if (sy.Type === "NodeDocument") delete attrObj["title"];
2059
+ return Object.entries(attrObj).map(([k, v]) => `${k}="${v}"`).join(" ");
2060
+ }
2061
+ var _emptyString = async (_sy) => "";
2062
+ var _dataString = async (sy) => sy.Data ?? "";
2063
+ var getRender = Effect3.gen(function* () {
2064
+ const render = yield* renderProgram;
2065
+ return {
2066
+ ...render,
2067
+ nodeStack: [],
2068
+ refs: /* @__PURE__ */ new Set()
2069
+ };
2070
+ });
2071
+ var renderProgram = Effect3.gen(function* () {
2072
+ const storeDep = yield* EffectRender;
2073
+ const config = yield* EffectConfigDep;
2074
+ const context = Context2.empty().pipe(
2075
+ Context2.add(EffectRender, storeDep),
2076
+ Context2.add(EffectConfigDep, config)
2077
+ );
2078
+ async function callChildRender(sy, renderInstance) {
2079
+ const children = sy?.Children ?? [];
2080
+ const promises = children.map(
2081
+ (el) => Effect3.runPromise(
2082
+ Effect3.provide(renderHTML(el, renderInstance), context)
2083
+ )
2084
+ );
2085
+ const results = await Promise.all(promises);
2086
+ return results.join("");
2087
+ }
2088
+ async function callRenderHTML(sy, render2) {
2089
+ return Effect3.runPromise(Effect3.provide(renderHTML(sy, render2), context));
2090
+ }
2091
+ const render = {
2092
+ nodeStack: [],
2093
+ refs: /* @__PURE__ */ new Set(),
2094
+ async getTopPathPrefix() {
2095
+ const sy = this.nodeStack[0];
2096
+ let prefix = ".";
2097
+ if (sy.Type === "NodeDocument" && sy.ID) {
2098
+ const path = await storeDep.getDocPathBySY(sy);
2099
+ if (path) {
2100
+ const level = path.split("/").length - 3;
2101
+ for (let i = 0; i < level; i++) {
2102
+ prefix += "/..";
2103
+ }
2104
+ }
2105
+ return prefix;
2106
+ } else {
2107
+ console.log("\u672A\u5B9A\u4E49\u9876\u5C42\u5143\u7D20\u975E NodeDocument \u65F6\u7684\u5904\u7406\u65B9\u5F0F", sy);
2108
+ return "";
2109
+ }
2110
+ },
2111
+ async NodeDocument(sy) {
2112
+ const isTopDoc = this.nodeStack.length === 1;
2113
+ let html2 = `<div style="min-height: 150px;" ${strAttr(sy)}>
2114
+ ${/** 题头图 */
2115
+ isTopDoc && sy.Properties?.["title-img"] ? `<div class="protyle-background__img" style="margin-bottom: 30px;position: relative;height: 16vh;${sy.Properties?.["title-img"].replace(
2116
+ /assets/,
2117
+ // 修改为相对路径
2118
+ await this.getTopPathPrefix() + "/assets"
2119
+ )}"/>${sy.Properties?.["icon"] ? `<div style="position: absolute;bottom:-10px;left:15px;height: 80px;width: 80px;transition: var(--b3-transition);cursor: pointer;font-size: 68px;line-height: 80px;text-align: center;font-family: var(--b3-font-family-emoji);margin-right: 16px;"> &#x${sy.Properties?.["icon"]} </div>` : ""}</div>` : ""}
2120
+ ${/** h1 文档标题 */
2121
+ isTopDoc ? `<h1 ${strAttr(sy)} data-type="NodeHeading" class="h1">${sy.Properties?.title}</h1>` : ""}
2122
+ ${await callChildRender(sy, this)}</div>`;
2123
+ if (isTopDoc) {
2124
+ html2 = `<div class="protyle-wysiwyg protyle-wysiwyg--attr" id="preview">
2125
+ <div id="oceanpress-left-sidebar">
2126
+ ${config.sidebarCode.leftCode}
2127
+ ${config.sidebarCode.enableDocTree ? `<div id="oceanpress-doctree"></div>
2128
+ <script src="${await this.getTopPathPrefix()}${renderDocTreeJsPath}" async></script>` : ""}
2129
+ </div>
2130
+ ${html2}
2131
+ <div id="oceanpress-right-sidebar">${config.sidebarCode.rightCode}</div>
2132
+ </div>`;
2133
+ }
2134
+ return html2;
2135
+ },
2136
+ async NodeHeading(sy) {
2137
+ const tagName = `h${sy.HeadingLevel}`;
2138
+ let html2 = `<${tagName} ${strAttr(sy)}>${await callChildRender(
2139
+ sy,
2140
+ this
2141
+ )}</${tagName}>`;
2142
+ const parentNode = this.nodeStack[
2143
+ this.nodeStack.length - 2
2144
+ /** 最后一个元素是 sy本身(NodeHeading)还得要往前一个,所以是2 */
2145
+ ];
2146
+ if (parentNode?.Type === "NodeBlockQueryEmbedScript") {
2147
+ let afterFlag = false;
2148
+ for (const node of sy.Parent.Children ?? []) {
2149
+ if (node === sy) {
2150
+ afterFlag = true;
2151
+ } else if (node !== sy && node.Type === "NodeHeading") {
2152
+ afterFlag = false;
2153
+ } else if (afterFlag) {
2154
+ html2 += "\n" + await callRenderHTML(node, this);
2155
+ }
2156
+ }
2157
+ }
2158
+ return html2;
2159
+ },
2160
+ NodeText: _dataString,
2161
+ async NodeList(sy) {
2162
+ return html`<div ${strAttr(sy)}>${await callChildRender(sy, this)}</div>`;
2163
+ },
2164
+ async NodeListItem(sy) {
2165
+ return html`<div ${await strAttr(sy)}>
2166
+ <div class="protyle-action">
2167
+ ${sy.ListData?.Typ === 1 ? (
2168
+ /** 有序列表 */
2169
+ atob(sy.ListData?.Marker ?? "")
2170
+ ) : sy.ListData?.Typ === 3 ? (
2171
+ /** 任务列表 */
2172
+ `<svg><use xlink:href="#${sy.Children?.find(
2173
+ (el) => el.Type === "NodeTaskListItemMarker"
2174
+ )?.TaskListItemChecked ? "iconCheck" : "iconUncheck"}"></use></svg>`
2175
+ ) : (
2176
+ /** 无序列表 */
2177
+ `<svg><use xlink:href="#iconDot"></use></svg>`
2178
+ )}
2179
+ </div>
2180
+ ${await callChildRender(sy, this)}
2181
+ </div>`;
2182
+ },
2183
+ NodeTaskListItemMarker: _emptyString,
2184
+ async NodeParagraph(sy) {
2185
+ return `<div ${strAttr(
2186
+ sy
2187
+ )}><div spellcheck="false">${await callChildRender(sy, this)}</div></div>`;
2188
+ },
2189
+ async NodeTextMark(sy) {
2190
+ const that = this;
2191
+ let r = "";
2192
+ for (const type of (sy.TextMarkType?.split(" ") ?? []).reverse()) {
2193
+ if (r === "") {
2194
+ r = await TextMarkRender(sy, type, sy.TextMarkTextContent ?? "");
2195
+ } else {
2196
+ r = await TextMarkRender(sy, type, r);
2197
+ }
2198
+ }
2199
+ return r;
2200
+ async function TextMarkRender(sy2, type, content) {
2201
+ if (type === "inline-math") {
2202
+ return `<span data-type="inline-math" data-subtype="math" data-content="${sy2.TextMarkInlineMathContent}" class="render-node"></span>`;
2203
+ } else if (type === "inline-memo") {
2204
+ return `${content}<sup>\uFF08${sy2.TextMarkInlineMemoContent}\uFF09</sup>`;
2205
+ } else if (type === "block-ref") {
2206
+ let href = "";
2207
+ if (sy2.TextMarkBlockRefID) {
2208
+ const doc = await storeDep.getDocByChildID(sy2.TextMarkBlockRefID);
2209
+ if (doc?.ID) {
2210
+ href = `${await that.getTopPathPrefix()}${await storeDep.getHPathByID_Node(
2211
+ doc
2212
+ )}.html#${sy2.TextMarkBlockRefID}`;
2213
+ that.refs.add(doc.ID);
2214
+ } else {
2215
+ warn(`\u672A\u67E5\u627E\u5230${sy2.ID}\u6240\u6307\u5411\u7684\u6587\u6863\u8282\u70B9 ${sy2.TextMarkBlockRefID}`);
2216
+ }
2217
+ } else {
2218
+ warn(`${sy2.ID} \u5757\u5F15\u7528\u6CA1\u6709\u8BBE\u5B9A ref id`);
2219
+ }
2220
+ return `<span data-type="${sy2.TextMarkType}" data-subtype="${/** "s" */
2221
+ sy2.TextMarkBlockRefSubtype}" data-id="${/** 被引用块的id */
2222
+ sy2.TextMarkBlockRefID}"><a href="${href}">${content}</a></span>`;
2223
+ } else if (type === "a") {
2224
+ let href = sy2.TextMarkAHref;
2225
+ if (href?.startsWith("assets/")) {
2226
+ href = `${await that.getTopPathPrefix()}/${href}`;
2227
+ }
2228
+ return `<a href="${href}">${content}</a>`;
2229
+ } else if (`strong em u s mark sup sub kbd tag code strong code text`.includes(
2230
+ type ?? ""
2231
+ )) {
2232
+ return `<span ${strAttr(sy2, { data_type: type })}>${content}</span>`;
2233
+ } else {
2234
+ return warnDiv(
2235
+ `\u6CA1\u6709\u627E\u5230\u5BF9\u5E94\u7684\u6E32\u67D3\u5668 ${sy2.TextMarkType} ${that.nodeStack[0].Properties?.title}`
2236
+ );
2237
+ }
2238
+ }
2239
+ },
2240
+ async NodeImage(sy) {
2241
+ let link = "";
2242
+ const LinkDest = sy.Children?.filter((c) => c.Type === "NodeLinkDest");
2243
+ if (LinkDest?.length === 1) {
2244
+ link = await callRenderHTML(LinkDest[0], this);
2245
+ } else if (LinkDest?.length && LinkDest.length > 1) {
2246
+ warn("NodeImage \u5B58\u5728\u591A\u4E2A LinkDest", sy);
2247
+ }
2248
+ let title = "";
2249
+ const LinkTitle = sy.Children?.filter((c) => c.Type === "NodeLinkTitle");
2250
+ if (LinkTitle?.length === 1) {
2251
+ title = await callRenderHTML(LinkTitle[0], this);
2252
+ } else if (LinkTitle?.length && LinkTitle.length > 1) {
2253
+ warn("NodeImage \u5B58\u5728\u591A\u4E2A LinkTitle", sy);
2254
+ }
2255
+ return `<span ${await strAttr(sy)} style="${sy.Properties?.["parent-style"] ?? ""}">
2256
+ <img
2257
+ src="${link}"
2258
+ data-src="${link}"
2259
+ title="${title}"
2260
+ style="${sy.Properties?.style ?? ""}"
2261
+ loading="lazy"
2262
+ />
2263
+ <span class="protyle-action__title">${title}</span></span>`;
2264
+ },
2265
+ async NodeLinkDest(sy) {
2266
+ if (/^(?:[a-z]+:)?\/\/|^(?:\/)/.test(sy.Data ?? "")) {
2267
+ return sy.Data ?? "";
2268
+ }
2269
+ return `${await this.getTopPathPrefix()}/${sy.Data}`;
2270
+ },
2271
+ NodeLinkTitle: _dataString,
2272
+ NodeKramdownSpanIAL: _emptyString,
2273
+ async NodeSuperBlock(sy) {
2274
+ return `<div ${strAttr(sy)} data-sb-layout="${childDateByType(
2275
+ sy,
2276
+ "NodeSuperBlockLayoutMarker"
2277
+ )}">${await callChildRender(sy, this)}</div>`;
2278
+ },
2279
+ NodeSuperBlockOpenMarker: _emptyString,
2280
+ NodeSuperBlockCloseMarker: _emptyString,
2281
+ NodeSuperBlockLayoutMarker: _emptyString,
2282
+ async NodeBlockQueryEmbed(sy) {
2283
+ return `<div ${strAttr(sy)} data-type="NodeBlockquote" class="bq"> ${await callChildRender(sy, this)} </div>`;
2284
+ },
2285
+ NodeOpenBrace: _emptyString,
2286
+ NodeCloseBrace: _emptyString,
2287
+ async NodeBlockQueryEmbedScript(sy) {
2288
+ const sql = sy.Data;
2289
+ if (!sql) {
2290
+ console.log("no sql", sy);
2291
+ return html`<pre>${sql}</pre>`;
2292
+ }
2293
+ let htmlStr = "";
2294
+ const blocks = await API.query_sql({
2295
+ stmt: (
2296
+ /** sql 被思源转义了,类似 :SELECT * FROM blocks WHERE id = &#39;20201227174241-nxny1tq&#39;
2297
+ 所以这里将它转义回来
2298
+ TODO 当用户确实使用了包含转义的字符串时,这个实现是错误的 */
2299
+ unescaping(
2300
+ sql
2301
+ ).replace(
2302
+ /** 我不理解lute为什么这样实现 https://github.com/88250/lute/blob/HEAD/editor/const.go#L38
2303
+ * https://ld246.com/article/1696750832289
2304
+ */
2305
+ /_esc_newline_/g,
2306
+ "\n"
2307
+ )
2308
+ )
2309
+ }).catch((err) => {
2310
+ throw new Error(
2311
+ `sql error: ${err.message}
2312
+ rawSql:${sql}
2313
+ unescapingSql:${unescaping(
2314
+ sql
2315
+ )}`
2316
+ );
2317
+ });
2318
+ for (const block of blocks) {
2319
+ const node = await storeDep.getNodeByID(block.id);
2320
+ if (node === void 0) {
2321
+ return warnDiv("\u672A\u627E\u5230\u6B64\u5757\uFF0C\u53EF\u80FD\u4E3A\u8DE8\u7B14\u8BB0\u5F15\u7528", block.id, sql);
2322
+ }
2323
+ htmlStr += await callRenderHTML(node, this);
2324
+ }
2325
+ return htmlStr;
2326
+ },
2327
+ async NodeBlockquote(sy) {
2328
+ return html`<div ${strAttr(sy)}>${await callChildRender(sy, this)}</div>`;
2329
+ },
2330
+ NodeBlockquoteMarker: _emptyString,
2331
+ NodeCodeBlock: async function(sy) {
2332
+ const [yes, _] = isRenderCode(sy);
2333
+ if (yes) {
2334
+ return `<div ${strAttr(sy)} data-content="${escaping(
2335
+ sy.Children?.find((el) => el.Type === "NodeCodeBlockCode")?.Data ?? ""
2336
+ )}">
2337
+ <div spin="1"></div>
2338
+ <div class="protyle-attr" contenteditable="false"></div>
2339
+ </div>`;
2340
+ }
2341
+ return `<div ${strAttr(sy)}>
2342
+ <div class="protyle-action">
2343
+ <span class="protyle-action--first protyle-action__language">${await callRenderHTML(
2344
+ sy.Children?.find(
2345
+ (el) => el.Type === "NodeCodeBlockFenceInfoMarker"
2346
+ ),
2347
+ this
2348
+ )}</span>
2349
+ <span class="fn__flex-1"></span><span class="protyle-icon protyle-icon--only protyle-action__copy"><svg><use xlink:href="#iconCopy"></use></svg></span>
2350
+ </div>
2351
+ ${await callRenderHTML(
2352
+ sy.Children?.find((el) => el.Type === "NodeCodeBlockCode"),
2353
+ this
2354
+ )}
2355
+ </div>`;
2356
+ },
2357
+ NodeCodeBlockFenceInfoMarker: async (sy) => atob(sy.CodeBlockInfo ?? ""),
2358
+ NodeCodeBlockCode: async (sy) => `<div class="hljs" spellcheck="false">${sy.Data}</div>`,
2359
+ NodeCodeBlockFenceOpenMarker: _emptyString,
2360
+ NodeCodeBlockFenceCloseMarker: _emptyString,
2361
+ async NodeTable(sy) {
2362
+ return `<div ${strAttr(sy)}>
2363
+ <div>
2364
+ <table spellcheck="false">
2365
+ <colgroup>
2366
+ ${sy.TableAligns?.map(() => "<col />").join("")}
2367
+ </colgroup>
2368
+ ${await callRenderHTML(
2369
+ sy.Children?.find((el) => el.Type === "NodeTableHead"),
2370
+ this
2371
+ )}
2372
+ <tbody>
2373
+ ${(await Promise.all(
2374
+ sy.Children?.filter((el) => el.Type === "NodeTableRow").map(
2375
+ (el) => callRenderHTML(el, this)
2376
+ ) ?? []
2377
+ )).join("\n")}
2378
+ </tbody>
2379
+ </table>
2380
+ </div>
2381
+ </div>`;
2382
+ },
2383
+ async NodeTableHead(sy) {
2384
+ return `<${sy.Data}>${await callChildRender(sy, this)}</${sy.Data}>`;
2385
+ },
2386
+ async NodeTableRow(sy) {
2387
+ return `<tr>${await callChildRender(sy, this)}</tr>`;
2388
+ },
2389
+ async NodeTableCell(sy) {
2390
+ return `<td>${await callChildRender(sy, this)}</td>`;
2391
+ },
2392
+ NodeHTMLBlock: async (sy) => `<div ${strAttr(sy)}>${sy.Data}</div>`,
2393
+ NodeThematicBreak: async (sy) => `<div ${strAttr(sy)}><div></div></div>`,
2394
+ NodeMathBlock: async (sy) => `<div ${strAttr(
2395
+ sy
2396
+ )} data-content="${childDateByType(sy, "NodeMathBlockContent")}">
2397
+ <div spin="1"></div>
2398
+ </div>`,
2399
+ NodeMathBlockOpenMarker: _emptyString,
2400
+ NodeMathBlockCloseMarker: _emptyString,
2401
+ async NodeIFrame(sy) {
2402
+ return ` <div ${strAttr(sy)}>
2403
+ <div class="iframe-content">
2404
+ ${/** 资源总是被复制到顶层目录,所以直接跳到顶层即可 */
2405
+ /** TODO 应该有一个统一处理资源的方案 */
2406
+ sy.Data?.replace(
2407
+ /src="assets\//,
2408
+ `src="${await this.getTopPathPrefix()}/assets/`
2409
+ )}
2410
+ </div>
2411
+ </div>`;
2412
+ },
2413
+ async NodeVideo(sy) {
2414
+ return await this.NodeIFrame(sy);
2415
+ },
2416
+ async NodeAudio(sy) {
2417
+ return await this.NodeIFrame(sy);
2418
+ },
2419
+ /** 虚拟链接 */
2420
+ NodeHeadingC8hMarker: _emptyString,
2421
+ async NodeSoftBreak(_sy) {
2422
+ return "\u200B";
2423
+ },
2424
+ async NodeBr(sy) {
2425
+ return `<${sy.Data}>`;
2426
+ },
2427
+ async NodeWidget(sy) {
2428
+ return `<div ${strAttr(
2429
+ sy
2430
+ )}><img src="${await this.getTopPathPrefix()}/assets/widget/${sy.ID}.jpg"/></div>`;
2431
+ },
2432
+ async NodeBackslash(sy) {
2433
+ if (sy.Data === void 0 || sy.Data === "span") {
2434
+ return `${await callChildRender(sy, this)}`;
2435
+ } else {
2436
+ return warnDiv(
2437
+ `\u672A\u5B9A\u4E49\u7684 NodeBackslash \u5904\u7406 ${sy.Data}`,
2438
+ this.nodeStack[0].Properties?.title
2439
+ );
2440
+ }
2441
+ },
2442
+ NodeBackslashContent: _dataString
2443
+ };
2444
+ return render;
2445
+ });
2446
+ function childDateByType(sy, type) {
2447
+ return sy.Children?.find((el) => el.Type === type)?.Data;
2448
+ }
2449
+ function warn(...arg) {
2450
+ console.warn("\n", ...arg);
2451
+ }
2452
+
2453
+ // src/core/build.ts
2454
+ function generateBreadcrumbs(path, config) {
2455
+ const breadcrumbs = [];
2456
+ if (config.sitemap?.siteLink) {
2457
+ breadcrumbs.push({
2458
+ name: config.sitemap?.title || "\u9996\u9875",
2459
+ url: config.sitemap.siteLink
2460
+ });
2461
+ }
2462
+ const pathSegments = path.split("/").filter((segment) => segment.length > 0);
2463
+ let currentPath = "";
2464
+ for (const segment of pathSegments) {
2465
+ currentPath += "/" + segment;
2466
+ breadcrumbs.push({
2467
+ name: segment,
2468
+ url: `${config.sitemap?.siteLink || ""}${currentPath}.html`
2469
+ });
2470
+ }
2471
+ return breadcrumbs;
2472
+ }
2473
+ function build(config, otherConfig) {
2474
+ return Effect4.gen(function* () {
2475
+ const effectApi = yield* EffectRender;
2476
+ const effectLog = yield* EffectLogDep;
2477
+ const _renderHTML = otherConfig?.renderHtmlFn ?? renderHTML;
2478
+ const book = config.notebook;
2479
+ const docTree = {};
2480
+ const skipBuilds = useSkipBuilds();
2481
+ let oldPercentage = 0;
2482
+ let total = 0;
2483
+ function processPercentage(percentage) {
2484
+ total += oldPercentage;
2485
+ return (process3) => {
2486
+ oldPercentage = process3 * percentage;
2487
+ effectLog.percentage((total + oldPercentage) * 100);
2488
+ };
2489
+ }
2490
+ effectLog.log(`=== \u5F00\u59CB\u7F16\u8BD1 ${book.name} ===`);
2491
+ let process2 = processPercentage(0.4);
2492
+ const Doc_blocks = yield* Effect4.tryPromise(() => allDocBlock_by_bookId(book.id));
2493
+ function refsNotUpdated(docBlock) {
2494
+ const refs = config.__skipBuilds__[docBlock.id]?.refs ?? [];
2495
+ for (const ref_id of refs) {
2496
+ const new_doc_hash = Doc_blocks.find(
2497
+ (docBlock2) => docBlock2.id === ref_id
2498
+ )?.hash;
2499
+ const old_doc_hash = config.__skipBuilds__[ref_id]?.hash;
2500
+ if (new_doc_hash === void 0 || old_doc_hash === void 0) {
2501
+ return false;
2502
+ } else if (new_doc_hash === old_doc_hash) {
2503
+ continue;
2504
+ } else {
2505
+ return false;
2506
+ }
2507
+ }
2508
+ return true;
2509
+ }
2510
+ effectLog.log(`=== \u67E5\u8BE2\u6587\u6863\u7EA7block\u5B8C\u6210 ===`);
2511
+ let i = 0;
2512
+ yield* Effect4.tryPromise(() => Promise.all(
2513
+ Doc_blocks.map(async (docBlock) => {
2514
+ const sy = await get_doc_by_SyPath(DB_block_path(docBlock));
2515
+ docTree[docBlock.hpath] = { sy, docBlock };
2516
+ i++;
2517
+ process2(i / Doc_blocks.length);
2518
+ })
2519
+ ));
2520
+ const fileTree = {};
2521
+ process2 = processPercentage(0.4);
2522
+ const enableIncrementalCompilation_doc = (() => {
2523
+ if (package_default.version !== config.OceanPress.version) {
2524
+ effectLog.log(
2525
+ `\u914D\u7F6E\u6587\u4EF6\u7248\u672C\u53F7[${config.OceanPress.version}]\u4E0EOceanPress\u7248\u672C[${package_default.version}]\u4E0D\u4E00\u81F4\uFF0C\u5C06\u8FDB\u884C\u6587\u6863\u5168\u91CF\u7F16\u8BD1`
2526
+ );
2527
+ return false;
2528
+ }
2529
+ return config.enableIncrementalCompilation_doc;
2530
+ })();
2531
+ effectLog.log(`=== \u5F00\u59CB\u6E32\u67D3\u6587\u6863 ===`);
2532
+ yield* Effect4.forEach(Object.entries(docTree), ([path, { sy, docBlock }]) => {
2533
+ return Effect4.gen(function* () {
2534
+ if (config.enableIncrementalCompilation && enableIncrementalCompilation_doc && /** 文档本身没有发生变化 */
2535
+ config.__skipBuilds__[docBlock.id]?.hash === docBlock.hash && /** docBlock所引用的文档也没有更新 */
2536
+ refsNotUpdated(docBlock)) {
2537
+ return "/** skip */";
1498
2538
  }
1499
- if (config.enableIncrementalCompilation && config.enableIncrementalCompilation_doc) {
2539
+ const renderInstance = yield* getRender;
2540
+ try {
2541
+ const rootLevel = path.split("/").length - 2;
2542
+ const htmlContent = yield* _renderHTML(sy, renderInstance);
2543
+ const pageUrl = config.sitemap.siteLink ? `${config.sitemap.siteLink}${path}.html` : `${path}.html`;
2544
+ fileTree[path + ".html"] = yield* Effect4.tryPromise(() => htmlTemplate(
2545
+ {
2546
+ title: sy.Properties?.title || "",
2547
+ htmlContent,
2548
+ level: rootLevel,
2549
+ seoData: {
2550
+ doc: sy,
2551
+ config,
2552
+ pageUrl,
2553
+ breadcrumbs: generateBreadcrumbs(path, config)
2554
+ }
2555
+ },
2556
+ {
2557
+ ...tempConfig.cdn,
2558
+ embedCode: config.embedCode
2559
+ }
2560
+ ));
2561
+ if (config.sitemap.rss && path.endsWith(".rss.xml")) {
2562
+ const rssPath = path;
2563
+ fileTree[rssPath] = yield* Effect4.tryPromise(() => generateRSSXML(rssPath, renderInstance, config, effectApi.getHPathByID_Node));
2564
+ effectLog.log(`\u6E32\u67D3 rss.xml:${rssPath} \u5B8C\u6BD5`);
2565
+ }
2566
+ if (config.enableIncrementalCompilation && config.enableIncrementalCompilation_doc) {
2567
+ skipBuilds.add(docBlock.id, {
2568
+ hash: docBlock.hash
2569
+ });
2570
+ }
1500
2571
  skipBuilds.add(docBlock.id, {
1501
- hash: docBlock.hash
2572
+ refs: (
2573
+ /** 保存引用 */
2574
+ [...renderInstance.refs.values()]
2575
+ )
1502
2576
  });
2577
+ effectLog.log(`\u6E32\u67D3\u5B8C\u6BD5:${path}`);
2578
+ } catch (error) {
2579
+ effectLog.log(`${path} \u6E32\u67D3\u5931\u8D25:${error}`);
2580
+ console.log(error);
1503
2581
  }
1504
- skipBuilds.add(docBlock.id, {
1505
- refs: (
1506
- /** 保存引用 */
1507
- [...renderInstance.refs.values()]
1508
- )
1509
- });
1510
- } catch (error) {
1511
- effect.log(`${path} \u6E32\u67D3\u5931\u8D25:${error}`);
1512
- console.log(error);
1513
- }
1514
- process2(i / Doc_blocks.length);
1515
- effect.log(`\u6E32\u67D3\u5B8C\u6BD5:${path}`);
1516
- })
1517
- );
1518
- effect.log(`=== \u6E32\u67D3\u6587\u6863\u5B8C\u6210 ===`);
1519
- effect.log(`=== \u5F00\u59CB\u751F\u6210 sitemap.xml ===`);
1520
- if (config.sitemap.enable) {
1521
- fileTree["sitemap.xml"] = sitemap_xml(Doc_blocks, config.sitemap);
1522
- }
1523
- if (config.excludeAssetsCopy === false) {
1524
- effect.log(`=== \u5F00\u59CB\u590D\u5236\u8D44\u6E90\u6587\u4EF6 ===`);
1525
- const assets = await API.query_sql({
1526
- stmt: `SELECT * from assets
1527
- WHERE box = '${book.id}'
1528
- limit 150000 OFFSET 0`
2582
+ process2(i / Doc_blocks.length);
2583
+ return `\u6E32\u67D3\u5B8C\u6BD5:${path}`;
2584
+ });
1529
2585
  });
1530
- await Promise.all(
1531
- assets.map(async (item) => {
1532
- if (config.enableIncrementalCompilation && /** 资源没有变化,直接跳过 */
1533
- config.__skipBuilds__[item.id]?.hash === item.hash) {
2586
+ effectLog.log(`=== \u6E32\u67D3\u6587\u6863\u5B8C\u6210 ===`);
2587
+ effectLog.log(`=== \u5F00\u59CB\u751F\u6210 sitemap.xml ===`);
2588
+ if (config.sitemap.enable) {
2589
+ fileTree["sitemap.xml"] = sitemap_xml(Doc_blocks, config.sitemap);
2590
+ }
2591
+ if (config.excludeAssetsCopy === false) {
2592
+ effectLog.log(`=== \u5F00\u59CB\u590D\u5236\u8D44\u6E90\u6587\u4EF6 ===`);
2593
+ const assets = yield* Effect4.tryPromise(() => API.query_sql({
2594
+ stmt: `SELECT * from assets
2595
+ WHERE box = '${book.id}'
2596
+ limit 150000 OFFSET 0`
2597
+ }));
2598
+ yield* Effect4.tryPromise(() => Promise.all(
2599
+ assets.map(async (item) => {
2600
+ if (config.enableIncrementalCompilation && /** 资源没有变化,直接跳过 */
2601
+ config.__skipBuilds__[item.id]?.hash === item.hash) {
2602
+ return;
2603
+ } else {
2604
+ fileTree[item.path] = await API.get_assets({
2605
+ path: item.path
2606
+ });
2607
+ if (config.enableIncrementalCompilation) {
2608
+ skipBuilds.add(item.id, { hash: item.hash });
2609
+ }
2610
+ }
2611
+ })
2612
+ ));
2613
+ effectLog.log(`=== \u5F00\u59CB\u590D\u5236\u6302\u4EF6\u8D44\u6E90\u6587\u4EF6 ===`);
2614
+ const widgetList = yield* Effect4.tryPromise(() => API.query_sql({
2615
+ stmt: `
2616
+ SELECT *
2617
+ from blocks
2618
+ WHERE box = '${book.id}'
2619
+ AND type = 'widget'
2620
+ limit 150000 OFFSET 0
2621
+ `
2622
+ }));
2623
+ const widgetNode = (yield* Effect4.tryPromise(() => Promise.all(
2624
+ widgetList.map(async (el) => await get_node_by_id(el.id))
2625
+ ))).filter(
2626
+ (widget) => widget?.Properties?.["custom-oceanpress-widget-update"]
2627
+ ).map(async (widget) => {
2628
+ if (!widget || !widget?.ID) return;
2629
+ const update = widget?.Properties?.["custom-oceanpress-widget-update"];
2630
+ if (config.enableIncrementalCompilation && config.__skipBuilds__[widget.ID]?.updated === update) {
1534
2631
  return;
1535
2632
  } else {
1536
- fileTree[item.path] = await API.get_assets({
1537
- path: item.path
2633
+ const id = widget.ID;
2634
+ fileTree[`assets/widget/${id}.jpg`] = await API.file_getFile({
2635
+ path: `data/storage/oceanpress/widget_img/${id}.jpg`
1538
2636
  });
1539
2637
  if (config.enableIncrementalCompilation) {
1540
- skipBuilds.add(item.id, { hash: item.hash });
2638
+ skipBuilds.add(id, { updated: update });
1541
2639
  }
1542
2640
  }
1543
- })
1544
- );
1545
- effect.log(`=== \u5F00\u59CB\u590D\u5236\u6302\u4EF6\u8D44\u6E90\u6587\u4EF6 ===`);
1546
- const widgetList = await API.query_sql({
1547
- stmt: `
1548
- SELECT *
1549
- from blocks
1550
- WHERE box = '${book.id}'
1551
- AND type = 'widget'
1552
- limit 150000 OFFSET 0
1553
- `
1554
- });
1555
- const widgetNode = (await Promise.all(
1556
- widgetList.map(async (el) => await get_node_by_id(el.id))
1557
- )).filter(
1558
- (widget) => widget?.Properties?.["custom-oceanpress-widget-update"]
1559
- ).map(async (widget) => {
1560
- if (!widget || !widget?.ID) return;
1561
- const update = widget?.Properties?.["custom-oceanpress-widget-update"];
1562
- if (config.enableIncrementalCompilation && config.__skipBuilds__[widget.ID]?.updated === update) {
1563
- return;
1564
- } else {
1565
- const id = widget.ID;
1566
- fileTree[`assets/widget/${id}.jpg`] = await API.file_getFile({
1567
- path: `data/storage/oceanpress/widget_img/${id}.jpg`
1568
- });
1569
- if (config.enableIncrementalCompilation) {
1570
- skipBuilds.add(id, { updated: update });
1571
- }
1572
- }
1573
- });
1574
- await Promise.all(widgetNode);
1575
- }
1576
- if (otherConfig?.onFileTree) {
1577
- otherConfig.onFileTree(fileTree);
1578
- }
1579
- if (config.compressedZip) {
1580
- effect.log(`=== \u5F00\u59CB\u751F\u6210\u538B\u7F29\u5305 ===`);
1581
- await downloadZIP(fileTree, {
1582
- // TODO 这里应该移出来成为全局的写选项
1583
- withoutZip: tempConfig.withoutPublicZip,
1584
- publicZip: tempConfig.cdn.publicZip
1585
- });
1586
- }
1587
- config.OceanPress.version = package_default.version;
1588
- skipBuilds.write();
1589
- effect.percentage(100);
1590
- effect.log("\u7F16\u8BD1\u5B8C\u6BD5");
1591
- return { fileTree };
2641
+ });
2642
+ yield* Effect4.tryPromise(() => Promise.all(widgetNode));
2643
+ }
2644
+ if (config.sidebarCode.enableDocTree) {
2645
+ effectLog.log(`=== \u5F00\u59CB\u751F\u6210\u6587\u6863\u6811 ===`);
2646
+ fileTree[renderDocTreeJsPath] = yield* renderDocTree();
2647
+ effectLog.log(`=== \u6587\u6863\u6811\u751F\u6210\u5B8C\u6210 ===`);
2648
+ }
2649
+ if (otherConfig?.onFileTree) {
2650
+ otherConfig.onFileTree(fileTree, effectLog);
2651
+ }
2652
+ if (config.compressedZip) {
2653
+ effectLog.log(`=== \u5F00\u59CB\u751F\u6210\u538B\u7F29\u5305 ===`);
2654
+ yield* Effect4.tryPromise(
2655
+ () => downloadZIP(fileTree, {
2656
+ // TODO 这里应该移出来成为全局的写选项
2657
+ withoutZip: tempConfig.withoutPublicZip,
2658
+ publicZip: tempConfig.cdn.publicZip
2659
+ })
2660
+ );
2661
+ }
2662
+ config.OceanPress.version = package_default.version;
2663
+ skipBuilds.write();
2664
+ effectLog.percentage(100);
2665
+ effectLog.log("\u7F16\u8BD1\u5B8C\u6BD5");
2666
+ return { fileTree };
2667
+ });
1592
2668
  }
1593
2669
  function useSkipBuilds() {
1594
2670
  const obj = {};
@@ -1606,56 +2682,73 @@ function useSkipBuilds() {
1606
2682
  };
1607
2683
  }
1608
2684
 
1609
- // src/plugins/publish/OceanPressServer.ts
1610
- import { createRPC } from "oceanpress-rpc";
1611
- import { stringify } from "superjson";
1612
- function deployOceanPressServer_plugin(config) {
1613
- const plugin = {
1614
- async build_onFileTree([tree], next) {
1615
- next(tree);
1616
- const client = await createRPC("apiConsumer", {
1617
- async remoteCall(method, data) {
1618
- let body;
1619
- let content_type;
1620
- if (data[0] instanceof ReadableStream) {
1621
- body = await new Response(data[0]).blob();
1622
- content_type = "application/octet-stream";
1623
- } else {
1624
- body = stringify(data);
1625
- console.log("[body]", body);
1626
- content_type = "application/json";
1627
- }
1628
- return fetch(`${config.oceanPressServer.apiBase}/api/${method}`, {
1629
- method: "POST",
1630
- body,
1631
- headers: {
1632
- "x-api-key": config.oceanPressServer.apiKey,
1633
- "Content-Type": content_type
1634
- },
1635
- // @ts-expect-error 在 node 运行的时候需要声明双工模式才能正确发送 ReadableStream,TODO 需要验证浏览器端可以这样运行吗
1636
- duplex: "half"
1637
- // 关键:显式声明半双工模式
1638
- }).then((res2) => res2.json()).then((r) => {
1639
- if (r.error) {
1640
- console.log("[r]", r);
1641
- throw new Error();
1642
- }
1643
- return r.result;
1644
- });
2685
+ // src/core/plugin.ts
2686
+ var PluginCenter = class {
2687
+ constructor(_funMap) {
2688
+ this._funMap = _funMap;
2689
+ __publicField(this, "plugins", []);
2690
+ /** 辅助类型,不可调用! */
2691
+ __publicField(this, "pluginType", 0);
2692
+ /** 对需要调用的函数进行代理,完成插件hook介入。 */
2693
+ __publicField(this, "fun");
2694
+ const that = this;
2695
+ this.fun = new Proxy({}, {
2696
+ get(_target, propertyKey, receiver) {
2697
+ const method = Reflect.get(that._funMap, propertyKey, receiver);
2698
+ if (typeof method === "function") {
2699
+ return (...args) => {
2700
+ return that.callFn(
2701
+ propertyKey,
2702
+ //@ts-ignore 懒得推类型了。属于内部实现,就直接忽略掉吧
2703
+ method
2704
+ )(...args);
2705
+ };
1645
2706
  }
1646
- });
1647
- const zip = await genZIP(tree, { withoutZip: true });
1648
- const sizeInMB = zip.size / (1024 * 1024);
1649
- console.log("[zip.size in MB]", sizeInMB.toFixed(2));
1650
- const readableStream = zip.stream();
1651
- const { chunkCount, fileId } = await client.API.upload(readableStream);
1652
- console.log("[res]", { chunkCount, fileId });
1653
- const res = await client.API.deploy({ zipFileId: fileId });
1654
- console.log("[deploy res]", res);
1655
- }
1656
- };
1657
- return plugin;
1658
- }
2707
+ return method;
2708
+ }
2709
+ });
2710
+ }
2711
+ registerPlugin(plugin) {
2712
+ this.plugins.push(plugin);
2713
+ }
2714
+ removePlugin(plugin) {
2715
+ this.plugins = this.plugins.filter((p) => p !== plugin);
2716
+ }
2717
+ /** 洋葱hook调用机制的实现 */
2718
+ callFn(name, fn) {
2719
+ return (...arg) => {
2720
+ const m = new middlewareRunner(fn);
2721
+ for (const plugin of this.plugins) {
2722
+ const middleware = plugin[name];
2723
+ if (middleware) {
2724
+ m.use(middleware);
2725
+ }
2726
+ }
2727
+ return m.runMiddlewareHandel(...arg);
2728
+ };
2729
+ }
2730
+ };
2731
+ var middlewareRunner = class {
2732
+ constructor(handel) {
2733
+ this.handel = handel;
2734
+ __publicField(this, "middlewares", []);
2735
+ }
2736
+ use(middleware) {
2737
+ this.middlewares.push(middleware);
2738
+ }
2739
+ runMiddlewareHandel(...ctx) {
2740
+ let index = 0;
2741
+ const next = (...ctx2) => {
2742
+ const middleware = this.middlewares[index];
2743
+ index++;
2744
+ if (middleware === void 0) {
2745
+ return this.handel.call(this, ...ctx2);
2746
+ }
2747
+ return middleware(ctx2, next);
2748
+ };
2749
+ return next.call(this, ...ctx);
2750
+ }
2751
+ };
1659
2752
 
1660
2753
  // src/core/ocean_press.ts
1661
2754
  var OceanPress = class {
@@ -1667,7 +2760,7 @@ var OceanPress = class {
1667
2760
  /** 用于渲染文档的函数 */
1668
2761
  build_renderHTML: renderHTML,
1669
2762
  /** 编译完成后文件树的处理回调函数 */
1670
- build_onFileTree: (_tree) => {
2763
+ build_onFileTree: (_tree, _effectApi) => {
1671
2764
  }
1672
2765
  });
1673
2766
  __publicField(this, "pluginCenter", new PluginCenter(
@@ -1687,8 +2780,8 @@ var OceanPress = class {
1687
2780
  );
1688
2781
  }
1689
2782
  }
1690
- async build(effect) {
1691
- const build_res = this.pluginCenter.fun.build(this.config, effect, {
2783
+ build() {
2784
+ const build_res = this.pluginCenter.fun.build(this.config, {
1692
2785
  renderHtmlFn: this.pluginCenter.fun.build_renderHTML,
1693
2786
  onFileTree: this.pluginCenter.fun.build_onFileTree
1694
2787
  });
@@ -1697,45 +2790,52 @@ var OceanPress = class {
1697
2790
  };
1698
2791
 
1699
2792
  // src/core/render.api.dep.ts
1700
- storeDep.getDocByChildID = async (id) => {
1701
- return await get_doc_by_child_id(id);
1702
- };
1703
- storeDep.getDocPathBySY = async (sy) => {
1704
- if (sy?.ID) {
1705
- const block = await get_block_by_id(sy.ID);
1706
- if (block) {
1707
- return DB_block_path(block);
2793
+ var renderApiDep = {
2794
+ getDocByChildID: async (id) => {
2795
+ return await get_doc_by_child_id(id);
2796
+ },
2797
+ getDocPathBySY: async (sy) => {
2798
+ if (sy?.ID) {
2799
+ const block = await get_block_by_id(sy.ID);
2800
+ if (block) {
2801
+ return DB_block_path(block);
2802
+ }
1708
2803
  }
1709
- }
1710
- };
1711
- storeDep.getHPathByID_Node = async (id_node) => {
1712
- const id = typeof id_node === "string" ? id_node : id_node.ID;
1713
- if (id === void 0) throw new Error("id is undefined");
1714
- const docNode = await get_doc_by_child_id(id);
1715
- if (docNode === void 0) throw new Error("docNode is undefined");
1716
- const docBlock = await get_block_by_id(id);
1717
- if (docBlock === void 0) throw new Error("docBlock is undefined");
1718
- return docBlock.hpath;
1719
- };
1720
- storeDep.getNodeByID = async (id) => {
1721
- if (id === void 0) return;
1722
- const doc = await storeDep.getDocByChildID(id);
1723
- if (doc === void 0) return;
1724
- return getNode(doc);
1725
- function getNode(node) {
1726
- if (node.ID === id) return node;
1727
- if (node.Children === void 0) return;
1728
- for (const child of node.Children) {
1729
- const n = getNode(child);
1730
- if (n) return n;
2804
+ },
2805
+ /** TODO 应该可以从cache中获取,而不需要查询 */
2806
+ getHPathByID_Node: async (id_node) => {
2807
+ const id = typeof id_node === "string" ? id_node : id_node.ID;
2808
+ if (id === void 0) throw new Error("id is undefined");
2809
+ const docNode = await get_doc_by_child_id(id);
2810
+ if (docNode === void 0) throw new Error("docNode is undefined");
2811
+ const docBlock = await get_block_by_id(id);
2812
+ if (docBlock === void 0) throw new Error("docBlock is undefined");
2813
+ return docBlock.hpath;
2814
+ },
2815
+ getNodeByID: async (id) => {
2816
+ if (id === void 0) return;
2817
+ const doc = await renderApiDep.getDocByChildID(id);
2818
+ if (doc === void 0) return;
2819
+ return getNode(doc);
2820
+ function getNode(node) {
2821
+ if (node.ID === id) return node;
2822
+ if (node.Children === void 0) return;
2823
+ for (const child of node.Children) {
2824
+ const n = getNode(child);
2825
+ if (n) return n;
2826
+ }
1731
2827
  }
2828
+ },
2829
+ log(msg) {
2830
+ console.log(msg);
2831
+ },
2832
+ percentage: (n) => {
2833
+ console.log(n);
1732
2834
  }
1733
2835
  };
1734
2836
 
1735
2837
  // src/util/store.node.dep.ts
1736
- import { writeFileSync, readFileSync, existsSync, mkdirSync } from "node:fs";
1737
- storeDep.getItem = getItem;
1738
- storeDep.setItem = setItem;
2838
+ import { writeFileSync, readFileSync, existsSync, mkdirSync } from "fs";
1739
2839
  function setItem(key, value) {
1740
2840
  if (!existsSync("./store/")) {
1741
2841
  mkdirSync("./store/", { recursive: true });
@@ -1751,6 +2851,10 @@ function getItem(key) {
1751
2851
  return void 0;
1752
2852
  }
1753
2853
  }
2854
+ var nodeApiDep = {
2855
+ setItem,
2856
+ getItem
2857
+ };
1754
2858
 
1755
2859
  // src/cli/common.ts
1756
2860
  import { Command } from "commander";
@@ -1758,13 +2862,13 @@ var program = new Command();
1758
2862
  program.name("OceanPress").description("\u8FD9\u662F\u4E00\u6B3E\u4ECE\u601D\u6E90\u7B14\u8BB0\u672C\u751F\u6210\u4E00\u4E2A\u9759\u6001\u7AD9\u70B9\u7684\u5DE5\u5177");
1759
2863
 
1760
2864
  // src/cli/deploy.ts
2865
+ import { Context as Context3, Effect as Effect5 } from "effect";
1761
2866
  program.command("deploy").description("\u90E8\u7F72\u7AD9\u70B9").option("-c, --config <string>", "\u6307\u5B9A\u914D\u7F6E\u6587\u4EF6\u7684\u4F4D\u7F6E").option("-h, --apiBase <string>", "OceanPress server \u5730\u5740").option("-k, --apiKey <string>", "OceanPress server Api \u5BC6\u94A5").action(async (opt) => {
1762
2867
  if (!opt.apiBase || !opt.apiKey) {
1763
2868
  return console.error(`\u8BF7\u914D\u7F6E apiBase \u548C apiKey`);
1764
2869
  }
1765
2870
  const config = await readFile(opt.config, "utf-8");
1766
- loadConfigFile(JSON.parse(config));
1767
- const client = await createRPC2("apiConsumer", {
2871
+ const client = await createRPC("apiConsumer", {
1768
2872
  remoteCall(method, data) {
1769
2873
  let body;
1770
2874
  let content_type;
@@ -1795,79 +2899,115 @@ program.command("deploy").description("\u90E8\u7F72\u7AD9\u70B9").option("-c, --
1795
2899
  });
1796
2900
  }
1797
2901
  });
1798
- const ocean_press = new OceanPress(currentConfig.value);
1799
- ocean_press.pluginCenter.registerPlugin({
1800
- async build_onFileTree([tree], next) {
1801
- const zip = await genZIP(tree, { withoutZip: true });
1802
- const sizeInMB = zip.size / (1024 * 1024);
1803
- console.log("[zip.size in MB]", sizeInMB.toFixed(2));
1804
- const readableStream = zip.stream();
1805
- const { chunkCount, fileId } = await client.API.upload(readableStream);
1806
- console.log("[res]", { chunkCount, fileId });
1807
- const res = await client.API.deploy({ zipFileId: fileId });
1808
- console.log("[deploy res]", res);
1809
- }
1810
- });
1811
- await ocean_press.build({
1812
- log: (msg) => {
1813
- if (msg.startsWith("\u6E32\u67D3\uFF1A")) {
1814
- process.stdout.write(`\r\x1B[K${msg}`);
1815
- } else {
1816
- process.stdout.write(`
2902
+ const context = Context3.empty().pipe(
2903
+ Context3.add(EffectRender, renderApiDep),
2904
+ Context3.add(EffectLocalStorageDep, nodeApiDep),
2905
+ Context3.add(EffectConfigDep, currentConfig.value),
2906
+ Context3.add(EffectLogDep, {
2907
+ log: (msg) => {
2908
+ if (msg.startsWith("\u6E32\u67D3\uFF1A")) {
2909
+ process.stdout.write(`\r\x1B[K${msg}`);
2910
+ } else {
2911
+ process.stdout.write(`
1817
2912
  ${msg}`);
2913
+ }
2914
+ },
2915
+ percentage: (n) => {
2916
+ process.stdout.write(`\r\x1B[K\u8FDB\u5EA6\uFF1A${n}%`);
1818
2917
  }
1819
- },
1820
- percentage: (n) => {
1821
- process.stdout.write(`\r\x1B[K\u8FDB\u5EA6\uFF1A${n}%`);
1822
- }
1823
- });
2918
+ })
2919
+ );
2920
+ const p = Effect5.provide(
2921
+ Effect5.gen(function* () {
2922
+ yield* loadConfigFile(JSON.parse(config));
2923
+ const ocean_press = new OceanPress(currentConfig.value);
2924
+ ocean_press.pluginCenter.registerPlugin({
2925
+ async build_onFileTree([tree], next) {
2926
+ const zip = await genZIP(tree, { withoutZip: true });
2927
+ const sizeInMB = zip.size / (1024 * 1024);
2928
+ console.log("[zip.size in MB]", sizeInMB.toFixed(2));
2929
+ const readableStream = zip.stream();
2930
+ const { chunkCount, fileId } = await client.API.upload(
2931
+ readableStream
2932
+ );
2933
+ console.log("[res]", { chunkCount, fileId });
2934
+ const res = await client.API.deploy({ zipFileId: fileId });
2935
+ console.log("[deploy res]", res);
2936
+ }
2937
+ });
2938
+ return yield* ocean_press.build();
2939
+ }),
2940
+ context
2941
+ );
2942
+ await Effect5.runPromise(p);
1824
2943
  });
1825
2944
 
1826
2945
  // src/cli/build.ts
1827
2946
  import { mkdir, readFile as readFile2, writeFile } from "fs/promises";
1828
2947
  import { resolve } from "path";
1829
2948
  import { join } from "path/posix";
2949
+ import { Context as Context4, Effect as Effect6 } from "effect";
1830
2950
  program.command("build").description("\u8F93\u51FA\u9759\u6001\u7AD9\u70B9\u6E90\u7801").option("-c, --config <string>", "\u6307\u5B9A\u914D\u7F6E\u6587\u4EF6\u7684\u4F4D\u7F6E").option("-o, --output <string>", "\u6307\u5B9A\u8F93\u51FA\u76EE\u5F55\u4F4D\u7F6E").action(async (opt) => {
1831
2951
  if (!opt.config || !opt.output) {
1832
2952
  console.log(`\u8BF7\u8BBE\u7F6E\u914D\u7F6E\u6587\u4EF6\u4F4D\u7F6E\u548C\u8F93\u51FA\u76EE\u5F55\u4F4D\u7F6E`);
1833
2953
  throw new Error("\u8BF7\u8BBE\u7F6E\u914D\u7F6E\u6587\u4EF6\u4F4D\u7F6E\u548C\u8F93\u51FA\u76EE\u5F55\u4F4D\u7F6E");
1834
2954
  }
1835
2955
  const config = await readFile2(opt.config, "utf-8");
1836
- loadConfigFile(JSON.parse(config));
1837
2956
  const filePath = resolve(opt.output);
1838
- const ocean_press = new OceanPress(currentConfig.value);
1839
- ocean_press.pluginCenter.registerPlugin({
1840
- async build_onFileTree([tree]) {
1841
- for (const [path, data] of Object.entries(tree)) {
1842
- const fullPath = join(filePath, "./", path);
1843
- const pathArray = fullPath.split("/").slice(0, -1);
1844
- const dirPath = pathArray.join("/");
1845
- mkdir(dirPath, { recursive: true });
1846
- try {
1847
- if (typeof data === "string") {
1848
- await writeFile(fullPath, data, "utf-8");
1849
- } else {
1850
- await writeFile(fullPath, new DataView(data));
1851
- }
1852
- } catch (error) {
1853
- console.log(`${fullPath} \u65E0\u6CD5\u5199\u5165`);
1854
- }
1855
- }
1856
- }
1857
- });
1858
- await ocean_press.build({
1859
- log: (msg) => {
1860
- if (msg.startsWith("\u6E32\u67D3\uFF1A")) {
1861
- process.stdout.write(`\r\x1B[K${msg}`);
1862
- } else {
1863
- process.stdout.write(`
2957
+ await Effect6.runPromise(
2958
+ Effect6.provideService(
2959
+ loadConfigFile(JSON.parse(config)),
2960
+ EffectLocalStorageDep,
2961
+ nodeApiDep
2962
+ )
2963
+ );
2964
+ const context = Context4.empty().pipe(
2965
+ Context4.add(EffectRender, renderApiDep),
2966
+ Context4.add(EffectLocalStorageDep, nodeApiDep),
2967
+ Context4.add(EffectConfigDep, currentConfig.value),
2968
+ Context4.add(EffectLogDep, {
2969
+ log: (msg) => {
2970
+ if (msg.startsWith("\u6E32\u67D3\uFF1A")) {
2971
+ process.stdout.write(`\r\x1B[K${msg}`);
2972
+ } else {
2973
+ process.stdout.write(`
1864
2974
  ${msg}`);
2975
+ }
2976
+ },
2977
+ percentage: (n) => {
2978
+ process.stdout.write(`\r\x1B[K\u8FDB\u5EA6\uFF1A${n}%`);
1865
2979
  }
1866
- },
1867
- percentage: (n) => {
1868
- process.stdout.write(`\r\x1B[K\u8FDB\u5EA6\uFF1A${n}%`);
1869
- }
1870
- });
2980
+ })
2981
+ );
2982
+ const p = Effect6.provide(
2983
+ Effect6.gen(function* () {
2984
+ const ocean_press = new OceanPress(currentConfig.value);
2985
+ ocean_press.pluginCenter.registerPlugin({
2986
+ async build_onFileTree([tree]) {
2987
+ for (const [path, data] of Object.entries(tree)) {
2988
+ const fullPath = join(filePath, "./", path);
2989
+ const pathArray = fullPath.split("/").slice(0, -1);
2990
+ const dirPath = pathArray.join("/");
2991
+ mkdir(dirPath, { recursive: true });
2992
+ try {
2993
+ if (typeof data === "string") {
2994
+ await writeFile(fullPath, data, "utf-8");
2995
+ } else {
2996
+ await writeFile(fullPath, new DataView(data));
2997
+ }
2998
+ } catch (error) {
2999
+ console.log(`${fullPath} \u65E0\u6CD5\u5199\u5165`);
3000
+ }
3001
+ }
3002
+ }
3003
+ });
3004
+ console.log("[config.__current__]", JSON.parse(config).__current__);
3005
+ console.log("[currentConfig.value.name]", currentConfig.value.name);
3006
+ return yield* ocean_press.build();
3007
+ }),
3008
+ context
3009
+ );
3010
+ await Effect6.runPromise(p);
1871
3011
  });
1872
3012
 
1873
3013
  // src/cli/server.ts
@@ -1875,28 +3015,35 @@ import { readFile as readFile3 } from "fs/promises";
1875
3015
 
1876
3016
  // src/server.ts
1877
3017
  import { serve } from "@hono/node-server";
3018
+ import { serveStatic } from "@hono/node-server/serve-static";
3019
+ import { Effect as Effect8 } from "effect";
3020
+ import { Hono as Hono2 } from "hono";
1878
3021
 
1879
3022
  // src/core/hono_server.ts
1880
3023
  import { Hono } from "hono";
1881
3024
  import { stream } from "hono/streaming";
1882
- function createHonoApp(app = new Hono()) {
3025
+ import { Effect as Effect7, Context as Context5 } from "effect";
3026
+ function createHonoApp(app = new Hono(), renderapi) {
3027
+ const context = Context5.empty().pipe(
3028
+ Context5.add(EffectRender, renderapi),
3029
+ Context5.add(EffectConfigDep, currentConfig.value)
3030
+ );
3031
+ app.get(renderDocTreeJsPath, async (c) => {
3032
+ const p = Effect7.provide(renderDocTree(), context);
3033
+ const r = await Effect7.runPromise(p);
3034
+ return c.text(r, 200, {
3035
+ "Content-Type": "application/javascript",
3036
+ "Cache-Control": "public, max-age=3600"
3037
+ });
3038
+ });
1883
3039
  app.get("/", (c) => c.redirect("/index.html"));
1884
3040
  app.get("/assets/*", assetsHandle);
3041
+ app.get("/favicon.ico", assetsHandle);
1885
3042
  app.get("*", async (c) => {
1886
3043
  const path = decodeURIComponent(c.req.path);
1887
- const r = await renderHtmlByUriPath(path).catch(async (err) => {
1888
- if (err.message.includes("not doc")) {
1889
- return await assetsHandle(c);
1890
- }
1891
- throw err;
1892
- });
1893
- if (r instanceof Error) {
1894
- throw r;
1895
- } else if (typeof r === "string") {
1896
- return c.html(r);
1897
- } else {
1898
- return r;
1899
- }
3044
+ const p = Effect7.provide(renderHtmlByUriPath(path), context);
3045
+ const r = await Effect7.runPromise(p);
3046
+ return c.html(r);
1900
3047
  });
1901
3048
  return app;
1902
3049
  }
@@ -1934,55 +3081,60 @@ async function assetsHandle(c) {
1934
3081
  }
1935
3082
  });
1936
3083
  }
1937
- async function renderHtmlByUriPath(path) {
1938
- const hpath = decodeURIComponent(path).replace(/\#(.*)?$/, "").replace(/\.html$/, "");
1939
- const doc = await get_doc_by_hpath(hpath);
1940
- return await htmlTemplate(
1941
- {
1942
- title: doc.Properties?.title || "",
1943
- htmlContent: await renderHTML(doc),
1944
- level: path.split("/").length - 1
1945
- },
1946
- {
1947
- ...tempConfig.cdn,
1948
- embedCode: currentConfig.value.embedCode
1949
- }
1950
- );
3084
+ function renderHtmlByUriPath(path) {
3085
+ return Effect7.gen(function* () {
3086
+ const hpath = decodeURIComponent(path).replace(/\#(.*)?$/, "").replace(/\.html$/, "");
3087
+ const doc = yield* Effect7.tryPromise(() => get_doc_by_hpath(hpath));
3088
+ const htmlContent = yield* renderHTML(doc);
3089
+ return yield* Effect7.tryPromise(
3090
+ () => htmlTemplate(
3091
+ {
3092
+ title: doc.Properties?.title || "",
3093
+ htmlContent,
3094
+ level: path.split("/").length - 1
3095
+ },
3096
+ {
3097
+ ...tempConfig.cdn,
3098
+ embedCode: currentConfig.value.embedCode
3099
+ }
3100
+ )
3101
+ );
3102
+ });
1951
3103
  }
1952
3104
 
1953
3105
  // src/server.ts
1954
- import { serveStatic } from "@hono/node-server/serve-static";
1955
- import { Hono as Hono2 } from "hono";
1956
- import { join as join2 } from "path/posix";
1957
- console.log(join2(import.meta.url.slice(5), "../../public/"));
1958
3106
  function server(config = { port: 80, hostname: "0.0.0.0" }) {
1959
- const app = new Hono2();
1960
- app.use(
1961
- "/notebook/*",
1962
- serveStatic({
1963
- root: "./public/",
1964
- onNotFound(path, c) {
1965
- console.log("[onNotFound notebook path]", path);
1966
- }
1967
- })
1968
- );
1969
- createHonoApp(app);
1970
- return new Promise((resolve3, _reject) => {
1971
- serve(
1972
- {
1973
- fetch: app.fetch,
1974
- port: config.port,
1975
- hostname: config.hostname
1976
- },
1977
- (info) => {
1978
- resolve3({ info, app });
1979
- console.log(`Listening on http://${info.address}:${info.port}`);
1980
- }
3107
+ return Effect8.gen(function* () {
3108
+ const app = new Hono2();
3109
+ app.use(
3110
+ "/notebook/*",
3111
+ serveStatic({
3112
+ root: "./public/",
3113
+ onNotFound(path, c) {
3114
+ console.log("[onNotFound notebook path]", path);
3115
+ }
3116
+ })
1981
3117
  );
3118
+ const effectDep = yield* EffectRender;
3119
+ createHonoApp(app, effectDep);
3120
+ return new Promise((resolve2, _reject) => {
3121
+ serve(
3122
+ {
3123
+ fetch: app.fetch,
3124
+ port: config.port,
3125
+ hostname: config.hostname
3126
+ },
3127
+ (info) => {
3128
+ resolve2({ info, app });
3129
+ console.log(`Listening on http://${info.address}:${info.port}`);
3130
+ }
3131
+ );
3132
+ });
1982
3133
  });
1983
3134
  }
1984
3135
 
1985
3136
  // src/cli/server.ts
3137
+ import { Context as Context6, Effect as Effect9 } from "effect";
1986
3138
  program.command("server").description("\u542F\u52A8\u52A8\u6001\u4EE3\u7406").option("-c, --config <string>", "\u6307\u5B9A\u914D\u7F6E\u6587\u4EF6\u7684\u4F4D\u7F6E").option("-h, --host <string>", "web\u670D\u52A1\u7ED1\u5B9A\u5230\u7684\u5730\u5740", "127.0.0.1").option("-p, --port <number>", "web\u670D\u52A1\u7ED1\u5B9A\u5230\u7684\u7AEF\u53E3", "80").option(
1987
3139
  "--cache <boolean>",
1988
3140
  "\u914D\u7F6E\u4E3A true \u65F6\u5F00\u542F\u7F13\u5B58,\u9ED8\u8BA4\u4E3A false \u4E0D\u5F00\u542F\u7F13\u5B58",
@@ -1993,12 +3145,36 @@ program.command("server").description("\u542F\u52A8\u52A8\u6001\u4EE3\u7406").op
1993
3145
  console.log(`\u8BF7\u8BBE\u7F6E\u914D\u7F6E\u6587\u4EF6\u4F4D\u7F6E`);
1994
3146
  }
1995
3147
  const config = await readFile3(opt.config, "utf-8");
1996
- loadConfigFile(JSON.parse(config));
1997
3148
  setCache(opt.cache !== "false");
1998
- server({
1999
- hostname: opt.host,
2000
- port: Number(opt.port)
2001
- });
3149
+ const context = Context6.empty().pipe(
3150
+ Context6.add(EffectRender, renderApiDep),
3151
+ Context6.add(EffectLocalStorageDep, nodeApiDep),
3152
+ Context6.add(EffectLogDep, {
3153
+ log: (msg) => {
3154
+ if (msg.startsWith("\u6E32\u67D3\uFF1A")) {
3155
+ process.stdout.write(`\r\x1B[K${msg}`);
3156
+ } else {
3157
+ process.stdout.write(`
3158
+ ${msg}`);
3159
+ }
3160
+ },
3161
+ percentage: (n) => {
3162
+ process.stdout.write(`\r\x1B[K\u8FDB\u5EA6\uFF1A${n}%`);
3163
+ }
3164
+ })
3165
+ );
3166
+ const p = Effect9.provide(
3167
+ Effect9.gen(function* () {
3168
+ yield* loadConfigFile(JSON.parse(config));
3169
+ tempConfig.cdn.siyuanPrefix = "/notebook/";
3170
+ return yield* server({
3171
+ hostname: opt.host,
3172
+ port: Number(opt.port)
3173
+ });
3174
+ }),
3175
+ context
3176
+ );
3177
+ await Effect9.runPromise(p);
2002
3178
  }
2003
3179
  );
2004
3180