dsh-quicknote 0.1.0 → 0.1.1
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/CHANGELOG.md +17 -0
- package/README.md +18 -12
- package/README.zh.md +16 -10
- package/icon.svg +2 -2
- package/lib/client.js +1010 -137
- package/lib/index.js +112 -36
- package/llms.txt +2 -0
- package/package.json +2 -2
package/lib/client.js
CHANGED
|
@@ -19,6 +19,23 @@ window.__ModuleLoader__.load({
|
|
|
19
19
|
function setClientContext(ctx) {
|
|
20
20
|
clientCtx = ctx;
|
|
21
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Read an optional client service. Cordis throws on `ctx.<service>` for a
|
|
24
|
+
* service the plugin did not declare in `inject`, while `ctx.get(name)`
|
|
25
|
+
* returns `undefined` instead — so resolve through `get` first and keep the
|
|
26
|
+
* property path only as a fallback for older shells.
|
|
27
|
+
*/
|
|
28
|
+
function service(name) {
|
|
29
|
+
try {
|
|
30
|
+
const resolved = clientCtx?.get?.(name);
|
|
31
|
+
if (resolved !== void 0) return resolved;
|
|
32
|
+
} catch {}
|
|
33
|
+
try {
|
|
34
|
+
return clientCtx?.[name];
|
|
35
|
+
} catch {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
22
39
|
function sessionIdNear(node) {
|
|
23
40
|
try {
|
|
24
41
|
const id = (typeof node?.closest === "function" ? node.closest("[data-conversation-session]") : null)?.getAttribute("data-conversation-session");
|
|
@@ -30,7 +47,7 @@ window.__ModuleLoader__.load({
|
|
|
30
47
|
function composerOf(sessionId) {
|
|
31
48
|
if (sessionId === void 0) return void 0;
|
|
32
49
|
try {
|
|
33
|
-
const input =
|
|
50
|
+
const input = service("conversation")?.input;
|
|
34
51
|
if (typeof input?.shell !== "function") return void 0;
|
|
35
52
|
const shell = input.shell(sessionId);
|
|
36
53
|
if (!shell?.state || !shell?.actions) return void 0;
|
|
@@ -46,19 +63,107 @@ window.__ModuleLoader__.load({
|
|
|
46
63
|
return "";
|
|
47
64
|
}
|
|
48
65
|
}
|
|
66
|
+
/** The composer the user is most likely writing in. */
|
|
67
|
+
function focusedSessionId() {
|
|
68
|
+
try {
|
|
69
|
+
const fromFocus = sessionIdNear(document.activeElement);
|
|
70
|
+
if (fromFocus !== void 0) return fromFocus;
|
|
71
|
+
} catch {}
|
|
72
|
+
try {
|
|
73
|
+
return sessionIdNear(document.querySelector("[data-conversation-session]"));
|
|
74
|
+
} catch {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* The Session the main column points at, even while a global panel covers it.
|
|
80
|
+
* The shell unmounts the Conversation for a panel, so the DOM carries no
|
|
81
|
+
* `data-conversation-session`; the workspace selection and the retained
|
|
82
|
+
* main-view reference still name it.
|
|
83
|
+
*/
|
|
84
|
+
function mainSessionId() {
|
|
85
|
+
try {
|
|
86
|
+
const selected = service("uiWorkspace")?.selection?.getSnapshot?.()?.sessionId;
|
|
87
|
+
if (typeof selected === "string" && selected !== "") return selected;
|
|
88
|
+
} catch {}
|
|
89
|
+
try {
|
|
90
|
+
const byId = service("sessions")?.list?.getSnapshot?.()?.byId ?? {};
|
|
91
|
+
for (const row of Object.values(byId)) if ((row?.retainedBy?.mainView ?? 0) > 0 && typeof row?.id === "string" && row.id !== "") return row.id;
|
|
92
|
+
} catch {}
|
|
93
|
+
}
|
|
94
|
+
/** The composer to act on: the focused one, else the Session the main column retained. */
|
|
95
|
+
function targetSessionId() {
|
|
96
|
+
return focusedSessionId() ?? mainSessionId();
|
|
97
|
+
}
|
|
98
|
+
/** Insert a note into the active draft, preserving anything already typed. */
|
|
99
|
+
function insertIntoComposer(text) {
|
|
100
|
+
const sessionId = targetSessionId();
|
|
101
|
+
const composer = composerOf(sessionId);
|
|
102
|
+
if (!composer || !sessionId) return false;
|
|
103
|
+
try {
|
|
104
|
+
const current = draftOf(sessionId);
|
|
105
|
+
const next = current.trim() === "" ? text : `${current.replace(/\s+$/, "")}\n\n${text}`;
|
|
106
|
+
composer.actions.setDraft(next);
|
|
107
|
+
return true;
|
|
108
|
+
} catch {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/** Provenance for a note captured from a Session. */
|
|
113
|
+
function originOf(sessionId) {
|
|
114
|
+
if (!sessionId) return {
|
|
115
|
+
sessionId: "",
|
|
116
|
+
title: ""
|
|
117
|
+
};
|
|
118
|
+
try {
|
|
119
|
+
const row = service("sessions")?.list?.getSnapshot?.()?.byId?.[sessionId];
|
|
120
|
+
return {
|
|
121
|
+
sessionId,
|
|
122
|
+
title: typeof row?.title === "string" ? row.title : ""
|
|
123
|
+
};
|
|
124
|
+
} catch {
|
|
125
|
+
return {
|
|
126
|
+
sessionId,
|
|
127
|
+
title: ""
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/** Show the notes page in the main column; false when the shell has no layout. */
|
|
132
|
+
function openPanel() {
|
|
133
|
+
try {
|
|
134
|
+
const layout = service("layout");
|
|
135
|
+
if (typeof layout?.selectPanel !== "function") return false;
|
|
136
|
+
layout.selectPanel(NAME);
|
|
137
|
+
return true;
|
|
138
|
+
} catch {
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/** Return the main column to the Conversation; false when the shell has no layout. */
|
|
143
|
+
function showConversation() {
|
|
144
|
+
try {
|
|
145
|
+
const layout = service("layout");
|
|
146
|
+
if (typeof layout?.selectPanel !== "function") return false;
|
|
147
|
+
layout.selectPanel(null);
|
|
148
|
+
return true;
|
|
149
|
+
} catch {
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/** Open the capture card for the active composer. */
|
|
154
|
+
function openCapture() {
|
|
155
|
+
const sessionId = focusedSessionId();
|
|
156
|
+
window.dispatchEvent(new CustomEvent("dsh-quicknote:open", { detail: {
|
|
157
|
+
sessionId,
|
|
158
|
+
text: draftOf(sessionId)
|
|
159
|
+
} }));
|
|
160
|
+
}
|
|
49
161
|
// src/client/ComposerButton.tsx
|
|
50
162
|
/** A labelled button in the composer toolbar. No icon-only destructive control. */
|
|
51
163
|
function ComposerButton({ t }) {
|
|
52
|
-
function open(event) {
|
|
53
|
-
const sessionId = sessionIdNear(event.currentTarget);
|
|
54
|
-
window.dispatchEvent(new CustomEvent("dsh-quicknote:open", { detail: {
|
|
55
|
-
sessionId,
|
|
56
|
-
text: draftOf(sessionId)
|
|
57
|
-
} }));
|
|
58
|
-
}
|
|
59
164
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
60
165
|
type: "button",
|
|
61
|
-
onClick:
|
|
166
|
+
onClick: openCapture,
|
|
62
167
|
"data-testid": "quicknote-composer",
|
|
63
168
|
style: {
|
|
64
169
|
display: "inline-flex",
|
|
@@ -66,52 +171,189 @@ window.__ModuleLoader__.load({
|
|
|
66
171
|
gap: 6,
|
|
67
172
|
padding: "5px 9px",
|
|
68
173
|
borderRadius: 8,
|
|
69
|
-
border: "1px solid var(--dsw-alias-
|
|
174
|
+
border: "1px solid var(--dsw-alias-border-l1, rgba(127,127,127,.24))",
|
|
70
175
|
background: "transparent",
|
|
71
|
-
color: "inherit",
|
|
176
|
+
color: "var(--dsw-alias-label-secondary, inherit)",
|
|
72
177
|
font: "inherit",
|
|
73
|
-
cursor: "pointer"
|
|
178
|
+
cursor: "pointer",
|
|
179
|
+
whiteSpace: "nowrap"
|
|
74
180
|
},
|
|
75
181
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
76
|
-
viewBox: "0 0
|
|
182
|
+
viewBox: "0 0 24 24",
|
|
77
183
|
width: "16",
|
|
78
184
|
height: "16",
|
|
79
185
|
fill: "none",
|
|
80
186
|
"aria-hidden": "true",
|
|
81
187
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
|
|
82
|
-
x: "
|
|
83
|
-
y: "
|
|
84
|
-
width: "
|
|
85
|
-
height: "
|
|
86
|
-
rx: "
|
|
87
|
-
stroke: "currentColor"
|
|
188
|
+
x: "4",
|
|
189
|
+
y: "3.5",
|
|
190
|
+
width: "16",
|
|
191
|
+
height: "17",
|
|
192
|
+
rx: "3",
|
|
193
|
+
stroke: "currentColor",
|
|
194
|
+
strokeWidth: "1.6"
|
|
88
195
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
89
|
-
d: "
|
|
196
|
+
d: "M8 8h8M8 12h8M8 16h5",
|
|
90
197
|
stroke: "currentColor",
|
|
198
|
+
strokeWidth: "1.6",
|
|
91
199
|
strokeLinecap: "round"
|
|
92
200
|
})]
|
|
93
201
|
}), t("toolbar.capture")]
|
|
94
202
|
});
|
|
95
203
|
}
|
|
204
|
+
// src/client/notes.ts
|
|
205
|
+
/**
|
|
206
|
+
* `#tag` must not continue a word, so `example.com#fragment` and `#` alone are not tags.
|
|
207
|
+
* Letters, marks, numbers, `_` and `-` are allowed, which covers `#工作` and `#release-1`.
|
|
208
|
+
*/
|
|
209
|
+
const TAG_PATTERN = /(?<![\p{L}\p{M}\p{N}_/.#&-])#([\p{L}\p{M}\p{N}_-]{1,32})/gu;
|
|
210
|
+
/** Tags in first-seen order, de-duplicated case-insensitively. */
|
|
211
|
+
function extractTags(text) {
|
|
212
|
+
const tags = [];
|
|
213
|
+
const seen = /* @__PURE__ */ new Set();
|
|
214
|
+
for (const match of text.matchAll(TAG_PATTERN)) {
|
|
215
|
+
const tag = match[1];
|
|
216
|
+
const key = tag.toLowerCase();
|
|
217
|
+
if (seen.has(key)) continue;
|
|
218
|
+
seen.add(key);
|
|
219
|
+
tags.push(tag);
|
|
220
|
+
}
|
|
221
|
+
return tags;
|
|
222
|
+
}
|
|
223
|
+
/** Every tag in the list, most-used first; ties sort alphabetically. */
|
|
224
|
+
function collectTags(notes) {
|
|
225
|
+
const counts = /* @__PURE__ */ new Map();
|
|
226
|
+
for (const note of notes) for (const tag of extractTags(note.text)) {
|
|
227
|
+
const key = tag.toLowerCase();
|
|
228
|
+
const current = counts.get(key);
|
|
229
|
+
if (current) current.count += 1;
|
|
230
|
+
else counts.set(key, {
|
|
231
|
+
tag,
|
|
232
|
+
count: 1
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
return [...counts.values()].sort((left, right) => right.count - left.count || left.tag.localeCompare(right.tag));
|
|
236
|
+
}
|
|
237
|
+
/** Apply the done filter, free-text search and active tag together. */
|
|
238
|
+
function noteMatches(note, options) {
|
|
239
|
+
if (options.filter === "open" && note.done) return false;
|
|
240
|
+
if (options.filter === "done" && !note.done) return false;
|
|
241
|
+
const needle = options.query.trim().toLowerCase();
|
|
242
|
+
if (needle !== "" && !note.text.toLowerCase().includes(needle)) return false;
|
|
243
|
+
if (options.tag !== null) {
|
|
244
|
+
const wanted = options.tag.toLowerCase();
|
|
245
|
+
if (!extractTags(note.text).some((tag) => tag.toLowerCase() === wanted)) return false;
|
|
246
|
+
}
|
|
247
|
+
return true;
|
|
248
|
+
}
|
|
249
|
+
function filterNotes(notes, options) {
|
|
250
|
+
return notes.filter((note) => noteMatches(note, options));
|
|
251
|
+
}
|
|
252
|
+
function formatTime(value) {
|
|
253
|
+
const date = new Date(value);
|
|
254
|
+
if (Number.isNaN(date.getTime())) return "";
|
|
255
|
+
const now = /* @__PURE__ */ new Date();
|
|
256
|
+
const clock = `${String(date.getHours()).padStart(2, "0")}:${String(date.getMinutes()).padStart(2, "0")}`;
|
|
257
|
+
if (date.toDateString() === now.toDateString()) return clock;
|
|
258
|
+
const monthDay = `${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
|
|
259
|
+
return date.getFullYear() === now.getFullYear() ? `${monthDay} ${clock}` : `${date.getFullYear()}-${monthDay}`;
|
|
260
|
+
}
|
|
261
|
+
/** Markdown export keeps the text literal, so tags already survive the round trip. */
|
|
262
|
+
function notesToMarkdown(notes, title) {
|
|
263
|
+
if (notes.length === 0) return "";
|
|
264
|
+
const lines = [`# ${title}`, ""];
|
|
265
|
+
for (const note of notes) {
|
|
266
|
+
lines.push(`- [${note.done ? "x" : " "}] ${note.text.replace(/\n/g, "\n ")}`);
|
|
267
|
+
lines.push(` <sub>${formatTime(note.createdAt)}</sub>`);
|
|
268
|
+
lines.push("");
|
|
269
|
+
}
|
|
270
|
+
return lines.join("\n");
|
|
271
|
+
}
|
|
272
|
+
// src/client/capture.ts
|
|
273
|
+
/**
|
|
274
|
+
* Unsaved capture text parked while the card sends the user to the notes page.
|
|
275
|
+
*
|
|
276
|
+
* @module dsh-quicknote/client/capture
|
|
277
|
+
*/
|
|
278
|
+
let stashed = null;
|
|
279
|
+
/** Remember the text the user typed before leaving the card for the notes page. */
|
|
280
|
+
function stashCaptureText(sessionId, text) {
|
|
281
|
+
stashed = {
|
|
282
|
+
sessionId,
|
|
283
|
+
text
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* The text to show when the card opens again: the stashed edit for the same
|
|
288
|
+
* Session, otherwise the composer draft. Reading consumes the stash.
|
|
289
|
+
*/
|
|
290
|
+
function takeCaptureText(sessionId, draft) {
|
|
291
|
+
const entry = stashed;
|
|
292
|
+
stashed = null;
|
|
293
|
+
return entry !== null && entry.sessionId === sessionId ? entry.text : draft;
|
|
294
|
+
}
|
|
295
|
+
// src/client/parked.ts
|
|
296
|
+
let current = null;
|
|
297
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
298
|
+
function emit() {
|
|
299
|
+
for (const listener of listeners) listener();
|
|
300
|
+
}
|
|
301
|
+
function setParked(next) {
|
|
302
|
+
current = next;
|
|
303
|
+
emit();
|
|
304
|
+
}
|
|
305
|
+
function parkedDraft() {
|
|
306
|
+
return current;
|
|
307
|
+
}
|
|
308
|
+
function useParkedDraft() {
|
|
309
|
+
return (0, react.useSyncExternalStore)((listener) => {
|
|
310
|
+
listeners.add(listener);
|
|
311
|
+
return () => listeners.delete(listener);
|
|
312
|
+
}, parkedDraft, parkedDraft);
|
|
313
|
+
}
|
|
96
314
|
// src/client/Overlay.tsx
|
|
97
315
|
/** Capture card in the shell overlay; saves a note and optionally clears the draft. */
|
|
98
316
|
function Overlay({ t }) {
|
|
99
317
|
const [open, setOpen] = (0, react.useState)(false);
|
|
100
318
|
const [text, setText] = (0, react.useState)("");
|
|
101
319
|
const [sessionId, setSessionId] = (0, react.useState)(void 0);
|
|
320
|
+
const [draftText, setDraftText] = (0, react.useState)("");
|
|
102
321
|
const [error, setError] = (0, react.useState)("");
|
|
103
322
|
const [saving, setSaving] = (0, react.useState)(false);
|
|
323
|
+
const [total, setTotal] = (0, react.useState)(null);
|
|
104
324
|
(0, react.useEffect)(() => {
|
|
105
325
|
const onOpen = (event) => {
|
|
106
326
|
const detail = event.detail;
|
|
107
|
-
|
|
327
|
+
const draft = detail?.text ?? "";
|
|
328
|
+
setText(takeCaptureText(detail?.sessionId, draft));
|
|
329
|
+
setDraftText(draft);
|
|
108
330
|
setSessionId(detail?.sessionId);
|
|
109
331
|
setError("");
|
|
332
|
+
setTotal(null);
|
|
110
333
|
setOpen(true);
|
|
334
|
+
refreshTotal();
|
|
111
335
|
};
|
|
112
336
|
window.addEventListener("dsh-quicknote:open", onOpen);
|
|
113
337
|
return () => window.removeEventListener("dsh-quicknote:open", onOpen);
|
|
114
338
|
}, []);
|
|
339
|
+
/** The count is decoration for the link; a failed read just hides it. */
|
|
340
|
+
async function refreshTotal() {
|
|
341
|
+
try {
|
|
342
|
+
const response = await fetch(`${API}/notes`);
|
|
343
|
+
if (!response.ok) return;
|
|
344
|
+
const data = await response.json();
|
|
345
|
+
if (Array.isArray(data.notes)) setTotal(data.notes.length);
|
|
346
|
+
} catch {}
|
|
347
|
+
}
|
|
348
|
+
/** Leave the card for the notes page, keeping what the user typed for the next open. */
|
|
349
|
+
function viewAll() {
|
|
350
|
+
if (!openPanel()) {
|
|
351
|
+
setError(t("overlay.openFailed"));
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
stashCaptureText(sessionId, text);
|
|
355
|
+
setOpen(false);
|
|
356
|
+
}
|
|
115
357
|
(0, react.useEffect)(() => {
|
|
116
358
|
if (!open) return;
|
|
117
359
|
const onKey = (event) => {
|
|
@@ -128,13 +370,24 @@ window.__ModuleLoader__.load({
|
|
|
128
370
|
const response = await fetch(`${API}/notes/create`, {
|
|
129
371
|
method: "POST",
|
|
130
372
|
headers: { "content-type": "application/json" },
|
|
131
|
-
body: JSON.stringify({
|
|
373
|
+
body: JSON.stringify({
|
|
374
|
+
text,
|
|
375
|
+
origin: originOf(sessionId)
|
|
376
|
+
})
|
|
132
377
|
});
|
|
133
378
|
const result = await response.json();
|
|
134
379
|
if (!response.ok || !result.ok) throw new Error(result.error ?? "save-failed");
|
|
135
|
-
if (clearDraft && sessionId !== void 0)
|
|
136
|
-
|
|
137
|
-
|
|
380
|
+
if (clearDraft && sessionId !== void 0 && result.note?.id) {
|
|
381
|
+
setParked({
|
|
382
|
+
noteId: result.note.id,
|
|
383
|
+
text,
|
|
384
|
+
draftText,
|
|
385
|
+
sessionId
|
|
386
|
+
});
|
|
387
|
+
try {
|
|
388
|
+
composerOf(sessionId)?.actions.setDraft("");
|
|
389
|
+
} catch {}
|
|
390
|
+
}
|
|
138
391
|
setOpen(false);
|
|
139
392
|
window.dispatchEvent(new Event("dsh-quicknote:changed"));
|
|
140
393
|
} catch (cause) {
|
|
@@ -163,16 +416,41 @@ window.__ModuleLoader__.load({
|
|
|
163
416
|
width: "min(560px, 100%)",
|
|
164
417
|
padding: 20,
|
|
165
418
|
borderRadius: 14,
|
|
166
|
-
background: "var(--dsw-alias-bg, #fff)",
|
|
167
|
-
boxShadow: "0 24px 70px rgba(0,0,0,.3)"
|
|
419
|
+
background: "var(--dsw-alias-bg-overlay, #fff)",
|
|
420
|
+
boxShadow: "var(--dsw-elevation-panel, 0 24px 70px rgba(0,0,0,.3))",
|
|
421
|
+
color: "var(--dsw-alias-label-primary, inherit)",
|
|
422
|
+
colorScheme: "light dark"
|
|
168
423
|
},
|
|
169
424
|
children: [
|
|
170
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
425
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
171
426
|
style: {
|
|
172
|
-
|
|
173
|
-
|
|
427
|
+
display: "flex",
|
|
428
|
+
alignItems: "baseline",
|
|
429
|
+
justifyContent: "space-between",
|
|
430
|
+
gap: 12
|
|
174
431
|
},
|
|
175
|
-
children:
|
|
432
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
433
|
+
style: {
|
|
434
|
+
margin: 0,
|
|
435
|
+
fontSize: 18
|
|
436
|
+
},
|
|
437
|
+
children: t("overlay.title")
|
|
438
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
439
|
+
type: "button",
|
|
440
|
+
"data-testid": "quicknote-overlay-view-all",
|
|
441
|
+
onClick: viewAll,
|
|
442
|
+
style: {
|
|
443
|
+
padding: 0,
|
|
444
|
+
border: 0,
|
|
445
|
+
background: "none",
|
|
446
|
+
color: "var(--dsw-alias-brand-primary, #4d6bfe)",
|
|
447
|
+
font: "inherit",
|
|
448
|
+
fontSize: 13,
|
|
449
|
+
whiteSpace: "nowrap",
|
|
450
|
+
cursor: "pointer"
|
|
451
|
+
},
|
|
452
|
+
children: total === null ? t("overlay.viewAll") : t("overlay.viewAllCount").replace("{count}", String(total))
|
|
453
|
+
})]
|
|
176
454
|
}),
|
|
177
455
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
|
|
178
456
|
"data-testid": "quicknote-overlay-input",
|
|
@@ -186,7 +464,9 @@ window.__ModuleLoader__.load({
|
|
|
186
464
|
marginTop: 12,
|
|
187
465
|
padding: 10,
|
|
188
466
|
borderRadius: 8,
|
|
189
|
-
border: "1px solid var(--dsw-alias-
|
|
467
|
+
border: "1px solid var(--dsw-alias-border-l1, rgba(127,127,127,.24))",
|
|
468
|
+
background: "var(--dsw-alias-bg-layer-1, transparent)",
|
|
469
|
+
color: "var(--dsw-alias-label-primary, inherit)",
|
|
190
470
|
font: "inherit",
|
|
191
471
|
resize: "vertical"
|
|
192
472
|
}
|
|
@@ -194,7 +474,7 @@ window.__ModuleLoader__.load({
|
|
|
194
474
|
error === "" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
195
475
|
role: "alert",
|
|
196
476
|
style: {
|
|
197
|
-
color: "var(--dsw-alias-
|
|
477
|
+
color: "var(--dsw-alias-state-error-primary, #c0392b)",
|
|
198
478
|
margin: "8px 0 0"
|
|
199
479
|
},
|
|
200
480
|
children: error
|
|
@@ -213,7 +493,12 @@ window.__ModuleLoader__.load({
|
|
|
213
493
|
onClick: () => setOpen(false),
|
|
214
494
|
style: {
|
|
215
495
|
padding: "7px 12px",
|
|
216
|
-
borderRadius: 8
|
|
496
|
+
borderRadius: 8,
|
|
497
|
+
border: "1px solid var(--dsw-alias-border-l1, rgba(127,127,127,.24))",
|
|
498
|
+
background: "var(--dsw-alias-bg-layer-1, transparent)",
|
|
499
|
+
color: "var(--dsw-alias-label-primary, inherit)",
|
|
500
|
+
font: "inherit",
|
|
501
|
+
cursor: "pointer"
|
|
217
502
|
},
|
|
218
503
|
children: t("overlay.cancel")
|
|
219
504
|
}),
|
|
@@ -223,7 +508,12 @@ window.__ModuleLoader__.load({
|
|
|
223
508
|
onClick: () => void save(false),
|
|
224
509
|
style: {
|
|
225
510
|
padding: "7px 12px",
|
|
226
|
-
borderRadius: 8
|
|
511
|
+
borderRadius: 8,
|
|
512
|
+
border: "1px solid var(--dsw-alias-border-l1, rgba(127,127,127,.24))",
|
|
513
|
+
background: "var(--dsw-alias-bg-layer-1, transparent)",
|
|
514
|
+
color: "var(--dsw-alias-label-primary, inherit)",
|
|
515
|
+
font: "inherit",
|
|
516
|
+
cursor: "pointer"
|
|
227
517
|
},
|
|
228
518
|
children: t("overlay.save")
|
|
229
519
|
}),
|
|
@@ -233,7 +523,12 @@ window.__ModuleLoader__.load({
|
|
|
233
523
|
onClick: () => void save(true),
|
|
234
524
|
style: {
|
|
235
525
|
padding: "7px 12px",
|
|
236
|
-
borderRadius: 8
|
|
526
|
+
borderRadius: 8,
|
|
527
|
+
border: "1px solid var(--dsw-alias-border-l1, rgba(127,127,127,.24))",
|
|
528
|
+
background: "var(--dsw-alias-bg-layer-1, transparent)",
|
|
529
|
+
color: "var(--dsw-alias-label-primary, inherit)",
|
|
530
|
+
font: "inherit",
|
|
531
|
+
cursor: "pointer"
|
|
237
532
|
},
|
|
238
533
|
children: t("overlay.saveClear")
|
|
239
534
|
})
|
|
@@ -243,6 +538,281 @@ window.__ModuleLoader__.load({
|
|
|
243
538
|
})
|
|
244
539
|
});
|
|
245
540
|
}
|
|
541
|
+
// src/client/ParkedBar.tsx
|
|
542
|
+
/** Receipt above the composer after a draft has been moved into Quick Note. */
|
|
543
|
+
function ParkedBar({ t, captureSessionId }) {
|
|
544
|
+
const parked = useParkedDraft();
|
|
545
|
+
const [busy, setBusy] = (0, react.useState)(false);
|
|
546
|
+
if (!parked) return null;
|
|
547
|
+
if (captureSessionId !== void 0 && parked.sessionId !== captureSessionId) return null;
|
|
548
|
+
const preview = parked.text.length > 72 ? `${parked.text.slice(0, 72)}…` : parked.text;
|
|
549
|
+
async function undo() {
|
|
550
|
+
if (!parked || busy) return;
|
|
551
|
+
setBusy(true);
|
|
552
|
+
try {
|
|
553
|
+
const response = await fetch(`${API}/notes/delete`, {
|
|
554
|
+
method: "POST",
|
|
555
|
+
headers: { "content-type": "application/json" },
|
|
556
|
+
body: JSON.stringify({ id: parked.noteId })
|
|
557
|
+
});
|
|
558
|
+
if (!response.ok) throw new Error(`undo-failed-${response.status}`);
|
|
559
|
+
const composer = composerOf(parked.sessionId);
|
|
560
|
+
if (composer) {
|
|
561
|
+
const current = draftOf(parked.sessionId);
|
|
562
|
+
const next = current.trim() === "" ? parked.draftText : `${current.replace(/\s+$/, "")}\n\n${parked.draftText}`;
|
|
563
|
+
composer.actions.setDraft(next);
|
|
564
|
+
}
|
|
565
|
+
setParked(null);
|
|
566
|
+
window.dispatchEvent(new Event("dsh-quicknote:changed"));
|
|
567
|
+
} catch {} finally {
|
|
568
|
+
setBusy(false);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
572
|
+
role: "status",
|
|
573
|
+
style: {
|
|
574
|
+
display: "flex",
|
|
575
|
+
alignItems: "center",
|
|
576
|
+
gap: 8,
|
|
577
|
+
margin: "0 0 6px",
|
|
578
|
+
padding: "6px 10px",
|
|
579
|
+
borderRadius: 8,
|
|
580
|
+
borderLeft: "3px solid var(--dsw-alias-brand-primary, #4d6bfe)",
|
|
581
|
+
background: "var(--dsw-alias-bg-layer-2, rgba(127,127,127,.1))",
|
|
582
|
+
color: "var(--dsw-alias-label-secondary, inherit)",
|
|
583
|
+
fontSize: 12,
|
|
584
|
+
lineHeight: "18px"
|
|
585
|
+
},
|
|
586
|
+
children: [
|
|
587
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
588
|
+
style: {
|
|
589
|
+
flex: 1,
|
|
590
|
+
minWidth: 0,
|
|
591
|
+
overflow: "hidden",
|
|
592
|
+
textOverflow: "ellipsis",
|
|
593
|
+
whiteSpace: "nowrap"
|
|
594
|
+
},
|
|
595
|
+
children: [
|
|
596
|
+
t("parked.saved"),
|
|
597
|
+
" · ",
|
|
598
|
+
preview
|
|
599
|
+
]
|
|
600
|
+
}),
|
|
601
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
602
|
+
type: "button",
|
|
603
|
+
onClick: openPanel,
|
|
604
|
+
style: {
|
|
605
|
+
border: 0,
|
|
606
|
+
background: "none",
|
|
607
|
+
color: "var(--dsw-alias-brand-primary, #4d6bfe)",
|
|
608
|
+
font: "inherit",
|
|
609
|
+
cursor: "pointer",
|
|
610
|
+
whiteSpace: "nowrap"
|
|
611
|
+
},
|
|
612
|
+
children: t("parked.open")
|
|
613
|
+
}),
|
|
614
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
615
|
+
type: "button",
|
|
616
|
+
disabled: busy,
|
|
617
|
+
onClick: () => void undo(),
|
|
618
|
+
style: {
|
|
619
|
+
border: 0,
|
|
620
|
+
background: "none",
|
|
621
|
+
color: "var(--dsw-alias-label-secondary, inherit)",
|
|
622
|
+
font: "inherit",
|
|
623
|
+
cursor: busy ? "default" : "pointer",
|
|
624
|
+
whiteSpace: "nowrap",
|
|
625
|
+
opacity: busy ? .5 : 1
|
|
626
|
+
},
|
|
627
|
+
children: t("parked.undo")
|
|
628
|
+
}),
|
|
629
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
630
|
+
type: "button",
|
|
631
|
+
onClick: () => setParked(null),
|
|
632
|
+
"aria-label": t("parked.dismiss"),
|
|
633
|
+
style: {
|
|
634
|
+
border: 0,
|
|
635
|
+
background: "none",
|
|
636
|
+
color: "var(--dsw-alias-label-tertiary, inherit)",
|
|
637
|
+
font: "inherit",
|
|
638
|
+
fontSize: 14,
|
|
639
|
+
cursor: "pointer",
|
|
640
|
+
padding: "0 2px"
|
|
641
|
+
},
|
|
642
|
+
children: "×"
|
|
643
|
+
})
|
|
644
|
+
]
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
// src/client/styles.ts
|
|
648
|
+
/**
|
|
649
|
+
* Scoped page styles. Colours stay on DSH theme tokens so light and dark mode
|
|
650
|
+
* come from the shell; the media queries handle narrow main columns.
|
|
651
|
+
*
|
|
652
|
+
* @module dsh-quicknote/client/styles
|
|
653
|
+
*/
|
|
654
|
+
const PAGE_CSS = `
|
|
655
|
+
.qn-page {
|
|
656
|
+
box-sizing: border-box;
|
|
657
|
+
min-height: 100%;
|
|
658
|
+
max-width: 760px;
|
|
659
|
+
padding: 24px 28px 48px;
|
|
660
|
+
background: var(--dsw-alias-bg-base, transparent);
|
|
661
|
+
color: var(--dsw-alias-label-primary, inherit);
|
|
662
|
+
color-scheme: light dark;
|
|
663
|
+
}
|
|
664
|
+
.qn-back {
|
|
665
|
+
display: block;
|
|
666
|
+
max-width: 100%;
|
|
667
|
+
margin: 0 0 10px;
|
|
668
|
+
padding: 0;
|
|
669
|
+
border: 0;
|
|
670
|
+
background: none;
|
|
671
|
+
color: var(--dsw-alias-brand-primary, #4d6bfe);
|
|
672
|
+
font: inherit;
|
|
673
|
+
font-size: 13px;
|
|
674
|
+
text-align: left;
|
|
675
|
+
white-space: nowrap;
|
|
676
|
+
overflow: hidden;
|
|
677
|
+
text-overflow: ellipsis;
|
|
678
|
+
cursor: pointer;
|
|
679
|
+
}
|
|
680
|
+
.qn-back:hover { text-decoration: underline; }
|
|
681
|
+
.qn-back:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary, #4d6bfe); outline-offset: 2px; }
|
|
682
|
+
.qn-title { margin: 0; font-size: 22px; line-height: 1.3; }
|
|
683
|
+
.qn-lede { margin: 6px 0 18px; color: var(--dsw-alias-label-secondary, inherit); }
|
|
684
|
+
.qn-compose { display: flex; gap: 8px; align-items: flex-start; }
|
|
685
|
+
.qn-textarea {
|
|
686
|
+
flex: 1;
|
|
687
|
+
min-width: 0;
|
|
688
|
+
resize: vertical;
|
|
689
|
+
padding: 10px;
|
|
690
|
+
border-radius: 8px;
|
|
691
|
+
border: 1px solid var(--dsw-alias-border-l1, rgba(127, 127, 127, 0.24));
|
|
692
|
+
background: var(--dsw-alias-bg-layer-1, transparent);
|
|
693
|
+
color: var(--dsw-alias-label-primary, inherit);
|
|
694
|
+
color-scheme: light dark;
|
|
695
|
+
font: inherit;
|
|
696
|
+
}
|
|
697
|
+
.qn-button {
|
|
698
|
+
padding: 5px 9px;
|
|
699
|
+
border-radius: 7px;
|
|
700
|
+
border: 1px solid var(--dsw-alias-border-l1, rgba(127, 127, 127, 0.24));
|
|
701
|
+
background: var(--dsw-alias-bg-layer-1, transparent);
|
|
702
|
+
color: var(--dsw-alias-label-primary, inherit);
|
|
703
|
+
font: inherit;
|
|
704
|
+
line-height: 1.35;
|
|
705
|
+
white-space: nowrap;
|
|
706
|
+
cursor: pointer;
|
|
707
|
+
}
|
|
708
|
+
.qn-button:hover:not(:disabled) { background: var(--dsw-alias-bg-layer-2, rgba(127, 127, 127, 0.12)); }
|
|
709
|
+
.qn-button:disabled { cursor: default; opacity: 0.5; }
|
|
710
|
+
.qn-button:focus-visible,
|
|
711
|
+
.qn-textarea:focus-visible,
|
|
712
|
+
.qn-search:focus-visible {
|
|
713
|
+
outline: 2px solid var(--dsw-alias-brand-primary, #4d6bfe);
|
|
714
|
+
outline-offset: 1px;
|
|
715
|
+
}
|
|
716
|
+
.qn-button[aria-pressed="true"] {
|
|
717
|
+
border-color: var(--dsw-alias-brand-primary, currentColor);
|
|
718
|
+
background: var(--dsw-alias-bg-layer-2, rgba(127, 127, 127, 0.12));
|
|
719
|
+
color: var(--dsw-alias-brand-primary, currentColor);
|
|
720
|
+
}
|
|
721
|
+
.qn-primary { padding: 9px 14px; border-radius: 8px; }
|
|
722
|
+
.qn-toolbar {
|
|
723
|
+
display: grid;
|
|
724
|
+
grid-template-columns: auto minmax(160px, 1fr) auto;
|
|
725
|
+
gap: 8px;
|
|
726
|
+
align-items: center;
|
|
727
|
+
margin-top: 14px;
|
|
728
|
+
}
|
|
729
|
+
.qn-filters { display: flex; gap: 6px; }
|
|
730
|
+
.qn-search {
|
|
731
|
+
min-width: 0;
|
|
732
|
+
padding: 5px 9px;
|
|
733
|
+
border-radius: 7px;
|
|
734
|
+
border: 1px solid var(--dsw-alias-border-l1, rgba(127, 127, 127, 0.24));
|
|
735
|
+
background: var(--dsw-alias-bg-layer-1, transparent);
|
|
736
|
+
color: var(--dsw-alias-label-primary, inherit);
|
|
737
|
+
font: inherit;
|
|
738
|
+
}
|
|
739
|
+
.qn-tags { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; margin-top: 10px; }
|
|
740
|
+
.qn-tags-label { font-size: 12px; color: var(--dsw-alias-label-tertiary, inherit); }
|
|
741
|
+
.qn-tag {
|
|
742
|
+
padding: 2px 9px;
|
|
743
|
+
border-radius: 999px;
|
|
744
|
+
font-size: 12px;
|
|
745
|
+
line-height: 1.5;
|
|
746
|
+
}
|
|
747
|
+
.qn-note-tags { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 6px; }
|
|
748
|
+
.qn-note-tags .qn-tag { padding: 1px 7px; font-size: 11px; }
|
|
749
|
+
.qn-list { list-style: none; padding: 0; margin: 20px 0 0; display: grid; gap: 8px; }
|
|
750
|
+
.qn-note {
|
|
751
|
+
display: grid;
|
|
752
|
+
grid-template-columns: minmax(0, 1fr) auto;
|
|
753
|
+
gap: 12px;
|
|
754
|
+
align-items: start;
|
|
755
|
+
padding: 12px;
|
|
756
|
+
border: 1px solid var(--dsw-alias-border-l1, rgba(127, 127, 127, 0.24));
|
|
757
|
+
border-radius: 10px;
|
|
758
|
+
background: var(--dsw-alias-bg-layer-1, transparent);
|
|
759
|
+
}
|
|
760
|
+
.qn-note-body { min-width: 0; }
|
|
761
|
+
.qn-note-text {
|
|
762
|
+
margin: 0;
|
|
763
|
+
white-space: pre-wrap;
|
|
764
|
+
overflow-wrap: anywhere;
|
|
765
|
+
}
|
|
766
|
+
.qn-note-text-done { text-decoration: line-through; opacity: 0.6; }
|
|
767
|
+
.qn-note-meta { margin-top: 4px; font-size: 11px; color: var(--dsw-alias-label-tertiary, inherit); }
|
|
768
|
+
.qn-note-actions { display: flex; flex-wrap: wrap; gap: 4px; justify-content: flex-end; }
|
|
769
|
+
.qn-alert { color: var(--dsw-alias-state-error-primary, #c0392b); }
|
|
770
|
+
.qn-status { margin: 8px 0 0; color: var(--dsw-alias-label-secondary, inherit); }
|
|
771
|
+
.qn-empty { margin-top: 24px; color: var(--dsw-alias-label-secondary, inherit); }
|
|
772
|
+
.qn-toast {
|
|
773
|
+
position: fixed;
|
|
774
|
+
z-index: 60;
|
|
775
|
+
left: 50%;
|
|
776
|
+
bottom: 24px;
|
|
777
|
+
transform: translateX(-50%);
|
|
778
|
+
display: flex;
|
|
779
|
+
align-items: center;
|
|
780
|
+
gap: 12px;
|
|
781
|
+
max-width: min(560px, calc(100vw - 32px));
|
|
782
|
+
padding: 10px 12px;
|
|
783
|
+
border: 1px solid var(--dsw-alias-border-l1, rgba(127, 127, 127, 0.24));
|
|
784
|
+
border-radius: 10px;
|
|
785
|
+
background: var(--dsw-alias-bg-overlay, #fff);
|
|
786
|
+
box-shadow: var(--dsw-elevation-panel, 0 18px 50px rgba(0, 0, 0, 0.24));
|
|
787
|
+
color: var(--dsw-alias-label-primary, inherit);
|
|
788
|
+
color-scheme: light dark;
|
|
789
|
+
}
|
|
790
|
+
.qn-toast-text {
|
|
791
|
+
flex: 1;
|
|
792
|
+
min-width: 0;
|
|
793
|
+
overflow: hidden;
|
|
794
|
+
text-overflow: ellipsis;
|
|
795
|
+
white-space: nowrap;
|
|
796
|
+
font-size: 13px;
|
|
797
|
+
}
|
|
798
|
+
.qn-toast-actions { display: flex; flex: none; gap: 4px; }
|
|
799
|
+
@media (max-width: 720px) {
|
|
800
|
+
.qn-page { padding: 16px 14px 40px; }
|
|
801
|
+
.qn-compose { flex-direction: column; align-items: stretch; }
|
|
802
|
+
.qn-compose .qn-primary { width: 100%; }
|
|
803
|
+
.qn-toolbar { grid-template-columns: minmax(0, 1fr) auto; }
|
|
804
|
+
.qn-filters { grid-column: 1 / -1; overflow-x: auto; padding-bottom: 2px; }
|
|
805
|
+
.qn-note { grid-template-columns: minmax(0, 1fr); gap: 8px; }
|
|
806
|
+
.qn-note-actions { justify-content: flex-start; }
|
|
807
|
+
.qn-toast { left: 12px; right: 12px; bottom: 12px; transform: none; max-width: none; }
|
|
808
|
+
}
|
|
809
|
+
@media (max-width: 420px) {
|
|
810
|
+
.qn-tags { flex-wrap: nowrap; overflow-x: auto; padding-bottom: 2px; }
|
|
811
|
+
.qn-tags-label { flex: none; }
|
|
812
|
+
.qn-tag { flex: none; }
|
|
813
|
+
.qn-note-actions { gap: 6px; }
|
|
814
|
+
}
|
|
815
|
+
`;
|
|
246
816
|
// src/client/index.tsx
|
|
247
817
|
/**
|
|
248
818
|
* Browser half of the Quick Note example. React is provided by DSH at runtime.
|
|
@@ -260,30 +830,41 @@ window.__ModuleLoader__.load({
|
|
|
260
830
|
}
|
|
261
831
|
function Icon({ size = 18 }) {
|
|
262
832
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
263
|
-
viewBox: "0 0
|
|
833
|
+
viewBox: "0 0 24 24",
|
|
264
834
|
width: size,
|
|
265
835
|
height: size,
|
|
266
836
|
fill: "none",
|
|
267
837
|
"aria-hidden": "true",
|
|
268
838
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
|
|
269
|
-
x: "
|
|
270
|
-
y: "
|
|
271
|
-
width: "
|
|
272
|
-
height: "
|
|
273
|
-
rx: "
|
|
274
|
-
stroke: "currentColor"
|
|
839
|
+
x: "4",
|
|
840
|
+
y: "3.5",
|
|
841
|
+
width: "16",
|
|
842
|
+
height: "17",
|
|
843
|
+
rx: "3",
|
|
844
|
+
stroke: "currentColor",
|
|
845
|
+
strokeWidth: "1.6"
|
|
275
846
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
276
|
-
d: "
|
|
847
|
+
d: "M8 8h8M8 12h8M8 16h5",
|
|
277
848
|
stroke: "currentColor",
|
|
849
|
+
strokeWidth: "1.6",
|
|
278
850
|
strokeLinecap: "round"
|
|
279
851
|
})]
|
|
280
852
|
});
|
|
281
853
|
}
|
|
854
|
+
const UNDO_WINDOW = 8e3;
|
|
855
|
+
function previewOf(text) {
|
|
856
|
+
const flat = text.replace(/\s+/g, " ").trim();
|
|
857
|
+
return flat.length > 80 ? `${flat.slice(0, 80)}…` : flat;
|
|
858
|
+
}
|
|
282
859
|
function Page({ t }) {
|
|
283
860
|
const [notes, setNotes] = (0, react.useState)([]);
|
|
284
861
|
const [text, setText] = (0, react.useState)("");
|
|
285
862
|
const [error, setError] = (0, react.useState)("");
|
|
286
|
-
const [
|
|
863
|
+
const [notice, setNotice] = (0, react.useState)("");
|
|
864
|
+
const [query, setQuery] = (0, react.useState)("");
|
|
865
|
+
const [filter, setFilter] = (0, react.useState)("all");
|
|
866
|
+
const [tag, setTag] = (0, react.useState)(null);
|
|
867
|
+
const [undo, setUndo] = (0, react.useState)([]);
|
|
287
868
|
async function load() {
|
|
288
869
|
try {
|
|
289
870
|
const data = await request("/notes");
|
|
@@ -299,8 +880,13 @@ window.__ModuleLoader__.load({
|
|
|
299
880
|
return () => window.removeEventListener("dsh-quicknote:changed", changed);
|
|
300
881
|
}, []);
|
|
301
882
|
(0, react.useEffect)(() => {
|
|
302
|
-
if (
|
|
303
|
-
const timer = window.setTimeout(() =>
|
|
883
|
+
if (notice === "") return;
|
|
884
|
+
const timer = window.setTimeout(() => setNotice(""), 3e3);
|
|
885
|
+
return () => window.clearTimeout(timer);
|
|
886
|
+
}, [notice]);
|
|
887
|
+
(0, react.useEffect)(() => {
|
|
888
|
+
if (undo.length === 0) return;
|
|
889
|
+
const timer = window.setTimeout(() => setUndo([]), UNDO_WINDOW);
|
|
304
890
|
return () => window.clearTimeout(timer);
|
|
305
891
|
}, [undo]);
|
|
306
892
|
async function add(event) {
|
|
@@ -309,7 +895,10 @@ window.__ModuleLoader__.load({
|
|
|
309
895
|
try {
|
|
310
896
|
await request("/notes/create", {
|
|
311
897
|
method: "POST",
|
|
312
|
-
body: JSON.stringify({
|
|
898
|
+
body: JSON.stringify({
|
|
899
|
+
text,
|
|
900
|
+
origin: originOf(focusedSessionId())
|
|
901
|
+
})
|
|
313
902
|
});
|
|
314
903
|
setText("");
|
|
315
904
|
setError("");
|
|
@@ -334,128 +923,295 @@ window.__ModuleLoader__.load({
|
|
|
334
923
|
}
|
|
335
924
|
async function remove(note) {
|
|
336
925
|
try {
|
|
337
|
-
await request("/notes/delete", {
|
|
926
|
+
const removed = (await request("/notes/delete", {
|
|
338
927
|
method: "POST",
|
|
339
928
|
body: JSON.stringify({ id: note.id })
|
|
340
|
-
});
|
|
929
|
+
})).note;
|
|
930
|
+
if (removed) setUndo((current) => [...current, removed]);
|
|
931
|
+
setError("");
|
|
341
932
|
await load();
|
|
342
933
|
} catch (cause) {
|
|
343
934
|
setError(String(cause.message));
|
|
344
935
|
}
|
|
345
936
|
}
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
937
|
+
async function undoDelete() {
|
|
938
|
+
if (undo.length === 0) return;
|
|
939
|
+
try {
|
|
940
|
+
await Promise.all(undo.map((note) => request("/notes/restore", {
|
|
941
|
+
method: "POST",
|
|
942
|
+
body: JSON.stringify({ note })
|
|
943
|
+
})));
|
|
944
|
+
setNotice(t(undo.length > 1 ? "panel.deletesUndone" : "panel.deleteUndone"));
|
|
945
|
+
setUndo([]);
|
|
946
|
+
setError("");
|
|
947
|
+
await load();
|
|
948
|
+
} catch (cause) {
|
|
949
|
+
setError(String(cause.message));
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
async function copy(note) {
|
|
953
|
+
try {
|
|
954
|
+
await navigator.clipboard.writeText(note.text);
|
|
955
|
+
setNotice(t("panel.copied"));
|
|
956
|
+
} catch {
|
|
957
|
+
setError(t("panel.copyFailed"));
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
function insert(note) {
|
|
961
|
+
setNotice(insertIntoComposer(note.text) ? t("panel.inserted") : t("panel.insertUnavailable"));
|
|
962
|
+
}
|
|
963
|
+
const tags = (0, react.useMemo)(() => collectTags(notes), [notes]);
|
|
964
|
+
(0, react.useEffect)(() => {
|
|
965
|
+
if (tag === null) return;
|
|
966
|
+
const wanted = tag.toLowerCase();
|
|
967
|
+
if (!tags.some((entry) => entry.tag.toLowerCase() === wanted)) setTag(null);
|
|
968
|
+
}, [tag, tags]);
|
|
969
|
+
const mainId = mainSessionId();
|
|
970
|
+
const mainTitle = mainId === void 0 ? "" : originOf(mainId).title;
|
|
971
|
+
const backHint = mainTitle === "" ? t("panel.back") : t("panel.backTo").replace("{title}", mainTitle);
|
|
972
|
+
const needle = query.trim().toLowerCase();
|
|
973
|
+
const counts = {
|
|
974
|
+
all: notes.length,
|
|
975
|
+
open: notes.filter((note) => !note.done).length,
|
|
976
|
+
done: notes.filter((note) => note.done).length
|
|
977
|
+
};
|
|
978
|
+
const visible = filterNotes(notes, {
|
|
979
|
+
filter,
|
|
980
|
+
query: needle,
|
|
981
|
+
tag
|
|
982
|
+
});
|
|
983
|
+
function activateTag(next) {
|
|
984
|
+
setTag((current) => current?.toLowerCase() === next.toLowerCase() ? null : next);
|
|
985
|
+
}
|
|
986
|
+
async function copyAll() {
|
|
987
|
+
try {
|
|
988
|
+
await navigator.clipboard.writeText(notesToMarkdown(visible, t("panel.title")));
|
|
989
|
+
setNotice(t("panel.copyAllDone"));
|
|
990
|
+
} catch {
|
|
991
|
+
setError(t("panel.copyFailed"));
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", {
|
|
995
|
+
"data-dsh-quicknote": "",
|
|
996
|
+
children: PAGE_CSS
|
|
997
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
998
|
+
className: "qn-page",
|
|
352
999
|
children: [
|
|
353
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
1000
|
+
mainId === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1001
|
+
type: "button",
|
|
1002
|
+
className: "qn-back",
|
|
1003
|
+
"data-testid": "note-back",
|
|
1004
|
+
title: backHint,
|
|
1005
|
+
onClick: () => {
|
|
1006
|
+
if (!showConversation()) setError(t("panel.backFailed"));
|
|
357
1007
|
},
|
|
1008
|
+
children: ["← ", backHint]
|
|
1009
|
+
}),
|
|
1010
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h1", {
|
|
1011
|
+
className: "qn-title",
|
|
358
1012
|
children: t("panel.title")
|
|
359
1013
|
}),
|
|
360
1014
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
361
|
-
|
|
362
|
-
margin: "6px 0 18px",
|
|
363
|
-
color: "var(--dsw-alias-label-secondary)"
|
|
364
|
-
},
|
|
1015
|
+
className: "qn-lede",
|
|
365
1016
|
children: t("panel.body")
|
|
366
1017
|
}),
|
|
367
1018
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
|
|
1019
|
+
className: "qn-compose",
|
|
368
1020
|
onSubmit: add,
|
|
369
|
-
style: {
|
|
370
|
-
display: "flex",
|
|
371
|
-
gap: 8,
|
|
372
|
-
alignItems: "flex-start"
|
|
373
|
-
},
|
|
374
1021
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
|
|
375
1022
|
"data-testid": "note-input",
|
|
1023
|
+
className: "qn-textarea",
|
|
376
1024
|
value: text,
|
|
377
1025
|
onChange: (event) => setText(event.target.value),
|
|
378
1026
|
placeholder: t("panel.placeholder"),
|
|
379
|
-
rows: 3
|
|
380
|
-
style: {
|
|
381
|
-
flex: 1,
|
|
382
|
-
resize: "vertical",
|
|
383
|
-
padding: 10,
|
|
384
|
-
borderRadius: 8,
|
|
385
|
-
border: "1px solid var(--dsw-alias-line, #d9dce1)"
|
|
386
|
-
}
|
|
1027
|
+
rows: 3
|
|
387
1028
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
388
1029
|
"data-testid": "note-add",
|
|
1030
|
+
className: "qn-button qn-primary",
|
|
389
1031
|
type: "submit",
|
|
390
1032
|
disabled: text.trim() === "",
|
|
391
|
-
style: {
|
|
392
|
-
padding: "9px 14px",
|
|
393
|
-
borderRadius: 8
|
|
394
|
-
},
|
|
395
1033
|
children: t("panel.add")
|
|
396
1034
|
})]
|
|
397
1035
|
}),
|
|
1036
|
+
notes.length === 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1037
|
+
className: "qn-toolbar",
|
|
1038
|
+
children: [
|
|
1039
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1040
|
+
className: "qn-filters",
|
|
1041
|
+
role: "group",
|
|
1042
|
+
"aria-label": t("panel.filters"),
|
|
1043
|
+
children: [
|
|
1044
|
+
["all", "panel.filterAll"],
|
|
1045
|
+
["open", "panel.filterOpen"],
|
|
1046
|
+
["done", "panel.filterDone"]
|
|
1047
|
+
].map(([id, label]) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1048
|
+
type: "button",
|
|
1049
|
+
className: "qn-button",
|
|
1050
|
+
"aria-pressed": filter === id,
|
|
1051
|
+
onClick: () => setFilter(id),
|
|
1052
|
+
children: [
|
|
1053
|
+
t(label),
|
|
1054
|
+
" ",
|
|
1055
|
+
counts[id]
|
|
1056
|
+
]
|
|
1057
|
+
}, id))
|
|
1058
|
+
}),
|
|
1059
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1060
|
+
type: "search",
|
|
1061
|
+
className: "qn-search",
|
|
1062
|
+
value: query,
|
|
1063
|
+
onChange: (event) => setQuery(event.target.value),
|
|
1064
|
+
placeholder: t("panel.search"),
|
|
1065
|
+
"aria-label": t("panel.search")
|
|
1066
|
+
}),
|
|
1067
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1068
|
+
type: "button",
|
|
1069
|
+
className: "qn-button",
|
|
1070
|
+
onClick: () => void copyAll(),
|
|
1071
|
+
disabled: visible.length === 0,
|
|
1072
|
+
children: t("panel.copyAll")
|
|
1073
|
+
})
|
|
1074
|
+
]
|
|
1075
|
+
}), tags.length === 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1076
|
+
className: "qn-tags",
|
|
1077
|
+
role: "group",
|
|
1078
|
+
"aria-label": t("panel.tags"),
|
|
1079
|
+
children: [
|
|
1080
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1081
|
+
className: "qn-tags-label",
|
|
1082
|
+
children: t("panel.tags")
|
|
1083
|
+
}),
|
|
1084
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1085
|
+
type: "button",
|
|
1086
|
+
className: "qn-button qn-tag",
|
|
1087
|
+
"aria-pressed": tag === null,
|
|
1088
|
+
onClick: () => setTag(null),
|
|
1089
|
+
children: t("panel.allTags")
|
|
1090
|
+
}),
|
|
1091
|
+
tags.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1092
|
+
type: "button",
|
|
1093
|
+
className: "qn-button qn-tag",
|
|
1094
|
+
"aria-pressed": tag?.toLowerCase() === entry.tag.toLowerCase(),
|
|
1095
|
+
onClick: () => activateTag(entry.tag),
|
|
1096
|
+
children: [
|
|
1097
|
+
"#",
|
|
1098
|
+
entry.tag,
|
|
1099
|
+
" ",
|
|
1100
|
+
entry.count
|
|
1101
|
+
]
|
|
1102
|
+
}, entry.tag.toLowerCase()))
|
|
1103
|
+
]
|
|
1104
|
+
})] }),
|
|
398
1105
|
error === "" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
399
1106
|
role: "alert",
|
|
400
|
-
|
|
1107
|
+
className: "qn-alert",
|
|
401
1108
|
children: error
|
|
402
1109
|
}),
|
|
1110
|
+
notice === "" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1111
|
+
role: "status",
|
|
1112
|
+
className: "qn-status",
|
|
1113
|
+
children: notice
|
|
1114
|
+
}),
|
|
403
1115
|
notes.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
404
|
-
|
|
405
|
-
marginTop: 24,
|
|
406
|
-
color: "var(--dsw-alias-label-secondary)"
|
|
407
|
-
},
|
|
1116
|
+
className: "qn-empty",
|
|
408
1117
|
children: t("panel.empty")
|
|
1118
|
+
}) : visible.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1119
|
+
className: "qn-empty",
|
|
1120
|
+
children: t("panel.noMatches")
|
|
409
1121
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
1122
|
+
className: "qn-list",
|
|
1123
|
+
children: visible.map((note) => {
|
|
1124
|
+
const noteTags = extractTags(note.text);
|
|
1125
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
1126
|
+
"data-testid": "note-item",
|
|
1127
|
+
className: "qn-note",
|
|
1128
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1129
|
+
className: "qn-note-body",
|
|
1130
|
+
children: [
|
|
1131
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1132
|
+
className: `qn-note-text${note.done ? " qn-note-text-done" : ""}`,
|
|
1133
|
+
children: note.text
|
|
1134
|
+
}),
|
|
1135
|
+
noteTags.length === 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1136
|
+
className: "qn-note-tags",
|
|
1137
|
+
children: noteTags.map((label) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1138
|
+
type: "button",
|
|
1139
|
+
className: "qn-button qn-tag",
|
|
1140
|
+
"aria-pressed": tag?.toLowerCase() === label.toLowerCase(),
|
|
1141
|
+
onClick: () => activateTag(label),
|
|
1142
|
+
children: ["#", label]
|
|
1143
|
+
}, label.toLowerCase()))
|
|
1144
|
+
}),
|
|
1145
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1146
|
+
className: "qn-note-meta",
|
|
1147
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("time", {
|
|
1148
|
+
dateTime: new Date(note.createdAt).toISOString(),
|
|
1149
|
+
children: formatTime(note.createdAt)
|
|
1150
|
+
}), note.origin?.title ? ` · ${note.origin.title}` : ""]
|
|
1151
|
+
})
|
|
1152
|
+
]
|
|
1153
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1154
|
+
className: "qn-note-actions",
|
|
1155
|
+
children: [
|
|
1156
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1157
|
+
type: "button",
|
|
1158
|
+
className: "qn-button",
|
|
1159
|
+
onClick: () => void copy(note),
|
|
1160
|
+
children: t("panel.copy")
|
|
1161
|
+
}),
|
|
1162
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1163
|
+
type: "button",
|
|
1164
|
+
className: "qn-button",
|
|
1165
|
+
onClick: () => insert(note),
|
|
1166
|
+
children: t("panel.insert")
|
|
1167
|
+
}),
|
|
1168
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1169
|
+
type: "button",
|
|
1170
|
+
className: "qn-button",
|
|
1171
|
+
onClick: () => void toggle(note),
|
|
1172
|
+
children: note.done ? t("panel.reopen") : t("panel.done")
|
|
1173
|
+
}),
|
|
1174
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1175
|
+
type: "button",
|
|
1176
|
+
className: "qn-button",
|
|
1177
|
+
title: t("panel.deleteHint"),
|
|
1178
|
+
onClick: () => void remove(note),
|
|
1179
|
+
children: t("panel.delete")
|
|
1180
|
+
})
|
|
1181
|
+
]
|
|
1182
|
+
})]
|
|
1183
|
+
}, note.id);
|
|
1184
|
+
})
|
|
1185
|
+
}),
|
|
1186
|
+
undo.length === 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1187
|
+
className: "qn-toast",
|
|
1188
|
+
role: "status",
|
|
1189
|
+
"aria-live": "polite",
|
|
1190
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1191
|
+
className: "qn-toast-text",
|
|
427
1192
|
children: [
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
textDecoration: note.done ? "line-through" : "none",
|
|
432
|
-
opacity: note.done ? .6 : 1
|
|
433
|
-
},
|
|
434
|
-
children: note.text
|
|
435
|
-
}),
|
|
436
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
437
|
-
type: "button",
|
|
438
|
-
onClick: () => void toggle(note),
|
|
439
|
-
style: {
|
|
440
|
-
padding: "5px 9px",
|
|
441
|
-
borderRadius: 7
|
|
442
|
-
},
|
|
443
|
-
children: note.done ? t("panel.reopen") : t("panel.done")
|
|
444
|
-
}),
|
|
445
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
446
|
-
type: "button",
|
|
447
|
-
onClick: () => void remove(note),
|
|
448
|
-
style: {
|
|
449
|
-
padding: "5px 9px",
|
|
450
|
-
borderRadius: 7
|
|
451
|
-
},
|
|
452
|
-
children: t("panel.delete")
|
|
453
|
-
})
|
|
1193
|
+
undo.length === 1 ? t("panel.deleted") : t("panel.deletedCount").replace("{count}", String(undo.length)),
|
|
1194
|
+
" ",
|
|
1195
|
+
previewOf(undo[undo.length - 1].text)
|
|
454
1196
|
]
|
|
455
|
-
},
|
|
1197
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1198
|
+
className: "qn-toast-actions",
|
|
1199
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1200
|
+
type: "button",
|
|
1201
|
+
className: "qn-button",
|
|
1202
|
+
onClick: () => void undoDelete(),
|
|
1203
|
+
children: undo.length > 1 ? t("panel.undoAll") : t("panel.undo")
|
|
1204
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1205
|
+
type: "button",
|
|
1206
|
+
className: "qn-button",
|
|
1207
|
+
"aria-label": t("panel.dismiss"),
|
|
1208
|
+
onClick: () => setUndo([]),
|
|
1209
|
+
children: "×"
|
|
1210
|
+
})]
|
|
1211
|
+
})]
|
|
456
1212
|
})
|
|
457
1213
|
]
|
|
458
|
-
});
|
|
1214
|
+
})] });
|
|
459
1215
|
}
|
|
460
1216
|
const name = NAME;
|
|
461
1217
|
const inject = ["slots", "locale"];
|
|
@@ -464,41 +1220,107 @@ window.__ModuleLoader__.load({
|
|
|
464
1220
|
ctx.effect(() => ctx.locale.register(NS, {
|
|
465
1221
|
en: {
|
|
466
1222
|
"panel.title": "Quick Note",
|
|
1223
|
+
"panel.back": "Back to conversation",
|
|
1224
|
+
"panel.backTo": "Back to {title}",
|
|
1225
|
+
"panel.backFailed": "Could not open the conversation. Pick a Session in the sidebar.",
|
|
467
1226
|
"panel.body": "Capture a note, mark it done, keep the list on one page.",
|
|
468
|
-
"panel.placeholder": "Write a note…",
|
|
1227
|
+
"panel.placeholder": "Write a note… Use #tags to group it.",
|
|
469
1228
|
"panel.add": "Add note",
|
|
470
1229
|
"panel.empty": "No notes yet.",
|
|
1230
|
+
"panel.noMatches": "No matching notes.",
|
|
1231
|
+
"panel.search": "Search notes",
|
|
1232
|
+
"panel.filters": "Filter notes",
|
|
1233
|
+
"panel.tags": "Tags",
|
|
1234
|
+
"panel.allTags": "All tags",
|
|
1235
|
+
"panel.filterAll": "All",
|
|
1236
|
+
"panel.filterOpen": "Open",
|
|
1237
|
+
"panel.filterDone": "Done",
|
|
1238
|
+
"panel.copy": "Copy",
|
|
1239
|
+
"panel.copied": "Copied.",
|
|
1240
|
+
"panel.copyFailed": "Could not copy this note.",
|
|
1241
|
+
"panel.copyAll": "Copy all",
|
|
1242
|
+
"panel.copyAllDone": "Copied the visible notes as Markdown.",
|
|
1243
|
+
"panel.insert": "Insert",
|
|
1244
|
+
"panel.inserted": "Inserted into the draft.",
|
|
1245
|
+
"panel.insertUnavailable": "No composer is open.",
|
|
471
1246
|
"panel.done": "Done",
|
|
472
1247
|
"panel.reopen": "Reopen",
|
|
473
1248
|
"panel.delete": "Delete",
|
|
1249
|
+
"panel.deleteHint": "Delete this note. You can undo it right after.",
|
|
474
1250
|
"panel.deleted": "Note deleted.",
|
|
1251
|
+
"panel.deletedCount": "Deleted {count} notes.",
|
|
1252
|
+
"panel.deleteUndone": "Note restored.",
|
|
1253
|
+
"panel.deletesUndone": "Notes restored.",
|
|
1254
|
+
"panel.undoAll": "Undo all",
|
|
1255
|
+
"panel.dismiss": "Dismiss",
|
|
475
1256
|
"panel.completed": "Note updated.",
|
|
476
1257
|
"panel.undo": "Undo",
|
|
477
|
-
"toolbar.capture": "Note",
|
|
1258
|
+
"toolbar.capture": "Quick Note",
|
|
478
1259
|
"overlay.title": "Save a quick note",
|
|
1260
|
+
"overlay.viewAll": "View all",
|
|
1261
|
+
"overlay.viewAllCount": "View all ({count})",
|
|
1262
|
+
"overlay.openFailed": "Could not open the notes page. Use Quick Note in the sidebar.",
|
|
479
1263
|
"overlay.cancel": "Cancel",
|
|
480
1264
|
"overlay.save": "Save note",
|
|
481
1265
|
"overlay.saveClear": "Save and clear draft",
|
|
482
|
-
"overlay.empty": "Nothing to save."
|
|
1266
|
+
"overlay.empty": "Nothing to save.",
|
|
1267
|
+
"parked.saved": "Moved into Quick Note",
|
|
1268
|
+
"parked.open": "Open the list",
|
|
1269
|
+
"parked.undo": "Undo",
|
|
1270
|
+
"parked.dismiss": "Dismiss",
|
|
1271
|
+
"shortcut.open": "Open Quick Note"
|
|
483
1272
|
},
|
|
484
1273
|
zh: {
|
|
485
1274
|
"panel.title": "随手记",
|
|
1275
|
+
"panel.back": "返回对话",
|
|
1276
|
+
"panel.backTo": "返回「{title}」",
|
|
1277
|
+
"panel.backFailed": "打不开对话,请从左侧栏选择会话。",
|
|
486
1278
|
"panel.body": "记下一条笔记,标记完成,在一页里管理。",
|
|
487
|
-
"panel.placeholder": "写一条笔记…",
|
|
1279
|
+
"panel.placeholder": "写一条笔记… 用 #标签 归类。",
|
|
488
1280
|
"panel.add": "添加",
|
|
489
1281
|
"panel.empty": "还没有笔记。",
|
|
1282
|
+
"panel.noMatches": "没有匹配的笔记。",
|
|
1283
|
+
"panel.search": "搜索笔记",
|
|
1284
|
+
"panel.filters": "筛选笔记",
|
|
1285
|
+
"panel.tags": "标签",
|
|
1286
|
+
"panel.allTags": "全部标签",
|
|
1287
|
+
"panel.filterAll": "全部",
|
|
1288
|
+
"panel.filterOpen": "未完成",
|
|
1289
|
+
"panel.filterDone": "已完成",
|
|
1290
|
+
"panel.copy": "复制",
|
|
1291
|
+
"panel.copied": "已复制。",
|
|
1292
|
+
"panel.copyFailed": "复制失败。",
|
|
1293
|
+
"panel.copyAll": "复制全部",
|
|
1294
|
+
"panel.copyAllDone": "已复制当前可见的笔记为 Markdown。",
|
|
1295
|
+
"panel.insert": "插入",
|
|
1296
|
+
"panel.inserted": "已插入草稿。",
|
|
1297
|
+
"panel.insertUnavailable": "当前没有可用的输入框。",
|
|
490
1298
|
"panel.done": "完成",
|
|
491
1299
|
"panel.reopen": "重新打开",
|
|
492
1300
|
"panel.delete": "删除",
|
|
1301
|
+
"panel.deleteHint": "删除这条笔记,随后可以撤销。",
|
|
493
1302
|
"panel.deleted": "笔记已删除。",
|
|
1303
|
+
"panel.deletedCount": "已删除 {count} 条笔记。",
|
|
1304
|
+
"panel.deleteUndone": "笔记已恢复。",
|
|
1305
|
+
"panel.deletesUndone": "笔记已恢复。",
|
|
1306
|
+
"panel.undoAll": "全部撤销",
|
|
1307
|
+
"panel.dismiss": "关闭提示",
|
|
494
1308
|
"panel.completed": "笔记已更新。",
|
|
495
1309
|
"panel.undo": "撤销",
|
|
496
|
-
"toolbar.capture": "
|
|
1310
|
+
"toolbar.capture": "随手记",
|
|
497
1311
|
"overlay.title": "保存随手记",
|
|
1312
|
+
"overlay.viewAll": "查看全部",
|
|
1313
|
+
"overlay.viewAllCount": "查看全部({count})",
|
|
1314
|
+
"overlay.openFailed": "打不开笔记页,请从左侧栏的「随手记」进入。",
|
|
498
1315
|
"overlay.cancel": "取消",
|
|
499
1316
|
"overlay.save": "保存笔记",
|
|
500
1317
|
"overlay.saveClear": "保存并清空输入框",
|
|
501
|
-
"overlay.empty": "没有可保存的内容。"
|
|
1318
|
+
"overlay.empty": "没有可保存的内容。",
|
|
1319
|
+
"parked.saved": "已收进随手记",
|
|
1320
|
+
"parked.open": "打开列表",
|
|
1321
|
+
"parked.undo": "撤销",
|
|
1322
|
+
"parked.dismiss": "关闭提示",
|
|
1323
|
+
"shortcut.open": "打开随手记"
|
|
502
1324
|
}
|
|
503
1325
|
}), `${NAME}: dictionaries`);
|
|
504
1326
|
ctx.slots.inject("sidebar.panellist", () => ctx.slots.register({
|
|
@@ -519,12 +1341,63 @@ window.__ModuleLoader__.load({
|
|
|
519
1341
|
order: 10,
|
|
520
1342
|
locale: NS
|
|
521
1343
|
}, ComposerButton));
|
|
1344
|
+
ctx.slots.inject("conversation.input.dock", () => ctx.slots.register({
|
|
1345
|
+
name: "conversation.input.dock",
|
|
1346
|
+
id: "quicknote.parked",
|
|
1347
|
+
order: 20,
|
|
1348
|
+
locale: NS,
|
|
1349
|
+
inject: (sessionId) => ({ captureSessionId: sessionId })
|
|
1350
|
+
}, ParkedBar));
|
|
522
1351
|
ctx.slots.inject("shell.overlay", () => ctx.slots.register({
|
|
523
1352
|
name: "shell.overlay",
|
|
524
1353
|
id: "quicknote.overlay",
|
|
525
1354
|
order: 40,
|
|
526
1355
|
locale: NS
|
|
527
1356
|
}, Overlay));
|
|
1357
|
+
if (typeof ctx.inject !== "function") return;
|
|
1358
|
+
ctx.inject(["shortcuts"], (scope) => {
|
|
1359
|
+
let shortcuts;
|
|
1360
|
+
try {
|
|
1361
|
+
shortcuts = scope.get("shortcuts");
|
|
1362
|
+
} catch {
|
|
1363
|
+
return;
|
|
1364
|
+
}
|
|
1365
|
+
if (typeof shortcuts?.register !== "function") return;
|
|
1366
|
+
scope.effect(() => {
|
|
1367
|
+
const binding = {
|
|
1368
|
+
code: "KeyS",
|
|
1369
|
+
modifiers: ["primary", "alt"]
|
|
1370
|
+
};
|
|
1371
|
+
return shortcuts.register({
|
|
1372
|
+
id: "quicknote.open",
|
|
1373
|
+
label: () => ctx.locale.bind(NS)("shortcut.open"),
|
|
1374
|
+
aliases: [
|
|
1375
|
+
"capture",
|
|
1376
|
+
"note",
|
|
1377
|
+
"quicknote",
|
|
1378
|
+
"随手记",
|
|
1379
|
+
"备忘"
|
|
1380
|
+
],
|
|
1381
|
+
defaults: {
|
|
1382
|
+
"desktop:macos": binding,
|
|
1383
|
+
"desktop:windows": binding,
|
|
1384
|
+
"desktop:linux": binding,
|
|
1385
|
+
"web:macos": binding,
|
|
1386
|
+
"web:windows": binding
|
|
1387
|
+
},
|
|
1388
|
+
regions: [
|
|
1389
|
+
"page",
|
|
1390
|
+
"editable",
|
|
1391
|
+
"terminal"
|
|
1392
|
+
],
|
|
1393
|
+
modals: [],
|
|
1394
|
+
resolve: () => ({
|
|
1395
|
+
status: "handled",
|
|
1396
|
+
run: openCapture
|
|
1397
|
+
})
|
|
1398
|
+
});
|
|
1399
|
+
}, "dsh-quicknote: shortcut");
|
|
1400
|
+
});
|
|
528
1401
|
}
|
|
529
1402
|
exports.apply = apply;
|
|
530
1403
|
exports.inject = inject;
|