oceanpress 1.0.4 → 1.0.10

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
@@ -70,61 +70,25 @@ async function createRPC(...[type, options]) {
70
70
  // src/cli/deploy.ts
71
71
  import { stringify as stringify2 } from "superjson";
72
72
 
73
- // src/core/dependency.ts
74
- var nullDep = () => {
75
- throw new Error("\u4E0D\u53EF\u8C03\u7528\u672A\u586B\u5145\u7684\u4F9D\u8D56");
76
- };
77
- var storeDep = {
78
- // 读写配置文件所依赖的副作用
79
- setItem: nullDep,
80
- getItem: nullDep,
81
- // render功能依赖的副作用
82
- getNodeByID: nullDep,
83
- getDocPathBySY: nullDep,
84
- getDocByChildID: nullDep,
85
- getHPathByID_Node: nullDep
86
- };
87
-
88
- // src/util/deep_assign.ts
89
- function deepAssign(target, source, config = { add: true, update: true }) {
90
- for (let key in source) {
91
- if (source.hasOwnProperty(key)) {
92
- if (source[key] instanceof Object && !Array.isArray(source[key])) {
93
- if (!target.hasOwnProperty(key)) {
94
- target[key] = {};
95
- }
96
- deepAssign(target[key], source[key], config);
97
- } else {
98
- if (!target.hasOwnProperty(key) && config.add) {
99
- target[key] = source[key];
100
- } else if (config.update) {
101
- target[key] = source[key];
102
- }
103
- }
104
- }
105
- }
106
- return target;
107
- }
108
-
109
73
  // src/core/config.ts
74
+ import { Effect } from "effect";
110
75
  import { computed, reactive, watch } from "vue";
111
76
 
112
77
  // package.json
113
78
  var package_default = {
114
79
  name: "oceanpress",
115
- version: "1.0.4",
80
+ version: "1.0.9",
116
81
  type: "module",
117
82
  scripts: {
118
83
  dev: "vite",
119
84
  cli: "tsx ./src/cli.ts",
120
- cli_watch: "tsx watch ./src/cli.ts",
85
+ "cli-w": "tsx watch ./src/cli.ts",
121
86
  build: "vite build && npm run build_lib",
122
87
  build_lib: "vite build --config vite.sw.config.ts",
123
88
  build_app: "vite build --mode library",
124
89
  build_cli: "tsup",
125
90
  build_plugin_ui: "vite build --config vite.plugin.config.ts",
126
91
  dev_plugin_ui: "vite build --watch --config vite.plugin.config.ts",
127
- generate_dependency_graph: "depcruise src --include-only '^src' --output-type dot > ./assets/dep.dot",
128
92
  preview: "vite preview"
129
93
  },
130
94
  bin: {
@@ -142,39 +106,73 @@ var package_default = {
142
106
  "*.md"
143
107
  ],
144
108
  dependencies: {
145
- "@aws-sdk/client-s3": "^3.758.0",
146
- "@hono/node-server": "^1.13.8",
147
- cheerio: "1.0.0",
148
- commander: "^13.1.0",
109
+ "@aws-sdk/client-s3": "^3.873.0",
110
+ "@hono/node-server": "^1.19.0",
111
+ cheerio: "1.1.2",
112
+ commander: "^14.0.0",
113
+ effect: "^3.17.8",
149
114
  fzstd: "^0.1.1",
150
- hono: "^4.7.4",
115
+ hono: "^4.9.4",
151
116
  jszip: "^3.10.1",
152
- meilisearch: "^0.49.0",
153
- "naive-ui": "^2.41.0",
154
- octokit: "^4.1.2",
117
+ meilisearch: "^0.52.0",
118
+ "naive-ui": "^2.42.0",
119
+ octokit: "^5.0.3",
155
120
  superjson: "^2.2.2",
156
- tsx: "^4.19.3",
157
- vditor: "^3.10.9",
158
- vue: "^3.5.13",
159
- "vue-router": "^4.5.0",
121
+ tsx: "^4.20.4",
122
+ vditor: "^3.11.1",
123
+ vue: "^3.5.19",
124
+ "vue-router": "^4.5.1",
160
125
  "zstd-codec": "^0.1.5"
161
126
  },
162
127
  devDependencies: {
128
+ "@vitejs/plugin-vue": "^6.0.1",
129
+ "@vitejs/plugin-vue-jsx": "^5.0.1",
130
+ "dependency-cruiser": "^17.0.1",
163
131
  "oceanpress-rpc": "workspace:*",
164
132
  "oceanpress-server": "workspace:*",
165
- "@vitejs/plugin-vue": "^5.2.1",
166
- "@vitejs/plugin-vue-jsx": "^4.1.1",
167
- "dependency-cruiser": "^16.10.0",
168
- tsup: "^8.4.0",
169
- typescript: "^5.8.2",
170
- vite: "^6.2.2",
171
- "vite-plugin-vue-devtools": "^7.7.2",
172
- "vue-tsc": "^2.2.8"
133
+ tsup: "^8.5.0",
134
+ typescript: "^5.9.2",
135
+ vite: "^7.1.3",
136
+ "vite-plugin-vue-devtools": "^8.0.0",
137
+ "vue-tsc": "^3.0.6"
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
+ }
173
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")() {
174
171
  };
175
172
 
176
173
  // src/core/config.ts
177
174
  var version = package_default.version;
175
+ console.log("[version]", version);
178
176
  var defaultConfig = {
179
177
  name: "default",
180
178
  /** 需要编译的笔记本 */
@@ -226,7 +224,7 @@ var defaultConfig = {
226
224
  // publicZip:
227
225
  // 'https://fastly.jsdelivr.net/gh/siyuan-note/oceanpress@v0.0.7/apps/frontend/public/public.zip',
228
226
  // },
229
- /** s3 上传配置
227
+ /** 部署到 s3 上传配置
230
228
  * https://help.aliyun.com/zh/oss/developer-reference/use-amazon-s3-sdks-to-access-oss#section-2ri-suq-pb3
231
229
  */
232
230
  s3: {
@@ -263,6 +261,15 @@ var defaultConfig = {
263
261
  </p>
264
262
  </footer>`
265
263
  },
264
+ /** 侧边栏配置 */
265
+ sidebarCode: {
266
+ /** 启用文档树,则 leftCode 将插入在文档树上方 */
267
+ enableDocTree: true,
268
+ /** 将插入主文档的左侧div中的html代码 */
269
+ leftCode: "",
270
+ /** 将插入主文档的右侧div中的html代码 */
271
+ rightCode: ""
272
+ },
266
273
  OceanPress: {
267
274
  /** 此配置文件编译时的版本 */
268
275
  version
@@ -276,47 +283,49 @@ var configs = reactive({
276
283
  default: deepAssign({}, defaultConfig)
277
284
  });
278
285
  var loadConfigFile = (c) => {
279
- if (c) {
280
- deepAssign(configs, c);
281
- } else {
282
- const localConfig = storeDep.getItem("configs");
283
- if (localConfig) {
284
- 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
+ }
285
295
  }
286
- }
287
- Object.entries(configs).filter(([key]) => key.startsWith("__") === false).forEach(([_key, obj]) => {
288
- 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;
289
315
  });
290
316
  };
291
317
  var currentConfig = computed(() => configs[configs.__current__]);
292
318
  var tempConfig = {
293
319
  cdn: {
294
320
  /** 思源 js、css等文件的前缀 */
295
- 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/',
296
323
  /** 思源 js、css等文件zip包地址 */
297
324
  publicZip: "https://fastly.jsdelivr.net/gh/siyuan-note/oceanpress@v0.0.7/apps/frontend/public/public.zip"
298
325
  },
299
326
  withoutPublicZip: true
300
327
  };
301
- var saveConfig = () => {
302
- if (configs.__init__ === false)
303
- storeDep.setItem("configs", JSON.stringify(configs, null, 2));
304
- };
305
- var timer = null;
306
- var debounceSaveConfig = () => {
307
- if (timer) {
308
- clearTimeout(timer);
309
- }
310
- timer = setTimeout(() => {
311
- saveConfig();
312
- timer = null;
313
- }, 700);
314
- };
315
- watch(configs, debounceSaveConfig, { deep: true });
316
328
  configs.__init__ = false;
317
- if (globalThis.document) {
318
- loadConfigFile();
319
- }
320
329
 
321
330
  // src/core/genZip.ts
322
331
  import JSZip from "jszip";
@@ -382,7 +391,7 @@ var MeilisearchPlugin = class {
382
391
  if (path.endsWith("index.html")) break;
383
392
  }
384
393
  this.updateDocument();
385
- return next(tree);
394
+ return next(...c);
386
395
  });
387
396
  this.host = option.host;
388
397
  this.apiKey = option.apiKey;
@@ -415,91 +424,72 @@ var MeilisearchPlugin = class {
415
424
  }
416
425
  };
417
426
 
418
- // src/core/plugin.ts
419
- var PluginCenter = class {
420
- constructor(_funMap) {
421
- this._funMap = _funMap;
422
- __publicField(this, "plugins", []);
423
- /** 辅助类型,不可调用! */
424
- __publicField(this, "pluginType", 0);
425
- /** 对需要调用的函数进行代理,完成插件hook介入。 */
426
- __publicField(this, "fun");
427
- const that = this;
428
- this.fun = new Proxy({}, {
429
- get(_target, propertyKey, receiver) {
430
- const method = Reflect.get(that._funMap, propertyKey, receiver);
431
- if (typeof method === "function") {
432
- return (...args) => {
433
- return that.callFn(
434
- propertyKey,
435
- //@ts-ignore 懒得推类型了。属于内部实现,就直接忽略掉吧
436
- method
437
- )(...args);
438
- };
439
- }
440
- return method;
441
- }
442
- });
443
- }
444
- registerPlugin(plugin) {
445
- this.plugins.push(plugin);
446
- }
447
- removePlugin(plugin) {
448
- this.plugins = this.plugins.filter((p) => p !== plugin);
449
- }
450
- /** 洋葱hook调用机制的实现 */
451
- callFn(name, fn) {
452
- return (...arg) => {
453
- const m = new middlewareRunner(fn);
454
- for (const plugin of this.plugins) {
455
- const middleware = plugin[name];
456
- if (middleware) {
457
- 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
+ });
458
462
  }
459
- }
460
- return m.runMiddlewareHandel(...arg);
461
- };
462
- }
463
- };
464
- var middlewareRunner = class {
465
- constructor(handel) {
466
- this.handel = handel;
467
- __publicField(this, "middlewares", []);
468
- }
469
- use(middleware) {
470
- this.middlewares.push(middleware);
471
- }
472
- runMiddlewareHandel(...ctx) {
473
- let index = 0;
474
- const next = (...ctx2) => {
475
- const middleware = this.middlewares[index];
476
- index++;
477
- if (middleware === void 0) {
478
- return this.handel.call(this, ...ctx2);
479
- }
480
- return middleware(ctx2, next);
481
- };
482
- return next.call(this, ...ctx);
483
- }
484
- };
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
+ }
485
476
 
486
477
  // src/plugins/publish/s3.ts
487
478
  import { S3 } from "@aws-sdk/client-s3";
488
479
  var s3Upload_plugin = {
489
- build: async function([config, effect, other], next) {
490
- const res = await next(config, effect, {
491
- ...other,
492
- onFileTree: async (tree) => {
493
- if (other?.onFileTree) {
494
- 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);
495
486
  }
496
487
  for await (const [fileName, ETag] of s3_uploads(tree, config)) {
497
- effect.log(`\u4E0A\u4F20\uFF1A ${fileName} ${ETag}`);
488
+ effectApi.log(`\u4E0A\u4F20\uFF1A ${fileName} ${ETag}`);
498
489
  }
490
+ effectApi.log(`s3 \u4E0A\u4F20\u5B8C\u6BD5`);
499
491
  }
500
492
  });
501
- effect.log(`s3 \u4E0A\u4F20\u5B8C\u6BD5`);
502
- return res;
503
493
  }
504
494
  };
505
495
  var s3_uploads = async function* (tree, config) {
@@ -528,115 +518,22 @@ var s3_uploads = async function* (tree, config) {
528
518
  }
529
519
  };
530
520
 
531
- // src/core/htmlTemplate.ts
532
- async function htmlTemplate(p, config) {
533
- let prePath = "";
534
- if (config?.siyuanPrefix) {
535
- prePath = config.siyuanPrefix;
536
- } else {
537
- for (let i = 0; i < p.level; i++) {
538
- prePath += "../";
539
- }
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());
540
534
  }
541
- const version2 = "2.10.5";
542
- return `<!DOCTYPE html>
543
- <html lang="zh_CN" data-theme-mode="light" data-light-theme="daylight" data-dark-theme="midnight">
544
- <head>
545
- ${config?.embedCode?.head ?? ""}
546
- <meta charset="utf-8" />
547
- <meta http-equiv="X-UA-Compatible" content="IE=edge" />
548
- <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"/>
549
- <meta name="apple-mobile-web-app-capable" content="yes" />
550
- <meta name="mobile-web-app-capable" content="yes" />
551
- <meta name="apple-mobile-web-app-status-bar-style" content="black" />
552
- <link rel="stylesheet" type="text/css" id="baseStyle" href="${prePath}stage/build/export/base.css?${version2}"/>
553
- <script>
554
- function isNightTime() {
555
- const currentHour = new Date().getHours();
556
- return currentHour >= 18 || currentHour < 6;
557
- }
558
- document.write('<link rel="stylesheet" type="text/css" id="themeDefaultStyle" href="${prePath}appearance/themes/'+(isNightTime()?'midnight':'daylight')+'/theme.css?${version2}"/>');
559
- </script>
560
- <link rel="stylesheet" type="text/css" href="${prePath}appearance/oceanpress.css"/>
561
- <title>${p.title}</title>
562
- </head>
563
- <body>
564
- ${config?.embedCode?.beforeBody ?? ""}
565
- <div class="protyle-wysiwyg protyle-wysiwyg--attr" id="preview">
566
- ${p.htmlContent}
567
- </div>
568
- <script src="${prePath}appearance/icons/material/icon.js?${version2}"></script>
569
- <script src="${prePath}stage/build/export/protyle-method.js?${version2}"></script>
570
- <script src="${prePath}stage/protyle/js/lute/lute.min.js?${version2}"></script>
571
- <script>
572
- window.siyuan = {
573
- config: {
574
- appearance: {
575
- mode: isNightTime()?1:0,//\u4E3B\u9898 \u660E\u4EAE=0 \u6697\u9ED1=1
576
- codeBlockThemeDark: "base16/dracula",
577
- codeBlockThemeLight: "github",
578
- },
579
- editor: {
580
- codeLineWrap: true,
581
- codeLigatures: false,
582
- plantUMLServePath: "https://www.plantuml.com/plantuml/svg/~1",
583
- codeSyntaxHighlightLineNum: true,
584
- katexMacros: JSON.stringify({}),
585
- },
586
- },
587
- languages: { copy: "\u590D\u5236" },
588
- };
589
- const cdn = "${prePath}stage/protyle";
590
- const previewElement = document.getElementById("preview");
591
-
592
- Protyle.highlightRender(previewElement, cdn);
593
- Protyle.mathRender(previewElement, cdn, false);
594
- Protyle.mermaidRender(previewElement, cdn);
595
- Protyle.flowchartRender(previewElement, cdn);
596
- Protyle.graphvizRender(previewElement, cdn);
597
- Protyle.chartRender(previewElement, cdn);
598
- Protyle.mindmapRender(previewElement, cdn);
599
- Protyle.abcRender(previewElement, cdn);
600
- Protyle.htmlRender(previewElement);
601
- Protyle.plantumlRender(previewElement, cdn);
602
- document.querySelectorAll(".protyle-action__copy").forEach((item) => {
603
- item.addEventListener("click", (event) => {
604
- navigator.clipboard.writeText(
605
- item.parentElement.nextElementSibling.textContent.trimEnd(),
606
- );
607
- event.preventDefault();
608
- event.stopPropagation();
609
- });
610
- });
611
- </script>
612
- ${config?.embedCode?.afterBody ?? ""}
613
- </body>
614
- </html>`;
615
- }
616
-
617
- // src/util/escaping.ts
618
- function escaping(s) {
619
- return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos");
620
- }
621
- function unescaping(s) {
622
- return s.replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&apos;/g, "'").replace(/&#(\d+);/g, (_sub, code) => {
623
- return String.fromCharCode(Number(code));
624
- });
625
- }
626
-
627
- // src/components/data_promise/index.ts
628
- import { customRef, nextTick, watch as watch2, watchEffect } from "vue";
629
- var PromiseObj = class {
630
- constructor() {
631
- __publicField(this, "pending", false);
632
- __publicField(this, "fulfilled", false);
633
- __publicField(this, "rejected", false);
634
- __publicField(this, "data", {});
635
- __publicField(this, "error", {});
636
- __publicField(this, "_p", Promise.resolve());
637
- }
638
- setP(p) {
639
- this._p = p;
535
+ setP(p) {
536
+ this._p = p;
640
537
  }
641
538
  equalP(p) {
642
539
  return this._p === p;
@@ -803,512 +700,6 @@ var vApi = new Proxy(
803
700
  }
804
701
  );
805
702
 
806
- // src/core/render.ts
807
- async function renderHTML(sy, renderInstance = getRender()) {
808
- if (sy === void 0) return "";
809
- const renderObj = {
810
- ...renderInstance,
811
- nodeStack: [
812
- /** 避免让所有的 renderInstance.nodeStack 是同一个对象 ,所以这里复制一个新的 */
813
- ...renderInstance.nodeStack
814
- ]
815
- };
816
- if (renderInstance.nodeStack.find(
817
- (node) => node.ID && sy.ID && node.ID === sy.ID
818
- )) {
819
- return warnDiv(
820
- "\u5FAA\u73AF\u5F15\u7528",
821
- [...renderInstance.nodeStack, sy].map((el) => el.ID)
822
- );
823
- }
824
- if (renderObj[sy.Type] === void 0) {
825
- return warnDiv(
826
- `\u6CA1\u6709\u627E\u5230\u5BF9\u5E94\u7684\u6E32\u67D3\u65B9\u6CD5 ${sy.Type} ${renderObj.nodeStack[0].Properties?.title}`
827
- );
828
- } else {
829
- renderObj.nodeStack.push(sy);
830
- if (sy.ID && renderInstance.nodeStack[0]?.ID) {
831
- const targetDoc = await storeDep.getDocByChildID(sy.ID);
832
- const currentDoc = renderInstance.nodeStack[0];
833
- if (targetDoc?.ID !== void 0 && targetDoc.ID !== currentDoc.ID && currentDoc.ID) {
834
- renderObj.refs.add(targetDoc.ID);
835
- }
836
- }
837
- const r = await renderObj[sy.Type](sy);
838
- renderObj.nodeStack.pop();
839
- return r;
840
- }
841
- }
842
- function warnDiv(msg, ...args) {
843
- warn(msg, ...args);
844
- return `<div class="ft__smaller ft__secondary b3-form__space--small">${msg}</div>`;
845
- }
846
- function isRenderCode(sy) {
847
- const mark = atob(
848
- sy.CodeBlockInfo ?? sy.Children?.find((el) => el.Type === "NodeCodeBlockFenceInfoMarker")?.CodeBlockInfo ?? ""
849
- );
850
- return [
851
- [
852
- "mindmap",
853
- "mermaid",
854
- "echarts",
855
- "abc",
856
- "graphviz",
857
- "flowchart",
858
- "plantuml"
859
- ].includes(mark),
860
- mark
861
- ];
862
- }
863
- var html = String.raw;
864
- async function childRender(sy, renderInstance) {
865
- let h = "";
866
- for await (const el of sy?.Children ?? []) {
867
- h += await renderHTML(el, renderInstance);
868
- }
869
- return h;
870
- }
871
- function strAttr(sy, config = {}) {
872
- if (config?.subtype_class === void 0) {
873
- config.subtype_class = (() => {
874
- const typ_subtype = sy.ListData?.Typ === 1 ? (
875
- /** 有序列表 */
876
- "o"
877
- ) : sy.ListData?.Typ === 3 ? (
878
- /** 任务列表 */
879
- "t"
880
- ) : (
881
- /** 无序列表 */
882
- "u"
883
- );
884
- if (sy.Type === "NodeDocument") return "h1";
885
- else if (sy.Type === "NodeHeading") return `h${sy.HeadingLevel}`;
886
- else if (sy.Type === "NodeList") return [typ_subtype, "list"];
887
- else if (sy.Type === "NodeListItem") return [typ_subtype, "li"];
888
- else if (sy.Type === "NodeParagraph") return ["", "p"];
889
- else if (sy.Type === "NodeImage") return ["", "img"];
890
- else if (sy.Type === "NodeBlockquote") return ["", "bq"];
891
- else if (sy.Type === "NodeSuperBlock") return ["", "sb"];
892
- else if (sy.Type === "NodeCodeBlock") {
893
- const [yes, mark] = isRenderCode(sy);
894
- if (yes) {
895
- return [mark, "render-node"];
896
- } else {
897
- return ["", "code-block"];
898
- }
899
- } else if (sy.Type === "NodeTable") return ["", "table"];
900
- else if (sy.Type === "NodeThematicBreak") return ["", "hr"];
901
- else if (sy.Type === "NodeMathBlock") return ["math", "render-node"];
902
- else if (sy.Type === "NodeIFrame") return ["", "iframe"];
903
- else if (sy.Type === "NodeVideo") return ["", "iframe"];
904
- else return "";
905
- })();
906
- }
907
- const attrObj = {};
908
- function addAttr(key, value) {
909
- attrObj[key] = value;
910
- }
911
- if (sy.ID) {
912
- addAttr("id", sy.ID);
913
- addAttr("data-node-id", sy.ID);
914
- }
915
- if (sy?.TextMarkType === "tag") {
916
- addAttr(`data-type`, sy.TextMarkType ?? "");
917
- } else {
918
- addAttr(`data-type`, config?.data_type ?? sy.Type);
919
- }
920
- if (sy.Properties?.updated) addAttr("updated", sy.Properties.updated);
921
- if (config?.subtype_class) {
922
- if (typeof config.subtype_class === "string") {
923
- addAttr("data-subtype", config.subtype_class);
924
- addAttr("class", config.subtype_class);
925
- } else {
926
- if (config.subtype_class[0] !== "")
927
- addAttr("data-subtype", config.subtype_class[0]);
928
- if (config.subtype_class[1] !== "")
929
- addAttr("class", config.subtype_class[1]);
930
- }
931
- }
932
- if (sy.Properties) {
933
- Object.entries(sy.Properties).forEach(([k, v]) => addAttr(k, v));
934
- }
935
- if (sy.ListData?.Marker) addAttr("data-marker", atob(sy.ListData.Marker));
936
- if (
937
- /** 任务列表 */
938
- sy.ListData?.Typ === 3 && /** 该项被选中 */
939
- sy.Children?.find(
940
- (el) => el.Type === "NodeTaskListItemMarker"
941
- )?.TaskListItemChecked
942
- ) {
943
- attrObj["class"] = (attrObj["class"] ?? "") + " protyle-task--done ";
944
- }
945
- delete attrObj["fold"];
946
- if (sy.Type === "NodeDocument") delete attrObj["title"];
947
- return Object.entries(attrObj).map(([k, v]) => `${k}="${v}"`).join(" ");
948
- }
949
- var _emptyString = async (_sy) => "";
950
- var _dataString = async (sy) => sy.Data ?? "";
951
- var getRender = () => {
952
- return {
953
- ...render,
954
- nodeStack: [],
955
- refs: /* @__PURE__ */ new Set()
956
- };
957
- };
958
- var render = {
959
- nodeStack: [],
960
- refs: /* @__PURE__ */ new Set(),
961
- async getTopPathPrefix() {
962
- const sy = this.nodeStack[0];
963
- let prefix = ".";
964
- if (sy.Type === "NodeDocument" && sy.ID) {
965
- const path = await storeDep.getDocPathBySY(sy);
966
- if (path) {
967
- const level = path.split("/").length - 3;
968
- for (let i = 0; i < level; i++) {
969
- prefix += "/..";
970
- }
971
- }
972
- return prefix;
973
- } else {
974
- console.log("\u672A\u5B9A\u4E49\u9876\u5C42\u5143\u7D20\u975E NodeDocument \u65F6\u7684\u5904\u7406\u65B9\u5F0F", sy);
975
- return "";
976
- }
977
- },
978
- async NodeDocument(sy) {
979
- let html2 = "";
980
- if (
981
- /** 只有顶层的文档块才渲染题图 */
982
- this.nodeStack.length === 1
983
- ) {
984
- html2 += `<div style="min-height: 150px;" ${strAttr(sy)}>`;
985
- if (sy.Properties?.["title-img"]) {
986
- html2 += `<div class="protyle-background__img" style="margin-bottom: 30px;position: relative;height: 16vh;${sy.Properties?.["title-img"].replace(
987
- /assets/,
988
- // 修改为相对路径
989
- await this.getTopPathPrefix() + "/assets"
990
- )}"/>${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>`;
991
- }
992
- html2 += "</div>";
993
- html2 += `<h1 ${strAttr(sy)} data-type="NodeHeading" class="h1">${sy.Properties?.title}</h1>`;
994
- }
995
- html2 += await childRender(sy, this);
996
- return html2;
997
- },
998
- async NodeHeading(sy) {
999
- const tagName = `h${sy.HeadingLevel}`;
1000
- let html2 = `<${tagName} ${strAttr(sy)}>${await childRender(
1001
- sy,
1002
- this
1003
- )}</${tagName}>`;
1004
- const parentNode = this.nodeStack[
1005
- this.nodeStack.length - 2
1006
- /** 最后一个元素是 sy本身(NodeHeading)还得要往前一个,所以是2 */
1007
- ];
1008
- if (parentNode?.Type === "NodeBlockQueryEmbedScript") {
1009
- let afterFlag = false;
1010
- for (const node of sy.Parent.Children ?? []) {
1011
- if (node === sy) {
1012
- afterFlag = true;
1013
- } else if (node !== sy && node.Type === "NodeHeading") {
1014
- afterFlag = false;
1015
- } else if (afterFlag) {
1016
- html2 += "\n" + await renderHTML(node, this);
1017
- }
1018
- }
1019
- }
1020
- return html2;
1021
- },
1022
- NodeText: _dataString,
1023
- async NodeList(sy) {
1024
- return html`<div ${strAttr(sy)}>${await childRender(sy, this)}</div>`;
1025
- },
1026
- async NodeListItem(sy) {
1027
- return html`<div ${await strAttr(sy)}>
1028
- <div class="protyle-action">
1029
- ${sy.ListData?.Typ === 1 ? (
1030
- /** 有序列表 */
1031
- atob(sy.ListData?.Marker ?? "")
1032
- ) : sy.ListData?.Typ === 3 ? (
1033
- /** 任务列表 */
1034
- `<svg><use xlink:href="#${sy.Children?.find((el) => el.Type === "NodeTaskListItemMarker")?.TaskListItemChecked ? "iconCheck" : "iconUncheck"}"></use></svg>`
1035
- ) : (
1036
- /** 无序列表 */
1037
- `<svg><use xlink:href="#iconDot"></use></svg>`
1038
- )}
1039
- </div>
1040
- ${await childRender(sy, this)}
1041
- </div>`;
1042
- },
1043
- NodeTaskListItemMarker: _emptyString,
1044
- async NodeParagraph(sy) {
1045
- return `<div ${strAttr(sy)}><div spellcheck="false">${await childRender(
1046
- sy,
1047
- this
1048
- )}</div></div>`;
1049
- },
1050
- async NodeTextMark(sy) {
1051
- const that = this;
1052
- let r = "";
1053
- for (const type of (sy.TextMarkType?.split(" ") ?? []).reverse()) {
1054
- if (r === "") {
1055
- r = await TextMarkRender(sy, type, sy.TextMarkTextContent ?? "");
1056
- } else {
1057
- r = await TextMarkRender(sy, type, r);
1058
- }
1059
- }
1060
- return r;
1061
- async function TextMarkRender(sy2, type, content) {
1062
- if (type === "inline-math") {
1063
- return `<span data-type="inline-math" data-subtype="math" data-content="${sy2.TextMarkInlineMathContent}" class="render-node"></span>`;
1064
- } else if (type === "inline-memo") {
1065
- return `${content}<sup>\uFF08${sy2.TextMarkInlineMemoContent}\uFF09</sup>`;
1066
- } else if (type === "block-ref") {
1067
- let href = "";
1068
- if (sy2.TextMarkBlockRefID) {
1069
- const doc = await storeDep.getDocByChildID(sy2.TextMarkBlockRefID);
1070
- if (doc?.ID) {
1071
- href = `${await that.getTopPathPrefix()}${await storeDep.getHPathByID_Node(
1072
- doc
1073
- )}.html#${sy2.TextMarkBlockRefID}`;
1074
- that.refs.add(doc.ID);
1075
- } else {
1076
- warn(`\u672A\u67E5\u627E\u5230${sy2.ID}\u6240\u6307\u5411\u7684\u6587\u6863\u8282\u70B9 ${sy2.TextMarkBlockRefID}`);
1077
- }
1078
- } else {
1079
- warn(`${sy2.ID} \u5757\u5F15\u7528\u6CA1\u6709\u8BBE\u5B9A ref id`);
1080
- }
1081
- return `<span data-type="${sy2.TextMarkType}" data-subtype="${/** "s" */
1082
- sy2.TextMarkBlockRefSubtype}" data-id="${/** 被引用块的id */
1083
- sy2.TextMarkBlockRefID}"><a href="${href}">${content}</a></span>`;
1084
- } else if (type === "a") {
1085
- let href = sy2.TextMarkAHref;
1086
- if (href?.startsWith("assets/")) {
1087
- href = `${await that.getTopPathPrefix()}/${href}`;
1088
- }
1089
- return `<a href="${href}">${content}</a>`;
1090
- } else if (`strong em u s mark sup sub kbd tag code strong code text`.includes(
1091
- type ?? ""
1092
- )) {
1093
- return `<span ${strAttr(sy2, { data_type: type })}>${content}</span>`;
1094
- } else {
1095
- return warnDiv(
1096
- `\u6CA1\u6709\u627E\u5230\u5BF9\u5E94\u7684\u6E32\u67D3\u5668 ${sy2.TextMarkType} ${that.nodeStack[0].Properties?.title}`
1097
- );
1098
- }
1099
- }
1100
- },
1101
- async NodeImage(sy) {
1102
- let link = "";
1103
- const LinkDest = sy.Children?.filter((c) => c.Type === "NodeLinkDest");
1104
- if (LinkDest?.length === 1) {
1105
- link = await renderHTML(LinkDest[0], this);
1106
- } else if (LinkDest?.length && LinkDest.length > 1) {
1107
- warn("NodeImage \u5B58\u5728\u591A\u4E2A LinkDest", sy);
1108
- }
1109
- let title = "";
1110
- const LinkTitle = sy.Children?.filter((c) => c.Type === "NodeLinkTitle");
1111
- if (LinkTitle?.length === 1) {
1112
- title = await renderHTML(LinkTitle[0], this);
1113
- } else if (LinkTitle?.length && LinkTitle.length > 1) {
1114
- warn("NodeImage \u5B58\u5728\u591A\u4E2A LinkTitle", sy);
1115
- }
1116
- return `<span ${await strAttr(sy)} style="${sy.Properties?.["parent-style"] ?? ""}">
1117
- <img
1118
- src="${link}"
1119
- data-src="${link}"
1120
- title="${title}"
1121
- style="${sy.Properties?.style ?? ""}"
1122
- loading="lazy"
1123
- />
1124
- <span class="protyle-action__title">${title}</span></span>`;
1125
- },
1126
- async NodeLinkDest(sy) {
1127
- if (/^(?:[a-z]+:)?\/\/|^(?:\/)/.test(sy.Data ?? "")) {
1128
- return sy.Data ?? "";
1129
- }
1130
- return `${await this.getTopPathPrefix()}/${sy.Data}`;
1131
- },
1132
- NodeLinkTitle: _dataString,
1133
- NodeKramdownSpanIAL: _emptyString,
1134
- async NodeSuperBlock(sy) {
1135
- return `<div ${strAttr(sy)} data-sb-layout="${childDateByType(
1136
- sy,
1137
- "NodeSuperBlockLayoutMarker"
1138
- )}">${await childRender(sy, this)}</div>`;
1139
- },
1140
- NodeSuperBlockOpenMarker: _emptyString,
1141
- NodeSuperBlockCloseMarker: _emptyString,
1142
- NodeSuperBlockLayoutMarker: _emptyString,
1143
- async NodeBlockQueryEmbed(sy) {
1144
- return `<div ${strAttr(sy)} data-type="NodeBlockquote" class="bq">${await childRender(sy, this)}</div>`;
1145
- },
1146
- NodeOpenBrace: _emptyString,
1147
- NodeCloseBrace: _emptyString,
1148
- async NodeBlockQueryEmbedScript(sy) {
1149
- const sql = sy.Data;
1150
- if (!sql) {
1151
- console.log("no sql", sy);
1152
- return html`<pre>${sql}</pre>`;
1153
- }
1154
- let htmlStr = "";
1155
- const blocks = await API.query_sql({
1156
- stmt: (
1157
- /** sql 被思源转义了,类似 :SELECT * FROM blocks WHERE id = &#39;20201227174241-nxny1tq&#39;
1158
- 所以这里将它转义回来
1159
- TODO 当用户确实使用了包含转义的字符串时,这个实现是错误的 */
1160
- unescaping(
1161
- sql
1162
- ).replace(
1163
- /** 我不理解lute为什么这样实现 https://github.com/88250/lute/blob/HEAD/editor/const.go#L38
1164
- * https://ld246.com/article/1696750832289
1165
- */
1166
- /_esc_newline_/g,
1167
- "\n"
1168
- )
1169
- )
1170
- }).catch((err) => {
1171
- throw new Error(
1172
- `sql error: ${err.message}
1173
- rawSql:${sql}
1174
- unescapingSql:${unescaping(
1175
- sql
1176
- )}`
1177
- );
1178
- });
1179
- for (const block of blocks) {
1180
- const node = await storeDep.getNodeByID(block.id);
1181
- if (node === void 0) {
1182
- return warnDiv("\u672A\u627E\u5230\u6B64\u5757\uFF0C\u53EF\u80FD\u4E3A\u8DE8\u7B14\u8BB0\u5F15\u7528", block.id, sql);
1183
- }
1184
- htmlStr += await renderHTML(node, this);
1185
- }
1186
- return htmlStr;
1187
- },
1188
- async NodeBlockquote(sy) {
1189
- return html`<div ${strAttr(sy)}>${await childRender(sy, this)}</div>`;
1190
- },
1191
- NodeBlockquoteMarker: _emptyString,
1192
- NodeCodeBlock: async (sy) => {
1193
- const [yes, _] = isRenderCode(sy);
1194
- if (yes) {
1195
- return `<div ${strAttr(sy)} data-content="${escaping(
1196
- sy.Children?.find((el) => el.Type === "NodeCodeBlockCode")?.Data ?? ""
1197
- )}">
1198
- <div spin="1"></div>
1199
- <div class="protyle-attr" contenteditable="false"></div>
1200
- </div>`;
1201
- }
1202
- return `<div ${strAttr(sy)}>
1203
- <div class="protyle-action">
1204
- <span class="protyle-action--first protyle-action__language">${await renderHTML(
1205
- sy.Children?.find(
1206
- (el) => el.Type === "NodeCodeBlockFenceInfoMarker"
1207
- ),
1208
- void 0
1209
- )}</span>
1210
- <span class="fn__flex-1"></span><span class="protyle-icon protyle-icon--only protyle-action__copy"><svg><use xlink:href="#iconCopy"></use></svg></span>
1211
- </div>
1212
- ${await renderHTML(
1213
- sy.Children?.find((el) => el.Type === "NodeCodeBlockCode"),
1214
- void 0
1215
- )}
1216
- </div>`;
1217
- },
1218
- NodeCodeBlockFenceInfoMarker: async (sy) => atob(sy.CodeBlockInfo ?? ""),
1219
- NodeCodeBlockCode: async (sy) => `<div class="hljs" spellcheck="false">${sy.Data}</div>`,
1220
- NodeCodeBlockFenceOpenMarker: _emptyString,
1221
- NodeCodeBlockFenceCloseMarker: _emptyString,
1222
- async NodeTable(sy) {
1223
- return `<div ${strAttr(sy)}>
1224
- <div>
1225
- <table spellcheck="false">
1226
- <colgroup>
1227
- ${sy.TableAligns?.map(() => "<col />").join("")}
1228
- </colgroup>
1229
- ${await renderHTML(
1230
- sy.Children?.find((el) => el.Type === "NodeTableHead"),
1231
- this
1232
- )}
1233
- <tbody>
1234
- ${(await Promise.all(
1235
- sy.Children?.filter((el) => el.Type === "NodeTableRow").map(
1236
- (el) => renderHTML(el, this)
1237
- ) ?? []
1238
- )).join("\n")}
1239
- </tbody>
1240
- </table>
1241
- </div>
1242
- </div>`;
1243
- },
1244
- async NodeTableHead(sy) {
1245
- return `<${sy.Data}>${await childRender(sy, this)}</${sy.Data}>`;
1246
- },
1247
- async NodeTableRow(sy) {
1248
- return `<tr>${await childRender(sy, this)}</tr>`;
1249
- },
1250
- async NodeTableCell(sy) {
1251
- return `<td>${await childRender(sy, this)}</td>`;
1252
- },
1253
- NodeHTMLBlock: async (sy) => `<div ${strAttr(sy)}>${sy.Data}</div>`,
1254
- NodeThematicBreak: async (sy) => `<div ${strAttr(sy)}><div></div></div>`,
1255
- NodeMathBlock: async (sy) => `<div ${strAttr(
1256
- sy
1257
- )} data-content="${childDateByType(sy, "NodeMathBlockContent")}">
1258
- <div spin="1"></div>
1259
- </div>`,
1260
- NodeMathBlockOpenMarker: _emptyString,
1261
- NodeMathBlockCloseMarker: _emptyString,
1262
- async NodeIFrame(sy) {
1263
- return ` <div ${strAttr(sy)}>
1264
- <div class="iframe-content">
1265
- ${/** 资源总是被复制到顶层目录,所以直接跳到顶层即可 */
1266
- /** TODO 应该有一个统一处理资源的方案 */
1267
- sy.Data?.replace(
1268
- /src="assets\//,
1269
- `src="${await this.getTopPathPrefix()}/assets/`
1270
- )}
1271
- </div>
1272
- </div>`;
1273
- },
1274
- async NodeVideo(sy) {
1275
- return await this.NodeIFrame(sy);
1276
- },
1277
- async NodeAudio(sy) {
1278
- return await this.NodeIFrame(sy);
1279
- },
1280
- /** 虚拟链接 */
1281
- NodeHeadingC8hMarker: _emptyString,
1282
- async NodeSoftBreak(_sy) {
1283
- return "\u200B";
1284
- },
1285
- async NodeBr(sy) {
1286
- return `<${sy.Data}>`;
1287
- },
1288
- async NodeWidget(sy) {
1289
- return `<div ${strAttr(
1290
- sy
1291
- )}><img src="${await this.getTopPathPrefix()}/assets/widget/${sy.ID}.jpg"/></div>`;
1292
- },
1293
- async NodeBackslash(sy) {
1294
- if (sy.Data === void 0 || sy.Data === "span") {
1295
- return `${await childRender(sy, this)}`;
1296
- } else {
1297
- return warnDiv(
1298
- `\u672A\u5B9A\u4E49\u7684 NodeBackslash \u5904\u7406 ${sy.Data}`,
1299
- this.nodeStack[0].Properties?.title
1300
- );
1301
- }
1302
- },
1303
- NodeBackslashContent: _dataString
1304
- };
1305
- function childDateByType(sy, type) {
1306
- return sy.Children?.find((el) => el.Type === type)?.Data;
1307
- }
1308
- function warn(...arg) {
1309
- console.warn("\n", ...arg);
1310
- }
1311
-
1312
703
  // src/core/siyuan_type.ts
1313
704
  function DB_block_path(p) {
1314
705
  return `data/${p.box}${p.path}`;
@@ -1440,7 +831,7 @@ function parentRef(sy) {
1440
831
  }
1441
832
 
1442
833
  // src/core/genRssXml.ts
1443
- async function generateRSSXML(path, renderInstance, config) {
834
+ async function generateRSSXML(path, renderInstance, config, getHPathByID_Node) {
1444
835
  const refNode = (await Promise.all([...renderInstance.refs.values()].map(get_node_by_id))).filter((el) => el);
1445
836
  return `<?xml version="1.0" encoding="UTF-8"?>
1446
837
  <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
@@ -1454,7 +845,7 @@ async function generateRSSXML(path, renderInstance, config) {
1454
845
  refNode.map(
1455
846
  async (node) => `<item>
1456
847
  <title>${node?.Properties?.title}</title>
1457
- <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>
1458
849
  <description>${""}</description>
1459
850
  <pubDate>${node?.Properties?.updated ? new Date(
1460
851
  node.Properties.updated.replace(
@@ -1490,181 +881,1790 @@ function sitemap_xml(docArr, config) {
1490
881
  </urlset>`;
1491
882
  }
1492
883
 
1493
- // src/core/build.ts
1494
- async function build(config, effect, otherConfig) {
1495
- const _renderHTML = otherConfig?.renderHtmlFn ?? renderHTML;
1496
- const book = config.notebook;
1497
- const docTree = {};
1498
- const skipBuilds = useSkipBuilds();
1499
- let oldPercentage = 0;
1500
- let total = 0;
1501
- function processPercentage(percentage) {
1502
- total += oldPercentage;
1503
- return (process3) => {
1504
- oldPercentage = process3 * percentage;
1505
- effect.percentage((total + oldPercentage) * 100);
1506
- };
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
+ ]));
1067
+ }
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;
1507
1082
  }
1508
- effect.log(`=== \u5F00\u59CB\u7F16\u8BD1 ${book.name} ===`);
1509
- let process2 = processPercentage(0.4);
1510
- const Doc_blocks = await allDocBlock_by_bookId(book.id);
1511
- function refsNotUpdated(docBlock) {
1512
- const refs = config.__skipBuilds__[docBlock.id]?.refs ?? [];
1513
- for (const ref_id of refs) {
1514
- const new_doc_hash = Doc_blocks.find(
1515
- (docBlock2) => docBlock2.id === ref_id
1516
- )?.hash;
1517
- const old_doc_hash = config.__skipBuilds__[ref_id]?.hash;
1518
- if (new_doc_hash === void 0 || old_doc_hash === void 0) {
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())) {
1519
1099
  return false;
1520
- } else if (new_doc_hash === old_doc_hash) {
1521
- continue;
1522
- } else {
1100
+ }
1101
+ if (token.match(/^[a-f0-9]{6,}$/)) return false;
1102
+ if (/[\u4e00-\u9fa5]/.test(token) && token.length < 2) {
1103
+ return false;
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
+ }
1130
+ }
1131
+ return tokens;
1132
+ }
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)) {
1523
1161
  return false;
1524
1162
  }
1525
1163
  }
1164
+ if (/(.)\1{2,}/.test(word)) {
1165
+ return false;
1166
+ }
1526
1167
  return true;
1527
1168
  }
1528
- effect.log(`=== \u67E5\u8BE2\u6587\u6863\u7EA7block\u5B8C\u6210 ===`);
1529
- let i = 0;
1530
- await Promise.all(
1531
- Doc_blocks.map(async (docBlock) => {
1532
- const sy = await get_doc_by_SyPath(DB_block_path(docBlock));
1533
- docTree[docBlock.hpath] = { sy, docBlock };
1534
- i++;
1535
- process2(i / Doc_blocks.length);
1536
- })
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("?")
1537
1232
  );
1538
- const fileTree = {};
1539
- process2 = processPercentage(0.4);
1540
- const enableIncrementalCompilation_doc = (() => {
1541
- if (package_default.version !== config.OceanPress.version) {
1542
- effect.log(
1543
- `\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`
1544
- );
1545
- return false;
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();
1546
1549
  }
1547
- return config.enableIncrementalCompilation_doc;
1548
1550
  })();
1549
- effect.log(`=== \u5F00\u59CB\u6E32\u67D3\u6587\u6863 ===`);
1550
- await Promise.all(
1551
- Object.entries(docTree).map(async ([path, { sy, docBlock }]) => {
1552
- if (config.enableIncrementalCompilation && enableIncrementalCompilation_doc && /** 文档本身没有发生变化 */
1553
- config.__skipBuilds__[docBlock.id]?.hash === docBlock.hash && /** docBlock所引用的文档也没有更新 */
1554
- refsNotUpdated(docBlock))
1555
- return;
1556
- try {
1557
- const rootLevel = path.split("/").length - 2;
1558
- const renderInstance = getRender();
1559
- fileTree[path + ".html"] = await htmlTemplate(
1560
- {
1561
- title: sy.Properties?.title || "",
1562
- htmlContent: await _renderHTML(sy, renderInstance),
1563
- level: rootLevel
1564
- },
1565
- {
1566
- ...tempConfig.cdn,
1567
- embedCode: config.embedCode
1568
- }
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`
1569
2526
  );
1570
- if (config.sitemap.rss && path.endsWith(".rss.xml")) {
1571
- const rssPath = path;
1572
- fileTree[rssPath] = await generateRSSXML(rssPath, renderInstance, config);
1573
- effect.log(`\u6E32\u67D3 rss.xml:${rssPath} \u5B8C\u6BD5`);
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 */";
1574
2538
  }
1575
- 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
+ }
1576
2571
  skipBuilds.add(docBlock.id, {
1577
- hash: docBlock.hash
2572
+ refs: (
2573
+ /** 保存引用 */
2574
+ [...renderInstance.refs.values()]
2575
+ )
1578
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);
1579
2581
  }
1580
- skipBuilds.add(docBlock.id, {
1581
- refs: (
1582
- /** 保存引用 */
1583
- [...renderInstance.refs.values()]
1584
- )
1585
- });
1586
- } catch (error) {
1587
- effect.log(`${path} \u6E32\u67D3\u5931\u8D25:${error}`);
1588
- console.log(error);
1589
- }
1590
- process2(i / Doc_blocks.length);
1591
- effect.log(`\u6E32\u67D3\u5B8C\u6BD5:${path}`);
1592
- })
1593
- );
1594
- effect.log(`=== \u6E32\u67D3\u6587\u6863\u5B8C\u6210 ===`);
1595
- effect.log(`=== \u5F00\u59CB\u751F\u6210 sitemap.xml ===`);
1596
- if (config.sitemap.enable) {
1597
- fileTree["sitemap.xml"] = sitemap_xml(Doc_blocks, config.sitemap);
1598
- }
1599
- if (config.excludeAssetsCopy === false) {
1600
- effect.log(`=== \u5F00\u59CB\u590D\u5236\u8D44\u6E90\u6587\u4EF6 ===`);
1601
- const assets = await API.query_sql({
1602
- stmt: `SELECT * from assets
1603
- WHERE box = '${book.id}'
1604
- limit 150000 OFFSET 0`
2582
+ process2(i / Doc_blocks.length);
2583
+ return `\u6E32\u67D3\u5B8C\u6BD5:${path}`;
2584
+ });
1605
2585
  });
1606
- await Promise.all(
1607
- assets.map(async (item) => {
1608
- if (config.enableIncrementalCompilation && /** 资源没有变化,直接跳过 */
1609
- 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) {
1610
2631
  return;
1611
2632
  } else {
1612
- fileTree[item.path] = await API.get_assets({
1613
- 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`
1614
2636
  });
1615
2637
  if (config.enableIncrementalCompilation) {
1616
- skipBuilds.add(item.id, { hash: item.hash });
2638
+ skipBuilds.add(id, { updated: update });
1617
2639
  }
1618
2640
  }
1619
- })
1620
- );
1621
- effect.log(`=== \u5F00\u59CB\u590D\u5236\u6302\u4EF6\u8D44\u6E90\u6587\u4EF6 ===`);
1622
- const widgetList = await API.query_sql({
1623
- stmt: `
1624
- SELECT *
1625
- from blocks
1626
- WHERE box = '${book.id}'
1627
- AND type = 'widget'
1628
- limit 150000 OFFSET 0
1629
- `
1630
- });
1631
- const widgetNode = (await Promise.all(
1632
- widgetList.map(async (el) => await get_node_by_id(el.id))
1633
- )).filter(
1634
- (widget) => widget?.Properties?.["custom-oceanpress-widget-update"]
1635
- ).map(async (widget) => {
1636
- if (!widget || !widget?.ID) return;
1637
- const update = widget?.Properties?.["custom-oceanpress-widget-update"];
1638
- if (config.enableIncrementalCompilation && config.__skipBuilds__[widget.ID]?.updated === update) {
1639
- return;
1640
- } else {
1641
- const id = widget.ID;
1642
- fileTree[`assets/widget/${id}.jpg`] = await API.file_getFile({
1643
- path: `data/storage/oceanpress/widget_img/${id}.jpg`
1644
- });
1645
- if (config.enableIncrementalCompilation) {
1646
- skipBuilds.add(id, { updated: update });
1647
- }
1648
- }
1649
- });
1650
- await Promise.all(widgetNode);
1651
- }
1652
- if (otherConfig?.onFileTree) {
1653
- otherConfig.onFileTree(fileTree);
1654
- }
1655
- if (config.compressedZip) {
1656
- effect.log(`=== \u5F00\u59CB\u751F\u6210\u538B\u7F29\u5305 ===`);
1657
- await downloadZIP(fileTree, {
1658
- // TODO 这里应该移出来成为全局的写选项
1659
- withoutZip: tempConfig.withoutPublicZip,
1660
- publicZip: tempConfig.cdn.publicZip
1661
- });
1662
- }
1663
- config.OceanPress.version = package_default.version;
1664
- skipBuilds.write();
1665
- effect.percentage(100);
1666
- effect.log("\u7F16\u8BD1\u5B8C\u6BD5");
1667
- 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
+ });
1668
2668
  }
1669
2669
  function useSkipBuilds() {
1670
2670
  const obj = {};
@@ -1682,55 +2682,73 @@ function useSkipBuilds() {
1682
2682
  };
1683
2683
  }
1684
2684
 
1685
- // src/plugins/publish/OceanPressServer.ts
1686
- import { stringify } from "superjson";
1687
- function deployOceanPressServer_plugin(config) {
1688
- const plugin = {
1689
- async build_onFileTree([tree], next) {
1690
- next(tree);
1691
- const client = await createRPC("apiConsumer", {
1692
- async remoteCall(method, data) {
1693
- let body;
1694
- let content_type;
1695
- if (data[0] instanceof ReadableStream) {
1696
- body = await new Response(data[0]).blob();
1697
- content_type = "application/octet-stream";
1698
- } else {
1699
- body = stringify(data);
1700
- console.log("[body]", body);
1701
- content_type = "application/json";
1702
- }
1703
- return fetch(`${config.oceanPressServer.apiBase}/api/${method}`, {
1704
- method: "POST",
1705
- body,
1706
- headers: {
1707
- "x-api-key": config.oceanPressServer.apiKey,
1708
- "Content-Type": content_type
1709
- },
1710
- // @ts-expect-error 在 node 运行的时候需要声明双工模式才能正确发送 ReadableStream,TODO 需要验证浏览器端可以这样运行吗
1711
- duplex: "half"
1712
- // 关键:显式声明半双工模式
1713
- }).then((res2) => res2.json()).then((r) => {
1714
- if (r.error) {
1715
- console.log("[r]", r);
1716
- throw new Error();
1717
- }
1718
- return r.result;
1719
- });
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
+ };
1720
2706
  }
1721
- });
1722
- const zip = await genZIP(tree, { withoutZip: true });
1723
- const sizeInMB = zip.size / (1024 * 1024);
1724
- console.log("[zip.size in MB]", sizeInMB.toFixed(2));
1725
- const readableStream = zip.stream();
1726
- const { chunkCount, fileId } = await client.API.upload(readableStream);
1727
- console.log("[res]", { chunkCount, fileId });
1728
- const res = await client.API.deploy({ zipFileId: fileId });
1729
- console.log("[deploy res]", res);
1730
- }
1731
- };
1732
- return plugin;
1733
- }
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
+ };
1734
2752
 
1735
2753
  // src/core/ocean_press.ts
1736
2754
  var OceanPress = class {
@@ -1742,7 +2760,7 @@ var OceanPress = class {
1742
2760
  /** 用于渲染文档的函数 */
1743
2761
  build_renderHTML: renderHTML,
1744
2762
  /** 编译完成后文件树的处理回调函数 */
1745
- build_onFileTree: (_tree) => {
2763
+ build_onFileTree: (_tree, _effectApi) => {
1746
2764
  }
1747
2765
  });
1748
2766
  __publicField(this, "pluginCenter", new PluginCenter(
@@ -1762,8 +2780,8 @@ var OceanPress = class {
1762
2780
  );
1763
2781
  }
1764
2782
  }
1765
- async build(effect) {
1766
- const build_res = this.pluginCenter.fun.build(this.config, effect, {
2783
+ build() {
2784
+ const build_res = this.pluginCenter.fun.build(this.config, {
1767
2785
  renderHtmlFn: this.pluginCenter.fun.build_renderHTML,
1768
2786
  onFileTree: this.pluginCenter.fun.build_onFileTree
1769
2787
  });
@@ -1772,45 +2790,52 @@ var OceanPress = class {
1772
2790
  };
1773
2791
 
1774
2792
  // src/core/render.api.dep.ts
1775
- storeDep.getDocByChildID = async (id) => {
1776
- return await get_doc_by_child_id(id);
1777
- };
1778
- storeDep.getDocPathBySY = async (sy) => {
1779
- if (sy?.ID) {
1780
- const block = await get_block_by_id(sy.ID);
1781
- if (block) {
1782
- 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
+ }
1783
2803
  }
1784
- }
1785
- };
1786
- storeDep.getHPathByID_Node = async (id_node) => {
1787
- const id = typeof id_node === "string" ? id_node : id_node.ID;
1788
- if (id === void 0) throw new Error("id is undefined");
1789
- const docNode = await get_doc_by_child_id(id);
1790
- if (docNode === void 0) throw new Error("docNode is undefined");
1791
- const docBlock = await get_block_by_id(id);
1792
- if (docBlock === void 0) throw new Error("docBlock is undefined");
1793
- return docBlock.hpath;
1794
- };
1795
- storeDep.getNodeByID = async (id) => {
1796
- if (id === void 0) return;
1797
- const doc = await storeDep.getDocByChildID(id);
1798
- if (doc === void 0) return;
1799
- return getNode(doc);
1800
- function getNode(node) {
1801
- if (node.ID === id) return node;
1802
- if (node.Children === void 0) return;
1803
- for (const child of node.Children) {
1804
- const n = getNode(child);
1805
- 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
+ }
1806
2827
  }
2828
+ },
2829
+ log(msg) {
2830
+ console.log(msg);
2831
+ },
2832
+ percentage: (n) => {
2833
+ console.log(n);
1807
2834
  }
1808
2835
  };
1809
2836
 
1810
2837
  // src/util/store.node.dep.ts
1811
- import { writeFileSync, readFileSync, existsSync, mkdirSync } from "node:fs";
1812
- storeDep.getItem = getItem;
1813
- storeDep.setItem = setItem;
2838
+ import { writeFileSync, readFileSync, existsSync, mkdirSync } from "fs";
1814
2839
  function setItem(key, value) {
1815
2840
  if (!existsSync("./store/")) {
1816
2841
  mkdirSync("./store/", { recursive: true });
@@ -1826,6 +2851,10 @@ function getItem(key) {
1826
2851
  return void 0;
1827
2852
  }
1828
2853
  }
2854
+ var nodeApiDep = {
2855
+ setItem,
2856
+ getItem
2857
+ };
1829
2858
 
1830
2859
  // src/cli/common.ts
1831
2860
  import { Command } from "commander";
@@ -1833,12 +2862,12 @@ var program = new Command();
1833
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");
1834
2863
 
1835
2864
  // src/cli/deploy.ts
2865
+ import { Context as Context3, Effect as Effect5 } from "effect";
1836
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) => {
1837
2867
  if (!opt.apiBase || !opt.apiKey) {
1838
2868
  return console.error(`\u8BF7\u914D\u7F6E apiBase \u548C apiKey`);
1839
2869
  }
1840
2870
  const config = await readFile(opt.config, "utf-8");
1841
- loadConfigFile(JSON.parse(config));
1842
2871
  const client = await createRPC("apiConsumer", {
1843
2872
  remoteCall(method, data) {
1844
2873
  let body;
@@ -1870,79 +2899,115 @@ program.command("deploy").description("\u90E8\u7F72\u7AD9\u70B9").option("-c, --
1870
2899
  });
1871
2900
  }
1872
2901
  });
1873
- const ocean_press = new OceanPress(currentConfig.value);
1874
- ocean_press.pluginCenter.registerPlugin({
1875
- async build_onFileTree([tree], next) {
1876
- const zip = await genZIP(tree, { withoutZip: true });
1877
- const sizeInMB = zip.size / (1024 * 1024);
1878
- console.log("[zip.size in MB]", sizeInMB.toFixed(2));
1879
- const readableStream = zip.stream();
1880
- const { chunkCount, fileId } = await client.API.upload(readableStream);
1881
- console.log("[res]", { chunkCount, fileId });
1882
- const res = await client.API.deploy({ zipFileId: fileId });
1883
- console.log("[deploy res]", res);
1884
- }
1885
- });
1886
- await ocean_press.build({
1887
- log: (msg) => {
1888
- if (msg.startsWith("\u6E32\u67D3\uFF1A")) {
1889
- process.stdout.write(`\r\x1B[K${msg}`);
1890
- } else {
1891
- 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(`
1892
2912
  ${msg}`);
2913
+ }
2914
+ },
2915
+ percentage: (n) => {
2916
+ process.stdout.write(`\r\x1B[K\u8FDB\u5EA6\uFF1A${n}%`);
1893
2917
  }
1894
- },
1895
- percentage: (n) => {
1896
- process.stdout.write(`\r\x1B[K\u8FDB\u5EA6\uFF1A${n}%`);
1897
- }
1898
- });
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);
1899
2943
  });
1900
2944
 
1901
2945
  // src/cli/build.ts
1902
2946
  import { mkdir, readFile as readFile2, writeFile } from "fs/promises";
1903
2947
  import { resolve } from "path";
1904
2948
  import { join } from "path/posix";
2949
+ import { Context as Context4, Effect as Effect6 } from "effect";
1905
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) => {
1906
2951
  if (!opt.config || !opt.output) {
1907
2952
  console.log(`\u8BF7\u8BBE\u7F6E\u914D\u7F6E\u6587\u4EF6\u4F4D\u7F6E\u548C\u8F93\u51FA\u76EE\u5F55\u4F4D\u7F6E`);
1908
2953
  throw new Error("\u8BF7\u8BBE\u7F6E\u914D\u7F6E\u6587\u4EF6\u4F4D\u7F6E\u548C\u8F93\u51FA\u76EE\u5F55\u4F4D\u7F6E");
1909
2954
  }
1910
2955
  const config = await readFile2(opt.config, "utf-8");
1911
- loadConfigFile(JSON.parse(config));
1912
2956
  const filePath = resolve(opt.output);
1913
- const ocean_press = new OceanPress(currentConfig.value);
1914
- ocean_press.pluginCenter.registerPlugin({
1915
- async build_onFileTree([tree]) {
1916
- for (const [path, data] of Object.entries(tree)) {
1917
- const fullPath = join(filePath, "./", path);
1918
- const pathArray = fullPath.split("/").slice(0, -1);
1919
- const dirPath = pathArray.join("/");
1920
- mkdir(dirPath, { recursive: true });
1921
- try {
1922
- if (typeof data === "string") {
1923
- await writeFile(fullPath, data, "utf-8");
1924
- } else {
1925
- await writeFile(fullPath, new DataView(data));
1926
- }
1927
- } catch (error) {
1928
- console.log(`${fullPath} \u65E0\u6CD5\u5199\u5165`);
1929
- }
1930
- }
1931
- }
1932
- });
1933
- await ocean_press.build({
1934
- log: (msg) => {
1935
- if (msg.startsWith("\u6E32\u67D3\uFF1A")) {
1936
- process.stdout.write(`\r\x1B[K${msg}`);
1937
- } else {
1938
- 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(`
1939
2974
  ${msg}`);
2975
+ }
2976
+ },
2977
+ percentage: (n) => {
2978
+ process.stdout.write(`\r\x1B[K\u8FDB\u5EA6\uFF1A${n}%`);
1940
2979
  }
1941
- },
1942
- percentage: (n) => {
1943
- process.stdout.write(`\r\x1B[K\u8FDB\u5EA6\uFF1A${n}%`);
1944
- }
1945
- });
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);
1946
3011
  });
1947
3012
 
1948
3013
  // src/cli/server.ts
@@ -1950,28 +3015,35 @@ import { readFile as readFile3 } from "fs/promises";
1950
3015
 
1951
3016
  // src/server.ts
1952
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";
1953
3021
 
1954
3022
  // src/core/hono_server.ts
1955
3023
  import { Hono } from "hono";
1956
3024
  import { stream } from "hono/streaming";
1957
- 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
+ });
1958
3039
  app.get("/", (c) => c.redirect("/index.html"));
1959
3040
  app.get("/assets/*", assetsHandle);
3041
+ app.get("/favicon.ico", assetsHandle);
1960
3042
  app.get("*", async (c) => {
1961
3043
  const path = decodeURIComponent(c.req.path);
1962
- const r = await renderHtmlByUriPath(path).catch(async (err) => {
1963
- if (err.message.includes("not doc")) {
1964
- return await assetsHandle(c);
1965
- }
1966
- throw err;
1967
- });
1968
- if (r instanceof Error) {
1969
- throw r;
1970
- } else if (typeof r === "string") {
1971
- return c.html(r);
1972
- } else {
1973
- return r;
1974
- }
3044
+ const p = Effect7.provide(renderHtmlByUriPath(path), context);
3045
+ const r = await Effect7.runPromise(p);
3046
+ return c.html(r);
1975
3047
  });
1976
3048
  return app;
1977
3049
  }
@@ -2009,55 +3081,60 @@ async function assetsHandle(c) {
2009
3081
  }
2010
3082
  });
2011
3083
  }
2012
- async function renderHtmlByUriPath(path) {
2013
- const hpath = decodeURIComponent(path).replace(/\#(.*)?$/, "").replace(/\.html$/, "");
2014
- const doc = await get_doc_by_hpath(hpath);
2015
- return await htmlTemplate(
2016
- {
2017
- title: doc.Properties?.title || "",
2018
- htmlContent: await renderHTML(doc),
2019
- level: path.split("/").length - 1
2020
- },
2021
- {
2022
- ...tempConfig.cdn,
2023
- embedCode: currentConfig.value.embedCode
2024
- }
2025
- );
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
+ });
2026
3103
  }
2027
3104
 
2028
3105
  // src/server.ts
2029
- import { serveStatic } from "@hono/node-server/serve-static";
2030
- import { Hono as Hono2 } from "hono";
2031
- import { join as join2 } from "path/posix";
2032
- console.log(join2(import.meta.url.slice(5), "../../public/"));
2033
3106
  function server(config = { port: 80, hostname: "0.0.0.0" }) {
2034
- const app = new Hono2();
2035
- app.use(
2036
- "/notebook/*",
2037
- serveStatic({
2038
- root: "./public/",
2039
- onNotFound(path, c) {
2040
- console.log("[onNotFound notebook path]", path);
2041
- }
2042
- })
2043
- );
2044
- createHonoApp(app);
2045
- return new Promise((resolve3, _reject) => {
2046
- serve(
2047
- {
2048
- fetch: app.fetch,
2049
- port: config.port,
2050
- hostname: config.hostname
2051
- },
2052
- (info) => {
2053
- resolve3({ info, app });
2054
- console.log(`Listening on http://${info.address}:${info.port}`);
2055
- }
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
+ })
2056
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
+ });
2057
3133
  });
2058
3134
  }
2059
3135
 
2060
3136
  // src/cli/server.ts
3137
+ import { Context as Context6, Effect as Effect9 } from "effect";
2061
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(
2062
3139
  "--cache <boolean>",
2063
3140
  "\u914D\u7F6E\u4E3A true \u65F6\u5F00\u542F\u7F13\u5B58,\u9ED8\u8BA4\u4E3A false \u4E0D\u5F00\u542F\u7F13\u5B58",
@@ -2068,12 +3145,36 @@ program.command("server").description("\u542F\u52A8\u52A8\u6001\u4EE3\u7406").op
2068
3145
  console.log(`\u8BF7\u8BBE\u7F6E\u914D\u7F6E\u6587\u4EF6\u4F4D\u7F6E`);
2069
3146
  }
2070
3147
  const config = await readFile3(opt.config, "utf-8");
2071
- loadConfigFile(JSON.parse(config));
2072
3148
  setCache(opt.cache !== "false");
2073
- server({
2074
- hostname: opt.host,
2075
- port: Number(opt.port)
2076
- });
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);
2077
3178
  }
2078
3179
  );
2079
3180