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.js
CHANGED
|
@@ -54,6 +54,21 @@ const CSS_TEXT = `
|
|
|
54
54
|
.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; }
|
|
55
55
|
.dsh-tw-entry svg { flex: none; }
|
|
56
56
|
.dsh-tw-entry .dsh-tw-entry-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
57
|
+
/* Collapsed rail: the shell narrows the sidebar to a 56px icon rail (dual
|
|
58
|
+
signals — the frame's data-sidebar-collapsed + the sidebar root's hashed
|
|
59
|
+
*_collapsed class, same doctrine as dsh-taskboard 0.4.3). Mirror the
|
|
60
|
+
native 36×36 icon-button geometry: center the icon, hide the label, and
|
|
61
|
+
scale it up to match the shell's 18px rail icons. */
|
|
62
|
+
[data-sidebar-collapsed] [data-dsh-tw-entry],
|
|
63
|
+
[class*="_collapsed"] [data-dsh-tw-entry] {
|
|
64
|
+
width: 36px; height: 36px; min-width: 36px;
|
|
65
|
+
margin: 0 0 12px; padding: 0;
|
|
66
|
+
justify-content: center; gap: 0; text-align: center;
|
|
67
|
+
}
|
|
68
|
+
[data-sidebar-collapsed] [data-dsh-tw-entry] .dsh-tw-entry-label,
|
|
69
|
+
[class*="_collapsed"] [data-dsh-tw-entry] .dsh-tw-entry-label { display: none; }
|
|
70
|
+
[data-sidebar-collapsed] [data-dsh-tw-entry] svg,
|
|
71
|
+
[class*="_collapsed"] [data-dsh-tw-entry] svg { width: 18px; height: 18px; }
|
|
57
72
|
|
|
58
73
|
/* ── center-column panel (fixed overlay, JS-pinned to the column rect) ── */
|
|
59
74
|
.dsh-tw-view {
|
|
@@ -80,6 +95,13 @@ html[data-dsh-tw-active] .dshDesktopConversationSurface > :not([data-dsh-tw-view
|
|
|
80
95
|
}
|
|
81
96
|
.dsh-tw-panel-bar button:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,.12)); }
|
|
82
97
|
.dsh-tw-panel-title { font-weight: 600; margin-right: auto; }
|
|
98
|
+
/* dsh-better-sidebar coexistence: the floating toggle cluster (two 28px
|
|
99
|
+
buttons at the viewport's top-right) lives above the panel (z-index is
|
|
100
|
+
handled in JS — the panel stays below the host layer), so reserve the
|
|
101
|
+
cluster's width at the panel bar's right end — mirroring how
|
|
102
|
+
dsh-better-sidebar pads the DSH session header — and the panel's own
|
|
103
|
+
状态/重载 buttons never sit underneath it. */
|
|
104
|
+
.dsh-tw-view[data-sidebar-host] .dsh-tw-panel-bar { padding-right: 80px; }
|
|
83
105
|
.dsh-tw-status-chip { font-variant-numeric: tabular-nums; }
|
|
84
106
|
.dsh-tw-status-chip[data-state="running"] { color: var(--dsw-alias-state-success-primary, #3eaa5f); }
|
|
85
107
|
.dsh-tw-status-chip[data-state="starting"] { color: var(--dsw-alias-state-warning-primary, #d9822b); }
|
|
@@ -162,6 +184,34 @@ html[data-dsh-tw-active] .dshDesktopConversationSurface > :not([data-dsh-tw-view
|
|
|
162
184
|
}
|
|
163
185
|
.dsh-tw-note-edit:hover:not(:disabled) { filter: brightness(.96); }
|
|
164
186
|
.dsh-tw-note-edit:disabled { opacity: .55; cursor: default; }
|
|
187
|
+
/* ── Multi-tag chip editor (quick-note tags) ─────────────────────────────── */
|
|
188
|
+
.dsh-tw-note-tags { position: relative; flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 4px; }
|
|
189
|
+
.dsh-tw-note-chips { display: flex; flex-wrap: wrap; gap: 4px; }
|
|
190
|
+
.dsh-tw-note-tagchip {
|
|
191
|
+
display: inline-flex; align-items: center; gap: 4px;
|
|
192
|
+
padding: 2px 6px 2px 8px; border-radius: 999px; font-size: 11px; line-height: 1.4;
|
|
193
|
+
background: color-mix(in srgb, var(--dsw-alias-brand-primary, #3e63dd) 10%, var(--dsw-alias-bg-layer-1, #fff));
|
|
194
|
+
border: 1px solid color-mix(in srgb, var(--dsw-alias-brand-primary, #3e63dd) 30%, transparent);
|
|
195
|
+
color: var(--dsw-alias-label-primary, #222);
|
|
196
|
+
}
|
|
197
|
+
.dsh-tw-note-tagchip-x {
|
|
198
|
+
cursor: pointer; font-size: 12px; line-height: 1; padding: 0 2px; border-radius: 50%;
|
|
199
|
+
color: var(--dsw-alias-label-secondary, #888);
|
|
200
|
+
}
|
|
201
|
+
.dsh-tw-note-tagchip-x:hover { color: var(--dsw-alias-state-error-primary, #d13b3b); }
|
|
202
|
+
.dsh-tw-note-taginput { width: 100%; box-sizing: border-box; }
|
|
203
|
+
.dsh-tw-note-tagsuggest {
|
|
204
|
+
position: absolute; left: 0; right: 0; top: calc(100% + 2px); z-index: 60;
|
|
205
|
+
max-height: 168px; overflow-y: auto; border-radius: 8px; padding: 4px;
|
|
206
|
+
background: var(--dsw-alias-bg-layer-2, #fff); color: var(--dsw-alias-label-primary, #222);
|
|
207
|
+
border: 1px solid var(--dsw-alias-border-l2, rgba(0,0,0,.18));
|
|
208
|
+
box-shadow: var(--dsw-shadow-lv2, 0 4px 16px rgba(0,0,0,.16));
|
|
209
|
+
font-size: 12px;
|
|
210
|
+
}
|
|
211
|
+
.dsh-tw-note-tagsuggest-item {
|
|
212
|
+
padding: 6px 9px; border-radius: 6px; cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
213
|
+
}
|
|
214
|
+
.dsh-tw-note-tagsuggest-item:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,.12)); }
|
|
165
215
|
/* ── Native-editor popup iframe (quick-note "在 TW 中编辑") ──────────────── */
|
|
166
216
|
.dsh-tw-editor-popup {
|
|
167
217
|
position: fixed; left: 0; right: 0; top: 0; bottom: 0; margin: auto;
|
|
@@ -435,10 +485,35 @@ const ACTIVATE_EVENT = "dsh-panel-activate";
|
|
|
435
485
|
const PANEL_NAME = "dsh-tiddlywiki";
|
|
436
486
|
/** Overlay z-index: above the shell content, below the note widget (950). */
|
|
437
487
|
const PANEL_Z_INDEX = 40;
|
|
488
|
+
/**
|
|
489
|
+
* dsh-better-sidebar's unified fixed host layer (its persistent
|
|
490
|
+
* expand/collapse toggle cluster lives inside it, at a global z-index of 25
|
|
491
|
+
* — its internal 45 is trapped by the host's stacking context). When this
|
|
492
|
+
* host is present the panel must stay BELOW it, so the sidebar toggle
|
|
493
|
+
* buttons stay visible and clickable above the full-screen TW panel.
|
|
494
|
+
*/
|
|
495
|
+
const PANEL_HOST_SELECTOR = "[data-dsh-panel-host]";
|
|
496
|
+
/** The app's own shell overlay layer (dsh-client-ui-layout pins it at 20). */
|
|
497
|
+
const APP_OVERLAY_Z_INDEX = 20;
|
|
438
498
|
/** Safety re-measure cadence for shell layout changes CSS can't see. */
|
|
439
499
|
const SYNC_INTERVAL_MS = 2e3;
|
|
440
500
|
const STATUS_ENDPOINT$1 = "/dsh-tiddlywiki/status";
|
|
441
501
|
const RESTART_ENDPOINT$1 = "/dsh-tiddlywiki/restart";
|
|
502
|
+
/**
|
|
503
|
+
* The panel's z-index: below any dsh-better-sidebar host layer so its
|
|
504
|
+
* persistent toggle cluster (top-right corner) stays visible and clickable
|
|
505
|
+
* above the full-screen TW panel, otherwise the default 40. The host's live
|
|
506
|
+
* computed z-index is read instead of hardcoding 25, so the rule tracks
|
|
507
|
+
* plugin updates; the panel is still clamped above the app's own shell
|
|
508
|
+
* overlay layer (dsh-client-ui-layout pins it at 20).
|
|
509
|
+
*/
|
|
510
|
+
function resolvePanelZIndex() {
|
|
511
|
+
const host = document.querySelector(PANEL_HOST_SELECTOR);
|
|
512
|
+
if (host === null) return PANEL_Z_INDEX;
|
|
513
|
+
const parsed = parseInt(getComputedStyle(host).zIndex, 10);
|
|
514
|
+
if (!Number.isFinite(parsed)) return PANEL_Z_INDEX;
|
|
515
|
+
return Math.max(APP_OVERLAY_Z_INDEX, Math.min(PANEL_Z_INDEX, parsed - 1));
|
|
516
|
+
}
|
|
442
517
|
function conversationColumn() {
|
|
443
518
|
for (const selector of COLUMN_SELECTORS) {
|
|
444
519
|
const el = document.querySelector(selector);
|
|
@@ -532,13 +607,25 @@ function mountPanel(state) {
|
|
|
532
607
|
if (container.style.width !== width) container.style.width = width;
|
|
533
608
|
if (container.style.height !== height) container.style.height = height;
|
|
534
609
|
};
|
|
610
|
+
/**
|
|
611
|
+
* Coexist with dsh-better-sidebar: keep the panel's z-index below the
|
|
612
|
+
* host layer (so its toggle cluster stays clickable above the panel) and
|
|
613
|
+
* flag the host's presence so CSS can reserve the cluster's width at the
|
|
614
|
+
* panel bar's right end. Re-run whenever the host mounts/unmounts.
|
|
615
|
+
*/
|
|
616
|
+
const applyChrome = () => {
|
|
617
|
+
if (container === void 0) return;
|
|
618
|
+
container.style.zIndex = String(resolvePanelZIndex());
|
|
619
|
+
if (document.querySelector(PANEL_HOST_SELECTOR) !== null) container.dataset.sidebarHost = "1";
|
|
620
|
+
else delete container.dataset.sidebarHost;
|
|
621
|
+
};
|
|
535
622
|
const ensure = () => {
|
|
536
623
|
if (container !== void 0) return;
|
|
537
624
|
columnEl = conversationColumn();
|
|
538
625
|
if (columnEl === void 0) return;
|
|
539
626
|
container = build();
|
|
540
627
|
container.style.position = "fixed";
|
|
541
|
-
|
|
628
|
+
applyChrome();
|
|
542
629
|
document.body.append(container);
|
|
543
630
|
syncRect();
|
|
544
631
|
};
|
|
@@ -640,6 +727,7 @@ function mountPanel(state) {
|
|
|
640
727
|
};
|
|
641
728
|
const waitObserver = new MutationObserver(() => {
|
|
642
729
|
ensure();
|
|
730
|
+
applyChrome();
|
|
643
731
|
});
|
|
644
732
|
waitObserver.observe(document.body, {
|
|
645
733
|
childList: true,
|
|
@@ -808,14 +896,23 @@ function ensurePopup() {
|
|
|
808
896
|
* collapsible, independent of any shell DOM. Lets the human jot drafts /
|
|
809
897
|
* scratch notes while waiting for the AI or drafting the next prompt.
|
|
810
898
|
*
|
|
811
|
-
* Save posts to /dsh-tiddlywiki/note → an independent tiddler (title &
|
|
899
|
+
* Save posts to /dsh-tiddlywiki/note → an independent tiddler (title & tags
|
|
812
900
|
* editable; defaults: timestamp title + config tag, usually "inbox").
|
|
813
901
|
*
|
|
902
|
+
* ui.showQuickNote config: the widget is mounted async and stays hidden (and
|
|
903
|
+
* never appended) when the option is off, so the toggle button can be disabled
|
|
904
|
+
* from the settings page without touching the shell.
|
|
905
|
+
*
|
|
906
|
+
* Tag editor: multi-select chips + autocomplete from the wiki's existing tags
|
|
907
|
+
* (GET /dsh-tiddlywiki/tags). Enter/`,` commits the typed value; Backspace on
|
|
908
|
+
* an empty field removes the last chip; the dropdown filters on typing.
|
|
909
|
+
*
|
|
814
910
|
* @module dsh-tiddlywiki/client/note-widget
|
|
815
911
|
*/
|
|
816
912
|
const NOTE_ENDPOINT = "/dsh-tiddlywiki/note";
|
|
817
913
|
const EDIT_ENDPOINT = "/dsh-tiddlywiki/edit";
|
|
818
914
|
const STATUS_ENDPOINT = "/dsh-tiddlywiki/status";
|
|
915
|
+
const TAGS_ENDPOINT = "/dsh-tiddlywiki/tags";
|
|
819
916
|
function pad(n) {
|
|
820
917
|
return n < 10 ? `0${n}` : String(n);
|
|
821
918
|
}
|
|
@@ -823,16 +920,137 @@ function pad(n) {
|
|
|
823
920
|
function timestampTitle(date = /* @__PURE__ */ new Date()) {
|
|
824
921
|
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
|
|
825
922
|
}
|
|
826
|
-
async function
|
|
923
|
+
async function fetchUiOptions() {
|
|
924
|
+
const fallback = {
|
|
925
|
+
showQuickNote: true,
|
|
926
|
+
defaultTag: "inbox"
|
|
927
|
+
};
|
|
827
928
|
try {
|
|
828
929
|
const res = await fetch(STATUS_ENDPOINT, { signal: AbortSignal.timeout(5e3) });
|
|
829
|
-
if (!res.ok) return
|
|
830
|
-
|
|
930
|
+
if (!res.ok) return fallback;
|
|
931
|
+
const payload = await res.json();
|
|
932
|
+
return {
|
|
933
|
+
showQuickNote: payload.ui?.showQuickNote !== false,
|
|
934
|
+
defaultTag: payload.note?.tag ?? "inbox"
|
|
935
|
+
};
|
|
831
936
|
} catch {
|
|
832
|
-
return
|
|
937
|
+
return fallback;
|
|
833
938
|
}
|
|
834
939
|
}
|
|
835
|
-
|
|
940
|
+
/** Multi-tag chip editor with autocomplete from the wiki's existing tags. */
|
|
941
|
+
function buildTagEditor() {
|
|
942
|
+
const wrap = document.createElement("div");
|
|
943
|
+
wrap.className = "dsh-tw-note-tags";
|
|
944
|
+
const chipWrap = document.createElement("div");
|
|
945
|
+
chipWrap.className = "dsh-tw-note-chips";
|
|
946
|
+
const input = document.createElement("input");
|
|
947
|
+
input.className = "dsh-tw-note-taginput";
|
|
948
|
+
input.placeholder = "tag(可多选,自动补全)";
|
|
949
|
+
const suggest = document.createElement("div");
|
|
950
|
+
suggest.className = "dsh-tw-note-tagsuggest";
|
|
951
|
+
suggest.hidden = true;
|
|
952
|
+
wrap.append(chipWrap, input, suggest);
|
|
953
|
+
const chips = [];
|
|
954
|
+
const hideSuggest = () => {
|
|
955
|
+
suggest.hidden = true;
|
|
956
|
+
};
|
|
957
|
+
const renderChips = () => {
|
|
958
|
+
chipWrap.replaceChildren();
|
|
959
|
+
for (const tag of chips) {
|
|
960
|
+
const chip = document.createElement("span");
|
|
961
|
+
chip.className = "dsh-tw-note-tagchip";
|
|
962
|
+
chip.textContent = tag;
|
|
963
|
+
const x = document.createElement("span");
|
|
964
|
+
x.className = "dsh-tw-note-tagchip-x";
|
|
965
|
+
x.textContent = "×";
|
|
966
|
+
x.title = `移除 tag「${tag}」`;
|
|
967
|
+
x.addEventListener("click", (event) => {
|
|
968
|
+
event.stopPropagation();
|
|
969
|
+
const i = chips.indexOf(tag);
|
|
970
|
+
if (i >= 0) {
|
|
971
|
+
chips.splice(i, 1);
|
|
972
|
+
renderChips();
|
|
973
|
+
}
|
|
974
|
+
});
|
|
975
|
+
chip.append(x);
|
|
976
|
+
chipWrap.append(chip);
|
|
977
|
+
}
|
|
978
|
+
};
|
|
979
|
+
const addTag = (tag) => {
|
|
980
|
+
const t = tag.trim();
|
|
981
|
+
if (t.length === 0 || chips.includes(t)) return;
|
|
982
|
+
chips.push(t);
|
|
983
|
+
input.value = "";
|
|
984
|
+
renderChips();
|
|
985
|
+
hideSuggest();
|
|
986
|
+
input.focus();
|
|
987
|
+
};
|
|
988
|
+
const commitInput = () => {
|
|
989
|
+
for (const raw of input.value.split(/\s+/)) addTag(raw);
|
|
990
|
+
};
|
|
991
|
+
let knownTags = [];
|
|
992
|
+
let tagsPromise;
|
|
993
|
+
const ensureTags = () => {
|
|
994
|
+
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(() => []);
|
|
995
|
+
tagsPromise.then((list) => {
|
|
996
|
+
knownTags = list;
|
|
997
|
+
});
|
|
998
|
+
return tagsPromise;
|
|
999
|
+
};
|
|
1000
|
+
const showSuggest = () => {
|
|
1001
|
+
const q = input.value.trim().toLowerCase();
|
|
1002
|
+
const matches = knownTags.filter((t) => !chips.includes(t) && (q.length === 0 || t.toLowerCase().includes(q))).slice(0, 8);
|
|
1003
|
+
suggest.replaceChildren();
|
|
1004
|
+
for (const tag of matches) {
|
|
1005
|
+
const item = document.createElement("div");
|
|
1006
|
+
item.className = "dsh-tw-note-tagsuggest-item";
|
|
1007
|
+
item.textContent = tag;
|
|
1008
|
+
item.addEventListener("mousedown", (event) => {
|
|
1009
|
+
event.preventDefault();
|
|
1010
|
+
addTag(tag);
|
|
1011
|
+
});
|
|
1012
|
+
suggest.append(item);
|
|
1013
|
+
}
|
|
1014
|
+
suggest.hidden = matches.length === 0;
|
|
1015
|
+
};
|
|
1016
|
+
input.addEventListener("focus", () => {
|
|
1017
|
+
ensureTags().then(showSuggest);
|
|
1018
|
+
});
|
|
1019
|
+
input.addEventListener("input", () => {
|
|
1020
|
+
if (knownTags.length === 0) ensureTags().then(showSuggest);
|
|
1021
|
+
else showSuggest();
|
|
1022
|
+
});
|
|
1023
|
+
input.addEventListener("keydown", (event) => {
|
|
1024
|
+
if (event.key === "Enter" || event.key === ",") {
|
|
1025
|
+
event.preventDefault();
|
|
1026
|
+
commitInput();
|
|
1027
|
+
} else if (event.key === "Backspace" && input.value.length === 0 && chips.length > 0) {
|
|
1028
|
+
chips.pop();
|
|
1029
|
+
renderChips();
|
|
1030
|
+
} else if (event.key === "Escape") hideSuggest();
|
|
1031
|
+
});
|
|
1032
|
+
document.addEventListener("click", (event) => {
|
|
1033
|
+
if (!wrap.contains(event.target)) hideSuggest();
|
|
1034
|
+
}, true);
|
|
1035
|
+
return {
|
|
1036
|
+
el: wrap,
|
|
1037
|
+
getTags: () => {
|
|
1038
|
+
commitInput();
|
|
1039
|
+
return [...chips];
|
|
1040
|
+
},
|
|
1041
|
+
setDefault: (tag) => {
|
|
1042
|
+
if (chips.length === 0) {
|
|
1043
|
+
const t = tag.trim();
|
|
1044
|
+
if (t.length > 0) {
|
|
1045
|
+
chips.push(t);
|
|
1046
|
+
renderChips();
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
};
|
|
1051
|
+
}
|
|
1052
|
+
/** Build the whole widget DOM and wire it up (returns the appended root). */
|
|
1053
|
+
function buildWidget() {
|
|
836
1054
|
const root = document.createElement("div");
|
|
837
1055
|
root.className = "dsh-tw-note";
|
|
838
1056
|
const card = document.createElement("div");
|
|
@@ -854,10 +1072,8 @@ function mountNoteWidget() {
|
|
|
854
1072
|
const titleInput = document.createElement("input");
|
|
855
1073
|
titleInput.className = "dsh-tw-note-title";
|
|
856
1074
|
titleInput.placeholder = "标题(默认时间戳)";
|
|
857
|
-
const
|
|
858
|
-
|
|
859
|
-
tagInput.placeholder = "tag(默认 inbox)";
|
|
860
|
-
fields.append(titleInput, tagInput);
|
|
1075
|
+
const tagEditor = buildTagEditor();
|
|
1076
|
+
fields.append(titleInput, tagEditor.el);
|
|
861
1077
|
const textarea = document.createElement("textarea");
|
|
862
1078
|
textarea.className = "dsh-tw-note-text";
|
|
863
1079
|
textarea.placeholder = "写点东西…(Ctrl+Enter 保存)";
|
|
@@ -892,10 +1108,9 @@ function mountNoteWidget() {
|
|
|
892
1108
|
card.hidden = false;
|
|
893
1109
|
opened = true;
|
|
894
1110
|
resetTitle();
|
|
895
|
-
if (tagInput.value.length === 0) tagInput.value = defaultTag;
|
|
896
1111
|
textarea.focus();
|
|
897
|
-
defaultTag = await
|
|
898
|
-
|
|
1112
|
+
defaultTag = (await fetchUiOptions()).defaultTag;
|
|
1113
|
+
tagEditor.setDefault(defaultTag);
|
|
899
1114
|
};
|
|
900
1115
|
const close = () => {
|
|
901
1116
|
card.hidden = true;
|
|
@@ -919,7 +1134,7 @@ function mountNoteWidget() {
|
|
|
919
1134
|
headers: { "content-type": "application/json" },
|
|
920
1135
|
body: JSON.stringify({
|
|
921
1136
|
title: titleInput.value.trim(),
|
|
922
|
-
|
|
1137
|
+
tags: tagEditor.getTags(),
|
|
923
1138
|
text
|
|
924
1139
|
}),
|
|
925
1140
|
signal: AbortSignal.timeout(1e4)
|
|
@@ -953,7 +1168,7 @@ function mountNoteWidget() {
|
|
|
953
1168
|
const doEdit = async () => {
|
|
954
1169
|
const title = titleInput.value.trim().length > 0 ? titleInput.value.trim() : timestampTitle();
|
|
955
1170
|
const text = textarea.value;
|
|
956
|
-
const
|
|
1171
|
+
const tags = tagEditor.getTags();
|
|
957
1172
|
edit.disabled = true;
|
|
958
1173
|
edit.textContent = "打开中…";
|
|
959
1174
|
try {
|
|
@@ -962,7 +1177,7 @@ function mountNoteWidget() {
|
|
|
962
1177
|
headers: { "content-type": "application/json" },
|
|
963
1178
|
body: JSON.stringify({
|
|
964
1179
|
title,
|
|
965
|
-
|
|
1180
|
+
tags,
|
|
966
1181
|
text
|
|
967
1182
|
}),
|
|
968
1183
|
signal: AbortSignal.timeout(1e4)
|
|
@@ -988,10 +1203,27 @@ function mountNoteWidget() {
|
|
|
988
1203
|
edit.addEventListener("click", () => {
|
|
989
1204
|
doEdit();
|
|
990
1205
|
});
|
|
991
|
-
return
|
|
992
|
-
|
|
1206
|
+
return root;
|
|
1207
|
+
}
|
|
1208
|
+
/**
|
|
1209
|
+
* Mount the floating quick-note widget. Fetches /status first: when
|
|
1210
|
+
* `ui.showQuickNote` is off the widget is never created (no DOM side effects).
|
|
1211
|
+
* Returns a disposer that removes it.
|
|
1212
|
+
*/
|
|
1213
|
+
function mountNoteWidget() {
|
|
1214
|
+
let disposed = false;
|
|
1215
|
+
let root;
|
|
1216
|
+
const disposer = () => {
|
|
1217
|
+
disposed = true;
|
|
1218
|
+
root?.remove();
|
|
993
1219
|
document.querySelector(".dsh-tw-toast")?.remove();
|
|
994
1220
|
};
|
|
1221
|
+
(async () => {
|
|
1222
|
+
const ui = await fetchUiOptions();
|
|
1223
|
+
if (disposed || !ui.showQuickNote) return;
|
|
1224
|
+
root = buildWidget();
|
|
1225
|
+
})();
|
|
1226
|
+
return disposer;
|
|
995
1227
|
}
|
|
996
1228
|
//#endregion
|
|
997
1229
|
//#region src/client/settings-page.ts
|
|
@@ -1109,6 +1341,7 @@ function renderConfigSection(body, config, refresh) {
|
|
|
1109
1341
|
section.append(make("h3", "dsh-tw-settings-h", "常规配置"));
|
|
1110
1342
|
const note = config.note ?? {};
|
|
1111
1343
|
const git = config.git ?? {};
|
|
1344
|
+
const ui = config.ui ?? {};
|
|
1112
1345
|
const fields = [];
|
|
1113
1346
|
const textField = (key, label, initial) => {
|
|
1114
1347
|
const input = make("input", "dsh-tw-settings-input");
|
|
@@ -1159,6 +1392,8 @@ function renderConfigSection(body, config, refresh) {
|
|
|
1159
1392
|
numField("git.debounceMs", "自动 commit 防抖(ms)", typeof git.debounceMs === "number" ? git.debounceMs : 6e4);
|
|
1160
1393
|
textField("git.remote", "git 远端(空=仅本地)", typeof git.remote === "string" ? git.remote : "");
|
|
1161
1394
|
textField("git.branch", "git 分支", typeof git.branch === "string" ? git.branch : "main");
|
|
1395
|
+
checkField("ui.showQuickNote", "显示「快速笔记」悬浮按钮", ui.showQuickNote !== false);
|
|
1396
|
+
checkField("ui.sidebarLeftCss", "侧边栏移左 CSS(随启动补丁还原被删除的样式)", ui.sidebarLeftCss !== false);
|
|
1162
1397
|
const save = make("button", "dsh-tw-settings-btn dsh-tw-settings-primary", "保存配置");
|
|
1163
1398
|
save.type = "button";
|
|
1164
1399
|
save.addEventListener("click", () => {
|