dsh-tiddlywiki 0.1.1 → 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/LICENSE +21 -21
- package/README.md +206 -201
- package/cordis.patch.yml +13 -13
- package/lib/client.bundle.js +254 -19
- package/lib/client.js +254 -19
- package/lib/index.js +133 -22
- package/lib/index.js.map +1 -1
- package/package.json +79 -79
- package/src/client/editor-popup.ts +121 -121
- package/src/client/index.ts +76 -76
- package/src/client/note-widget.ts +183 -20
- package/src/client/panel.ts +345 -304
- package/src/client/settings-page.ts +400 -397
- package/src/client/sidebar-entry.ts +148 -148
- package/src/client/state.ts +39 -39
- package/src/client/styles.ts +285 -235
- package/src/client/toast.ts +22 -22
- package/src/host/admin.ts +408 -408
- package/src/host/config.ts +92 -86
- package/src/host/git.ts +218 -218
- package/src/host/routes.ts +272 -233
- package/src/host/seed-notes.ts +122 -62
- package/src/host/tools.ts +254 -254
- package/src/host/tw-api.ts +157 -157
- package/src/host/wiki.ts +287 -287
- package/src/index.ts +346 -331
- package/src/sdk.ts +198 -198
package/lib/client.bundle.js
CHANGED
|
@@ -48,6 +48,21 @@ const CSS_TEXT = `
|
|
|
48
48
|
.dsh-tw-entry[data-active="true"] { background: var(--dsw-alias-interactive-bg-active, rgba(128,128,128,.18)); color: var(--dsw-alias-label-primary, inherit); font-weight: 500; }
|
|
49
49
|
.dsh-tw-entry svg { flex: none; }
|
|
50
50
|
.dsh-tw-entry .dsh-tw-entry-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
51
|
+
/* Collapsed rail: the shell narrows the sidebar to a 56px icon rail (dual
|
|
52
|
+
signals — the frame's data-sidebar-collapsed + the sidebar root's hashed
|
|
53
|
+
*_collapsed class, same doctrine as dsh-taskboard 0.4.3). Mirror the
|
|
54
|
+
native 36×36 icon-button geometry: center the icon, hide the label, and
|
|
55
|
+
scale it up to match the shell's 18px rail icons. */
|
|
56
|
+
[data-sidebar-collapsed] [data-dsh-tw-entry],
|
|
57
|
+
[class*="_collapsed"] [data-dsh-tw-entry] {
|
|
58
|
+
width: 36px; height: 36px; min-width: 36px;
|
|
59
|
+
margin: 0 0 12px; padding: 0;
|
|
60
|
+
justify-content: center; gap: 0; text-align: center;
|
|
61
|
+
}
|
|
62
|
+
[data-sidebar-collapsed] [data-dsh-tw-entry] .dsh-tw-entry-label,
|
|
63
|
+
[class*="_collapsed"] [data-dsh-tw-entry] .dsh-tw-entry-label { display: none; }
|
|
64
|
+
[data-sidebar-collapsed] [data-dsh-tw-entry] svg,
|
|
65
|
+
[class*="_collapsed"] [data-dsh-tw-entry] svg { width: 18px; height: 18px; }
|
|
51
66
|
|
|
52
67
|
/* ── center-column panel (fixed overlay, JS-pinned to the column rect) ── */
|
|
53
68
|
.dsh-tw-view {
|
|
@@ -74,6 +89,13 @@ html[data-dsh-tw-active] .dshDesktopConversationSurface > :not([data-dsh-tw-view
|
|
|
74
89
|
}
|
|
75
90
|
.dsh-tw-panel-bar button:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,.12)); }
|
|
76
91
|
.dsh-tw-panel-title { font-weight: 600; margin-right: auto; }
|
|
92
|
+
/* dsh-better-sidebar coexistence: the floating toggle cluster (two 28px
|
|
93
|
+
buttons at the viewport's top-right) lives above the panel (z-index is
|
|
94
|
+
handled in JS — the panel stays below the host layer), so reserve the
|
|
95
|
+
cluster's width at the panel bar's right end — mirroring how
|
|
96
|
+
dsh-better-sidebar pads the DSH session header — and the panel's own
|
|
97
|
+
状态/重载 buttons never sit underneath it. */
|
|
98
|
+
.dsh-tw-view[data-sidebar-host] .dsh-tw-panel-bar { padding-right: 80px; }
|
|
77
99
|
.dsh-tw-status-chip { font-variant-numeric: tabular-nums; }
|
|
78
100
|
.dsh-tw-status-chip[data-state="running"] { color: var(--dsw-alias-state-success-primary, #3eaa5f); }
|
|
79
101
|
.dsh-tw-status-chip[data-state="starting"] { color: var(--dsw-alias-state-warning-primary, #d9822b); }
|
|
@@ -156,6 +178,34 @@ html[data-dsh-tw-active] .dshDesktopConversationSurface > :not([data-dsh-tw-view
|
|
|
156
178
|
}
|
|
157
179
|
.dsh-tw-note-edit:hover:not(:disabled) { filter: brightness(.96); }
|
|
158
180
|
.dsh-tw-note-edit:disabled { opacity: .55; cursor: default; }
|
|
181
|
+
/* ── Multi-tag chip editor (quick-note tags) ─────────────────────────────── */
|
|
182
|
+
.dsh-tw-note-tags { position: relative; flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 4px; }
|
|
183
|
+
.dsh-tw-note-chips { display: flex; flex-wrap: wrap; gap: 4px; }
|
|
184
|
+
.dsh-tw-note-tagchip {
|
|
185
|
+
display: inline-flex; align-items: center; gap: 4px;
|
|
186
|
+
padding: 2px 6px 2px 8px; border-radius: 999px; font-size: 11px; line-height: 1.4;
|
|
187
|
+
background: color-mix(in srgb, var(--dsw-alias-brand-primary, #3e63dd) 10%, var(--dsw-alias-bg-layer-1, #fff));
|
|
188
|
+
border: 1px solid color-mix(in srgb, var(--dsw-alias-brand-primary, #3e63dd) 30%, transparent);
|
|
189
|
+
color: var(--dsw-alias-label-primary, #222);
|
|
190
|
+
}
|
|
191
|
+
.dsh-tw-note-tagchip-x {
|
|
192
|
+
cursor: pointer; font-size: 12px; line-height: 1; padding: 0 2px; border-radius: 50%;
|
|
193
|
+
color: var(--dsw-alias-label-secondary, #888);
|
|
194
|
+
}
|
|
195
|
+
.dsh-tw-note-tagchip-x:hover { color: var(--dsw-alias-state-error-primary, #d13b3b); }
|
|
196
|
+
.dsh-tw-note-taginput { width: 100%; box-sizing: border-box; }
|
|
197
|
+
.dsh-tw-note-tagsuggest {
|
|
198
|
+
position: absolute; left: 0; right: 0; top: calc(100% + 2px); z-index: 60;
|
|
199
|
+
max-height: 168px; overflow-y: auto; border-radius: 8px; padding: 4px;
|
|
200
|
+
background: var(--dsw-alias-bg-layer-2, #fff); color: var(--dsw-alias-label-primary, #222);
|
|
201
|
+
border: 1px solid var(--dsw-alias-border-l2, rgba(0,0,0,.18));
|
|
202
|
+
box-shadow: var(--dsw-shadow-lv2, 0 4px 16px rgba(0,0,0,.16));
|
|
203
|
+
font-size: 12px;
|
|
204
|
+
}
|
|
205
|
+
.dsh-tw-note-tagsuggest-item {
|
|
206
|
+
padding: 6px 9px; border-radius: 6px; cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
207
|
+
}
|
|
208
|
+
.dsh-tw-note-tagsuggest-item:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,.12)); }
|
|
159
209
|
/* ── Native-editor popup iframe (quick-note "在 TW 中编辑") ──────────────── */
|
|
160
210
|
.dsh-tw-editor-popup {
|
|
161
211
|
position: fixed; left: 0; right: 0; top: 0; bottom: 0; margin: auto;
|
|
@@ -429,10 +479,35 @@ const ACTIVATE_EVENT = "dsh-panel-activate";
|
|
|
429
479
|
const PANEL_NAME = "dsh-tiddlywiki";
|
|
430
480
|
/** Overlay z-index: above the shell content, below the note widget (950). */
|
|
431
481
|
const PANEL_Z_INDEX = 40;
|
|
482
|
+
/**
|
|
483
|
+
* dsh-better-sidebar's unified fixed host layer (its persistent
|
|
484
|
+
* expand/collapse toggle cluster lives inside it, at a global z-index of 25
|
|
485
|
+
* — its internal 45 is trapped by the host's stacking context). When this
|
|
486
|
+
* host is present the panel must stay BELOW it, so the sidebar toggle
|
|
487
|
+
* buttons stay visible and clickable above the full-screen TW panel.
|
|
488
|
+
*/
|
|
489
|
+
const PANEL_HOST_SELECTOR = "[data-dsh-panel-host]";
|
|
490
|
+
/** The app's own shell overlay layer (dsh-client-ui-layout pins it at 20). */
|
|
491
|
+
const APP_OVERLAY_Z_INDEX = 20;
|
|
432
492
|
/** Safety re-measure cadence for shell layout changes CSS can't see. */
|
|
433
493
|
const SYNC_INTERVAL_MS = 2e3;
|
|
434
494
|
const STATUS_ENDPOINT$1 = "/dsh-tiddlywiki/status";
|
|
435
495
|
const RESTART_ENDPOINT$1 = "/dsh-tiddlywiki/restart";
|
|
496
|
+
/**
|
|
497
|
+
* The panel's z-index: below any dsh-better-sidebar host layer so its
|
|
498
|
+
* persistent toggle cluster (top-right corner) stays visible and clickable
|
|
499
|
+
* above the full-screen TW panel, otherwise the default 40. The host's live
|
|
500
|
+
* computed z-index is read instead of hardcoding 25, so the rule tracks
|
|
501
|
+
* plugin updates; the panel is still clamped above the app's own shell
|
|
502
|
+
* overlay layer (dsh-client-ui-layout pins it at 20).
|
|
503
|
+
*/
|
|
504
|
+
function resolvePanelZIndex() {
|
|
505
|
+
const host = document.querySelector(PANEL_HOST_SELECTOR);
|
|
506
|
+
if (host === null) return PANEL_Z_INDEX;
|
|
507
|
+
const parsed = parseInt(getComputedStyle(host).zIndex, 10);
|
|
508
|
+
if (!Number.isFinite(parsed)) return PANEL_Z_INDEX;
|
|
509
|
+
return Math.max(APP_OVERLAY_Z_INDEX, Math.min(PANEL_Z_INDEX, parsed - 1));
|
|
510
|
+
}
|
|
436
511
|
function conversationColumn() {
|
|
437
512
|
for (const selector of COLUMN_SELECTORS) {
|
|
438
513
|
const el = document.querySelector(selector);
|
|
@@ -526,13 +601,25 @@ function mountPanel(state) {
|
|
|
526
601
|
if (container.style.width !== width) container.style.width = width;
|
|
527
602
|
if (container.style.height !== height) container.style.height = height;
|
|
528
603
|
};
|
|
604
|
+
/**
|
|
605
|
+
* Coexist with dsh-better-sidebar: keep the panel's z-index below the
|
|
606
|
+
* host layer (so its toggle cluster stays clickable above the panel) and
|
|
607
|
+
* flag the host's presence so CSS can reserve the cluster's width at the
|
|
608
|
+
* panel bar's right end. Re-run whenever the host mounts/unmounts.
|
|
609
|
+
*/
|
|
610
|
+
const applyChrome = () => {
|
|
611
|
+
if (container === void 0) return;
|
|
612
|
+
container.style.zIndex = String(resolvePanelZIndex());
|
|
613
|
+
if (document.querySelector(PANEL_HOST_SELECTOR) !== null) container.dataset.sidebarHost = "1";
|
|
614
|
+
else delete container.dataset.sidebarHost;
|
|
615
|
+
};
|
|
529
616
|
const ensure = () => {
|
|
530
617
|
if (container !== void 0) return;
|
|
531
618
|
columnEl = conversationColumn();
|
|
532
619
|
if (columnEl === void 0) return;
|
|
533
620
|
container = build();
|
|
534
621
|
container.style.position = "fixed";
|
|
535
|
-
|
|
622
|
+
applyChrome();
|
|
536
623
|
document.body.append(container);
|
|
537
624
|
syncRect();
|
|
538
625
|
};
|
|
@@ -634,6 +721,7 @@ function mountPanel(state) {
|
|
|
634
721
|
};
|
|
635
722
|
const waitObserver = new MutationObserver(() => {
|
|
636
723
|
ensure();
|
|
724
|
+
applyChrome();
|
|
637
725
|
});
|
|
638
726
|
waitObserver.observe(document.body, {
|
|
639
727
|
childList: true,
|
|
@@ -802,14 +890,23 @@ function ensurePopup() {
|
|
|
802
890
|
* collapsible, independent of any shell DOM. Lets the human jot drafts /
|
|
803
891
|
* scratch notes while waiting for the AI or drafting the next prompt.
|
|
804
892
|
*
|
|
805
|
-
* Save posts to /dsh-tiddlywiki/note → an independent tiddler (title &
|
|
893
|
+
* Save posts to /dsh-tiddlywiki/note → an independent tiddler (title & tags
|
|
806
894
|
* editable; defaults: timestamp title + config tag, usually "inbox").
|
|
807
895
|
*
|
|
896
|
+
* ui.showQuickNote config: the widget is mounted async and stays hidden (and
|
|
897
|
+
* never appended) when the option is off, so the toggle button can be disabled
|
|
898
|
+
* from the settings page without touching the shell.
|
|
899
|
+
*
|
|
900
|
+
* Tag editor: multi-select chips + autocomplete from the wiki's existing tags
|
|
901
|
+
* (GET /dsh-tiddlywiki/tags). Enter/`,` commits the typed value; Backspace on
|
|
902
|
+
* an empty field removes the last chip; the dropdown filters on typing.
|
|
903
|
+
*
|
|
808
904
|
* @module dsh-tiddlywiki/client/note-widget
|
|
809
905
|
*/
|
|
810
906
|
const NOTE_ENDPOINT = "/dsh-tiddlywiki/note";
|
|
811
907
|
const EDIT_ENDPOINT = "/dsh-tiddlywiki/edit";
|
|
812
908
|
const STATUS_ENDPOINT = "/dsh-tiddlywiki/status";
|
|
909
|
+
const TAGS_ENDPOINT = "/dsh-tiddlywiki/tags";
|
|
813
910
|
function pad(n) {
|
|
814
911
|
return n < 10 ? `0${n}` : String(n);
|
|
815
912
|
}
|
|
@@ -817,16 +914,137 @@ function pad(n) {
|
|
|
817
914
|
function timestampTitle(date = /* @__PURE__ */ new Date()) {
|
|
818
915
|
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
|
|
819
916
|
}
|
|
820
|
-
async function
|
|
917
|
+
async function fetchUiOptions() {
|
|
918
|
+
const fallback = {
|
|
919
|
+
showQuickNote: true,
|
|
920
|
+
defaultTag: "inbox"
|
|
921
|
+
};
|
|
821
922
|
try {
|
|
822
923
|
const res = await fetch(STATUS_ENDPOINT, { signal: AbortSignal.timeout(5e3) });
|
|
823
|
-
if (!res.ok) return
|
|
824
|
-
|
|
924
|
+
if (!res.ok) return fallback;
|
|
925
|
+
const payload = await res.json();
|
|
926
|
+
return {
|
|
927
|
+
showQuickNote: payload.ui?.showQuickNote !== false,
|
|
928
|
+
defaultTag: payload.note?.tag ?? "inbox"
|
|
929
|
+
};
|
|
825
930
|
} catch {
|
|
826
|
-
return
|
|
931
|
+
return fallback;
|
|
827
932
|
}
|
|
828
933
|
}
|
|
829
|
-
|
|
934
|
+
/** Multi-tag chip editor with autocomplete from the wiki's existing tags. */
|
|
935
|
+
function buildTagEditor() {
|
|
936
|
+
const wrap = document.createElement("div");
|
|
937
|
+
wrap.className = "dsh-tw-note-tags";
|
|
938
|
+
const chipWrap = document.createElement("div");
|
|
939
|
+
chipWrap.className = "dsh-tw-note-chips";
|
|
940
|
+
const input = document.createElement("input");
|
|
941
|
+
input.className = "dsh-tw-note-taginput";
|
|
942
|
+
input.placeholder = "tag(可多选,自动补全)";
|
|
943
|
+
const suggest = document.createElement("div");
|
|
944
|
+
suggest.className = "dsh-tw-note-tagsuggest";
|
|
945
|
+
suggest.hidden = true;
|
|
946
|
+
wrap.append(chipWrap, input, suggest);
|
|
947
|
+
const chips = [];
|
|
948
|
+
const hideSuggest = () => {
|
|
949
|
+
suggest.hidden = true;
|
|
950
|
+
};
|
|
951
|
+
const renderChips = () => {
|
|
952
|
+
chipWrap.replaceChildren();
|
|
953
|
+
for (const tag of chips) {
|
|
954
|
+
const chip = document.createElement("span");
|
|
955
|
+
chip.className = "dsh-tw-note-tagchip";
|
|
956
|
+
chip.textContent = tag;
|
|
957
|
+
const x = document.createElement("span");
|
|
958
|
+
x.className = "dsh-tw-note-tagchip-x";
|
|
959
|
+
x.textContent = "×";
|
|
960
|
+
x.title = `移除 tag「${tag}」`;
|
|
961
|
+
x.addEventListener("click", (event) => {
|
|
962
|
+
event.stopPropagation();
|
|
963
|
+
const i = chips.indexOf(tag);
|
|
964
|
+
if (i >= 0) {
|
|
965
|
+
chips.splice(i, 1);
|
|
966
|
+
renderChips();
|
|
967
|
+
}
|
|
968
|
+
});
|
|
969
|
+
chip.append(x);
|
|
970
|
+
chipWrap.append(chip);
|
|
971
|
+
}
|
|
972
|
+
};
|
|
973
|
+
const addTag = (tag) => {
|
|
974
|
+
const t = tag.trim();
|
|
975
|
+
if (t.length === 0 || chips.includes(t)) return;
|
|
976
|
+
chips.push(t);
|
|
977
|
+
input.value = "";
|
|
978
|
+
renderChips();
|
|
979
|
+
hideSuggest();
|
|
980
|
+
input.focus();
|
|
981
|
+
};
|
|
982
|
+
const commitInput = () => {
|
|
983
|
+
for (const raw of input.value.split(/\s+/)) addTag(raw);
|
|
984
|
+
};
|
|
985
|
+
let knownTags = [];
|
|
986
|
+
let tagsPromise;
|
|
987
|
+
const ensureTags = () => {
|
|
988
|
+
tagsPromise ??= fetch(TAGS_ENDPOINT, { signal: AbortSignal.timeout(5e3) }).then((r) => r.ok ? r.json() : Promise.resolve({})).then((p) => [...p.tags ?? []].sort((a, b) => a.localeCompare(b, "zh"))).catch(() => []);
|
|
989
|
+
tagsPromise.then((list) => {
|
|
990
|
+
knownTags = list;
|
|
991
|
+
});
|
|
992
|
+
return tagsPromise;
|
|
993
|
+
};
|
|
994
|
+
const showSuggest = () => {
|
|
995
|
+
const q = input.value.trim().toLowerCase();
|
|
996
|
+
const matches = knownTags.filter((t) => !chips.includes(t) && (q.length === 0 || t.toLowerCase().includes(q))).slice(0, 8);
|
|
997
|
+
suggest.replaceChildren();
|
|
998
|
+
for (const tag of matches) {
|
|
999
|
+
const item = document.createElement("div");
|
|
1000
|
+
item.className = "dsh-tw-note-tagsuggest-item";
|
|
1001
|
+
item.textContent = tag;
|
|
1002
|
+
item.addEventListener("mousedown", (event) => {
|
|
1003
|
+
event.preventDefault();
|
|
1004
|
+
addTag(tag);
|
|
1005
|
+
});
|
|
1006
|
+
suggest.append(item);
|
|
1007
|
+
}
|
|
1008
|
+
suggest.hidden = matches.length === 0;
|
|
1009
|
+
};
|
|
1010
|
+
input.addEventListener("focus", () => {
|
|
1011
|
+
ensureTags().then(showSuggest);
|
|
1012
|
+
});
|
|
1013
|
+
input.addEventListener("input", () => {
|
|
1014
|
+
if (knownTags.length === 0) ensureTags().then(showSuggest);
|
|
1015
|
+
else showSuggest();
|
|
1016
|
+
});
|
|
1017
|
+
input.addEventListener("keydown", (event) => {
|
|
1018
|
+
if (event.key === "Enter" || event.key === ",") {
|
|
1019
|
+
event.preventDefault();
|
|
1020
|
+
commitInput();
|
|
1021
|
+
} else if (event.key === "Backspace" && input.value.length === 0 && chips.length > 0) {
|
|
1022
|
+
chips.pop();
|
|
1023
|
+
renderChips();
|
|
1024
|
+
} else if (event.key === "Escape") hideSuggest();
|
|
1025
|
+
});
|
|
1026
|
+
document.addEventListener("click", (event) => {
|
|
1027
|
+
if (!wrap.contains(event.target)) hideSuggest();
|
|
1028
|
+
}, true);
|
|
1029
|
+
return {
|
|
1030
|
+
el: wrap,
|
|
1031
|
+
getTags: () => {
|
|
1032
|
+
commitInput();
|
|
1033
|
+
return [...chips];
|
|
1034
|
+
},
|
|
1035
|
+
setDefault: (tag) => {
|
|
1036
|
+
if (chips.length === 0) {
|
|
1037
|
+
const t = tag.trim();
|
|
1038
|
+
if (t.length > 0) {
|
|
1039
|
+
chips.push(t);
|
|
1040
|
+
renderChips();
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
};
|
|
1045
|
+
}
|
|
1046
|
+
/** Build the whole widget DOM and wire it up (returns the appended root). */
|
|
1047
|
+
function buildWidget() {
|
|
830
1048
|
const root = document.createElement("div");
|
|
831
1049
|
root.className = "dsh-tw-note";
|
|
832
1050
|
const card = document.createElement("div");
|
|
@@ -848,10 +1066,8 @@ function mountNoteWidget() {
|
|
|
848
1066
|
const titleInput = document.createElement("input");
|
|
849
1067
|
titleInput.className = "dsh-tw-note-title";
|
|
850
1068
|
titleInput.placeholder = "标题(默认时间戳)";
|
|
851
|
-
const
|
|
852
|
-
|
|
853
|
-
tagInput.placeholder = "tag(默认 inbox)";
|
|
854
|
-
fields.append(titleInput, tagInput);
|
|
1069
|
+
const tagEditor = buildTagEditor();
|
|
1070
|
+
fields.append(titleInput, tagEditor.el);
|
|
855
1071
|
const textarea = document.createElement("textarea");
|
|
856
1072
|
textarea.className = "dsh-tw-note-text";
|
|
857
1073
|
textarea.placeholder = "写点东西…(Ctrl+Enter 保存)";
|
|
@@ -886,10 +1102,9 @@ function mountNoteWidget() {
|
|
|
886
1102
|
card.hidden = false;
|
|
887
1103
|
opened = true;
|
|
888
1104
|
resetTitle();
|
|
889
|
-
if (tagInput.value.length === 0) tagInput.value = defaultTag;
|
|
890
1105
|
textarea.focus();
|
|
891
|
-
defaultTag = await
|
|
892
|
-
|
|
1106
|
+
defaultTag = (await fetchUiOptions()).defaultTag;
|
|
1107
|
+
tagEditor.setDefault(defaultTag);
|
|
893
1108
|
};
|
|
894
1109
|
const close = () => {
|
|
895
1110
|
card.hidden = true;
|
|
@@ -913,7 +1128,7 @@ function mountNoteWidget() {
|
|
|
913
1128
|
headers: { "content-type": "application/json" },
|
|
914
1129
|
body: JSON.stringify({
|
|
915
1130
|
title: titleInput.value.trim(),
|
|
916
|
-
|
|
1131
|
+
tags: tagEditor.getTags(),
|
|
917
1132
|
text
|
|
918
1133
|
}),
|
|
919
1134
|
signal: AbortSignal.timeout(1e4)
|
|
@@ -947,7 +1162,7 @@ function mountNoteWidget() {
|
|
|
947
1162
|
const doEdit = async () => {
|
|
948
1163
|
const title = titleInput.value.trim().length > 0 ? titleInput.value.trim() : timestampTitle();
|
|
949
1164
|
const text = textarea.value;
|
|
950
|
-
const
|
|
1165
|
+
const tags = tagEditor.getTags();
|
|
951
1166
|
edit.disabled = true;
|
|
952
1167
|
edit.textContent = "打开中…";
|
|
953
1168
|
try {
|
|
@@ -956,7 +1171,7 @@ function mountNoteWidget() {
|
|
|
956
1171
|
headers: { "content-type": "application/json" },
|
|
957
1172
|
body: JSON.stringify({
|
|
958
1173
|
title,
|
|
959
|
-
|
|
1174
|
+
tags,
|
|
960
1175
|
text
|
|
961
1176
|
}),
|
|
962
1177
|
signal: AbortSignal.timeout(1e4)
|
|
@@ -982,10 +1197,27 @@ function mountNoteWidget() {
|
|
|
982
1197
|
edit.addEventListener("click", () => {
|
|
983
1198
|
doEdit();
|
|
984
1199
|
});
|
|
985
|
-
return
|
|
986
|
-
|
|
1200
|
+
return root;
|
|
1201
|
+
}
|
|
1202
|
+
/**
|
|
1203
|
+
* Mount the floating quick-note widget. Fetches /status first: when
|
|
1204
|
+
* `ui.showQuickNote` is off the widget is never created (no DOM side effects).
|
|
1205
|
+
* Returns a disposer that removes it.
|
|
1206
|
+
*/
|
|
1207
|
+
function mountNoteWidget() {
|
|
1208
|
+
let disposed = false;
|
|
1209
|
+
let root;
|
|
1210
|
+
const disposer = () => {
|
|
1211
|
+
disposed = true;
|
|
1212
|
+
root?.remove();
|
|
987
1213
|
document.querySelector(".dsh-tw-toast")?.remove();
|
|
988
1214
|
};
|
|
1215
|
+
(async () => {
|
|
1216
|
+
const ui = await fetchUiOptions();
|
|
1217
|
+
if (disposed || !ui.showQuickNote) return;
|
|
1218
|
+
root = buildWidget();
|
|
1219
|
+
})();
|
|
1220
|
+
return disposer;
|
|
989
1221
|
}
|
|
990
1222
|
//#endregion
|
|
991
1223
|
//#region src/client/settings-page.ts
|
|
@@ -1103,6 +1335,7 @@ function renderConfigSection(body, config, refresh) {
|
|
|
1103
1335
|
section.append(make("h3", "dsh-tw-settings-h", "常规配置"));
|
|
1104
1336
|
const note = config.note ?? {};
|
|
1105
1337
|
const git = config.git ?? {};
|
|
1338
|
+
const ui = config.ui ?? {};
|
|
1106
1339
|
const fields = [];
|
|
1107
1340
|
const textField = (key, label, initial) => {
|
|
1108
1341
|
const input = make("input", "dsh-tw-settings-input");
|
|
@@ -1153,6 +1386,8 @@ function renderConfigSection(body, config, refresh) {
|
|
|
1153
1386
|
numField("git.debounceMs", "自动 commit 防抖(ms)", typeof git.debounceMs === "number" ? git.debounceMs : 6e4);
|
|
1154
1387
|
textField("git.remote", "git 远端(空=仅本地)", typeof git.remote === "string" ? git.remote : "");
|
|
1155
1388
|
textField("git.branch", "git 分支", typeof git.branch === "string" ? git.branch : "main");
|
|
1389
|
+
checkField("ui.showQuickNote", "显示「快速笔记」悬浮按钮", ui.showQuickNote !== false);
|
|
1390
|
+
checkField("ui.sidebarLeftCss", "侧边栏移左 CSS(随启动补丁还原被删除的样式)", ui.sidebarLeftCss !== false);
|
|
1156
1391
|
const save = make("button", "dsh-tw-settings-btn dsh-tw-settings-primary", "保存配置");
|
|
1157
1392
|
save.type = "button";
|
|
1158
1393
|
save.addEventListener("click", () => {
|