anywork-library-plugin 0.0.0-stage → 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 ADDED
@@ -0,0 +1,190 @@
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 ADDED
@@ -0,0 +1,490 @@
1
+ var inject = ["slots"];
2
+ var ZLK_SEQ = 0;
3
+
4
+ var ZLK_CSS = `
5
+ .zlk-nav { display:flex; align-items:center; gap:10px; width:100%; padding:7px 10px; border:0; border-radius:10px; background:transparent; color:inherit; cursor:pointer; font:400 14px/22px -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Noto Sans SC", "Microsoft YaHei", sans-serif; text-align:left; }
6
+ .zlk-nav:hover { background:rgba(127,127,127,.12); }
7
+ .zlk-nav.on { background:rgba(79,124,247,.13); }
8
+ .zlk-nav .ic { width:16px; height:16px; display:flex; align-items:center; justify-content:center; flex:0 0 auto; }
9
+ .zlk-nav .lbl { flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
10
+ .zlk-fv { position:fixed; left:var(--zlk-fv-left, 0px); top:0; right:0; bottom:0; z-index:9999; display:flex; flex-direction:column; background:#e6e6e6; color:#1c1e21; overflow:auto; font-family:-apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Noto Sans SC", "Microsoft YaHei", sans-serif; }
11
+ .zlk-hd { position:sticky; top:0; z-index:2; display:flex; align-items:center; gap:12px; padding:16px 16px 14px; background:#fff; }
12
+ .zlk-mark { width:44px; height:44px; border-radius:12px; display:flex; align-items:center; justify-content:center; background:#fff; border:1px solid #e4e4e4; color:#4b5563; flex:0 0 auto; }
13
+ .zlk-hd-text { flex:1; min-width:0; }
14
+ .zlk-t1 { font-size:18px; font-weight:700; line-height:1.3; }
15
+ .zlk-t2 { margin-top:3px; font-size:12px; line-height:1.45; color:#8b919a; }
16
+ .zlk-back { margin-left:auto; flex:0 0 auto; height:34px; padding:0 14px; border:1px solid #d5d5d5; border-radius:999px; background:#fff; color:#3a3a3a; font:500 12.5px/1 inherit; cursor:pointer; white-space:nowrap; }
17
+ .zlk-back:hover { background:#f6f6f6; }
18
+ .zlk-bd { padding:14px 16px 72px; }
19
+ .zlk-tabs { display:flex; gap:4px; padding:4px; margin:0 0 14px; background:#fff; border-radius:12px; box-shadow:0 1px 2px rgba(16,24,40,.04); }
20
+ .zlk-tab { flex:1; display:flex; align-items:center; justify-content:center; gap:6px; height:36px; border:0; border-radius:9px; background:transparent; color:#6b7280; font:500 14px/1 inherit; cursor:pointer; }
21
+ .zlk-tab.on { background:#fff; color:#1c1e21; font-weight:650; box-shadow:0 1px 3px rgba(16,24,40,.14); }
22
+ .zlk-hint { margin:0 2px 12px; font-size:13px; line-height:1.65; color:#8b9098; }
23
+ .zlk-row { display:flex; gap:8px; align-items:center; margin-top:12px; flex-wrap:wrap; }
24
+ .zlk-inp { width:100%; min-width:0; height:40px; box-sizing:border-box; border:1px solid #e1e1e1; border-radius:10px; padding:0 12px; background:#fff; color:inherit; font:400 14px/1.4 inherit; outline:none; }
25
+ .zlk-inp::placeholder { color:#c5c8ce; }
26
+ .zlk-inp:focus { border-color:#4f7cf7; box-shadow:0 0 0 3px rgba(79,124,247,.15); }
27
+ .zlk-ta { height:auto; min-height:88px; padding:10px 12px; resize:vertical; }
28
+ .zlk-search { flex:1; }
29
+ .zlk-btn { height:36px; padding:0 16px; border:0; border-radius:10px; background:#3478f6; color:#fff; font:600 14px/1 inherit; cursor:pointer; }
30
+ .zlk-btn:hover { background:#2d6ae0; }
31
+ .zlk-btn.gh { background:#fff; border:1px solid #e1e1e1; color:#3a3a3a; font-weight:500; }
32
+ .zlk-btn.gh:hover { background:#f7f7f7; }
33
+ .zlk-form { display:flex; flex-direction:column; gap:8px; margin-top:12px; }
34
+ .zlk-msg { margin-top:8px; font-size:12.5px; color:#8b9098; }
35
+ .zlk-msg.ok { color:#248a3d; }
36
+ .zlk-msg.err { color:#d64545; }
37
+ .zlk-it { display:flex; gap:10px; align-items:flex-start; padding:8px 2px 14px; }
38
+ .zlk-ico { width:36px; height:36px; border-radius:10px; display:flex; align-items:center; justify-content:center; background:#fff; border:1px solid #ececec; color:#6b7280; flex:0 0 auto; font-size:16px; }
39
+ .zlk-gr { flex:1; min-width:0; }
40
+ .zlk-top { display:flex; align-items:flex-start; gap:10px; }
41
+ .zlk-title { flex:1; min-width:0; font-size:15px; font-weight:650; line-height:1.45; color:#222; word-break:break-word; }
42
+ .zlk-title.link { cursor:pointer; }
43
+ .zlk-meta { margin-top:6px; font-size:13px; line-height:1.4; color:#9aa0a6; }
44
+ .zlk-acts { display:flex; flex-direction:column; gap:8px; flex:0 0 auto; }
45
+ .zlk-acts.row { flex-direction:row; flex-wrap:wrap; justify-content:flex-end; }
46
+ .zlk-mini { min-width:64px; height:32px; padding:0 12px; border:1px solid #d8d8d8; border-radius:8px; background:#fff; color:#3a3a3a; font:400 13px/1 inherit; cursor:pointer; text-decoration:none; display:inline-flex; align-items:center; justify-content:center; box-sizing:border-box; }
47
+ .zlk-mini:hover { background:#f7f7f7; }
48
+ .zlk-body { margin-top:10px; padding:10px 12px; border-radius:10px; background:#fff; border:1px solid #ececec; font-size:13.5px; line-height:1.65; white-space:pre-wrap; word-break:break-word; color:#333; }
49
+ .zlk-empty { margin-top:12px; padding:18px 14px; border:1.5px dashed #cfd3d8; border-radius:12px; background:#f7f8fa; color:#4f7cf7; cursor:pointer; }
50
+ .zlk-empty b { display:block; font-size:14px; }
51
+ .zlk-empty span { display:block; margin-top:4px; font-size:12px; color:#8b9098; font-weight:400; }
52
+ .zlk-file { position:absolute; width:1px; height:1px; overflow:hidden; clip:rect(0 0 0 0); }
53
+ .zlk-bc { font-size:13px; margin:2px 0 4px; color:#4b5563; }
54
+ .zlk-bc .lk { color:#3478f6; cursor:pointer; }
55
+ .zlk-drop { margin-top:12px; border:1.5px dashed #cfd3d8; border-radius:12px; background:#f7f8fa; text-align:center; padding:14px 10px; }
56
+ .zlk-drop.on { border-color:#3478f6; background:#f3f7ff; }
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; }
89
+ `;
90
+
91
+ function ensureCss() {
92
+ if (typeof document === "undefined") return;
93
+ if (document.getElementById("library-plugin-css")) return;
94
+ var st = document.createElement("style");
95
+ st.id = "library-plugin-css";
96
+ st.textContent = ZLK_CSS;
97
+ (document.head || document.documentElement).appendChild(st);
98
+ }
99
+
100
+ function h(type, props) {
101
+ var args = [type, props];
102
+ for (var i = 2; i < arguments.length; i++) args.push(arguments[i]);
103
+ return React.createElement.apply(React, args);
104
+ }
105
+
106
+ function svg(size) {
107
+ var props = {
108
+ viewBox: "0 0 24 24", width: size, height: size, fill: "none", stroke: "currentColor",
109
+ strokeWidth: 1.7, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true",
110
+ };
111
+ var args = ["svg", props];
112
+ for (var i = 1; i < arguments.length; i++) args.push(arguments[i]);
113
+ return h.apply(null, args);
114
+ }
115
+
116
+ function iconFolder(size) {
117
+ return svg(size, h("path", { d: "M3.5 7.5A1.5 1.5 0 0 1 5 6h4l2 2h8a1.5 1.5 0 0 1 1.5 1.5v8A1.5 1.5 0 0 1 19 19H5a1.5 1.5 0 0 1-1.5-1.5v-10z" }));
118
+ }
119
+
120
+ function measureFv() {
121
+ var left = 0;
122
+ try {
123
+ if (!(window.matchMedia && window.matchMedia("(max-width: 820px)").matches)) {
124
+ var sc = document.querySelector('[class*="sidebarCol"]');
125
+ if (sc) {
126
+ var r = sc.getBoundingClientRect();
127
+ if (r && r.right > 40 && r.right < window.innerWidth * 0.6) left = Math.round(r.right);
128
+ }
129
+ }
130
+ document.documentElement.style.setProperty("--zlk-fv-left", left + "px");
131
+ } catch (e) { /* ignore */ }
132
+ }
133
+
134
+ function notifyFv(id) {
135
+ try { window.dispatchEvent(new CustomEvent("desk-fv", { detail: { id: id } })); } catch (e) { /* ignore */ }
136
+ }
137
+
138
+ function deskLang() {
139
+ try {
140
+ var lang = (document.documentElement && document.documentElement.lang) || (navigator.language || "zh");
141
+ return String(lang).toLowerCase().indexOf("en") === 0 ? "en" : "zh";
142
+ } catch (e) { return "zh"; }
143
+ }
144
+
145
+ function formatSize(n) {
146
+ n = Number(n) || 0;
147
+ if (n < 1024) return n + " B";
148
+ if (n < 1048576) return (n / 1024).toFixed(n < 10240 ? 1 : 0) + " KB";
149
+ return (n / 1048576).toFixed(1) + " MB";
150
+ }
151
+
152
+ function formatWhen(value) {
153
+ var m = /^(\d{4}-\d{2}-\d{2})[T ](\d{2}:\d{2})/.exec(String(value || ""));
154
+ if (m) return m[1] + " " + m[2];
155
+ return String(value || "");
156
+ }
157
+
158
+ function joinPath(dir, name) {
159
+ return dir ? dir + "/" + name : name;
160
+ }
161
+
162
+ function LibraryPanel(props) {
163
+ var wide = !!(props && props.wide);
164
+ var openPair = React.useState(false);
165
+ var open = openPair[0];
166
+ var setOpen = openPair[1];
167
+ var tabPair = React.useState("notes");
168
+ var tab = tabPair[0];
169
+ var setTab = tabPair[1];
170
+ var fsPair = React.useState({ phase: "idle", path: "", entries: [], message: "", newFolder: false, pick: null, drag: false });
171
+ var fs = fsPair[0];
172
+ var setFs = fsPair[1];
173
+ var nsPair = React.useState({ phase: "idle", role: "member", notes: [], q: "", hits: [], message: "", expanded: "", content: "", composing: false });
174
+ var ns = nsPair[0];
175
+ var setNs = nsPair[1];
176
+ var apiRef = React.useRef(null);
177
+ var idRef = React.useRef("");
178
+ var openRef = React.useRef(false);
179
+ var fileRef = React.useRef(null);
180
+ var qRef = React.useRef(null);
181
+ var titleRef = React.useRef(null);
182
+ var bodyRef = React.useRef(null);
183
+ var folderRef = React.useRef(null);
184
+ var pathRef = React.useRef("");
185
+ if (!idRef.current) idRef.current = "ziliaoku-" + (++ZLK_SEQ);
186
+ if (!apiRef.current) apiRef.current = createPortalApi(function () { return window.fetch.bind(window); });
187
+ openRef.current = open;
188
+ pathRef.current = fs.path;
189
+
190
+ function patchFs(p) { setFs(function (prev) { return Object.assign({}, prev, p); }); }
191
+ function patchNs(p) { setNs(function (prev) { return Object.assign({}, prev, p); }); }
192
+
193
+ function loadFiles(path) {
194
+ var p = String(path == null ? "" : path);
195
+ patchFs({ phase: "loading", path: p, entries: [], message: "", pick: null, newFolder: false });
196
+ apiRef.current.listDrive(p).then(function (d) {
197
+ patchFs({ phase: "ready", path: d.path != null ? d.path : p, entries: d.entries || [], message: "" });
198
+ }).catch(function (e) {
199
+ patchFs({ phase: "error", message: portalErrorText(e) });
200
+ });
201
+ }
202
+
203
+ function loadNotes() {
204
+ patchNs({ phase: "loading", message: "" });
205
+ apiRef.current.listNotes().then(function (d) {
206
+ patchNs({ phase: "ready", role: d.role || "member", notes: d.notes || [], message: "", tone: "" });
207
+ }).catch(function (e) {
208
+ patchNs({ phase: "error", tone: "err", message: portalErrorText(e) });
209
+ });
210
+ }
211
+
212
+ React.useEffect(function () {
213
+ function onFv(e) {
214
+ var who = e && e.detail ? e.detail.id : null;
215
+ if (who !== idRef.current) setOpen(false);
216
+ }
217
+ function onKey(e) { if (e.key === "Escape" && openRef.current) setOpen(false); }
218
+ function onDown(e) {
219
+ if (!openRef.current) return;
220
+ var t = e.target;
221
+ if (t && t.closest && (t.closest(".zlk-fv") || t.closest(".zlk-nav"))) return;
222
+ setOpen(false);
223
+ }
224
+ window.addEventListener("desk-fv", onFv);
225
+ document.addEventListener("keydown", onKey);
226
+ document.addEventListener("pointerdown", onDown, true);
227
+ return function () {
228
+ window.removeEventListener("desk-fv", onFv);
229
+ document.removeEventListener("keydown", onKey);
230
+ document.removeEventListener("pointerdown", onDown, true);
231
+ };
232
+ }, []);
233
+
234
+ React.useEffect(function () {
235
+ if (!open) return;
236
+ measureFv();
237
+ notifyFv(idRef.current);
238
+ loadFiles("");
239
+ loadNotes();
240
+ }, [open]);
241
+
242
+ function toggle() { setOpen(function (v) { return !v; }); }
243
+ function close() { setOpen(false); }
244
+
245
+ function uploadFiles(list) {
246
+ var files = [];
247
+ if (list) for (var i = 0; i < list.length; i++) files.push(list[i]);
248
+ var cursor = 0;
249
+ function next() {
250
+ if (cursor >= files.length) { loadFiles(pathRef.current); return; }
251
+ var f = files[cursor++];
252
+ if (f.size > MAX_UPLOAD) {
253
+ patchFs({ message: "上传失败:" + f.name + " 超过 50MB" });
254
+ next();
255
+ return;
256
+ }
257
+ patchFs({ message: "上传中:" + f.name + " …" });
258
+ apiRef.current.upload(pathRef.current, f).then(next).catch(function (e) {
259
+ patchFs({ message: "上传失败:" + portalErrorText(e) });
260
+ });
261
+ }
262
+ if (files.length) next();
263
+ }
264
+
265
+ function createFolder() {
266
+ var name = folderRef.current ? String(folderRef.current.value || "").trim() : "";
267
+ if (!name) { if (folderRef.current) folderRef.current.focus(); return; }
268
+ apiRef.current.mkdir(fs.path, name).then(function () { loadFiles(fs.path); }).catch(function (e) {
269
+ patchFs({ message: portalErrorText(e) });
270
+ });
271
+ }
272
+
273
+ function removeEntry(full, isDir) {
274
+ var label = (isDir ? "删除文件夹「" : "删除文件「") + full.split("/").pop() + "」?此操作不可恢复。";
275
+ if (window.confirm && !window.confirm(label)) return;
276
+ apiRef.current.rm(full).then(function () { loadFiles(fs.path); }).catch(function (e) {
277
+ patchFs({ message: portalErrorText(e) });
278
+ });
279
+ }
280
+
281
+ function openPicker(name) {
282
+ patchFs({ pick: { name: name, path: "", entries: [] } });
283
+ apiRef.current.listDrive("").then(function (d) {
284
+ patchFs({ pick: { name: name, path: d.path != null ? d.path : "", entries: d.entries || [] } });
285
+ }).catch(function (e) { patchFs({ message: portalErrorText(e), pick: null }); });
286
+ }
287
+
288
+ function pickerGo(path) {
289
+ apiRef.current.listDrive(path).then(function (d) {
290
+ setFs(function (prev) {
291
+ var pick = prev.pick || { name: "" };
292
+ return Object.assign({}, prev, { pick: { name: pick.name, path: d.path != null ? d.path : path, entries: d.entries || [] } });
293
+ });
294
+ }).catch(function (e) { patchFs({ message: portalErrorText(e) }); });
295
+ }
296
+
297
+ function moveHere() {
298
+ if (!fs.pick) return;
299
+ var from = joinPath(fs.path, fs.pick.name);
300
+ apiRef.current.mv(from, fs.pick.path).then(function () { loadFiles(fs.path); }).catch(function (e) {
301
+ patchFs({ message: portalErrorText(e) });
302
+ });
303
+ }
304
+
305
+ function runSearch() {
306
+ var q = qRef.current ? String(qRef.current.value || "").trim() : "";
307
+ if (!q) { patchNs({ q: "", hits: [] }); return; }
308
+ apiRef.current.searchNotes(q).then(function (d) {
309
+ patchNs({ q: q, hits: (d && d.hits) || [], message: "" });
310
+ }).catch(function (e) { patchNs({ message: portalErrorText(e) }); });
311
+ }
312
+
313
+ function saveNote() {
314
+ var title = titleRef.current ? String(titleRef.current.value || "").trim() : "";
315
+ var body = bodyRef.current ? String(bodyRef.current.value || "").trim() : "";
316
+ if (!body) { if (bodyRef.current) bodyRef.current.focus(); return; }
317
+ if (!title) title = body.split("\n")[0].slice(0, 36);
318
+ apiRef.current.saveNote({ title: title, content: body }, deskLang()).then(function () {
319
+ if (qRef.current) qRef.current.value = "";
320
+ return apiRef.current.listNotes();
321
+ }).then(function (d) {
322
+ patchNs({ phase: "ready", role: (d && d.role) || "member", notes: (d && d.notes) || [], composing: false, q: "", hits: [], message: "已记下 ✓", tone: "ok" });
323
+ }).catch(function (e) { patchNs({ message: portalErrorText(e), tone: "err" }); });
324
+ }
325
+
326
+ function toggleNote(name) {
327
+ if (ns.expanded === name) { patchNs({ expanded: "", content: "" }); return; }
328
+ patchNs({ expanded: name, content: "读取中…" });
329
+ apiRef.current.readNote(name).then(function (d) {
330
+ patchNs({ expanded: name, content: d && d.ok ? d.content : ((d && d.error) || "读取失败") });
331
+ }).catch(function (e) { patchNs({ expanded: name, content: portalErrorText(e) }); });
332
+ }
333
+
334
+ function deleteNote(name) {
335
+ if (window.confirm && !window.confirm("删除笔记:" + name + "?")) return;
336
+ apiRef.current.removeNote(name, deskLang()).then(function () {
337
+ patchNs({ expanded: "", content: "" });
338
+ loadNotes();
339
+ }).catch(function (e) { patchNs({ message: portalErrorText(e) }); });
340
+ }
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
+
354
+ var trigger = h("button", {
355
+ type: "button", className: "zlk-nav" + (open ? " on" : ""), onClick: toggle, title: "资料库", "data-zlk-open": "1",
356
+ }, h("span", { className: "ic" }, iconFolder(16)), wide ? h("span", { className: "lbl" }, "资料库") : null);
357
+ if (!open) return h("div", { "data-zlk-root": "1" }, trigger);
358
+
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
+
460
+ return h("div", { "data-zlk-root": "1" },
461
+ trigger,
462
+ h("section", { className: "zlk-fv", "data-zlk-panel": "1" },
463
+ h("header", { className: "zlk-hd" },
464
+ h("span", { className: "zlk-mark" }, iconFolder(22)),
465
+ h("div", { className: "zlk-hd-text" },
466
+ h("div", { className: "zlk-t1" }, "资料库"),
467
+ h("div", { className: "zlk-t2" }, "丢进来,全员就能看见")
468
+ ),
469
+ h("button", { type: "button", className: "zlk-back", "data-zlk-back": "1", onClick: close }, "← 返回对话")
470
+ ),
471
+ h("div", { className: "zlk-bd" }, bodyKids)
472
+ )
473
+ );
474
+ }
475
+
476
+ function apply(ctx) {
477
+ ctx.slots.inject("sidebar.footer.action", function () {
478
+ return ctx.slots.register(
479
+ {
480
+ name: "sidebar.footer.action",
481
+ id: "ziliaoku",
482
+ order: 91,
483
+ label: function () { return "资料库"; },
484
+ },
485
+ LibraryPanel
486
+ );
487
+ });
488
+ }
489
+
490
+ ensureCss();