oceanpress 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2007 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
+
5
+ // src/cli/deploy.ts
6
+ import { readFile } from "fs/promises";
7
+ import { createRPC as createRPC2 } from "oceanpress-rpc";
8
+ import { stringify as stringify2 } from "superjson";
9
+
10
+ // src/core/dependency.ts
11
+ var nullDep = () => {
12
+ throw new Error("\u4E0D\u53EF\u8C03\u7528\u672A\u586B\u5145\u7684\u4F9D\u8D56");
13
+ };
14
+ var storeDep = {
15
+ // 读写配置文件所依赖的副作用
16
+ setItem: nullDep,
17
+ getItem: nullDep,
18
+ // render功能依赖的副作用
19
+ getNodeByID: nullDep,
20
+ getDocPathBySY: nullDep,
21
+ getDocByChildID: nullDep,
22
+ getHPathByID_Node: nullDep
23
+ };
24
+
25
+ // src/util/deep_assign.ts
26
+ function deepAssign(target, source, config = { add: true, update: true }) {
27
+ for (let key in source) {
28
+ if (source.hasOwnProperty(key)) {
29
+ if (source[key] instanceof Object && !Array.isArray(source[key])) {
30
+ if (!target.hasOwnProperty(key)) {
31
+ target[key] = {};
32
+ }
33
+ deepAssign(target[key], source[key], config);
34
+ } else {
35
+ if (!target.hasOwnProperty(key) && config.add) {
36
+ target[key] = source[key];
37
+ } else if (config.update) {
38
+ target[key] = source[key];
39
+ }
40
+ }
41
+ }
42
+ }
43
+ return target;
44
+ }
45
+
46
+ // src/core/config.ts
47
+ import { computed, reactive, watch } from "vue";
48
+
49
+ // package.json
50
+ var package_default = {
51
+ name: "oceanpress",
52
+ private: true,
53
+ version: "1.0.2",
54
+ type: "module",
55
+ scripts: {
56
+ dev: "vite",
57
+ cli: "tsx ./src/cli.ts",
58
+ cli_watch: "tsx watch ./src/cli.ts",
59
+ build: "vite build && npm run build_lib",
60
+ build_lib: "vite build --config vite.sw.config.ts",
61
+ build_npmlib: "vite build --mode library",
62
+ build_plugin_ui: "vite build --config vite.plugin.config.ts",
63
+ dev_plugin_ui: "vite build --watch --config vite.plugin.config.ts",
64
+ generate_dependency_graph: "depcruise src --include-only '^src' --output-type dot > ./assets/dep.dot",
65
+ preview: "vite preview"
66
+ },
67
+ bin: "./dist/cli.js",
68
+ dependencies: {
69
+ "@aws-sdk/client-s3": "^3.758.0",
70
+ "@hono/node-server": "^1.13.8",
71
+ cheerio: "1.0.0",
72
+ commander: "^13.1.0",
73
+ fzstd: "^0.1.1",
74
+ hono: "^4.7.4",
75
+ jszip: "^3.10.1",
76
+ meilisearch: "^0.49.0",
77
+ "naive-ui": "^2.41.0",
78
+ "oceanpress-rpc": "workspace:*",
79
+ "oceanpress-server": "workspace:*",
80
+ octokit: "^4.1.2",
81
+ superjson: "^2.2.2",
82
+ tsx: "^4.19.3",
83
+ vditor: "^3.10.9",
84
+ vue: "^3.5.13",
85
+ "vue-router": "^4.5.0",
86
+ "zstd-codec": "^0.1.5"
87
+ },
88
+ devDependencies: {
89
+ "@vitejs/plugin-vue": "^5.2.1",
90
+ "@vitejs/plugin-vue-jsx": "^4.1.1",
91
+ "dependency-cruiser": "^16.10.0",
92
+ tsup: "^8.4.0",
93
+ typescript: "^5.8.2",
94
+ vite: "^6.2.2",
95
+ "vite-plugin-vue-devtools": "^7.7.2",
96
+ "vue-tsc": "^2.2.8"
97
+ }
98
+ };
99
+
100
+ // src/core/config.ts
101
+ var version = package_default.version;
102
+ var defaultConfig = {
103
+ name: "default",
104
+ /** 需要编译的笔记本 */
105
+ notebook: {},
106
+ /** 思源的鉴权key */
107
+ authorized: "",
108
+ /** 思源的api服务地址 */
109
+ apiPrefix: "http://127.0.0.1:6806",
110
+ /** 打包成 zip */
111
+ compressedZip: true,
112
+ /** 不将 publicZip 打包到 zip 包中 */
113
+ // withoutPublicZip: true,
114
+ /** 不复制 assets/ ,勾选此选项则需要自行处理资源文件 */
115
+ excludeAssetsCopy: false,
116
+ /** 输出站点地图相关 */
117
+ sitemap: {
118
+ /** 控制是否输出 sitemap.xml,不影响 rss 选项 */
119
+ enable: true,
120
+ /** 默认为 "." 生成路径例如 "./record/思源笔记.html"
121
+ * 但 sitemap 并不建议采用相对路径所以应该替换成例如 "https://shenzilong.cn"
122
+ * 则会生成 "https://shenzilong.cn/record/思源笔记.html" 这样的绝对路径
123
+ * 参见 https://www.sitemaps.org/protocol.html#escaping
124
+ */
125
+ sitePrefix: ".",
126
+ /** 站点地址 */
127
+ siteLink: "",
128
+ /** 站点描述 */
129
+ description: "",
130
+ /** 站点标题 */
131
+ title: "",
132
+ /** 开启 rss 生成,对于文件名为 .rss.xml 结尾的文档生效 */
133
+ rss: true
134
+ },
135
+ /** 开启增量编译,当开启增量编译时,
136
+ * 在编译过程中会依据 __skipBuilds__ 的内容来跳过一些没有变化不需要重新输出的内容
137
+ */
138
+ enableIncrementalCompilation: false,
139
+ /**
140
+ * 要全量编译文档时将此选项设置为false,当OceanPress版本和上次编译时不同时会忽略此属性全量编译文档
141
+ */
142
+ enableIncrementalCompilation_doc: true,
143
+ /** 跳过编译的资源 */
144
+ __skipBuilds__: {},
145
+ // cdn: {
146
+ // /** 思源 js、css等文件的前缀 */
147
+ // siyuanPrefix:
148
+ // 'https://fastly.jsdelivr.net/gh/siyuan-note/oceanpress@v0.0.7/apps/frontend/public/notebook/',
149
+ // /** 思源 js、css等文件zip包地址 */
150
+ // publicZip:
151
+ // 'https://fastly.jsdelivr.net/gh/siyuan-note/oceanpress@v0.0.7/apps/frontend/public/public.zip',
152
+ // },
153
+ /** s3 上传配置
154
+ * https://help.aliyun.com/zh/oss/developer-reference/use-amazon-s3-sdks-to-access-oss#section-2ri-suq-pb3
155
+ */
156
+ s3: {
157
+ enable: false,
158
+ bucket: "",
159
+ region: "",
160
+ pathPrefix: "",
161
+ endpoint: "",
162
+ accessKeyId: "",
163
+ secretAccessKey: ""
164
+ },
165
+ /** 部署到 oceanPressServer 的配置 */
166
+ oceanPressServer: {
167
+ enable: false,
168
+ apiBase: "",
169
+ apiKey: ""
170
+ },
171
+ meilisearch: {
172
+ enable: false,
173
+ host: "",
174
+ apiKey: "",
175
+ indexName: ""
176
+ },
177
+ /** html模板嵌入代码块,会将此处配置中的代码嵌入到生成的html所对应的位置 */
178
+ embedCode: {
179
+ head: "",
180
+ beforeBody: "",
181
+ afterBody: `<footer>
182
+ <p style="text-align:center;margin:15px 0;">
183
+ \u6280\u672F\u652F\u6301\uFF1A
184
+ <a target="_blank" href="https://github.com/2234839/oceanPress_js">OceanPress</a> |
185
+ \u5F00\u53D1\u8005\uFF1A
186
+ <a target="_blank" href="https://shenzilong.cn">\u5D2E\u751F\uFF08\u5B50\u865A\uFF09</a>
187
+ </p>
188
+ </footer>`
189
+ },
190
+ OceanPress: {
191
+ /** 此配置文件编译时的版本 */
192
+ version
193
+ }
194
+ };
195
+ var configs = reactive({
196
+ /** 当前所使用的配置项的 key */
197
+ __current__: "default",
198
+ /** 为true是表示是代码中设置的默认值,不会保存到本地,避免覆盖之前保存的数据,在加载本地配置后会自动修改为false */
199
+ __init__: true,
200
+ default: deepAssign({}, defaultConfig)
201
+ });
202
+ var loadConfigFile = (c) => {
203
+ if (c) {
204
+ deepAssign(configs, c);
205
+ } else {
206
+ const localConfig = storeDep.getItem("configs");
207
+ if (localConfig) {
208
+ deepAssign(configs, JSON.parse(localConfig));
209
+ }
210
+ }
211
+ Object.entries(configs).filter(([key]) => key.startsWith("__") === false).forEach(([_key, obj]) => {
212
+ deepAssign(obj, defaultConfig, { update: false, add: true });
213
+ });
214
+ };
215
+ var currentConfig = computed(() => configs[configs.__current__]);
216
+ var tempConfig = {
217
+ cdn: {
218
+ /** 思源 js、css等文件的前缀 */
219
+ siyuanPrefix: "https://fastly.jsdelivr.net/gh/siyuan-note/oceanpress@latest/apps/frontend/public/notebook/",
220
+ /** 思源 js、css等文件zip包地址 */
221
+ publicZip: "https://fastly.jsdelivr.net/gh/siyuan-note/oceanpress@v0.0.7/apps/frontend/public/public.zip"
222
+ },
223
+ withoutPublicZip: true
224
+ };
225
+ var saveConfig = () => {
226
+ if (configs.__init__ === false)
227
+ storeDep.setItem("configs", JSON.stringify(configs, null, 2));
228
+ };
229
+ var timer = null;
230
+ var debounceSaveConfig = () => {
231
+ if (timer) {
232
+ clearTimeout(timer);
233
+ }
234
+ timer = setTimeout(() => {
235
+ saveConfig();
236
+ timer = null;
237
+ }, 700);
238
+ };
239
+ watch(configs, debounceSaveConfig, { deep: true });
240
+ configs.__init__ = false;
241
+ if (globalThis.document) {
242
+ loadConfigFile();
243
+ }
244
+
245
+ // src/core/genZip.ts
246
+ import JSZip from "jszip";
247
+ async function downloadZIP(docTree, config) {
248
+ const content = await genZIP(docTree, config);
249
+ if (globalThis.document) {
250
+ const link = document.createElement("a");
251
+ link.href = URL.createObjectURL(content);
252
+ link.download = `notebook.zip`;
253
+ link.click();
254
+ } else {
255
+ }
256
+ }
257
+ async function genZIP(docTree, config) {
258
+ const zip = new JSZip();
259
+ if (config?.withoutZip !== true) {
260
+ const presetZip = await (await fetch(config?.publicZip ?? "/public.zip")).arrayBuffer();
261
+ await zip.loadAsync(presetZip);
262
+ }
263
+ for (const [path, html2] of Object.entries(docTree)) {
264
+ const newPath = path.startsWith("/") ? path.slice(1) : path;
265
+ zip.file(newPath, html2);
266
+ }
267
+ return await zip.generateAsync({ type: "blob" });
268
+ }
269
+
270
+ // src/plugins/meilisearch_plugin/meilisearch_upload.ts
271
+ import { Meilisearch } from "meilisearch";
272
+ import { load } from "cheerio";
273
+ var MeilisearchPlugin = class {
274
+ constructor(option) {
275
+ __publicField(this, "_melisearch");
276
+ __publicField(this, "_index");
277
+ __publicField(this, "_indexName");
278
+ __publicField(this, "host");
279
+ __publicField(this, "apiKey");
280
+ __publicField(this, "docs", {});
281
+ __publicField(this, "build_onFileTree", (c, next) => {
282
+ const [tree] = c;
283
+ console.log("\u5F00\u59CB\u751F\u6210 meilisearch \u6240\u9700\u6570\u636E\u7ED3\u6784");
284
+ const htmlTree = Object.keys(tree).filter((path) => path.endsWith(".html")).map((path) => [path, tree[path]]);
285
+ for (const [path, html2] of htmlTree) {
286
+ const $ = load(html2.toString());
287
+ const entries = $(".h1,.h2,.h3,.h4,.h5,.h6,.p").toArray();
288
+ const level = { lvl0: $("title").text() };
289
+ for (const el of entries) {
290
+ const c2 = el.attribs.class;
291
+ if (c2 !== "p") {
292
+ Object.keys(level).forEach((lv) => {
293
+ if (lv.substring(3, 4) > c2.substring(1, 2)) {
294
+ delete level[lv];
295
+ }
296
+ });
297
+ level[`lvl${c2.substring(1, 2)}`] = $(el).text();
298
+ }
299
+ this.addDocument({
300
+ id: el.attribs.id,
301
+ content: $(el).text(),
302
+ url: `${path}#${el.attribs.id}`,
303
+ ...level
304
+ });
305
+ }
306
+ if (path.endsWith("index.html")) break;
307
+ }
308
+ this.updateDocument();
309
+ return next(tree);
310
+ });
311
+ this.host = option.host;
312
+ this.apiKey = option.apiKey;
313
+ this._indexName = option.indexName;
314
+ }
315
+ async _getMeliSearch() {
316
+ if (this._melisearch === void 0) {
317
+ this._melisearch = new Meilisearch({
318
+ host: this.host,
319
+ apiKey: this.apiKey
320
+ });
321
+ }
322
+ return this._melisearch;
323
+ }
324
+ async _getIndex() {
325
+ if (this._index === void 0) {
326
+ const meilisearch = await this._getMeliSearch();
327
+ this._index = meilisearch.index(this._indexName);
328
+ }
329
+ return this._index;
330
+ }
331
+ async addDocument(doc) {
332
+ this.docs[doc.id] = doc;
333
+ }
334
+ async updateDocument() {
335
+ console.log(`\u5F00\u59CB\u4E0A\u4F20\u6570\u636E\u5230 ${this.host}`);
336
+ const index = await this._getIndex();
337
+ const res = await index.addDocuments(Object.values(this.docs));
338
+ console.log(`\u4E0A\u4F20\u7ED3\u679C`, res);
339
+ }
340
+ };
341
+
342
+ // src/core/plugin.ts
343
+ var PluginCenter = class {
344
+ constructor(_funMap) {
345
+ this._funMap = _funMap;
346
+ __publicField(this, "plugins", []);
347
+ /** 辅助类型,不可调用! */
348
+ __publicField(this, "pluginType", 0);
349
+ /** 对需要调用的函数进行代理,完成插件hook介入。 */
350
+ __publicField(this, "fun");
351
+ const that = this;
352
+ this.fun = new Proxy({}, {
353
+ get(_target, propertyKey, receiver) {
354
+ const method = Reflect.get(that._funMap, propertyKey, receiver);
355
+ if (typeof method === "function") {
356
+ return (...args) => {
357
+ return that.callFn(
358
+ propertyKey,
359
+ //@ts-ignore 懒得推类型了。属于内部实现,就直接忽略掉吧
360
+ method
361
+ )(...args);
362
+ };
363
+ }
364
+ return method;
365
+ }
366
+ });
367
+ }
368
+ registerPlugin(plugin) {
369
+ this.plugins.push(plugin);
370
+ }
371
+ removePlugin(plugin) {
372
+ this.plugins = this.plugins.filter((p) => p !== plugin);
373
+ }
374
+ /** 洋葱hook调用机制的实现 */
375
+ callFn(name, fn) {
376
+ return (...arg) => {
377
+ const m = new middlewareRunner(fn);
378
+ for (const plugin of this.plugins) {
379
+ const middleware = plugin[name];
380
+ if (middleware) {
381
+ m.use(middleware);
382
+ }
383
+ }
384
+ return m.runMiddlewareHandel(...arg);
385
+ };
386
+ }
387
+ };
388
+ var middlewareRunner = class {
389
+ constructor(handel) {
390
+ this.handel = handel;
391
+ __publicField(this, "middlewares", []);
392
+ }
393
+ use(middleware) {
394
+ this.middlewares.push(middleware);
395
+ }
396
+ runMiddlewareHandel(...ctx) {
397
+ let index = 0;
398
+ const next = (...ctx2) => {
399
+ const middleware = this.middlewares[index];
400
+ index++;
401
+ if (middleware === void 0) {
402
+ return this.handel.call(this, ...ctx2);
403
+ }
404
+ return middleware(ctx2, next);
405
+ };
406
+ return next.call(this, ...ctx);
407
+ }
408
+ };
409
+
410
+ // src/plugins/publish/s3.ts
411
+ import { S3 } from "@aws-sdk/client-s3";
412
+ var s3Upload_plugin = {
413
+ build: async function([config, effect, other], next) {
414
+ const res = await next(config, effect, {
415
+ ...other,
416
+ onFileTree: async (tree) => {
417
+ if (other?.onFileTree) {
418
+ await other.onFileTree(tree);
419
+ }
420
+ for await (const [fileName, ETag] of s3_uploads(tree, config)) {
421
+ effect.log(`\u4E0A\u4F20\uFF1A ${fileName} ${ETag}`);
422
+ }
423
+ }
424
+ });
425
+ effect.log(`s3 \u4E0A\u4F20\u5B8C\u6BD5`);
426
+ return res;
427
+ }
428
+ };
429
+ var s3_uploads = async function* (tree, config) {
430
+ const s3 = new S3({
431
+ region: config.s3.region,
432
+ endpoint: config.s3.endpoint,
433
+ credentials: {
434
+ accessKeyId: config.s3.accessKeyId,
435
+ secretAccessKey: config.s3.secretAccessKey
436
+ }
437
+ });
438
+ const encoder = new TextEncoder();
439
+ for (const [path, value] of Object.entries(tree)) {
440
+ let buffer;
441
+ if (typeof value === "string") {
442
+ buffer = encoder.encode(value);
443
+ } else {
444
+ buffer = new Uint8Array(value);
445
+ }
446
+ const r = await s3.putObject({
447
+ Bucket: config.s3.bucket,
448
+ Key: (config.s3.pathPrefix + path).replace(/\/\//g, "/"),
449
+ Body: buffer
450
+ });
451
+ yield [path, r.ETag];
452
+ }
453
+ };
454
+
455
+ // src/core/htmlTemplate.ts
456
+ async function htmlTemplate(p, config) {
457
+ let prePath = "";
458
+ if (config?.siyuanPrefix) {
459
+ prePath = config.siyuanPrefix;
460
+ } else {
461
+ for (let i = 0; i < p.level; i++) {
462
+ prePath += "../";
463
+ }
464
+ }
465
+ const version2 = "2.10.5";
466
+ return `<!DOCTYPE html>
467
+ <html lang="zh_CN" data-theme-mode="light" data-light-theme="daylight" data-dark-theme="midnight">
468
+ <head>
469
+ ${config?.embedCode?.head ?? ""}
470
+ <meta charset="utf-8" />
471
+ <meta http-equiv="X-UA-Compatible" content="IE=edge" />
472
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"/>
473
+ <meta name="apple-mobile-web-app-capable" content="yes" />
474
+ <meta name="mobile-web-app-capable" content="yes" />
475
+ <meta name="apple-mobile-web-app-status-bar-style" content="black" />
476
+ <link rel="stylesheet" type="text/css" id="baseStyle" href="${prePath}stage/build/export/base.css?${version2}"/>
477
+ <script>
478
+ function isNightTime() {
479
+ const currentHour = new Date().getHours();
480
+ return currentHour >= 18 || currentHour < 6;
481
+ }
482
+ document.write('<link rel="stylesheet" type="text/css" id="themeDefaultStyle" href="${prePath}appearance/themes/'+(isNightTime()?'midnight':'daylight')+'/theme.css?${version2}"/>');
483
+ </script>
484
+ <link rel="stylesheet" type="text/css" href="${prePath}appearance/oceanpress.css"/>
485
+ <title>${p.title}</title>
486
+ </head>
487
+ <body>
488
+ ${config?.embedCode?.beforeBody ?? ""}
489
+ <div class="protyle-wysiwyg protyle-wysiwyg--attr" id="preview">
490
+ ${p.htmlContent}
491
+ </div>
492
+ <script src="${prePath}appearance/icons/material/icon.js?${version2}"></script>
493
+ <script src="${prePath}stage/build/export/protyle-method.js?${version2}"></script>
494
+ <script src="${prePath}stage/protyle/js/lute/lute.min.js?${version2}"></script>
495
+ <script>
496
+ window.siyuan = {
497
+ config: {
498
+ appearance: {
499
+ mode: isNightTime()?1:0,//\u4E3B\u9898 \u660E\u4EAE=0 \u6697\u9ED1=1
500
+ codeBlockThemeDark: "base16/dracula",
501
+ codeBlockThemeLight: "github",
502
+ },
503
+ editor: {
504
+ codeLineWrap: true,
505
+ codeLigatures: false,
506
+ plantUMLServePath: "https://www.plantuml.com/plantuml/svg/~1",
507
+ codeSyntaxHighlightLineNum: true,
508
+ katexMacros: JSON.stringify({}),
509
+ },
510
+ },
511
+ languages: { copy: "\u590D\u5236" },
512
+ };
513
+ const cdn = "${prePath}stage/protyle";
514
+ const previewElement = document.getElementById("preview");
515
+
516
+ Protyle.highlightRender(previewElement, cdn);
517
+ Protyle.mathRender(previewElement, cdn, false);
518
+ Protyle.mermaidRender(previewElement, cdn);
519
+ Protyle.flowchartRender(previewElement, cdn);
520
+ Protyle.graphvizRender(previewElement, cdn);
521
+ Protyle.chartRender(previewElement, cdn);
522
+ Protyle.mindmapRender(previewElement, cdn);
523
+ Protyle.abcRender(previewElement, cdn);
524
+ Protyle.htmlRender(previewElement);
525
+ Protyle.plantumlRender(previewElement, cdn);
526
+ document.querySelectorAll(".protyle-action__copy").forEach((item) => {
527
+ item.addEventListener("click", (event) => {
528
+ navigator.clipboard.writeText(
529
+ item.parentElement.nextElementSibling.textContent.trimEnd(),
530
+ );
531
+ event.preventDefault();
532
+ event.stopPropagation();
533
+ });
534
+ });
535
+ </script>
536
+ ${config?.embedCode?.afterBody ?? ""}
537
+ </body>
538
+ </html>`;
539
+ }
540
+
541
+ // src/util/escaping.ts
542
+ function escaping(s) {
543
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos");
544
+ }
545
+ function unescaping(s) {
546
+ return s.replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&apos;/g, "'").replace(/&#(\d+);/g, (_sub, code) => {
547
+ return String.fromCharCode(Number(code));
548
+ });
549
+ }
550
+
551
+ // src/components/data_promise/index.ts
552
+ import { customRef, nextTick, watch as watch2, watchEffect } from "vue";
553
+ var PromiseObj = class {
554
+ constructor() {
555
+ __publicField(this, "pending", false);
556
+ __publicField(this, "fulfilled", false);
557
+ __publicField(this, "rejected", false);
558
+ __publicField(this, "data", {});
559
+ __publicField(this, "error", {});
560
+ __publicField(this, "_p", Promise.resolve());
561
+ }
562
+ setP(p) {
563
+ this._p = p;
564
+ }
565
+ equalP(p) {
566
+ return this._p === p;
567
+ }
568
+ reLoad() {
569
+ }
570
+ setValue(_data) {
571
+ }
572
+ };
573
+ var continueLoading = Symbol();
574
+ function usePromiseComputed({
575
+ deps,
576
+ getter,
577
+ dataMergeFun = (_oldData, newData) => newData,
578
+ defaultData
579
+ }) {
580
+ const r = new PromiseObj();
581
+ if (defaultData !== void 0) {
582
+ r.data = defaultData;
583
+ }
584
+ return customRef((track, trigger) => {
585
+ if (!deps && getter) {
586
+ watchEffect(() => update(getter()));
587
+ } else if (deps && getter) {
588
+ if (deps instanceof Function) {
589
+ const depsSource = deps();
590
+ if (Array.isArray(depsSource) && depsSource.length === 0) {
591
+ } else {
592
+ watch2(deps, () => update(getter()), { immediate: true });
593
+ }
594
+ } else {
595
+ watch2(deps, () => update(getter()), { immediate: true });
596
+ }
597
+ }
598
+ function update(p) {
599
+ r.pending = true;
600
+ r.fulfilled = false;
601
+ r.rejected = false;
602
+ if (p === continueLoading) {
603
+ nextTick(trigger);
604
+ return;
605
+ }
606
+ r.setP(p);
607
+ nextTick(trigger);
608
+ p.then((res) => {
609
+ if (r.equalP(p)) {
610
+ r.pending = false;
611
+ r.fulfilled = true;
612
+ r.data = dataMergeFun(r.data, res);
613
+ }
614
+ }).catch((e) => {
615
+ if (r.equalP(p)) {
616
+ r.pending = false;
617
+ r.rejected = true;
618
+ r.error = e;
619
+ }
620
+ }).finally(() => {
621
+ if (r.equalP(p)) {
622
+ trigger();
623
+ }
624
+ });
625
+ }
626
+ r.reLoad = () => {
627
+ if (getter) update(getter());
628
+ };
629
+ r.setValue = (data) => {
630
+ r.pending = false;
631
+ r.fulfilled = true;
632
+ r.data = dataMergeFun(r.data, data);
633
+ trigger();
634
+ };
635
+ return {
636
+ get() {
637
+ track();
638
+ return r;
639
+ },
640
+ set(_newValue) {
641
+ console.warn("\u4E0D\u53EF\u8BBE\u7F6E\u503C");
642
+ }
643
+ };
644
+ });
645
+ }
646
+ ((usePromiseComputed2) => {
647
+ function nullDeps(getter) {
648
+ return usePromiseComputed2({
649
+ deps: () => [],
650
+ getter
651
+ });
652
+ }
653
+ usePromiseComputed2.nullDeps = nullDeps;
654
+ function fn(fn2) {
655
+ const p = usePromiseComputed2({
656
+ getter() {
657
+ return fn2();
658
+ }
659
+ });
660
+ return p;
661
+ }
662
+ usePromiseComputed2.fn = fn;
663
+ })(usePromiseComputed || (usePromiseComputed = {}));
664
+
665
+ // src/core/siyuan_api.ts
666
+ var rpcCount = 0;
667
+ async function rpc(method, arg) {
668
+ const apiPrefix = currentConfig.value.apiPrefix;
669
+ const Authorization = currentConfig.value.authorized;
670
+ rpcCount++;
671
+ if (method === "get_assets") {
672
+ return fetch(`${apiPrefix}/${arg[0].path}`, {
673
+ headers: {
674
+ Authorization: `Token ${Authorization}`
675
+ },
676
+ body: null,
677
+ method: "GET",
678
+ mode: "cors"
679
+ }).then((r) => r.arrayBuffer());
680
+ }
681
+ const res = await fetch(`${apiPrefix}/api/${method.replace(/_/g, "/")}`, {
682
+ headers: {
683
+ Authorization: `Token ${Authorization}`
684
+ },
685
+ body: JSON.stringify(arg[0]),
686
+ method: "POST"
687
+ }).catch((err) => {
688
+ err.message = `\u8BBF\u95EE\u601D\u6E90\u63A5\u53E3\u65F6\u51FA\u9519\u4E86\uFF0C\u8BF7\u68C0\u67E5\u601D\u6E90\u670D\u52A1\u662F\u5426\u542F\u52A8\u4EE5\u53CA\u914D\u7F6E\u63A5\u53E3\u5730\u5740\u662F\u5426\u6B63\u786E\u3002`;
689
+ throw err;
690
+ });
691
+ if (method === "file_getFile") {
692
+ const path = arg[0].path;
693
+ if (path.endsWith(".sy")) {
694
+ return await res.json();
695
+ } else {
696
+ const buffer = await res.arrayBuffer();
697
+ if (buffer.byteLength < 200) {
698
+ const decoder = new TextDecoder();
699
+ const text = decoder.decode(buffer);
700
+ if (JSON.parse(text).code === 404) {
701
+ throw new Error(`\u6587\u4EF6\u4E0D\u5B58\u5728: ${path}`);
702
+ }
703
+ }
704
+ return buffer;
705
+ }
706
+ }
707
+ const json = await res.json();
708
+ if (json.code !== 0) {
709
+ throw new Error(json.msg);
710
+ }
711
+ return json.data;
712
+ }
713
+ var API = new Proxy(
714
+ {},
715
+ {
716
+ get(_, method) {
717
+ return (...arg) => rpc(method, arg);
718
+ }
719
+ }
720
+ );
721
+ var vApi = new Proxy(
722
+ {},
723
+ {
724
+ get(_, method) {
725
+ return (...arg) => usePromiseComputed.fn(() => rpc(method, arg));
726
+ }
727
+ }
728
+ );
729
+
730
+ // src/core/render.ts
731
+ async function renderHTML(sy, renderInstance = getRender()) {
732
+ if (sy === void 0) return "";
733
+ const renderObj = {
734
+ ...renderInstance,
735
+ nodeStack: [
736
+ /** 避免让所有的 renderInstance.nodeStack 是同一个对象 ,所以这里复制一个新的 */
737
+ ...renderInstance.nodeStack
738
+ ]
739
+ };
740
+ if (renderInstance.nodeStack.find(
741
+ (node) => node.ID && sy.ID && node.ID === sy.ID
742
+ )) {
743
+ return warnDiv(
744
+ "\u5FAA\u73AF\u5F15\u7528",
745
+ [...renderInstance.nodeStack, sy].map((el) => el.ID)
746
+ );
747
+ }
748
+ if (renderObj[sy.Type] === void 0) {
749
+ return warnDiv(
750
+ `\u6CA1\u6709\u627E\u5230\u5BF9\u5E94\u7684\u6E32\u67D3\u65B9\u6CD5 ${sy.Type} ${renderObj.nodeStack[0].Properties?.title}`
751
+ );
752
+ } else {
753
+ renderObj.nodeStack.push(sy);
754
+ if (sy.ID && renderInstance.nodeStack[0]?.ID) {
755
+ const targetDoc = await storeDep.getDocByChildID(sy.ID);
756
+ const currentDoc = renderInstance.nodeStack[0];
757
+ if (targetDoc?.ID !== void 0 && targetDoc.ID !== currentDoc.ID && currentDoc.ID) {
758
+ renderObj.refs.add(targetDoc.ID);
759
+ }
760
+ }
761
+ const r = await renderObj[sy.Type](sy);
762
+ renderObj.nodeStack.pop();
763
+ return r;
764
+ }
765
+ }
766
+ function warnDiv(msg, ...args) {
767
+ warn(msg, ...args);
768
+ return `<div class="ft__smaller ft__secondary b3-form__space--small">${msg}</div>`;
769
+ }
770
+ function isRenderCode(sy) {
771
+ const mark = atob(
772
+ sy.CodeBlockInfo ?? sy.Children?.find((el) => el.Type === "NodeCodeBlockFenceInfoMarker")?.CodeBlockInfo ?? ""
773
+ );
774
+ return [
775
+ [
776
+ "mindmap",
777
+ "mermaid",
778
+ "echarts",
779
+ "abc",
780
+ "graphviz",
781
+ "flowchart",
782
+ "plantuml"
783
+ ].includes(mark),
784
+ mark
785
+ ];
786
+ }
787
+ var html = String.raw;
788
+ async function childRender(sy, renderInstance) {
789
+ let h = "";
790
+ for await (const el of sy?.Children ?? []) {
791
+ h += await renderHTML(el, renderInstance);
792
+ }
793
+ return h;
794
+ }
795
+ function strAttr(sy, config = {}) {
796
+ if (config?.subtype_class === void 0) {
797
+ config.subtype_class = (() => {
798
+ const typ_subtype = sy.ListData?.Typ === 1 ? (
799
+ /** 有序列表 */
800
+ "o"
801
+ ) : sy.ListData?.Typ === 3 ? (
802
+ /** 任务列表 */
803
+ "t"
804
+ ) : (
805
+ /** 无序列表 */
806
+ "u"
807
+ );
808
+ if (sy.Type === "NodeDocument") return "h1";
809
+ else if (sy.Type === "NodeHeading") return `h${sy.HeadingLevel}`;
810
+ else if (sy.Type === "NodeList") return [typ_subtype, "list"];
811
+ else if (sy.Type === "NodeListItem") return [typ_subtype, "li"];
812
+ else if (sy.Type === "NodeParagraph") return ["", "p"];
813
+ else if (sy.Type === "NodeImage") return ["", "img"];
814
+ else if (sy.Type === "NodeBlockquote") return ["", "bq"];
815
+ else if (sy.Type === "NodeSuperBlock") return ["", "sb"];
816
+ else if (sy.Type === "NodeCodeBlock") {
817
+ const [yes, mark] = isRenderCode(sy);
818
+ if (yes) {
819
+ return [mark, "render-node"];
820
+ } else {
821
+ return ["", "code-block"];
822
+ }
823
+ } else if (sy.Type === "NodeTable") return ["", "table"];
824
+ else if (sy.Type === "NodeThematicBreak") return ["", "hr"];
825
+ else if (sy.Type === "NodeMathBlock") return ["math", "render-node"];
826
+ else if (sy.Type === "NodeIFrame") return ["", "iframe"];
827
+ else if (sy.Type === "NodeVideo") return ["", "iframe"];
828
+ else return "";
829
+ })();
830
+ }
831
+ const attrObj = {};
832
+ function addAttr(key, value) {
833
+ attrObj[key] = value;
834
+ }
835
+ if (sy.ID) {
836
+ addAttr("id", sy.ID);
837
+ addAttr("data-node-id", sy.ID);
838
+ }
839
+ if (sy?.TextMarkType === "tag") {
840
+ addAttr(`data-type`, sy.TextMarkType ?? "");
841
+ } else {
842
+ addAttr(`data-type`, config?.data_type ?? sy.Type);
843
+ }
844
+ if (sy.Properties?.updated) addAttr("updated", sy.Properties.updated);
845
+ if (config?.subtype_class) {
846
+ if (typeof config.subtype_class === "string") {
847
+ addAttr("data-subtype", config.subtype_class);
848
+ addAttr("class", config.subtype_class);
849
+ } else {
850
+ if (config.subtype_class[0] !== "")
851
+ addAttr("data-subtype", config.subtype_class[0]);
852
+ if (config.subtype_class[1] !== "")
853
+ addAttr("class", config.subtype_class[1]);
854
+ }
855
+ }
856
+ if (sy.Properties) {
857
+ Object.entries(sy.Properties).forEach(([k, v]) => addAttr(k, v));
858
+ }
859
+ if (sy.ListData?.Marker) addAttr("data-marker", atob(sy.ListData.Marker));
860
+ if (
861
+ /** 任务列表 */
862
+ sy.ListData?.Typ === 3 && /** 该项被选中 */
863
+ sy.Children?.find(
864
+ (el) => el.Type === "NodeTaskListItemMarker"
865
+ )?.TaskListItemChecked
866
+ ) {
867
+ attrObj["class"] = (attrObj["class"] ?? "") + " protyle-task--done ";
868
+ }
869
+ delete attrObj["fold"];
870
+ if (sy.Type === "NodeDocument") delete attrObj["title"];
871
+ return Object.entries(attrObj).map(([k, v]) => `${k}="${v}"`).join(" ");
872
+ }
873
+ var _emptyString = async (_sy) => "";
874
+ var _dataString = async (sy) => sy.Data ?? "";
875
+ var getRender = () => {
876
+ return {
877
+ ...render,
878
+ nodeStack: [],
879
+ refs: /* @__PURE__ */ new Set()
880
+ };
881
+ };
882
+ var render = {
883
+ nodeStack: [],
884
+ refs: /* @__PURE__ */ new Set(),
885
+ async getTopPathPrefix() {
886
+ const sy = this.nodeStack[0];
887
+ let prefix = ".";
888
+ if (sy.Type === "NodeDocument" && sy.ID) {
889
+ const path = await storeDep.getDocPathBySY(sy);
890
+ if (path) {
891
+ const level = path.split("/").length - 3;
892
+ for (let i = 0; i < level; i++) {
893
+ prefix += "/..";
894
+ }
895
+ }
896
+ return prefix;
897
+ } else {
898
+ console.log("\u672A\u5B9A\u4E49\u9876\u5C42\u5143\u7D20\u975E NodeDocument \u65F6\u7684\u5904\u7406\u65B9\u5F0F", sy);
899
+ return "";
900
+ }
901
+ },
902
+ async NodeDocument(sy) {
903
+ let html2 = "";
904
+ if (
905
+ /** 只有顶层的文档块才渲染题图 */
906
+ this.nodeStack.length === 1
907
+ ) {
908
+ html2 += `<div style="min-height: 150px;" ${strAttr(sy)}>`;
909
+ if (sy.Properties?.["title-img"]) {
910
+ html2 += `<div class="protyle-background__img" style="margin-bottom: 30px;position: relative;height: 16vh;${sy.Properties?.["title-img"].replace(
911
+ /assets/,
912
+ // 修改为相对路径
913
+ await this.getTopPathPrefix() + "/assets"
914
+ )}"/>${sy.Properties?.["icon"] ? `<div style="position: absolute;bottom:-10px;left:15px;height: 80px;width: 80px;transition: var(--b3-transition);cursor: pointer;font-size: 68px;line-height: 80px;text-align: center;font-family: var(--b3-font-family-emoji);margin-right: 16px;"> &#x${sy.Properties?.["icon"]} </div>` : ""}</div>`;
915
+ }
916
+ html2 += "</div>";
917
+ html2 += `<h1 ${strAttr(sy)} data-type="NodeHeading" class="h1">${sy.Properties?.title}</h1>`;
918
+ }
919
+ html2 += await childRender(sy, this);
920
+ return html2;
921
+ },
922
+ async NodeHeading(sy) {
923
+ const tagName = `h${sy.HeadingLevel}`;
924
+ let html2 = `<${tagName} ${strAttr(sy)}>${await childRender(
925
+ sy,
926
+ this
927
+ )}</${tagName}>`;
928
+ const parentNode = this.nodeStack[
929
+ this.nodeStack.length - 2
930
+ /** 最后一个元素是 sy本身(NodeHeading)还得要往前一个,所以是2 */
931
+ ];
932
+ if (parentNode?.Type === "NodeBlockQueryEmbedScript") {
933
+ let afterFlag = false;
934
+ for (const node of sy.Parent.Children ?? []) {
935
+ if (node === sy) {
936
+ afterFlag = true;
937
+ } else if (node !== sy && node.Type === "NodeHeading") {
938
+ afterFlag = false;
939
+ } else if (afterFlag) {
940
+ html2 += "\n" + await renderHTML(node, this);
941
+ }
942
+ }
943
+ }
944
+ return html2;
945
+ },
946
+ NodeText: _dataString,
947
+ async NodeList(sy) {
948
+ return html`<div ${strAttr(sy)}>${await childRender(sy, this)}</div>`;
949
+ },
950
+ async NodeListItem(sy) {
951
+ return html`<div ${await strAttr(sy)}>
952
+ <div class="protyle-action">
953
+ ${sy.ListData?.Typ === 1 ? (
954
+ /** 有序列表 */
955
+ atob(sy.ListData?.Marker ?? "")
956
+ ) : sy.ListData?.Typ === 3 ? (
957
+ /** 任务列表 */
958
+ `<svg><use xlink:href="#${sy.Children?.find((el) => el.Type === "NodeTaskListItemMarker")?.TaskListItemChecked ? "iconCheck" : "iconUncheck"}"></use></svg>`
959
+ ) : (
960
+ /** 无序列表 */
961
+ `<svg><use xlink:href="#iconDot"></use></svg>`
962
+ )}
963
+ </div>
964
+ ${await childRender(sy, this)}
965
+ </div>`;
966
+ },
967
+ NodeTaskListItemMarker: _emptyString,
968
+ async NodeParagraph(sy) {
969
+ return `<div ${strAttr(sy)}><div spellcheck="false">${await childRender(
970
+ sy,
971
+ this
972
+ )}</div></div>`;
973
+ },
974
+ async NodeTextMark(sy) {
975
+ const that = this;
976
+ let r = "";
977
+ for (const type of (sy.TextMarkType?.split(" ") ?? []).reverse()) {
978
+ if (r === "") {
979
+ r = await TextMarkRender(sy, type, sy.TextMarkTextContent ?? "");
980
+ } else {
981
+ r = await TextMarkRender(sy, type, r);
982
+ }
983
+ }
984
+ return r;
985
+ async function TextMarkRender(sy2, type, content) {
986
+ if (type === "inline-math") {
987
+ return `<span data-type="inline-math" data-subtype="math" data-content="${sy2.TextMarkInlineMathContent}" class="render-node"></span>`;
988
+ } else if (type === "inline-memo") {
989
+ return `${content}<sup>\uFF08${sy2.TextMarkInlineMemoContent}\uFF09</sup>`;
990
+ } else if (type === "block-ref") {
991
+ let href = "";
992
+ if (sy2.TextMarkBlockRefID) {
993
+ const doc = await storeDep.getDocByChildID(sy2.TextMarkBlockRefID);
994
+ if (doc?.ID) {
995
+ href = `${await that.getTopPathPrefix()}${await storeDep.getHPathByID_Node(
996
+ doc
997
+ )}.html#${sy2.TextMarkBlockRefID}`;
998
+ that.refs.add(doc.ID);
999
+ } else {
1000
+ warn(`\u672A\u67E5\u627E\u5230${sy2.ID}\u6240\u6307\u5411\u7684\u6587\u6863\u8282\u70B9 ${sy2.TextMarkBlockRefID}`);
1001
+ }
1002
+ } else {
1003
+ warn(`${sy2.ID} \u5757\u5F15\u7528\u6CA1\u6709\u8BBE\u5B9A ref id`);
1004
+ }
1005
+ return `<span data-type="${sy2.TextMarkType}" data-subtype="${/** "s" */
1006
+ sy2.TextMarkBlockRefSubtype}" data-id="${/** 被引用块的id */
1007
+ sy2.TextMarkBlockRefID}"><a href="${href}">${content}</a></span>`;
1008
+ } else if (type === "a") {
1009
+ let href = sy2.TextMarkAHref;
1010
+ if (href?.startsWith("assets/")) {
1011
+ href = `${await that.getTopPathPrefix()}/${href}`;
1012
+ }
1013
+ return `<a href="${href}">${content}</a>`;
1014
+ } else if (`strong em u s mark sup sub kbd tag code strong code text`.includes(
1015
+ type ?? ""
1016
+ )) {
1017
+ return `<span ${strAttr(sy2, { data_type: type })}>${content}</span>`;
1018
+ } else {
1019
+ return warnDiv(
1020
+ `\u6CA1\u6709\u627E\u5230\u5BF9\u5E94\u7684\u6E32\u67D3\u5668 ${sy2.TextMarkType} ${that.nodeStack[0].Properties?.title}`
1021
+ );
1022
+ }
1023
+ }
1024
+ },
1025
+ async NodeImage(sy) {
1026
+ let link = "";
1027
+ const LinkDest = sy.Children?.filter((c) => c.Type === "NodeLinkDest");
1028
+ if (LinkDest?.length === 1) {
1029
+ link = await renderHTML(LinkDest[0], this);
1030
+ } else if (LinkDest?.length && LinkDest.length > 1) {
1031
+ warn("NodeImage \u5B58\u5728\u591A\u4E2A LinkDest", sy);
1032
+ }
1033
+ let title = "";
1034
+ const LinkTitle = sy.Children?.filter((c) => c.Type === "NodeLinkTitle");
1035
+ if (LinkTitle?.length === 1) {
1036
+ title = await renderHTML(LinkTitle[0], this);
1037
+ } else if (LinkTitle?.length && LinkTitle.length > 1) {
1038
+ warn("NodeImage \u5B58\u5728\u591A\u4E2A LinkTitle", sy);
1039
+ }
1040
+ return `<span ${await strAttr(sy)} style="${sy.Properties?.["parent-style"] ?? ""}">
1041
+ <img
1042
+ src="${link}"
1043
+ data-src="${link}"
1044
+ title="${title}"
1045
+ style="${sy.Properties?.style ?? ""}"
1046
+ loading="lazy"
1047
+ />
1048
+ <span class="protyle-action__title">${title}</span></span>`;
1049
+ },
1050
+ async NodeLinkDest(sy) {
1051
+ if (/^(?:[a-z]+:)?\/\/|^(?:\/)/.test(sy.Data ?? "")) {
1052
+ return sy.Data ?? "";
1053
+ }
1054
+ return `${await this.getTopPathPrefix()}/${sy.Data}`;
1055
+ },
1056
+ NodeLinkTitle: _dataString,
1057
+ NodeKramdownSpanIAL: _emptyString,
1058
+ async NodeSuperBlock(sy) {
1059
+ return `<div ${strAttr(sy)} data-sb-layout="${childDateByType(
1060
+ sy,
1061
+ "NodeSuperBlockLayoutMarker"
1062
+ )}">${await childRender(sy, this)}</div>`;
1063
+ },
1064
+ NodeSuperBlockOpenMarker: _emptyString,
1065
+ NodeSuperBlockCloseMarker: _emptyString,
1066
+ NodeSuperBlockLayoutMarker: _emptyString,
1067
+ async NodeBlockQueryEmbed(sy) {
1068
+ return `<div ${strAttr(sy)} data-type="NodeBlockquote" class="bq">${await childRender(sy, this)}</div>`;
1069
+ },
1070
+ NodeOpenBrace: _emptyString,
1071
+ NodeCloseBrace: _emptyString,
1072
+ async NodeBlockQueryEmbedScript(sy) {
1073
+ const sql = sy.Data;
1074
+ if (!sql) {
1075
+ console.log("no sql", sy);
1076
+ return html`<pre>${sql}</pre>`;
1077
+ }
1078
+ let htmlStr = "";
1079
+ const blocks = await API.query_sql({
1080
+ stmt: (
1081
+ /** sql 被思源转义了,类似 :SELECT * FROM blocks WHERE id = &#39;20201227174241-nxny1tq&#39;
1082
+ 所以这里将它转义回来
1083
+ TODO 当用户确实使用了包含转义的字符串时,这个实现是错误的 */
1084
+ unescaping(
1085
+ sql
1086
+ ).replace(
1087
+ /** 我不理解lute为什么这样实现 https://github.com/88250/lute/blob/HEAD/editor/const.go#L38
1088
+ * https://ld246.com/article/1696750832289
1089
+ */
1090
+ /_esc_newline_/g,
1091
+ "\n"
1092
+ )
1093
+ )
1094
+ }).catch((err) => {
1095
+ throw new Error(
1096
+ `sql error: ${err.message}
1097
+ rawSql:${sql}
1098
+ unescapingSql:${unescaping(
1099
+ sql
1100
+ )}`
1101
+ );
1102
+ });
1103
+ for (const block of blocks) {
1104
+ const node = await storeDep.getNodeByID(block.id);
1105
+ if (node === void 0) {
1106
+ return warnDiv("\u672A\u627E\u5230\u6B64\u5757\uFF0C\u53EF\u80FD\u4E3A\u8DE8\u7B14\u8BB0\u5F15\u7528", block.id, sql);
1107
+ }
1108
+ htmlStr += await renderHTML(node, this);
1109
+ }
1110
+ return htmlStr;
1111
+ },
1112
+ async NodeBlockquote(sy) {
1113
+ return html`<div ${strAttr(sy)}>${await childRender(sy, this)}</div>`;
1114
+ },
1115
+ NodeBlockquoteMarker: _emptyString,
1116
+ NodeCodeBlock: async (sy) => {
1117
+ const [yes, _] = isRenderCode(sy);
1118
+ if (yes) {
1119
+ return `<div ${strAttr(sy)} data-content="${escaping(
1120
+ sy.Children?.find((el) => el.Type === "NodeCodeBlockCode")?.Data ?? ""
1121
+ )}">
1122
+ <div spin="1"></div>
1123
+ <div class="protyle-attr" contenteditable="false"></div>
1124
+ </div>`;
1125
+ }
1126
+ return `<div ${strAttr(sy)}>
1127
+ <div class="protyle-action">
1128
+ <span class="protyle-action--first protyle-action__language">${await renderHTML(
1129
+ sy.Children?.find(
1130
+ (el) => el.Type === "NodeCodeBlockFenceInfoMarker"
1131
+ ),
1132
+ void 0
1133
+ )}</span>
1134
+ <span class="fn__flex-1"></span><span class="protyle-icon protyle-icon--only protyle-action__copy"><svg><use xlink:href="#iconCopy"></use></svg></span>
1135
+ </div>
1136
+ ${await renderHTML(
1137
+ sy.Children?.find((el) => el.Type === "NodeCodeBlockCode"),
1138
+ void 0
1139
+ )}
1140
+ </div>`;
1141
+ },
1142
+ NodeCodeBlockFenceInfoMarker: async (sy) => atob(sy.CodeBlockInfo ?? ""),
1143
+ NodeCodeBlockCode: async (sy) => `<div class="hljs" spellcheck="false">${sy.Data}</div>`,
1144
+ NodeCodeBlockFenceOpenMarker: _emptyString,
1145
+ NodeCodeBlockFenceCloseMarker: _emptyString,
1146
+ async NodeTable(sy) {
1147
+ return `<div ${strAttr(sy)}>
1148
+ <div>
1149
+ <table spellcheck="false">
1150
+ <colgroup>
1151
+ ${sy.TableAligns?.map(() => "<col />").join("")}
1152
+ </colgroup>
1153
+ ${await renderHTML(
1154
+ sy.Children?.find((el) => el.Type === "NodeTableHead"),
1155
+ this
1156
+ )}
1157
+ <tbody>
1158
+ ${(await Promise.all(
1159
+ sy.Children?.filter((el) => el.Type === "NodeTableRow").map(
1160
+ (el) => renderHTML(el, this)
1161
+ ) ?? []
1162
+ )).join("\n")}
1163
+ </tbody>
1164
+ </table>
1165
+ </div>
1166
+ </div>`;
1167
+ },
1168
+ async NodeTableHead(sy) {
1169
+ return `<${sy.Data}>${await childRender(sy, this)}</${sy.Data}>`;
1170
+ },
1171
+ async NodeTableRow(sy) {
1172
+ return `<tr>${await childRender(sy, this)}</tr>`;
1173
+ },
1174
+ async NodeTableCell(sy) {
1175
+ return `<td>${await childRender(sy, this)}</td>`;
1176
+ },
1177
+ NodeHTMLBlock: async (sy) => `<div ${strAttr(sy)}>${sy.Data}</div>`,
1178
+ NodeThematicBreak: async (sy) => `<div ${strAttr(sy)}><div></div></div>`,
1179
+ NodeMathBlock: async (sy) => `<div ${strAttr(
1180
+ sy
1181
+ )} data-content="${childDateByType(sy, "NodeMathBlockContent")}">
1182
+ <div spin="1"></div>
1183
+ </div>`,
1184
+ NodeMathBlockOpenMarker: _emptyString,
1185
+ NodeMathBlockCloseMarker: _emptyString,
1186
+ async NodeIFrame(sy) {
1187
+ return ` <div ${strAttr(sy)}>
1188
+ <div class="iframe-content">
1189
+ ${/** 资源总是被复制到顶层目录,所以直接跳到顶层即可 */
1190
+ /** TODO 应该有一个统一处理资源的方案 */
1191
+ sy.Data?.replace(
1192
+ /src="assets\//,
1193
+ `src="${await this.getTopPathPrefix()}/assets/`
1194
+ )}
1195
+ </div>
1196
+ </div>`;
1197
+ },
1198
+ async NodeVideo(sy) {
1199
+ return await this.NodeIFrame(sy);
1200
+ },
1201
+ async NodeAudio(sy) {
1202
+ return await this.NodeIFrame(sy);
1203
+ },
1204
+ /** 虚拟链接 */
1205
+ NodeHeadingC8hMarker: _emptyString,
1206
+ async NodeSoftBreak(_sy) {
1207
+ return "\u200B";
1208
+ },
1209
+ async NodeBr(sy) {
1210
+ return `<${sy.Data}>`;
1211
+ },
1212
+ async NodeWidget(sy) {
1213
+ return `<div ${strAttr(
1214
+ sy
1215
+ )}><img src="${await this.getTopPathPrefix()}/assets/widget/${sy.ID}.jpg"/></div>`;
1216
+ },
1217
+ async NodeBackslash(sy) {
1218
+ if (sy.Data === void 0 || sy.Data === "span") {
1219
+ return `${await childRender(sy, this)}`;
1220
+ } else {
1221
+ return warnDiv(
1222
+ `\u672A\u5B9A\u4E49\u7684 NodeBackslash \u5904\u7406 ${sy.Data}`,
1223
+ this.nodeStack[0].Properties?.title
1224
+ );
1225
+ }
1226
+ },
1227
+ NodeBackslashContent: _dataString
1228
+ };
1229
+ function childDateByType(sy, type) {
1230
+ return sy.Children?.find((el) => el.Type === type)?.Data;
1231
+ }
1232
+ function warn(...arg) {
1233
+ console.warn("\n", ...arg);
1234
+ }
1235
+
1236
+ // src/core/siyuan_type.ts
1237
+ function DB_block_path(p) {
1238
+ return `data/${p.box}${p.path}`;
1239
+ }
1240
+
1241
+ // src/core/cache.ts
1242
+ var cache = true;
1243
+ function setCache(b) {
1244
+ cache = b;
1245
+ }
1246
+ var sqlCacheMap = /* @__PURE__ */ new Map();
1247
+ var hpathCacheMap = /* @__PURE__ */ new Map();
1248
+ var idCacheMap = /* @__PURE__ */ new Map();
1249
+ var blockCacheMap = /* @__PURE__ */ new Map();
1250
+ async function query_sql(stmt) {
1251
+ if (cache && sqlCacheMap.has(stmt)) {
1252
+ return sqlCacheMap.get(stmt);
1253
+ }
1254
+ const res = await API.query_sql({
1255
+ stmt
1256
+ });
1257
+ if (cache) {
1258
+ sqlCacheMap.set(stmt, res);
1259
+ }
1260
+ return res;
1261
+ }
1262
+ async function get_doc_by_hpath(hpath) {
1263
+ if (cache) {
1264
+ const c = hpathCacheMap.get(hpath);
1265
+ if (c) return c;
1266
+ }
1267
+ const docBlock = (await query_sql(
1268
+ `SELECT * FROM blocks WHERE hpath = '${hpath}'`
1269
+ ))[0];
1270
+ if (docBlock === void 0) throw new Error(`not doc by:${hpath}`);
1271
+ const res = await get_doc_by_SyPath(DB_block_path(docBlock));
1272
+ if (cache) {
1273
+ hpathCacheMap.set(hpath, res);
1274
+ }
1275
+ return res;
1276
+ }
1277
+ async function get_doc_by_SyPath(path) {
1278
+ const res = parentRef(
1279
+ await API.file_getFile({
1280
+ path
1281
+ })
1282
+ );
1283
+ if (cache) {
1284
+ idCache(res);
1285
+ }
1286
+ return res;
1287
+ }
1288
+ async function get_block_by_id(id) {
1289
+ if (cache) {
1290
+ const block = blockCacheMap.get(id);
1291
+ if (block) return block;
1292
+ }
1293
+ const blocks = await query_sql(`
1294
+ SELECT * from blocks
1295
+ WHERE id = '${id}'
1296
+ `);
1297
+ if (blocks.length === 0) {
1298
+ return;
1299
+ }
1300
+ if (cache) blockCacheMap.set(id, blocks[0]);
1301
+ return blocks[0];
1302
+ }
1303
+ async function get_node_by_id(id) {
1304
+ if (id === void 0) return;
1305
+ if (cache) {
1306
+ const node = idCacheMap.get(id);
1307
+ if (node) return node;
1308
+ }
1309
+ const doc = await get_doc_by_child_id(id);
1310
+ if (doc === void 0) return;
1311
+ return findNode(doc);
1312
+ function findNode(node) {
1313
+ if (node.ID === id) return node;
1314
+ if (node.Children) {
1315
+ return node.Children.find((child) => findNode(child));
1316
+ }
1317
+ }
1318
+ }
1319
+ async function allDocBlock_by_bookId(id) {
1320
+ const res = await query_sql(`
1321
+ SELECT * from blocks
1322
+ WHERE box = '${id}'
1323
+ AND type = 'd'
1324
+ limit 150000 OFFSET 0
1325
+ `);
1326
+ if (cache) {
1327
+ res.forEach((block) => blockCacheMap.set(block.id, block));
1328
+ }
1329
+ return res;
1330
+ }
1331
+ async function get_doc_by_child_id(id) {
1332
+ if (cache) {
1333
+ const child = idCacheMap.get(id);
1334
+ if (child) {
1335
+ let node = child;
1336
+ while (true) {
1337
+ if (node.Type === "NodeDocument") {
1338
+ return node;
1339
+ } else if (node === void 0) {
1340
+ break;
1341
+ }
1342
+ node = node.Parent;
1343
+ }
1344
+ }
1345
+ }
1346
+ const block = await get_block_by_id(id);
1347
+ if (block === void 0) return;
1348
+ return await get_doc_by_hpath(block.hpath);
1349
+ }
1350
+ function idCache(node) {
1351
+ if (node.ID) {
1352
+ idCacheMap.set(node.ID, node);
1353
+ }
1354
+ if (node.Children) {
1355
+ node.Children.forEach(idCache);
1356
+ }
1357
+ }
1358
+ function parentRef(sy) {
1359
+ for (const child of sy?.Children ?? []) {
1360
+ child.Parent = sy;
1361
+ parentRef(child);
1362
+ }
1363
+ return sy;
1364
+ }
1365
+
1366
+ // src/core/genRssXml.ts
1367
+ async function generateRSSXML(path, renderInstance, config) {
1368
+ const refNode = (await Promise.all([...renderInstance.refs.values()].map(get_node_by_id))).filter((el) => el);
1369
+ return `<?xml version="1.0" encoding="UTF-8"?>
1370
+ <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
1371
+ <channel>
1372
+ <title>${config.sitemap.title}</title>
1373
+ <link>${config.sitemap.siteLink}</link>
1374
+ <description>${config.sitemap.description}</description>
1375
+ <atom:link href="${config.sitemap.sitePrefix}${path}" rel="self" type="application/rss+xml"/>
1376
+ <lastBuildDate>${(/* @__PURE__ */ new Date()).toISOString()}</lastBuildDate>
1377
+ ${(await Promise.all(
1378
+ refNode.map(
1379
+ async (node) => `<item>
1380
+ <title>${node?.Properties?.title}</title>
1381
+ <link>${config.sitemap.sitePrefix}${node?.ID ? await storeDep.getHPathByID_Node(node?.ID) + ".html" : ""}</link>
1382
+ <description>${""}</description>
1383
+ <pubDate>${node?.Properties?.updated ? new Date(
1384
+ node.Properties.updated.replace(
1385
+ /(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/,
1386
+ "$1/$2/$3 $4:$5:$6"
1387
+ )
1388
+ ).toISOString() : ""}</pubDate>
1389
+ </item>`
1390
+ )
1391
+ )).join("\n")}
1392
+ </channel>
1393
+ </rss>`;
1394
+ }
1395
+ function sitemap_xml(docArr, config) {
1396
+ const urlList = docArr.map((doc) => {
1397
+ let lastmod = "";
1398
+ const time = doc.ial.match(/updated=\"(\d+)\"/)?.[1] ?? doc.created;
1399
+ if (time) {
1400
+ lastmod = `
1401
+ <lastmod>${time.slice(0, 4)}-${time.slice(
1402
+ 4,
1403
+ 6
1404
+ )}-${time.slice(6, 8)}</lastmod>`;
1405
+ }
1406
+ return `<url>
1407
+ <loc>${config.sitePrefix}${doc.hpath}.html</loc>${lastmod}
1408
+ </url>
1409
+ `;
1410
+ }).join("");
1411
+ return `<?xml version="1.0" encoding="UTF-8"?>
1412
+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
1413
+ ${urlList}
1414
+ </urlset>`;
1415
+ }
1416
+
1417
+ // src/core/build.ts
1418
+ async function build(config, effect, otherConfig) {
1419
+ const _renderHTML = otherConfig?.renderHtmlFn ?? renderHTML;
1420
+ const book = config.notebook;
1421
+ const docTree = {};
1422
+ const skipBuilds = useSkipBuilds();
1423
+ let oldPercentage = 0;
1424
+ let total = 0;
1425
+ function processPercentage(percentage) {
1426
+ total += oldPercentage;
1427
+ return (process3) => {
1428
+ oldPercentage = process3 * percentage;
1429
+ effect.percentage((total + oldPercentage) * 100);
1430
+ };
1431
+ }
1432
+ effect.log(`=== \u5F00\u59CB\u7F16\u8BD1 ${book.name} ===`);
1433
+ let process2 = processPercentage(0.4);
1434
+ const Doc_blocks = await allDocBlock_by_bookId(book.id);
1435
+ function refsNotUpdated(docBlock) {
1436
+ const refs = config.__skipBuilds__[docBlock.id]?.refs ?? [];
1437
+ for (const ref_id of refs) {
1438
+ const new_doc_hash = Doc_blocks.find(
1439
+ (docBlock2) => docBlock2.id === ref_id
1440
+ )?.hash;
1441
+ const old_doc_hash = config.__skipBuilds__[ref_id]?.hash;
1442
+ if (new_doc_hash === void 0 || old_doc_hash === void 0) {
1443
+ return false;
1444
+ } else if (new_doc_hash === old_doc_hash) {
1445
+ continue;
1446
+ } else {
1447
+ return false;
1448
+ }
1449
+ }
1450
+ return true;
1451
+ }
1452
+ effect.log(`=== \u67E5\u8BE2\u6587\u6863\u7EA7block\u5B8C\u6210 ===`);
1453
+ let i = 0;
1454
+ await Promise.all(
1455
+ Doc_blocks.map(async (docBlock) => {
1456
+ const sy = await get_doc_by_SyPath(DB_block_path(docBlock));
1457
+ docTree[docBlock.hpath] = { sy, docBlock };
1458
+ i++;
1459
+ process2(i / Doc_blocks.length);
1460
+ })
1461
+ );
1462
+ const fileTree = {};
1463
+ process2 = processPercentage(0.4);
1464
+ const enableIncrementalCompilation_doc = (() => {
1465
+ if (package_default.version !== config.OceanPress.version) {
1466
+ effect.log(
1467
+ `\u914D\u7F6E\u6587\u4EF6\u7248\u672C\u53F7[${config.OceanPress.version}]\u4E0EOceanPress\u7248\u672C[${package_default.version}]\u4E0D\u4E00\u81F4\uFF0C\u5C06\u8FDB\u884C\u6587\u6863\u5168\u91CF\u7F16\u8BD1`
1468
+ );
1469
+ return false;
1470
+ }
1471
+ return config.enableIncrementalCompilation_doc;
1472
+ })();
1473
+ effect.log(`=== \u5F00\u59CB\u6E32\u67D3\u6587\u6863 ===`);
1474
+ await Promise.all(
1475
+ Object.entries(docTree).map(async ([path, { sy, docBlock }]) => {
1476
+ if (config.enableIncrementalCompilation && enableIncrementalCompilation_doc && /** 文档本身没有发生变化 */
1477
+ config.__skipBuilds__[docBlock.id]?.hash === docBlock.hash && /** docBlock所引用的文档也没有更新 */
1478
+ refsNotUpdated(docBlock))
1479
+ return;
1480
+ try {
1481
+ const rootLevel = path.split("/").length - 2;
1482
+ const renderInstance = getRender();
1483
+ fileTree[path + ".html"] = await htmlTemplate(
1484
+ {
1485
+ title: sy.Properties?.title || "",
1486
+ htmlContent: await _renderHTML(sy, renderInstance),
1487
+ level: rootLevel
1488
+ },
1489
+ {
1490
+ ...tempConfig.cdn,
1491
+ embedCode: config.embedCode
1492
+ }
1493
+ );
1494
+ if (config.sitemap.rss && path.endsWith(".rss.xml")) {
1495
+ const rssPath = path;
1496
+ fileTree[rssPath] = await generateRSSXML(rssPath, renderInstance, config);
1497
+ effect.log(`\u6E32\u67D3 rss.xml:${rssPath} \u5B8C\u6BD5`);
1498
+ }
1499
+ if (config.enableIncrementalCompilation && config.enableIncrementalCompilation_doc) {
1500
+ skipBuilds.add(docBlock.id, {
1501
+ hash: docBlock.hash
1502
+ });
1503
+ }
1504
+ skipBuilds.add(docBlock.id, {
1505
+ refs: (
1506
+ /** 保存引用 */
1507
+ [...renderInstance.refs.values()]
1508
+ )
1509
+ });
1510
+ } catch (error) {
1511
+ effect.log(`${path} \u6E32\u67D3\u5931\u8D25:${error}`);
1512
+ console.log(error);
1513
+ }
1514
+ process2(i / Doc_blocks.length);
1515
+ effect.log(`\u6E32\u67D3\u5B8C\u6BD5:${path}`);
1516
+ })
1517
+ );
1518
+ effect.log(`=== \u6E32\u67D3\u6587\u6863\u5B8C\u6210 ===`);
1519
+ effect.log(`=== \u5F00\u59CB\u751F\u6210 sitemap.xml ===`);
1520
+ if (config.sitemap.enable) {
1521
+ fileTree["sitemap.xml"] = sitemap_xml(Doc_blocks, config.sitemap);
1522
+ }
1523
+ if (config.excludeAssetsCopy === false) {
1524
+ effect.log(`=== \u5F00\u59CB\u590D\u5236\u8D44\u6E90\u6587\u4EF6 ===`);
1525
+ const assets = await API.query_sql({
1526
+ stmt: `SELECT * from assets
1527
+ WHERE box = '${book.id}'
1528
+ limit 150000 OFFSET 0`
1529
+ });
1530
+ await Promise.all(
1531
+ assets.map(async (item) => {
1532
+ if (config.enableIncrementalCompilation && /** 资源没有变化,直接跳过 */
1533
+ config.__skipBuilds__[item.id]?.hash === item.hash) {
1534
+ return;
1535
+ } else {
1536
+ fileTree[item.path] = await API.get_assets({
1537
+ path: item.path
1538
+ });
1539
+ if (config.enableIncrementalCompilation) {
1540
+ skipBuilds.add(item.id, { hash: item.hash });
1541
+ }
1542
+ }
1543
+ })
1544
+ );
1545
+ effect.log(`=== \u5F00\u59CB\u590D\u5236\u6302\u4EF6\u8D44\u6E90\u6587\u4EF6 ===`);
1546
+ const widgetList = await API.query_sql({
1547
+ stmt: `
1548
+ SELECT *
1549
+ from blocks
1550
+ WHERE box = '${book.id}'
1551
+ AND type = 'widget'
1552
+ limit 150000 OFFSET 0
1553
+ `
1554
+ });
1555
+ const widgetNode = (await Promise.all(
1556
+ widgetList.map(async (el) => await get_node_by_id(el.id))
1557
+ )).filter(
1558
+ (widget) => widget?.Properties?.["custom-oceanpress-widget-update"]
1559
+ ).map(async (widget) => {
1560
+ if (!widget || !widget?.ID) return;
1561
+ const update = widget?.Properties?.["custom-oceanpress-widget-update"];
1562
+ if (config.enableIncrementalCompilation && config.__skipBuilds__[widget.ID]?.updated === update) {
1563
+ return;
1564
+ } else {
1565
+ const id = widget.ID;
1566
+ fileTree[`assets/widget/${id}.jpg`] = await API.file_getFile({
1567
+ path: `data/storage/oceanpress/widget_img/${id}.jpg`
1568
+ });
1569
+ if (config.enableIncrementalCompilation) {
1570
+ skipBuilds.add(id, { updated: update });
1571
+ }
1572
+ }
1573
+ });
1574
+ await Promise.all(widgetNode);
1575
+ }
1576
+ if (otherConfig?.onFileTree) {
1577
+ otherConfig.onFileTree(fileTree);
1578
+ }
1579
+ if (config.compressedZip) {
1580
+ effect.log(`=== \u5F00\u59CB\u751F\u6210\u538B\u7F29\u5305 ===`);
1581
+ await downloadZIP(fileTree, {
1582
+ // TODO 这里应该移出来成为全局的写选项
1583
+ withoutZip: tempConfig.withoutPublicZip,
1584
+ publicZip: tempConfig.cdn.publicZip
1585
+ });
1586
+ }
1587
+ config.OceanPress.version = package_default.version;
1588
+ skipBuilds.write();
1589
+ effect.percentage(100);
1590
+ effect.log("\u7F16\u8BD1\u5B8C\u6BD5");
1591
+ return { fileTree };
1592
+ }
1593
+ function useSkipBuilds() {
1594
+ const obj = {};
1595
+ return {
1596
+ add(id, value) {
1597
+ if (obj[id] === void 0) {
1598
+ obj[id] = {};
1599
+ }
1600
+ deepAssign(obj[id], value);
1601
+ },
1602
+ /** 将缓存的写入到配置文件 */
1603
+ write() {
1604
+ deepAssign(currentConfig.value.__skipBuilds__, obj);
1605
+ }
1606
+ };
1607
+ }
1608
+
1609
+ // src/plugins/publish/OceanPressServer.ts
1610
+ import { createRPC } from "oceanpress-rpc";
1611
+ import { stringify } from "superjson";
1612
+ function deployOceanPressServer_plugin(config) {
1613
+ const plugin = {
1614
+ async build_onFileTree([tree], next) {
1615
+ next(tree);
1616
+ const client = await createRPC("apiConsumer", {
1617
+ async remoteCall(method, data) {
1618
+ let body;
1619
+ let content_type;
1620
+ if (data[0] instanceof ReadableStream) {
1621
+ body = await new Response(data[0]).blob();
1622
+ content_type = "application/octet-stream";
1623
+ } else {
1624
+ body = stringify(data);
1625
+ console.log("[body]", body);
1626
+ content_type = "application/json";
1627
+ }
1628
+ return fetch(`${config.oceanPressServer.apiBase}/api/${method}`, {
1629
+ method: "POST",
1630
+ body,
1631
+ headers: {
1632
+ "x-api-key": config.oceanPressServer.apiKey,
1633
+ "Content-Type": content_type
1634
+ },
1635
+ // @ts-expect-error 在 node 运行的时候需要声明双工模式才能正确发送 ReadableStream,TODO 需要验证浏览器端可以这样运行吗
1636
+ duplex: "half"
1637
+ // 关键:显式声明半双工模式
1638
+ }).then((res2) => res2.json()).then((r) => {
1639
+ if (r.error) {
1640
+ console.log("[r]", r);
1641
+ throw new Error();
1642
+ }
1643
+ return r.result;
1644
+ });
1645
+ }
1646
+ });
1647
+ const zip = await genZIP(tree, { withoutZip: true });
1648
+ const sizeInMB = zip.size / (1024 * 1024);
1649
+ console.log("[zip.size in MB]", sizeInMB.toFixed(2));
1650
+ const readableStream = zip.stream();
1651
+ const { chunkCount, fileId } = await client.API.upload(readableStream);
1652
+ console.log("[res]", { chunkCount, fileId });
1653
+ const res = await client.API.deploy({ zipFileId: fileId });
1654
+ console.log("[deploy res]", res);
1655
+ }
1656
+ };
1657
+ return plugin;
1658
+ }
1659
+
1660
+ // src/core/ocean_press.ts
1661
+ var OceanPress = class {
1662
+ constructor(config) {
1663
+ this.config = config;
1664
+ __publicField(this, "funMap", {
1665
+ /** 开始整体编译 */
1666
+ build,
1667
+ /** 用于渲染文档的函数 */
1668
+ build_renderHTML: renderHTML,
1669
+ /** 编译完成后文件树的处理回调函数 */
1670
+ build_onFileTree: (_tree) => {
1671
+ }
1672
+ });
1673
+ __publicField(this, "pluginCenter", new PluginCenter(
1674
+ this.funMap
1675
+ ));
1676
+ if (config.meilisearch.enable) {
1677
+ this.pluginCenter.registerPlugin(
1678
+ new MeilisearchPlugin(config.meilisearch)
1679
+ );
1680
+ }
1681
+ if (config.s3.enable) {
1682
+ this.pluginCenter.registerPlugin(s3Upload_plugin);
1683
+ }
1684
+ if (config.oceanPressServer.enable) {
1685
+ this.pluginCenter.registerPlugin(
1686
+ deployOceanPressServer_plugin(this.config)
1687
+ );
1688
+ }
1689
+ }
1690
+ async build(effect) {
1691
+ const build_res = this.pluginCenter.fun.build(this.config, effect, {
1692
+ renderHtmlFn: this.pluginCenter.fun.build_renderHTML,
1693
+ onFileTree: this.pluginCenter.fun.build_onFileTree
1694
+ });
1695
+ return build_res;
1696
+ }
1697
+ };
1698
+
1699
+ // src/core/render.api.dep.ts
1700
+ storeDep.getDocByChildID = async (id) => {
1701
+ return await get_doc_by_child_id(id);
1702
+ };
1703
+ storeDep.getDocPathBySY = async (sy) => {
1704
+ if (sy?.ID) {
1705
+ const block = await get_block_by_id(sy.ID);
1706
+ if (block) {
1707
+ return DB_block_path(block);
1708
+ }
1709
+ }
1710
+ };
1711
+ storeDep.getHPathByID_Node = async (id_node) => {
1712
+ const id = typeof id_node === "string" ? id_node : id_node.ID;
1713
+ if (id === void 0) throw new Error("id is undefined");
1714
+ const docNode = await get_doc_by_child_id(id);
1715
+ if (docNode === void 0) throw new Error("docNode is undefined");
1716
+ const docBlock = await get_block_by_id(id);
1717
+ if (docBlock === void 0) throw new Error("docBlock is undefined");
1718
+ return docBlock.hpath;
1719
+ };
1720
+ storeDep.getNodeByID = async (id) => {
1721
+ if (id === void 0) return;
1722
+ const doc = await storeDep.getDocByChildID(id);
1723
+ if (doc === void 0) return;
1724
+ return getNode(doc);
1725
+ function getNode(node) {
1726
+ if (node.ID === id) return node;
1727
+ if (node.Children === void 0) return;
1728
+ for (const child of node.Children) {
1729
+ const n = getNode(child);
1730
+ if (n) return n;
1731
+ }
1732
+ }
1733
+ };
1734
+
1735
+ // src/util/store.node.dep.ts
1736
+ import { writeFileSync, readFileSync, existsSync, mkdirSync } from "node:fs";
1737
+ storeDep.getItem = getItem;
1738
+ storeDep.setItem = setItem;
1739
+ function setItem(key, value) {
1740
+ if (!existsSync("./store/")) {
1741
+ mkdirSync("./store/", { recursive: true });
1742
+ }
1743
+ return writeFileSync(`./store/${key}`, value, {
1744
+ encoding: "utf-8"
1745
+ });
1746
+ }
1747
+ function getItem(key) {
1748
+ try {
1749
+ return readFileSync(`./store/${key}`, "utf-8");
1750
+ } catch (_) {
1751
+ return void 0;
1752
+ }
1753
+ }
1754
+
1755
+ // src/cli/common.ts
1756
+ import { Command } from "commander";
1757
+ var program = new Command();
1758
+ program.name("OceanPress").description("\u8FD9\u662F\u4E00\u6B3E\u4ECE\u601D\u6E90\u7B14\u8BB0\u672C\u751F\u6210\u4E00\u4E2A\u9759\u6001\u7AD9\u70B9\u7684\u5DE5\u5177");
1759
+
1760
+ // src/cli/deploy.ts
1761
+ program.command("deploy").description("\u90E8\u7F72\u7AD9\u70B9").option("-c, --config <string>", "\u6307\u5B9A\u914D\u7F6E\u6587\u4EF6\u7684\u4F4D\u7F6E").option("-h, --apiBase <string>", "OceanPress server \u5730\u5740").option("-k, --apiKey <string>", "OceanPress server Api \u5BC6\u94A5").action(async (opt) => {
1762
+ if (!opt.apiBase || !opt.apiKey) {
1763
+ return console.error(`\u8BF7\u914D\u7F6E apiBase \u548C apiKey`);
1764
+ }
1765
+ const config = await readFile(opt.config, "utf-8");
1766
+ loadConfigFile(JSON.parse(config));
1767
+ const client = await createRPC2("apiConsumer", {
1768
+ remoteCall(method, data) {
1769
+ let body;
1770
+ let content_type;
1771
+ if (data[0] instanceof ReadableStream) {
1772
+ body = data[0];
1773
+ content_type = "application/octet-stream";
1774
+ } else {
1775
+ body = stringify2(data);
1776
+ console.log("[body]", body);
1777
+ content_type = "application/json";
1778
+ }
1779
+ return fetch(`${opt.apiBase}/api/${method}`, {
1780
+ method: "POST",
1781
+ body,
1782
+ headers: {
1783
+ "x-api-key": opt.apiKey,
1784
+ "Content-Type": content_type
1785
+ },
1786
+ // @ts-expect-error 在 node 运行的时候需要声明双工模式才能正确发送 ReadableStream,TODO 需要验证浏览器端可以这样运行吗
1787
+ duplex: "half"
1788
+ // 关键:显式声明半双工模式
1789
+ }).then((res) => res.json()).then((r) => {
1790
+ if (r.error) {
1791
+ console.log("[r]", r);
1792
+ throw new Error();
1793
+ }
1794
+ return r.result;
1795
+ });
1796
+ }
1797
+ });
1798
+ const ocean_press = new OceanPress(currentConfig.value);
1799
+ ocean_press.pluginCenter.registerPlugin({
1800
+ async build_onFileTree([tree], next) {
1801
+ const zip = await genZIP(tree, { withoutZip: true });
1802
+ const sizeInMB = zip.size / (1024 * 1024);
1803
+ console.log("[zip.size in MB]", sizeInMB.toFixed(2));
1804
+ const readableStream = zip.stream();
1805
+ const { chunkCount, fileId } = await client.API.upload(readableStream);
1806
+ console.log("[res]", { chunkCount, fileId });
1807
+ const res = await client.API.deploy({ zipFileId: fileId });
1808
+ console.log("[deploy res]", res);
1809
+ }
1810
+ });
1811
+ await ocean_press.build({
1812
+ log: (msg) => {
1813
+ if (msg.startsWith("\u6E32\u67D3\uFF1A")) {
1814
+ process.stdout.write(`\r\x1B[K${msg}`);
1815
+ } else {
1816
+ process.stdout.write(`
1817
+ ${msg}`);
1818
+ }
1819
+ },
1820
+ percentage: (n) => {
1821
+ process.stdout.write(`\r\x1B[K\u8FDB\u5EA6\uFF1A${n}%`);
1822
+ }
1823
+ });
1824
+ });
1825
+
1826
+ // src/cli/build.ts
1827
+ import { mkdir, readFile as readFile2, writeFile } from "fs/promises";
1828
+ import { resolve } from "path";
1829
+ import { join } from "path/posix";
1830
+ program.command("build").description("\u8F93\u51FA\u9759\u6001\u7AD9\u70B9\u6E90\u7801").option("-c, --config <string>", "\u6307\u5B9A\u914D\u7F6E\u6587\u4EF6\u7684\u4F4D\u7F6E").option("-o, --output <string>", "\u6307\u5B9A\u8F93\u51FA\u76EE\u5F55\u4F4D\u7F6E").action(async (opt) => {
1831
+ if (!opt.config || !opt.output) {
1832
+ console.log(`\u8BF7\u8BBE\u7F6E\u914D\u7F6E\u6587\u4EF6\u4F4D\u7F6E\u548C\u8F93\u51FA\u76EE\u5F55\u4F4D\u7F6E`);
1833
+ throw new Error("\u8BF7\u8BBE\u7F6E\u914D\u7F6E\u6587\u4EF6\u4F4D\u7F6E\u548C\u8F93\u51FA\u76EE\u5F55\u4F4D\u7F6E");
1834
+ }
1835
+ const config = await readFile2(opt.config, "utf-8");
1836
+ loadConfigFile(JSON.parse(config));
1837
+ const filePath = resolve(opt.output);
1838
+ const ocean_press = new OceanPress(currentConfig.value);
1839
+ ocean_press.pluginCenter.registerPlugin({
1840
+ async build_onFileTree([tree]) {
1841
+ for (const [path, data] of Object.entries(tree)) {
1842
+ const fullPath = join(filePath, "./", path);
1843
+ const pathArray = fullPath.split("/").slice(0, -1);
1844
+ const dirPath = pathArray.join("/");
1845
+ mkdir(dirPath, { recursive: true });
1846
+ try {
1847
+ if (typeof data === "string") {
1848
+ await writeFile(fullPath, data, "utf-8");
1849
+ } else {
1850
+ await writeFile(fullPath, new DataView(data));
1851
+ }
1852
+ } catch (error) {
1853
+ console.log(`${fullPath} \u65E0\u6CD5\u5199\u5165`);
1854
+ }
1855
+ }
1856
+ }
1857
+ });
1858
+ await ocean_press.build({
1859
+ log: (msg) => {
1860
+ if (msg.startsWith("\u6E32\u67D3\uFF1A")) {
1861
+ process.stdout.write(`\r\x1B[K${msg}`);
1862
+ } else {
1863
+ process.stdout.write(`
1864
+ ${msg}`);
1865
+ }
1866
+ },
1867
+ percentage: (n) => {
1868
+ process.stdout.write(`\r\x1B[K\u8FDB\u5EA6\uFF1A${n}%`);
1869
+ }
1870
+ });
1871
+ });
1872
+
1873
+ // src/cli/server.ts
1874
+ import { readFile as readFile3 } from "fs/promises";
1875
+
1876
+ // src/server.ts
1877
+ import { serve } from "@hono/node-server";
1878
+
1879
+ // src/core/hono_server.ts
1880
+ import { Hono } from "hono";
1881
+ import { stream } from "hono/streaming";
1882
+ function createHonoApp(app = new Hono()) {
1883
+ app.get("/", (c) => c.redirect("/index.html"));
1884
+ app.get("/assets/*", assetsHandle);
1885
+ app.get("*", async (c) => {
1886
+ const path = decodeURIComponent(c.req.path);
1887
+ const r = await renderHtmlByUriPath(path).catch(async (err) => {
1888
+ if (err.message.includes("not doc")) {
1889
+ return await assetsHandle(c);
1890
+ }
1891
+ throw err;
1892
+ });
1893
+ if (r instanceof Error) {
1894
+ throw r;
1895
+ } else if (typeof r === "string") {
1896
+ return c.html(r);
1897
+ } else {
1898
+ return r;
1899
+ }
1900
+ });
1901
+ return app;
1902
+ }
1903
+ async function assetsHandle(c) {
1904
+ const file = c.req.path;
1905
+ const widgetPrefix = "/assets/widget/";
1906
+ const isWidget = file.startsWith(widgetPrefix);
1907
+ const apiPath = `${currentConfig.value.apiPrefix}${isWidget ? "/api/file/getFile" : file}`;
1908
+ const r = await fetch(apiPath, {
1909
+ headers: {
1910
+ Authorization: `Token ${currentConfig.value.authorized}`
1911
+ },
1912
+ method: isWidget ? "POST" : "GET",
1913
+ body: isWidget ? JSON.stringify({
1914
+ path: `/data/storage/oceanpress/widget_img/${file.substring(
1915
+ widgetPrefix.length
1916
+ )}`
1917
+ }) : void 0
1918
+ });
1919
+ const body = r.body;
1920
+ if (!body) {
1921
+ return c.text("\u54CD\u5E94\u4F53\u4E3A null", 500, { "Content-Type": "text/plain" });
1922
+ }
1923
+ c.status(r.status);
1924
+ return stream(c, async (writeStream) => {
1925
+ const reader = body.getReader();
1926
+ while (true) {
1927
+ const r2 = await reader.read();
1928
+ if (r2.done) {
1929
+ writeStream.close();
1930
+ break;
1931
+ } else {
1932
+ writeStream.write(r2.value);
1933
+ }
1934
+ }
1935
+ });
1936
+ }
1937
+ async function renderHtmlByUriPath(path) {
1938
+ const hpath = decodeURIComponent(path).replace(/\#(.*)?$/, "").replace(/\.html$/, "");
1939
+ const doc = await get_doc_by_hpath(hpath);
1940
+ return await htmlTemplate(
1941
+ {
1942
+ title: doc.Properties?.title || "",
1943
+ htmlContent: await renderHTML(doc),
1944
+ level: path.split("/").length - 1
1945
+ },
1946
+ {
1947
+ ...tempConfig.cdn,
1948
+ embedCode: currentConfig.value.embedCode
1949
+ }
1950
+ );
1951
+ }
1952
+
1953
+ // src/server.ts
1954
+ import { serveStatic } from "@hono/node-server/serve-static";
1955
+ import { Hono as Hono2 } from "hono";
1956
+ import { join as join2 } from "path/posix";
1957
+ console.log(join2(import.meta.url.slice(5), "../../public/"));
1958
+ function server(config = { port: 80, hostname: "0.0.0.0" }) {
1959
+ const app = new Hono2();
1960
+ app.use(
1961
+ "/notebook/*",
1962
+ serveStatic({
1963
+ root: "./public/",
1964
+ onNotFound(path, c) {
1965
+ console.log("[onNotFound notebook path]", path);
1966
+ }
1967
+ })
1968
+ );
1969
+ createHonoApp(app);
1970
+ return new Promise((resolve3, _reject) => {
1971
+ serve(
1972
+ {
1973
+ fetch: app.fetch,
1974
+ port: config.port,
1975
+ hostname: config.hostname
1976
+ },
1977
+ (info) => {
1978
+ resolve3({ info, app });
1979
+ console.log(`Listening on http://${info.address}:${info.port}`);
1980
+ }
1981
+ );
1982
+ });
1983
+ }
1984
+
1985
+ // src/cli/server.ts
1986
+ program.command("server").description("\u542F\u52A8\u52A8\u6001\u4EE3\u7406").option("-c, --config <string>", "\u6307\u5B9A\u914D\u7F6E\u6587\u4EF6\u7684\u4F4D\u7F6E").option("-h, --host <string>", "web\u670D\u52A1\u7ED1\u5B9A\u5230\u7684\u5730\u5740", "127.0.0.1").option("-p, --port <number>", "web\u670D\u52A1\u7ED1\u5B9A\u5230\u7684\u7AEF\u53E3", "80").option(
1987
+ "--cache <boolean>",
1988
+ "\u914D\u7F6E\u4E3A true \u65F6\u5F00\u542F\u7F13\u5B58,\u9ED8\u8BA4\u4E3A false \u4E0D\u5F00\u542F\u7F13\u5B58",
1989
+ "false"
1990
+ ).action(
1991
+ async (opt) => {
1992
+ if (!opt.config) {
1993
+ console.log(`\u8BF7\u8BBE\u7F6E\u914D\u7F6E\u6587\u4EF6\u4F4D\u7F6E`);
1994
+ }
1995
+ const config = await readFile3(opt.config, "utf-8");
1996
+ loadConfigFile(JSON.parse(config));
1997
+ setCache(opt.cache !== "false");
1998
+ server({
1999
+ hostname: opt.host,
2000
+ port: Number(opt.port)
2001
+ });
2002
+ }
2003
+ );
2004
+
2005
+ // src/cli.ts
2006
+ program.parse(process.argv);
2007
+ //# sourceMappingURL=cli.js.map