anywork-library-plugin 0.1.0 → 0.2.0

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/lib/index.js CHANGED
@@ -1,8 +1,190 @@
1
- /**
2
- * Host entry for library-plugin.
3
- * The browser surface (sidebar 「资料库」, panel id ziliaoku) lives in ./client.js.
4
- * This process does not read or write the library.
5
- */
6
- export const name = "library-plugin";
7
-
8
- export function apply() {}
1
+ import { createReadStream } from "node:fs";
2
+ import {
3
+ MAX_UPLOAD,
4
+ fileForDownload,
5
+ listDrive,
6
+ listNotes,
7
+ makeDir,
8
+ movePath,
9
+ readNote,
10
+ removeNote,
11
+ removePath,
12
+ saveFile,
13
+ saveNote,
14
+ searchNotes,
15
+ } from "./store.js";
16
+
17
+ export const name = "anywork-library-plugin";
18
+
19
+ const PREFIX = "/plugins/anywork-library-plugin";
20
+
21
+ function send(res, status, body) {
22
+ const payload = Buffer.from(JSON.stringify(body));
23
+ res.writeHead(status, {
24
+ "content-type": "application/json; charset=utf-8",
25
+ "cache-control": "no-store",
26
+ "content-length": payload.length,
27
+ });
28
+ res.end(payload);
29
+ }
30
+
31
+ function readJson(req) {
32
+ return new Promise((resolve, reject) => {
33
+ const chunks = [];
34
+ let size = 0;
35
+ req.on("data", (chunk) => {
36
+ size += chunk.length;
37
+ if (size > 1024 * 1024) {
38
+ reject(Object.assign(new Error("too large"), { status: 413 }));
39
+ req.destroy();
40
+ return;
41
+ }
42
+ chunks.push(chunk);
43
+ });
44
+ req.on("end", () => {
45
+ if (!chunks.length) return resolve({});
46
+ try { resolve(JSON.parse(Buffer.concat(chunks).toString("utf8"))); }
47
+ catch { reject(Object.assign(new Error("bad json"), { status: 400 })); }
48
+ });
49
+ req.on("error", reject);
50
+ });
51
+ }
52
+
53
+ function readRaw(req) {
54
+ return new Promise((resolve, reject) => {
55
+ const chunks = [];
56
+ let size = 0;
57
+ req.on("data", (chunk) => {
58
+ size += chunk.length;
59
+ if (size > MAX_UPLOAD) {
60
+ reject(Object.assign(new Error("too large"), { status: 413 }));
61
+ req.destroy();
62
+ return;
63
+ }
64
+ chunks.push(chunk);
65
+ });
66
+ req.on("end", () => resolve(Buffer.concat(chunks)));
67
+ req.on("error", reject);
68
+ });
69
+ }
70
+
71
+ function statusFor(result) {
72
+ if (!result || !result.error) return 200;
73
+ if (/不存在|not found/.test(result.error)) return 404;
74
+ return 400;
75
+ }
76
+
77
+ async function handle(req, res) {
78
+ const url = new URL(req.url || "/", "http://library.local");
79
+ const pathname = url.pathname;
80
+ if (req.method === "GET" && pathname === PREFIX + "/drive/list") {
81
+ const result = listDrive(url.searchParams.get("path") || "");
82
+ return send(res, statusFor(result), result);
83
+ }
84
+ if (req.method === "POST" && pathname === PREFIX + "/drive/upload") {
85
+ const data = await readRaw(req);
86
+ const result = saveFile(url.searchParams.get("path") || "", url.searchParams.get("name") || "", data);
87
+ return send(res, statusFor(result), result);
88
+ }
89
+ if (req.method === "POST" && pathname === PREFIX + "/drive/mkdir") {
90
+ const body = await readJson(req);
91
+ const result = makeDir(body.path || "", body.name || "");
92
+ return send(res, statusFor(result), result);
93
+ }
94
+ if (req.method === "POST" && pathname === PREFIX + "/drive/rm") {
95
+ const body = await readJson(req);
96
+ const result = removePath(body.path || "");
97
+ return send(res, statusFor(result), result);
98
+ }
99
+ if (req.method === "POST" && pathname === PREFIX + "/drive/mv") {
100
+ const body = await readJson(req);
101
+ const result = movePath(body.from || "", body.to || "");
102
+ return send(res, statusFor(result), result);
103
+ }
104
+ if (req.method === "GET" && pathname === PREFIX + "/drive/download") {
105
+ const result = fileForDownload(url.searchParams.get("path") || "");
106
+ if (result.error) return send(res, statusFor(result), result);
107
+ res.writeHead(200, {
108
+ "content-type": "application/octet-stream",
109
+ "content-length": result.size,
110
+ "content-disposition": "attachment; filename*=UTF-8''" + encodeURIComponent(result.name),
111
+ "cache-control": "no-store",
112
+ });
113
+ createReadStream(result.abs).pipe(res);
114
+ return;
115
+ }
116
+ if (req.method === "GET" && pathname === PREFIX + "/kb/list") {
117
+ return send(res, 200, listNotes());
118
+ }
119
+ if (req.method === "GET" && pathname === PREFIX + "/kb/search") {
120
+ return send(res, 200, searchNotes(url.searchParams.get("q") || ""));
121
+ }
122
+ if (req.method === "GET" && pathname === PREFIX + "/kb/note") {
123
+ const result = readNote(url.searchParams.get("name") || "");
124
+ return send(res, statusFor(result), result);
125
+ }
126
+ if (req.method === "POST" && pathname === PREFIX + "/kb/save") {
127
+ const body = await readJson(req);
128
+ const result = saveNote(body);
129
+ return send(res, statusFor(result), result);
130
+ }
131
+ if (req.method === "POST" && pathname === PREFIX + "/kb/rm") {
132
+ const body = await readJson(req);
133
+ const result = removeNote(body.name || "");
134
+ return send(res, statusFor(result), result);
135
+ }
136
+ send(res, 404, { error: "not found" });
137
+ }
138
+
139
+ function routes() {
140
+ return [
141
+ "/drive/list",
142
+ "/drive/upload",
143
+ "/drive/mkdir",
144
+ "/drive/rm",
145
+ "/drive/mv",
146
+ "/drive/download",
147
+ "/kb/list",
148
+ "/kb/search",
149
+ "/kb/note",
150
+ "/kb/save",
151
+ "/kb/rm",
152
+ ].map((suffix) => ({
153
+ kind: "exact",
154
+ path: PREFIX + suffix,
155
+ handler: (req, res) => handle(req, res).catch((err) => {
156
+ if (res.headersSent) return;
157
+ send(res, err.status || 500, { error: err.message || "failed" });
158
+ }),
159
+ }));
160
+ }
161
+
162
+ function bindRoutes(ctx) {
163
+ const web = ctx.get?.("webServer") ?? ctx.get?.("httpServer");
164
+ if (!web || typeof web.register !== "function") return null;
165
+ const stops = [];
166
+ for (const route of routes()) {
167
+ const stop = web.register(route);
168
+ if (typeof stop === "function") stops.push(stop);
169
+ }
170
+ return () => {
171
+ for (const stop of stops) stop();
172
+ };
173
+ }
174
+
175
+ export function apply(ctx) {
176
+ if (!ctx || typeof ctx.effect !== "function") return;
177
+ ctx.effect(() => {
178
+ let unbind = bindRoutes(ctx);
179
+ let alive = true;
180
+ const onService = (name) => {
181
+ if (!alive || unbind) return;
182
+ if (name === "webServer" || name === "httpServer") unbind = bindRoutes(ctx);
183
+ };
184
+ if (typeof ctx.on === "function") ctx.on("internal/service", onService);
185
+ return () => {
186
+ alive = false;
187
+ if (unbind) unbind();
188
+ };
189
+ });
190
+ }
package/lib/panel.js CHANGED
@@ -54,9 +54,38 @@ var ZLK_CSS = `
54
54
  .zlk-bc .lk { color:#3478f6; cursor:pointer; }
55
55
  .zlk-drop { margin-top:12px; border:1.5px dashed #cfd3d8; border-radius:12px; background:#f7f8fa; text-align:center; padding:14px 10px; }
56
56
  .zlk-drop.on { border-color:#3478f6; background:#f3f7ff; }
57
- .zlk-drop b { display:block; color:#3478f6; font-size:14px; }
58
- .zlk-drop span { display:block; margin-top:4px; font-size:12px; color:#8b9098; }
59
- .zlk-pick { margin-top:12px; padding:12px; border-radius:12px; background:#fff; border:1px solid #e6e6e6; }
57
+ .zlk-intake {
58
+ position: relative;
59
+ display: flex;
60
+ flex-direction: column;
61
+ justify-content: flex-end;
62
+ gap: 14px;
63
+ min-height: 240px;
64
+ margin: 0 0 22px;
65
+ padding: 28px 26px 22px;
66
+ border-radius: 28px;
67
+ background: #141414;
68
+ color: #f6f1e8;
69
+ box-shadow: 0 24px 60px -36px rgba(0,0,0,.7);
70
+ cursor: pointer;
71
+ }
72
+ .zlk-intake.empty { min-height: 68vh; }
73
+ .zlk-intake.on { outline: 2px solid #f6f1e8; outline-offset: 4px; }
74
+ .zlk-kicker { font-size: 12px; letter-spacing: .16em; text-transform: uppercase; color: #b7aa96; }
75
+ .zlk-headline { font-size: 64px; line-height: .9; font-weight: 720; letter-spacing: -0.04em; }
76
+ .zlk-lead { max-width: 28em; font-size: 16px; line-height: 1.5; color: #d9d0c3; }
77
+ .zlk-drop-hint { font-size: 13px; color: #a89880; }
78
+ .zlk-line { display: flex; gap: 8px; align-items: center; }
79
+ .zlk-line input {
80
+ flex: 1; height: 52px; border: 0; border-radius: 16px; padding: 0 16px;
81
+ background: #f6f1e8; color: #161616; font: 500 16px/1 inherit; outline: none;
82
+ }
83
+ .zlk-line button {
84
+ height: 52px; padding: 0 18px; border: 0; border-radius: 16px;
85
+ background: #f6f1e8; color: #161616; font: 700 15px/1 inherit; cursor: pointer;
86
+ }
87
+ .zlk-shelf-title { margin: 8px 2px 4px; font-size: 13px; letter-spacing: .08em; color: #6b7280; }
88
+ .zlk-pick { margin-top: 12px; padding: 12px; border-radius: 12px; background: #fff; border: 1px solid #e6e6e6; }
60
89
  `;
61
90
 
62
91
  function ensureCss() {
@@ -310,169 +339,124 @@ function LibraryPanel(props) {
310
339
  }).catch(function (e) { patchNs({ message: portalErrorText(e) }); });
311
340
  }
312
341
 
342
+ function saveLine() {
343
+ var body = bodyRef.current ? String(bodyRef.current.value || "").trim() : "";
344
+ if (!body) { if (bodyRef.current) bodyRef.current.focus(); return; }
345
+ var title = body.split("\n")[0].slice(0, 36);
346
+ apiRef.current.saveNote({ title: title, content: body }, deskLang()).then(function () {
347
+ if (bodyRef.current) bodyRef.current.value = "";
348
+ return apiRef.current.listNotes();
349
+ }).then(function (d) {
350
+ patchNs({ phase: "ready", role: (d && d.role) || "admin", notes: (d && d.notes) || [], composing: false, q: "", hits: [], message: "已记下", tone: "ok" });
351
+ }).catch(function (e) { patchNs({ message: portalErrorText(e), tone: "err" }); });
352
+ }
353
+
313
354
  var trigger = h("button", {
314
355
  type: "button", className: "zlk-nav" + (open ? " on" : ""), onClick: toggle, title: "资料库", "data-zlk-open": "1",
315
356
  }, h("span", { className: "ic" }, iconFolder(16)), wide ? h("span", { className: "lbl" }, "资料库") : null);
316
357
  if (!open) return h("div", { "data-zlk-root": "1" }, trigger);
317
358
 
318
- var bodyKids = [
319
- h("div", { key: "tabs", className: "zlk-tabs" },
320
- h("button", { type: "button", className: "zlk-tab" + (tab === "files" ? " on" : ""), "data-zlk-tab": "files", onClick: function () { setTab("files"); } }, "📁 文件"),
321
- h("button", { type: "button", className: "zlk-tab" + (tab === "notes" ? " on" : ""), "data-zlk-tab": "notes", onClick: function () { setTab("notes"); } }, "📝 笔记")
322
- ),
323
- ];
324
-
325
- if (tab === "files") {
326
- bodyKids.push(h("div", { key: "hint", className: "zlk-hint" }, "放原始文件;按项目或客户建文件夹,找起来最快"));
327
- var segs = String(fs.path || "").split("/").filter(Boolean);
328
- var bc = [h("span", { key: "root", className: "lk", onClick: function () { loadFiles(""); } }, "🏠 根目录")];
329
- var acc = "";
330
- segs.forEach(function (seg, i) {
331
- acc = acc ? acc + "/" + seg : seg;
332
- var here = acc;
333
- bc.push(h("span", { key: "s" + i }, " / "));
334
- bc.push(h("span", { key: "c" + i, className: "lk", onClick: function () { loadFiles(here); } }, seg));
335
- });
336
- bodyKids.push(h("div", { key: "bc", className: "zlk-bc" }, bc));
337
- bodyKids.push(h("div", { key: "bar", className: "zlk-row" },
338
- h("button", { type: "button", className: "zlk-btn gh", "data-zlk-mkdir": "1", onClick: function () { patchFs({ newFolder: !fs.newFolder, pick: null }); } }, "+ 新建文件夹"),
339
- h("button", { type: "button", className: "zlk-btn", "data-zlk-upload": "1", onClick: function () { if (fileRef.current) fileRef.current.click(); } }, "⬆ 上传"),
340
- h("input", { ref: fileRef, className: "zlk-file", type: "file", multiple: true, "data-zlk-file-input": "1", onChange: function (e) { uploadFiles(e.target.files); e.target.value = ""; } })
341
- ));
342
- if (fs.newFolder) {
343
- bodyKids.push(h("div", { key: "nf", className: "zlk-row" },
344
- h("input", { ref: folderRef, className: "zlk-inp zlk-search", "data-zlk-folder-name": "1", placeholder: "文件夹名字,比如「客户A」「2026报价」", onKeyDown: function (e) { if (e.key === "Enter") createFolder(); } }),
345
- h("button", { type: "button", className: "zlk-btn", "data-zlk-mkdir-save": "1", onClick: createFolder }, "创建"),
346
- h("button", { type: "button", className: "zlk-btn gh", onClick: function () { patchFs({ newFolder: false }); } }, "取消")
347
- ));
348
- }
349
- bodyKids.push(h("div", {
350
- key: "drop",
351
- className: "zlk-drop" + (fs.drag ? " on" : ""),
352
- "data-zlk-drop": "1",
353
- onDragOver: function (e) { e.preventDefault(); if (!fs.drag) patchFs({ drag: true }); },
354
- onDragLeave: function () { patchFs({ drag: false }); },
355
- onDrop: function (e) { e.preventDefault(); patchFs({ drag: false }); uploadFiles(e.dataTransfer && e.dataTransfer.files); },
356
- }, h("b", null, "⬆ 拖文件到这里上传"), h("span", null, "上传到当前文件夹 · 单个 ≤50MB")));
357
- if (fs.message) bodyKids.push(h("div", { key: "msg", className: "zlk-msg" + (fs.phase === "error" || fs.message.indexOf("失败") >= 0 ? " err" : "") }, fs.message));
358
- if (fs.phase === "loading") bodyKids.push(h("div", { key: "ld", className: "zlk-hint" }, "读取中…"));
359
- if (fs.pick) {
360
- var pk = fs.pick;
361
- var psegs = String(pk.path || "").split("/").filter(Boolean);
362
- var pbc = [h("span", { key: "pr", className: "lk", onClick: function () { pickerGo(""); } }, "🏠 根目录")];
363
- var pacc = "";
364
- psegs.forEach(function (seg, i) {
365
- pacc = pacc ? pacc + "/" + seg : seg;
366
- var there = pacc;
367
- pbc.push(h("span", { key: "ps" + i }, " / "));
368
- pbc.push(h("span", { key: "pc" + i, className: "lk", onClick: function () { pickerGo(there); } }, seg));
369
- });
370
- var folders = (pk.entries || []).filter(function (en) { return en.dir; }).map(function (en) {
371
- var next = joinPath(pk.path, en.name);
372
- return h("div", { key: en.name, className: "zlk-it", onClick: function () { pickerGo(next); } },
373
- h("span", { className: "zlk-ico" }, "📁"),
374
- h("div", { className: "zlk-title link" }, en.name)
375
- );
376
- });
377
- bodyKids.push(h("div", { key: "pick", className: "zlk-pick" },
378
- h("div", { className: "zlk-hint" }, "把「" + pk.name + "」移动到:"),
379
- h("div", { className: "zlk-bc" }, pbc),
380
- folders.length ? h("div", null, folders) : h("div", { className: "zlk-hint" }, "(没有子文件夹,可直接「移到这里」)"),
381
- h("div", { className: "zlk-row" },
382
- h("button", { type: "button", className: "zlk-btn", "data-zlk-move-here": "1", onClick: moveHere }, "移到这里"),
383
- h("button", { type: "button", className: "zlk-btn gh", onClick: function () { patchFs({ pick: null }); } }, "取消")
384
- )
385
- ));
386
- }
387
- var rootEmpty = !fs.path && !(fs.entries && fs.entries.length) && fs.phase !== "error";
388
- (fs.entries || []).forEach(function (en) {
389
- var full = joinPath(fs.path, en.name);
390
- var acts = [];
391
- if (en.dir) acts.push(h("button", { key: "o", type: "button", className: "zlk-mini", onClick: function () { loadFiles(full); } }, "打开"));
392
- else acts.push(h("a", { key: "d", className: "zlk-mini", href: apiRef.current.downloadUrl(full) }, "下载"));
393
- acts.push(h("button", { key: "m", type: "button", className: "zlk-mini", "data-zlk-move": en.name, onClick: function () { openPicker(en.name); } }, "移动到…"));
394
- acts.push(h("button", { key: "x", type: "button", className: "zlk-mini", "data-zlk-del-file": en.name, onClick: function () { removeEntry(full, en.dir); } }, "删除"));
395
- bodyKids.push(h("div", { key: "f-" + en.name, className: "zlk-it", "data-zlk-file": en.name },
396
- h("span", { className: "zlk-ico" }, en.dir ? "📁" : "📄"),
397
- h("div", { className: "zlk-gr" },
398
- h("div", { className: "zlk-top" },
399
- h("div", { className: "zlk-title" + (en.dir ? " link" : ""), onClick: en.dir ? function () { loadFiles(full); } : null }, en.name),
400
- h("div", { className: "zlk-acts row" }, acts)
401
- ),
402
- h("div", { className: "zlk-meta" }, (en.dir ? "文件夹" : formatSize(en.size)) + (en.mtime ? " · " + formatWhen(en.mtime) : ""))
403
- )
404
- ));
405
- });
406
- if (rootEmpty) {
407
- bodyKids.push(h("div", { key: "ea", className: "zlk-empty", onClick: function () { if (fileRef.current) fileRef.current.click(); } },
408
- h("b", null, "① 建个文件夹,把文件传进来"), h("span", null, "比如「客户A」「2026报价」—— 拖进去就行")));
409
- bodyKids.push(h("div", { key: "eb", className: "zlk-empty", onClick: function () { setTab("notes"); patchNs({ composing: true }); } },
410
- h("b", null, "② 记第一条笔记"), h("span", null, "今天有什么结论?写一句话也算")));
411
- }
412
- } else {
413
- bodyKids.push(h("div", { key: "hint", className: "zlk-hint" }, "记结论和经验,搜一下就能找到 —— AI 助理也能直接引用"));
414
- bodyKids.push(h("div", { key: "search", className: "zlk-row" },
415
- h("input", { ref: qRef, className: "zlk-inp zlk-search", "data-zlk-search": "1", placeholder: "搜笔记,比如「印尼」「定价」…", onKeyDown: function (e) { if (e.key === "Enter") runSearch(); } }),
416
- h("button", { type: "button", className: "zlk-btn gh", "data-zlk-search-btn": "1", onClick: runSearch }, "搜索")
417
- ));
418
- bodyKids.push(h("div", { key: "compose", className: "zlk-row" },
419
- h("button", { type: "button", className: "zlk-btn", "data-zlk-compose": "1", onClick: function () { patchNs({ composing: !ns.composing }); } }, "+ 记一条")
420
- ));
421
- if (ns.composing) {
422
- bodyKids.push(h("div", { key: "form", className: "zlk-form" },
423
- h("input", { ref: titleRef, className: "zlk-inp", "data-zlk-title": "1", placeholder: "标题(不填就用第一行)" }),
424
- h("textarea", { ref: bodyRef, className: "zlk-inp zlk-ta", "data-zlk-body": "1", placeholder: "想到什么写什么…… 一句话也算" }),
425
- h("div", { className: "zlk-row" },
426
- h("button", { type: "button", className: "zlk-btn", "data-zlk-save": "1", onClick: saveNote }, "保存 ✓"),
427
- h("button", { type: "button", className: "zlk-btn gh", onClick: function () { patchNs({ composing: false }); } }, "取消")
428
- )
429
- ));
430
- }
431
- if (ns.message) bodyKids.push(h("div", { key: "nm", className: "zlk-msg" + (ns.tone === "err" || ns.phase === "error" ? " err" : " ok") }, ns.message));
432
- if (ns.phase === "loading") bodyKids.push(h("div", { key: "ld", className: "zlk-hint" }, "读取中…"));
433
- if (ns.q) {
434
- bodyKids.push(h("div", { key: "hc", className: "zlk-hint" }, "命中 " + ns.hits.length));
435
- if (!ns.hits.length) bodyKids.push(h("div", { key: "miss", className: "zlk-empty" }, h("b", null, "没有命中,换个短点的词试试")));
436
- ns.hits.forEach(function (hit, i) {
437
- bodyKids.push(h("div", { key: "h" + i, className: "zlk-it" },
438
- h("span", { className: "zlk-ico" }, "🔎"),
439
- h("div", { className: "zlk-gr" },
440
- h("div", { className: "zlk-meta" }, hit.file + " : " + hit.line),
441
- h("div", { className: "zlk-body" }, hit.text)
442
- )
443
- ));
444
- });
445
- bodyKids.push(h("div", { key: "back", className: "zlk-row" },
446
- h("button", { type: "button", className: "zlk-btn gh", "data-zlk-back-list": "1", onClick: function () { if (qRef.current) qRef.current.value = ""; patchNs({ q: "", hits: [] }); } }, "返回笔记列表")
447
- ));
448
- } else if (ns.phase === "ready") {
449
- if (!ns.notes.length) {
450
- bodyKids.push(h("div", { key: "empty", className: "zlk-empty", onClick: function () { patchNs({ composing: true }); } },
451
- h("b", null, "还没有笔记 —— 点「+ 记一条」写第一句"),
452
- h("span", null, "结论、口径、经验,一句话也算")
453
- ));
454
- }
455
- ns.notes.forEach(function (note) {
456
- var shown = ns.expanded === note.name;
457
- var acts = [h("button", { key: "v", type: "button", className: "zlk-mini", "data-zlk-view": note.name, onClick: function () { toggleNote(note.name); } }, shown ? "收起" : "查看")];
458
- if (canDeleteNote(ns.role)) {
459
- acts.push(h("button", { key: "x", type: "button", className: "zlk-mini", "data-zlk-del": note.name, onClick: function () { deleteNote(note.name); } }, "删除"));
460
- }
461
- bodyKids.push(h("div", { key: note.name, className: "zlk-it", "data-zlk-note": note.name },
462
- h("span", { className: "zlk-ico" }, "📝"),
463
- h("div", { className: "zlk-gr" },
464
- h("div", { className: "zlk-top" },
465
- h("div", { className: "zlk-title" }, note.title || note.name),
466
- h("div", { className: "zlk-acts" }, acts)
467
- ),
468
- h("div", { className: "zlk-meta" }, formatWhen(note.mtime) + (note.author ? " · " + note.author : "")),
469
- shown ? h("div", { className: "zlk-body" }, ns.content) : null
470
- )
471
- ));
472
- });
473
- }
474
- }
475
-
359
+ var shelfEmpty = fs.phase !== "error" && ns.phase !== "error" && !fs.path && !(fs.entries && fs.entries.length) && !(ns.notes && ns.notes.length) && !ns.q;
360
+ var bodyKids = [];
361
+ bodyKids.push(h("div", {
362
+ key: "intake",
363
+ className: "zlk-intake" + (shelfEmpty ? " empty" : "") + (fs.drag ? " on" : ""),
364
+ "data-zlk-drop": "1",
365
+ onDragOver: function (e) { e.preventDefault(); if (!fs.drag) patchFs({ drag: true }); },
366
+ onDragLeave: function () { patchFs({ drag: false }); },
367
+ onDrop: function (e) { e.preventDefault(); patchFs({ drag: false }); uploadFiles(e.dataTransfer && e.dataTransfer.files); },
368
+ onClick: function (e) {
369
+ if (e.target.closest("input, button, a")) return;
370
+ if (fileRef.current) fileRef.current.click();
371
+ },
372
+ },
373
+ h("div", { className: "zlk-kicker" }, "SHARED SHELF"),
374
+ h("div", { className: "zlk-headline" }, shelfEmpty ? "丢进来" : "再丢一份"),
375
+ h("div", { className: "zlk-lead" }, "文件,或一句话。放在这块黑板上,用这个工作台的人都能看见。"),
376
+ h("div", { className: "zlk-drop-hint" }, "拖文件到这里 · 点这块黑板也能选文件 · 单个 ≤50MB"),
377
+ h("div", { className: "zlk-line", onClick: function (e) { e.stopPropagation(); } },
378
+ h("input", { ref: bodyRef, "data-zlk-body": "1", "data-zlk-line": "1", placeholder: "或写一句,回车记下", onKeyDown: function (e) { if (e.key === "Enter") saveLine(); } }),
379
+ h("button", { type: "button", "data-zlk-save": "1", onClick: saveLine }, "记下")
380
+ ),
381
+ h("input", { ref: fileRef, className: "zlk-file", type: "file", multiple: true, "data-zlk-file-input": "1", onChange: function (e) { uploadFiles(e.target.files); e.target.value = ""; } })
382
+ ));
383
+ if (fs.message) bodyKids.push(h("div", { key: "fmsg", className: "zlk-msg" + (fs.phase === "error" || fs.message.indexOf("失败") >= 0 ? " err" : "") }, fs.message));
384
+ if (ns.message) bodyKids.push(h("div", { key: "nmsg", className: "zlk-msg" + (ns.tone === "err" || ns.phase === "error" ? " err" : " ok") }, ns.message));
385
+ if (fs.phase === "loading" || ns.phase === "loading") bodyKids.push(h("div", { key: "ld", className: "zlk-hint" }, "读取中…"));
386
+ var segs = String(fs.path || "").split("/").filter(Boolean);
387
+ if (segs.length) {
388
+ var bc = [h("span", { key: "root", className: "lk", onClick: function () { loadFiles(""); } }, "架上")];
389
+ var acc = "";
390
+ segs.forEach(function (seg, i) {
391
+ acc = acc ? acc + "/" + seg : seg;
392
+ var here = acc;
393
+ bc.push(h("span", { key: "s" + i }, " / "));
394
+ bc.push(h("span", { key: "c" + i, className: "lk", onClick: function () { loadFiles(here); } }, seg));
395
+ });
396
+ bodyKids.push(h("div", { key: "bc", className: "zlk-bc" }, bc));
397
+ }
398
+ bodyKids.push(h("div", { key: "bar", className: "zlk-row" },
399
+ h("button", { type: "button", className: "zlk-btn gh", "data-zlk-mkdir": "1", onClick: function () { patchFs({ newFolder: !fs.newFolder }); } }, "新建文件夹"),
400
+ h("input", { ref: qRef, className: "zlk-inp zlk-search", "data-zlk-search": "1", placeholder: "在架上找一句", onKeyDown: function (e) { if (e.key === "Enter") runSearch(); } }),
401
+ h("button", { type: "button", className: "zlk-btn gh", "data-zlk-search-btn": "1", onClick: runSearch }, "找")
402
+ ));
403
+ if (fs.newFolder) {
404
+ bodyKids.push(h("div", { key: "nf", className: "zlk-row" },
405
+ h("input", { ref: folderRef, className: "zlk-inp zlk-search", "data-zlk-folder-name": "1", placeholder: "文件夹名字", onKeyDown: function (e) { if (e.key === "Enter") createFolder(); } }),
406
+ h("button", { type: "button", className: "zlk-btn", "data-zlk-mkdir-save": "1", onClick: createFolder }, "创建"),
407
+ h("button", { type: "button", className: "zlk-btn gh", onClick: function () { patchFs({ newFolder: false }); } }, "取消")
408
+ ));
409
+ }
410
+ if (ns.q) {
411
+ bodyKids.push(h("div", { key: "hc", className: "zlk-shelf-title" }, "找到 " + ns.hits.length));
412
+ if (!ns.hits.length) bodyKids.push(h("div", { key: "miss", className: "zlk-hint" }, "没有这句话"));
413
+ ns.hits.forEach(function (hit, i) {
414
+ bodyKids.push(h("div", { key: "h" + i, className: "zlk-it" },
415
+ h("span", { className: "zlk-ico" }, "🔎"),
416
+ h("div", { className: "zlk-gr" }, h("div", { className: "zlk-title" }, hit.text))
417
+ ));
418
+ });
419
+ }
420
+ if (!ns.q && ns.notes && ns.notes.length) {
421
+ bodyKids.push(h("div", { key: "nt", className: "zlk-shelf-title" }, "句子"));
422
+ ns.notes.forEach(function (note) {
423
+ var shown = ns.expanded === note.name;
424
+ var acts = [h("button", { key: "v", type: "button", className: "zlk-mini", "data-zlk-view": note.name, onClick: function () { toggleNote(note.name); } }, shown ? "收起" : "查看")];
425
+ if (canDeleteNote(ns.role)) acts.push(h("button", { key: "x", type: "button", className: "zlk-mini", "data-zlk-del": note.name, onClick: function () { deleteNote(note.name); } }, "删除"));
426
+ bodyKids.push(h("div", { key: note.name, className: "zlk-it", "data-zlk-note": note.name },
427
+ h("span", { className: "zlk-ico" }, "✎"),
428
+ h("div", { className: "zlk-gr" },
429
+ h("div", { className: "zlk-top" },
430
+ h("div", { className: "zlk-title" }, note.title || note.name),
431
+ h("div", { className: "zlk-acts row" }, acts)
432
+ ),
433
+ h("div", { className: "zlk-meta" }, formatWhen(note.mtime) + (note.author ? " · " + note.author : "")),
434
+ shown ? h("div", { className: "zlk-body" }, ns.content) : null
435
+ )
436
+ ));
437
+ });
438
+ }
439
+ if (fs.entries && fs.entries.length) {
440
+ bodyKids.push(h("div", { key: "ft", className: "zlk-shelf-title" }, "文件"));
441
+ fs.entries.forEach(function (en) {
442
+ var full = joinPath(fs.path, en.name);
443
+ var acts = [];
444
+ if (en.dir) acts.push(h("button", { key: "o", type: "button", className: "zlk-mini", onClick: function () { loadFiles(full); } }, "打开"));
445
+ else acts.push(h("a", { key: "d", className: "zlk-mini", href: apiRef.current.downloadUrl(full) }, "下载"));
446
+ acts.push(h("button", { key: "x", type: "button", className: "zlk-mini", "data-zlk-del-file": en.name, onClick: function () { removeEntry(full, en.dir); } }, "删除"));
447
+ bodyKids.push(h("div", { key: "f-" + en.name, className: "zlk-it", "data-zlk-file": en.name },
448
+ h("span", { className: "zlk-ico" }, en.dir ? "📁" : "📄"),
449
+ h("div", { className: "zlk-gr" },
450
+ h("div", { className: "zlk-top" },
451
+ h("div", { className: "zlk-title" + (en.dir ? " link" : ""), onClick: en.dir ? function () { loadFiles(full); } : null }, en.name),
452
+ h("div", { className: "zlk-acts row" }, acts)
453
+ ),
454
+ h("div", { className: "zlk-meta" }, (en.dir ? "文件夹" : formatSize(en.size)) + (en.mtime ? " · " + formatWhen(en.mtime) : ""))
455
+ )
456
+ ));
457
+ });
458
+ }
459
+
476
460
  return h("div", { "data-zlk-root": "1" },
477
461
  trigger,
478
462
  h("section", { className: "zlk-fv", "data-zlk-panel": "1" },
@@ -480,7 +464,7 @@ function LibraryPanel(props) {
480
464
  h("span", { className: "zlk-mark" }, iconFolder(22)),
481
465
  h("div", { className: "zlk-hd-text" },
482
466
  h("div", { className: "zlk-t1" }, "资料库"),
483
- h("div", { className: "zlk-t2" }, "文件 + 笔记 · 放进来 = 全员可见")
467
+ h("div", { className: "zlk-t2" }, "丢进来,全员就能看见")
484
468
  ),
485
469
  h("button", { type: "button", className: "zlk-back", "data-zlk-back": "1", onClick: close }, "← 返回对话")
486
470
  ),