dsh-sidebar-suite 0.2.7 → 0.2.9
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 +28 -1
- package/DETAIL.md +9 -5
- package/README.md +29 -7
- package/lib/client.js +310 -27
- package/lib/temp-session-host.mjs +918 -842
- package/locale/en.json +1 -1
- package/locale/zh.json +1 -1
- package/package.json +3 -3
- package/temp-session-config.txt +3 -3
- package//344/275/277/347/224/250/350/257/264/346/230/216.md +52 -26
package/lib/client.js
CHANGED
|
@@ -179,6 +179,25 @@ window.__ModuleLoader__.load({
|
|
|
179
179
|
'settings.support': '喜欢这个插件?请作者喝杯咖啡 · Enjoy it? Buy me a coffee',
|
|
180
180
|
'settings.supportHint': '完全自愿,无任何回报承诺。用微信扫一扫即可;「设置 → 支持贡献者」里也登记了同一个码。 / Purely voluntary, no rewards promised. Scan with WeChat Pay; the same QR is registered under Settings → Support contributors.',
|
|
181
181
|
'settings.qrAlt': '微信支付收款码 / WeChat Pay QR',
|
|
182
|
+
// 0.2.8 — every remaining config key becomes a switch on this page.
|
|
183
|
+
'options.title': '侧栏与临时会话 · Sidebar & temp session options',
|
|
184
|
+
'options.help': '每一项都会写入 temp-session-config.txt。也可以直接用记事本改那个文件,保存后约 3 秒自动生效,不用重启 DSH;两边是同一个文件,不会打架。 / Every switch writes temp-session-config.txt. You can also edit that file in Notepad — saves apply in about 3 seconds, no restart. Both paths share one file so they never disagree.',
|
|
185
|
+
'options.showButton': '显示「临时」按钮 / Show the Temp button',
|
|
186
|
+
'options.inChatMode': 'CHAT 官网模式 / In CHAT mode',
|
|
187
|
+
'options.chatHide': '隐藏按钮 / Hide',
|
|
188
|
+
'options.chatShow': '照常显示 / Show',
|
|
189
|
+
'options.buttonStyle': '按钮摆法 / Button layout',
|
|
190
|
+
'options.stylePair': '并排 / Pair',
|
|
191
|
+
'options.styleOwn': '独占一行 / Own row',
|
|
192
|
+
'options.expandAll': '会话默认全展开 / Always expand sessions',
|
|
193
|
+
'options.expandHint': '关掉后恢复 DSH 原来的折叠行为;已经展开的列表要切换一次工作区才会收回去。 / Turning it off restores host folding; already-expanded groups collapse after you switch workspaces once.',
|
|
194
|
+
'options.maxKept': '最多保留 / Keep at most',
|
|
195
|
+
'options.maxKeptHint': '个临时会话(1–200) / temp sessions (1–200)',
|
|
196
|
+
'options.purge': '立即清理临时会话 / Clean up now',
|
|
197
|
+
'options.purging': '清理中… / Cleaning…',
|
|
198
|
+
'options.purged': '已清理 / Removed ',
|
|
199
|
+
'options.purgeFailed': '清理失败 / Cleanup failed: ',
|
|
200
|
+
'options.rev': '设置版本 / Settings rev',
|
|
182
201
|
'feedback.title': '反馈 · Feedback',
|
|
183
202
|
'feedback.help': '遇到问题,或者想要某个功能?写在下面,然后选一个入口。 / Hit a bug or want a feature? Write it below, then pick a channel.',
|
|
184
203
|
'feedback.placeholder': '例如:点了「临时」之后,侧栏没有出现新会话…… / e.g. clicking Temp adds no session to the sidebar…',
|
|
@@ -288,6 +307,55 @@ window.__ModuleLoader__.load({
|
|
|
288
307
|
'html[data-dsbs-rail="true"] [data-dsbs-temp-row]::after{display:none}',
|
|
289
308
|
'[data-dsbs-temp-toast]{position:fixed;left:50%;bottom:28px;transform:translateX(-50%);z-index:9500;max-width:min(560px,calc(100vw - 40px));box-sizing:border-box;padding:9px 14px;border-radius:10px;background:var(--dsw-alias-bg-overlay,var(--dsw-alias-bg-base,#1f1f1f));color:var(--dsw-alias-label-primary,inherit);border:1px solid var(--dsw-alias-border-l2,rgba(127,127,127,.3));box-shadow:0 12px 34px rgba(0,0,0,.34);font:inherit;font-size:12px;line-height:1.6;word-break:break-word}',
|
|
290
309
|
'[data-dsbs-temp-toast][data-dsbs-temp-toast-error="true"]{color:var(--dsw-alias-state-business-danger,#d9534f);border-color:rgba(217,83,79,.45)}',
|
|
310
|
+
// ---- sidebar polish (0.2.9) --------------------------------------
|
|
311
|
+
// The three layout modes are mutually exclusive, so they share one
|
|
312
|
+
// segmented pill; the buttons that belong to the chosen layout sit on
|
|
313
|
+
// the far side of a divider, which keeps "pick a layout" and "act on
|
|
314
|
+
// it" visually distinct in a 280px column.
|
|
315
|
+
//
|
|
316
|
+
// Glyphs are CSS masks, not <svg> children: makeButton() owns a plain
|
|
317
|
+
// text node that relabel() rewrites (an inline <svg> would be wiped),
|
|
318
|
+
// and the test stubs have no createElementNS. A mask also paints in
|
|
319
|
+
// currentColor, so the icons follow the host theme on their own.
|
|
320
|
+
'[data-dsbs-bar]{display:flex;align-items:center;gap:6px;min-height:30px;flex-wrap:nowrap}',
|
|
321
|
+
'[data-dsbs-seg]{display:inline-flex;align-items:center;gap:2px;padding:2px;border-radius:9px;flex:none;background:color-mix(in srgb,currentColor 11%,transparent)}',
|
|
322
|
+
'[data-dsbs-bar] button[data-dsbs-glyph]{position:relative;display:flex;align-items:center;justify-content:center;width:28px;height:26px;min-width:0;padding:0!important;border:0;border-radius:7px;font-size:0!important;line-height:0!important;background:transparent;color:inherit;cursor:pointer;transition:background-color .14s ease,opacity .14s ease}',
|
|
323
|
+
'[data-dsbs-bar] button[data-dsbs-glyph]:not([aria-pressed="true"]):hover{background:color-mix(in srgb,currentColor 12%,transparent)}',
|
|
324
|
+
'[data-dsbs-seg] button[aria-pressed="true"]{background:color-mix(in srgb,currentColor 20%,transparent);box-shadow:0 1px 2px rgba(0,0,0,.22)}',
|
|
325
|
+
'[data-dsbs-seg] button[aria-pressed="false"]{opacity:.70}',
|
|
326
|
+
'[data-dsbs-seg] button[aria-pressed="false"]:hover{opacity:.9;background:color-mix(in srgb,currentColor 9%,transparent)}',
|
|
327
|
+
'[data-dsbs-bar] button[data-dsbs-glyph]::before{content:"";position:absolute;inset:0;margin:auto;width:16px;height:16px;background:currentColor;opacity:.88;-webkit-mask-image:var(--dsbs-icon);-webkit-mask-size:16px 16px;-webkit-mask-position:center;-webkit-mask-repeat:no-repeat;mask-image:var(--dsbs-icon);mask-size:16px 16px;mask-position:center;mask-repeat:no-repeat}',
|
|
328
|
+
// a — one column of stacked rows
|
|
329
|
+
'[data-dsbs-bar] [data-dsbs-glyph="a"]{--dsbs-icon:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 16 16%22 fill=%22none%22 stroke=%22%23000%22 stroke-width=%221.5%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22%3E%3Crect x=%222.5%22 y=%222.5%22 width=%2211%22 height=%2211%22 rx=%222.5%22/%3E%3Cpath d=%22M5.5 6.5h5M5.5 9.5h3%22/%3E%3C/svg%3E")}',
|
|
330
|
+
// b — one column split down the middle
|
|
331
|
+
'[data-dsbs-bar] [data-dsbs-glyph="b"]{--dsbs-icon:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 16 16%22 fill=%22none%22 stroke=%22%23000%22 stroke-width=%221.5%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22%3E%3Crect x=%222.5%22 y=%222.5%22 width=%2211%22 height=%2211%22 rx=%222.5%22/%3E%3Cpath d=%22M8 3v10%22/%3E%3C/svg%3E")}',
|
|
332
|
+
// c — a panel floating above the column
|
|
333
|
+
'[data-dsbs-bar] [data-dsbs-glyph="c"]{--dsbs-icon:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 16 16%22 fill=%22none%22 stroke=%22%23000%22 stroke-width=%221.5%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22%3E%3Crect x=%222.5%22 y=%226.5%22 width=%229%22 height=%227%22 rx=%221.8%22/%3E%3Cpath d=%22M7.2 6.5V4.4a1.6 1.6 0 011.6-1.6h3.1a1.6 1.6 0 011.6 1.6v3.9%22/%3E%3C/svg%3E")}',
|
|
334
|
+
// fold — collapse one list (single chevron)
|
|
335
|
+
'[data-dsbs-bar] [data-dsbs-glyph="fold"]{--dsbs-icon:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 16 16%22 fill=%22none%22 stroke=%22%23000%22 stroke-width=%221.5%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22%3E%3Cpath d=%22M3 3.5h10M8 6v6M5.6 9.6L8 12l2.4-2.4%22/%3E%3C/svg%3E")}',
|
|
336
|
+
// fold all — two chevrons, so the two fold buttons never look alike
|
|
337
|
+
'[data-dsbs-bar] [data-dsbs-glyph="fold2"]{--dsbs-icon:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 16 16%22 fill=%22none%22 stroke=%22%23000%22 stroke-width=%221.5%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22%3E%3Cpath d=%22M3 3h10M5.6 6.4L8 8.8l2.4-2.4M5.6 10.2L8 12.6l2.4-2.4%22/%3E%3C/svg%3E")}',
|
|
338
|
+
// swap — trade the two tabs
|
|
339
|
+
'[data-dsbs-bar] [data-dsbs-glyph="swap"]{--dsbs-icon:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 16 16%22 fill=%22none%22 stroke=%22%23000%22 stroke-width=%221.5%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22%3E%3Cpath d=%22M2.5 5.5h9.5M9.5 3.2l2.5 2.3-2.5 2.3%22/%3E%3Cpath d=%22M13.5 10.5H4M6.5 8.2l-2.5 2.3 2.5 2.3%22/%3E%3C/svg%3E")}',
|
|
340
|
+
// pop — detach the panel
|
|
341
|
+
'[data-dsbs-bar] [data-dsbs-glyph="pop"]{--dsbs-icon:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 16 16%22 fill=%22none%22 stroke=%22%23000%22 stroke-width=%221.5%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22%3E%3Crect x=%222.5%22 y=%222.5%22 width=%228%22 height=%228%22 rx=%221.8%22/%3E%3Cpath d=%22M9.5 13.5h3.5v-3.5M13 13l-4-4%22/%3E%3C/svg%3E")}',
|
|
342
|
+
// cycle — walk the three layouts in order
|
|
343
|
+
'[data-dsbs-bar] [data-dsbs-glyph="quick"]{--dsbs-icon:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 16 16%22 fill=%22none%22 stroke=%22%23000%22 stroke-width=%221.5%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22%3E%3Cpath d=%22M13.4 7A5.5 5.5 0 004 4.2M2.6 9A5.5 5.5 0 0012 11.8%22/%3E%3Cpath d=%22M12.6 1.8v3h-3M3.4 14.2v-3h3%22/%3E%3C/svg%3E")}',
|
|
344
|
+
// The divider keeps the action buttons apart from the mode pill; it
|
|
345
|
+
// rides along to the right edge so the row never looks lopsided.
|
|
346
|
+
'[data-dsbs-bar]>span{margin-left:auto;margin-right:4px;height:16px;width:1px;opacity:.26;background:currentColor;flex:none}',
|
|
347
|
+
'[data-dsbs-bar]>span[hidden]{display:none!important}',
|
|
348
|
+
// Wide column: the action buttons win their words back (the mode pill
|
|
349
|
+
// stays iconic — its three options are read as one control). Only the
|
|
350
|
+
// direct children grow, so the pill is untouched.
|
|
351
|
+
'html:not([data-dsbs-narrow="true"]) [data-dsbs-bar]>button[data-dsbs-glyph]{width:auto!important;min-width:max-content!important;max-width:none!important;box-sizing:border-box;white-space:nowrap;padding:0 11px 0 31px!important;font-size:12px!important;line-height:1!important;justify-content:flex-start}',
|
|
352
|
+
'html:not([data-dsbs-narrow="true"]) [data-dsbs-bar]>button[data-dsbs-glyph]::before{inset:auto;left:9px;top:50%;margin:0;transform:translateY(-50%)}',
|
|
353
|
+
// Host session rows: a 2px accent rail on the active row, drawn with an
|
|
354
|
+
// inset shadow so no box changes size, plus a soft transition. Both are
|
|
355
|
+
// read-only decoration on the host's own markup.
|
|
356
|
+
'[data-row-key^="session:"]{transition:background-color .13s ease,box-shadow .13s ease,color .13s ease;font-variant-numeric:tabular-nums}',
|
|
357
|
+
'[data-row-key^="session:"][aria-selected="true"]{box-shadow:inset 2px 0 0 var(--dsw-alias-state-business-primary,var(--dsw-alias-label-primary,#4d6bfe))!important}',
|
|
358
|
+
'html[data-dsbs-rail="true"] [data-row-key^="session:"][aria-selected="true"]{box-shadow:none!important}',
|
|
291
359
|
].join('\n')
|
|
292
360
|
|
|
293
361
|
function assign(target) {
|
|
@@ -498,6 +566,10 @@ window.__ModuleLoader__.load({
|
|
|
498
566
|
var tempSlot = null // wrapper that keeps the root's top-level <button> count at one
|
|
499
567
|
var tempNewChatHost = null
|
|
500
568
|
var tempChatPolicy = 'hide' // TEMP_IN_CHAT_MODE in temp-session-config.txt
|
|
569
|
+
// 0.2.8 hot reload: these are re-applied whenever the config file changes.
|
|
570
|
+
var tempShowButton = true
|
|
571
|
+
var tempConfigRevision = null
|
|
572
|
+
var configPollPending = false
|
|
501
573
|
var tempChatGuard = null
|
|
502
574
|
var tempIds = [] // session ids the Host half will delete on quit
|
|
503
575
|
var tempBusy = false
|
|
@@ -802,9 +874,23 @@ window.__ModuleLoader__.load({
|
|
|
802
874
|
return slot
|
|
803
875
|
}
|
|
804
876
|
|
|
877
|
+
// 0.2.8: the config can change while DSH is running, so visibility is
|
|
878
|
+
// re-applied on every paint instead of being a one-way boot-time hide.
|
|
879
|
+
function applyTempButtonVisibility() {
|
|
880
|
+
var hidden = !tempShowButton
|
|
881
|
+
if (tempButton) {
|
|
882
|
+
if (hidden) tempButton.setAttribute('hidden', '')
|
|
883
|
+
else tempButton.removeAttribute('hidden')
|
|
884
|
+
}
|
|
885
|
+
if (tempSlot) {
|
|
886
|
+
if (hidden) tempSlot.setAttribute('hidden', '')
|
|
887
|
+
else tempSlot.removeAttribute('hidden')
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
|
|
805
891
|
function hideTempButton() {
|
|
806
|
-
|
|
807
|
-
|
|
892
|
+
tempShowButton = false
|
|
893
|
+
applyTempButtonVisibility()
|
|
808
894
|
}
|
|
809
895
|
|
|
810
896
|
function placeTempButton() {
|
|
@@ -837,6 +923,7 @@ window.__ModuleLoader__.load({
|
|
|
837
923
|
if (tempButton.textContent !== label) tempButton.textContent = label
|
|
838
924
|
var hint = t('temp.hint')
|
|
839
925
|
if (tempButton.title !== hint) tempButton.title = hint
|
|
926
|
+
applyTempButtonVisibility()
|
|
840
927
|
return true
|
|
841
928
|
}
|
|
842
929
|
|
|
@@ -849,6 +936,26 @@ window.__ModuleLoader__.load({
|
|
|
849
936
|
setFlag(html, 'data-dsbs-expand-all', expandAllKnown && expandAll)
|
|
850
937
|
}
|
|
851
938
|
|
|
939
|
+
/** Apply a Host status payload to the running sidebar. No restart. */
|
|
940
|
+
function applyTempSettings(data) {
|
|
941
|
+
if (runtime.disposed) return
|
|
942
|
+
tempShowButton = !data || data.showButton !== false
|
|
943
|
+
applyTempButtonVisibility()
|
|
944
|
+
if (data && data.chatModePolicy) tempChatPolicy = String(data.chatModePolicy)
|
|
945
|
+
if (data && data.buttonStyle) setFlag(html, 'data-dsbs-temp-style', String(data.buttonStyle) === 'own')
|
|
946
|
+
// Older Host halves (<= 0.2.3) do not send this key; default to on.
|
|
947
|
+
expandAll = !data || data.expandAllSessions !== false
|
|
948
|
+
expandAllKnown = true
|
|
949
|
+
applyExpandFlag()
|
|
950
|
+
syncChatMode()
|
|
951
|
+
expandAllSessions()
|
|
952
|
+
scheduleSync()
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
function rememberConfigRevision(data) {
|
|
956
|
+
if (data && typeof data.configRevision === 'number') tempConfigRevision = data.configRevision
|
|
957
|
+
}
|
|
958
|
+
|
|
852
959
|
/**
|
|
853
960
|
* The host keeps five session rows per workspace and renders one overflow
|
|
854
961
|
* button for the rest: <button data-row-key="overflow:<group>">.
|
|
@@ -930,16 +1037,9 @@ window.__ModuleLoader__.load({
|
|
|
930
1037
|
tempStatusPending = false
|
|
931
1038
|
tempStatusLoaded = true
|
|
932
1039
|
if (expandGateTimer) { clearTimeout(expandGateTimer); expandGateTimer = 0 }
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
if (data && data.buttonStyle) setFlag(html, 'data-dsbs-temp-style', String(data.buttonStyle) === 'own')
|
|
936
|
-
// Older Host halves (<= 0.2.3) do not send this key; default to on.
|
|
937
|
-
expandAll = !data || data.expandAllSessions !== false
|
|
938
|
-
expandAllKnown = true
|
|
939
|
-
applyExpandFlag()
|
|
940
|
-
syncChatMode()
|
|
1040
|
+
rememberConfigRevision(data)
|
|
1041
|
+
applyTempSettings(data)
|
|
941
1042
|
refreshTempIds()
|
|
942
|
-
scheduleSync()
|
|
943
1043
|
}).catch(function (error) {
|
|
944
1044
|
void error
|
|
945
1045
|
tempStatusPending = false
|
|
@@ -980,21 +1080,30 @@ window.__ModuleLoader__.load({
|
|
|
980
1080
|
tempChatGuard = setInterval(syncChatMode, 1500)
|
|
981
1081
|
}
|
|
982
1082
|
|
|
983
|
-
//
|
|
984
|
-
//
|
|
985
|
-
//
|
|
1083
|
+
// 0.2.9: the three layouts are one segmented pill (they are mutually
|
|
1084
|
+
// exclusive), and the actions that belong to the chosen layout sit after
|
|
1085
|
+
// a divider. Glyphs are CSS pseudo elements, so makeButton() keeps owning
|
|
1086
|
+
// the plain text node that relabel() rewrites.
|
|
986
1087
|
function buildBar() {
|
|
987
1088
|
var bar = document.createElement('div')
|
|
988
1089
|
bar.setAttribute('data-dsbs-bar', '')
|
|
989
1090
|
bar.setAttribute('role', 'toolbar')
|
|
990
1091
|
|
|
1092
|
+
var seg = document.createElement('div')
|
|
1093
|
+
seg.setAttribute('data-dsbs-seg', '')
|
|
1094
|
+
seg.setAttribute('role', 'group')
|
|
1095
|
+
seg.setAttribute('aria-label', t('bar.label'))
|
|
1096
|
+
|
|
991
1097
|
var i
|
|
992
1098
|
for (i = 0; i < MODES.length; i++) {
|
|
993
1099
|
var mode = MODES[i]
|
|
994
|
-
|
|
1100
|
+
var modeButton = makeButton('mode-' + mode, 'mode.' + mode, 'mode.' + mode + '.hint', (function (value) {
|
|
995
1101
|
return function () { setMode(value) }
|
|
996
|
-
})(mode))
|
|
1102
|
+
})(mode))
|
|
1103
|
+
modeButton.setAttribute('data-dsbs-glyph', mode === 'a' ? 'a' : mode === 'b' ? 'b' : 'c')
|
|
1104
|
+
seg.appendChild(modeButton)
|
|
997
1105
|
}
|
|
1106
|
+
bar.appendChild(seg)
|
|
998
1107
|
|
|
999
1108
|
var separator = makeSeparator()
|
|
1000
1109
|
bar.appendChild(separator)
|
|
@@ -1013,6 +1122,14 @@ window.__ModuleLoader__.load({
|
|
|
1013
1122
|
var index = MODES.indexOf(config.values.mode)
|
|
1014
1123
|
setMode(MODES[(index + 1) % MODES.length])
|
|
1015
1124
|
})
|
|
1125
|
+
foldExt.setAttribute('data-dsbs-glyph', 'fold')
|
|
1126
|
+
// Two fold buttons sit side by side in layout a, so they must not
|
|
1127
|
+
// share a glyph — a single chevron vs. a double one is the only cue
|
|
1128
|
+
// available once the labels collapse to icons.
|
|
1129
|
+
foldWs.setAttribute('data-dsbs-glyph', 'fold2')
|
|
1130
|
+
tabSwap.setAttribute('data-dsbs-glyph', 'swap')
|
|
1131
|
+
pop.setAttribute('data-dsbs-glyph', 'pop')
|
|
1132
|
+
quick.setAttribute('data-dsbs-glyph', 'quick')
|
|
1016
1133
|
bar.appendChild(foldExt)
|
|
1017
1134
|
bar.appendChild(foldWs)
|
|
1018
1135
|
bar.appendChild(tabSwap)
|
|
@@ -1155,6 +1272,7 @@ window.__ModuleLoader__.load({
|
|
|
1155
1272
|
if (fx.nav && !fx.nav.hasAttribute('data-dsbs-ext')) fx.nav.setAttribute('data-dsbs-ext', '')
|
|
1156
1273
|
ensureBar(fx)
|
|
1157
1274
|
placeTempButton()
|
|
1275
|
+
applyTempButtonVisibility()
|
|
1158
1276
|
loadTempStatus()
|
|
1159
1277
|
// Bars left behind by an earlier client generation are dead weight and
|
|
1160
1278
|
// read as a duplicated control; keep exactly one.
|
|
@@ -1584,6 +1702,10 @@ window.__ModuleLoader__.load({
|
|
|
1584
1702
|
var saveState = ReactLib.useState('')
|
|
1585
1703
|
var saveStatus = saveState[0]
|
|
1586
1704
|
var setSaveStatus = saveState[1]
|
|
1705
|
+
// '' | 'busy' | a result line for the manual cleanup button.
|
|
1706
|
+
var purgeState = ReactLib.useState('')
|
|
1707
|
+
var purgeStatus = purgeState[0]
|
|
1708
|
+
var setPurgeStatus = purgeState[1]
|
|
1587
1709
|
|
|
1588
1710
|
// ---- feedback -------------------------------------------------
|
|
1589
1711
|
// A plugin bundle has no server to post to, and no mail client it can
|
|
@@ -1758,27 +1880,63 @@ window.__ModuleLoader__.load({
|
|
|
1758
1880
|
}
|
|
1759
1881
|
|
|
1760
1882
|
function adoptPolicy(data, fallbackTime) {
|
|
1883
|
+
var kept = Number(data && data.maxKept)
|
|
1761
1884
|
setPolicy({
|
|
1762
1885
|
deleteOnExit: data.deleteOnExit !== false,
|
|
1763
1886
|
scheduleDelete: data.scheduleDelete === true,
|
|
1764
1887
|
scheduleTime: typeof data.scheduleTime === 'string' && data.scheduleTime
|
|
1765
1888
|
? data.scheduleTime
|
|
1766
1889
|
: (fallbackTime || '03:00'),
|
|
1890
|
+
// 0.2.8 — the remaining config keys, all editable on this page.
|
|
1891
|
+
showButton: data.showButton !== false,
|
|
1892
|
+
inChatMode: data.inChatMode === 'show' ? 'show' : 'hide',
|
|
1893
|
+
buttonStyle: data.buttonStyle === 'own' ? 'own' : 'pair',
|
|
1894
|
+
expandAllSessions: data.expandAllSessions !== false,
|
|
1895
|
+
maxKept: kept > 0 ? Math.min(Math.round(kept), 200) : 20,
|
|
1896
|
+
configRevision: typeof data.configRevision === 'number' ? data.configRevision : null,
|
|
1897
|
+
configFile: typeof data.configFile === 'string' ? data.configFile : '',
|
|
1767
1898
|
})
|
|
1768
1899
|
}
|
|
1769
1900
|
|
|
1770
1901
|
ReactLib.useEffect(function () {
|
|
1771
1902
|
var stopped = false
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1903
|
+
var loaded = false
|
|
1904
|
+
var revision = null
|
|
1905
|
+
function read(first) {
|
|
1906
|
+
tempCall('api/dsh-temp-session.config')
|
|
1907
|
+
.then(function (data) {
|
|
1908
|
+
if (stopped) return
|
|
1909
|
+
if (!data || data.ok !== true) {
|
|
1910
|
+
if (first) setPolicy(false)
|
|
1911
|
+
return
|
|
1912
|
+
}
|
|
1913
|
+
loaded = true
|
|
1914
|
+
var rev = typeof data.configRevision === 'number' ? data.configRevision : null
|
|
1915
|
+
// Only re-render on a real change. Re-rendering on every tick
|
|
1916
|
+
// would fight a user who is halfway through typing a time.
|
|
1917
|
+
if (first || rev === null || rev !== revision) {
|
|
1918
|
+
revision = rev
|
|
1919
|
+
adoptPolicy(data)
|
|
1920
|
+
}
|
|
1921
|
+
})
|
|
1922
|
+
.catch(function () {
|
|
1923
|
+
if (!stopped && first) setPolicy(false)
|
|
1924
|
+
})
|
|
1925
|
+
}
|
|
1926
|
+
read(true)
|
|
1927
|
+
// 0.2.8: a save in Notepad is picked up here within ~2.5 seconds.
|
|
1928
|
+
var timer = null
|
|
1929
|
+
if (typeof window.setInterval === 'function') {
|
|
1930
|
+
timer = window.setInterval(function () {
|
|
1931
|
+
if (stopped || !loaded) return
|
|
1932
|
+
if (typeof document.hidden === 'boolean' && document.hidden) return
|
|
1933
|
+
read(false)
|
|
1934
|
+
}, 2500)
|
|
1935
|
+
}
|
|
1936
|
+
return function () {
|
|
1937
|
+
stopped = true
|
|
1938
|
+
if (timer !== null && typeof window.clearInterval === 'function') window.clearInterval(timer)
|
|
1939
|
+
}
|
|
1782
1940
|
}, [])
|
|
1783
1941
|
|
|
1784
1942
|
function persist(patch) {
|
|
@@ -1804,6 +1962,20 @@ window.__ModuleLoader__.load({
|
|
|
1804
1962
|
})
|
|
1805
1963
|
}
|
|
1806
1964
|
|
|
1965
|
+
function purgeNow() {
|
|
1966
|
+
if (purgeStatus === 'busy') return
|
|
1967
|
+
setPurgeStatus('busy')
|
|
1968
|
+
tempCall('api/dsh-temp-session.purge', { method: 'POST' })
|
|
1969
|
+
.then(function (data) {
|
|
1970
|
+
if (!data || data.ok !== true) throw new Error((data && data.error) || 'purge failed')
|
|
1971
|
+
var n = typeof data.removed === 'number' ? data.removed : 0
|
|
1972
|
+
setPurgeStatus(t('options.purged') + n)
|
|
1973
|
+
})
|
|
1974
|
+
.catch(function (error) {
|
|
1975
|
+
setPurgeStatus(t('options.purgeFailed') + String((error && error.message) || error))
|
|
1976
|
+
})
|
|
1977
|
+
}
|
|
1978
|
+
|
|
1807
1979
|
var available = !!configController()
|
|
1808
1980
|
var policyReady = !!(policy && policy !== false)
|
|
1809
1981
|
var dim = { fontSize: '11px', lineHeight: '16px', color: 'var(--dsw-alias-label-secondary,inherit)' }
|
|
@@ -1835,6 +2007,26 @@ window.__ModuleLoader__.load({
|
|
|
1835
2007
|
extra || null
|
|
1836
2008
|
)
|
|
1837
2009
|
}
|
|
2010
|
+
function optionRow(label, value, choices, onPick) {
|
|
2011
|
+
return h('div', {
|
|
2012
|
+
key: label,
|
|
2013
|
+
style: { display: 'flex', alignItems: 'center', gap: '8px', flexWrap: 'wrap' }
|
|
2014
|
+
},
|
|
2015
|
+
h('span', { style: { fontSize: '12px', color: 'var(--dsw-alias-label-primary,inherit)' } }, label),
|
|
2016
|
+
h('div', { style: { display: 'flex', gap: '4px' } },
|
|
2017
|
+
choices.map(function (choice) {
|
|
2018
|
+
var active = value === choice.value
|
|
2019
|
+
return h('button', {
|
|
2020
|
+
key: choice.value, type: 'button',
|
|
2021
|
+
'aria-pressed': active ? 'true' : 'false',
|
|
2022
|
+
disabled: !policyReady,
|
|
2023
|
+
onClick: function () { onPick(choice.value) },
|
|
2024
|
+
style: assign({}, pillBase, { minWidth: '0' }, active ? pillOn : {})
|
|
2025
|
+
}, choice.label)
|
|
2026
|
+
})
|
|
2027
|
+
)
|
|
2028
|
+
)
|
|
2029
|
+
}
|
|
1838
2030
|
var cardBase = {
|
|
1839
2031
|
display: 'flex', flexDirection: 'column', alignItems: 'flex-start', gap: '5px',
|
|
1840
2032
|
flex: '1 1 140px', minWidth: '140px', maxWidth: '230px', padding: '10px',
|
|
@@ -1875,6 +2067,60 @@ window.__ModuleLoader__.load({
|
|
|
1875
2067
|
})
|
|
1876
2068
|
),
|
|
1877
2069
|
available ? null : h('div', { style: { fontSize: '11px', color: 'var(--dsw-alias-label-secondary,inherit)' } }, t('settings.unavailable')),
|
|
2070
|
+
h('div', { 'data-dsbs-options': ENTRY, style: {
|
|
2071
|
+
display: 'flex', flexDirection: 'column', gap: '8px',
|
|
2072
|
+
paddingTop: '12px', marginTop: '2px',
|
|
2073
|
+
borderTop: '1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.2))'
|
|
2074
|
+
} },
|
|
2075
|
+
h('div', { style: { fontSize: '13px', fontWeight: '600', color: 'var(--dsw-alias-label-primary,inherit)' } }, t('options.title')),
|
|
2076
|
+
h('div', { style: dim }, t('options.help')),
|
|
2077
|
+
policy === null
|
|
2078
|
+
? h('div', { style: dim }, t('policy.loading'))
|
|
2079
|
+
: policy === false
|
|
2080
|
+
? h('div', { style: dim }, t('policy.unavailable'))
|
|
2081
|
+
: h('div', { style: { display: 'flex', flexDirection: 'column', gap: '8px' } },
|
|
2082
|
+
policyRow(t('options.showButton'), policy.showButton === true, function (next) {
|
|
2083
|
+
persist({ showButton: next })
|
|
2084
|
+
}),
|
|
2085
|
+
optionRow(t('options.inChatMode'), policy.inChatMode, [
|
|
2086
|
+
{ value: 'hide', label: t('options.chatHide') },
|
|
2087
|
+
{ value: 'show', label: t('options.chatShow') },
|
|
2088
|
+
], function (next) { persist({ inChatMode: next }) }),
|
|
2089
|
+
optionRow(t('options.buttonStyle'), policy.buttonStyle, [
|
|
2090
|
+
{ value: 'pair', label: t('options.stylePair') },
|
|
2091
|
+
{ value: 'own', label: t('options.styleOwn') },
|
|
2092
|
+
], function (next) { persist({ buttonStyle: next }) }),
|
|
2093
|
+
policyRow(t('options.expandAll'), policy.expandAllSessions === true, function (next) {
|
|
2094
|
+
persist({ expandAllSessions: next })
|
|
2095
|
+
}),
|
|
2096
|
+
h('div', { style: dim }, t('options.expandHint')),
|
|
2097
|
+
h('div', { style: { display: 'flex', alignItems: 'center', gap: '8px', flexWrap: 'wrap' } },
|
|
2098
|
+
h('span', { style: { fontSize: '12px', color: 'var(--dsw-alias-label-primary,inherit)' } }, t('options.maxKept')),
|
|
2099
|
+
h('input', {
|
|
2100
|
+
type: 'number', min: 1, max: 200, step: 1,
|
|
2101
|
+
value: String(policy.maxKept),
|
|
2102
|
+
disabled: !policyReady,
|
|
2103
|
+
'aria-label': t('options.maxKept'),
|
|
2104
|
+
onChange: function (event) {
|
|
2105
|
+
var raw = event && event.target ? event.target.value : ''
|
|
2106
|
+
var n = Number(raw)
|
|
2107
|
+
if (!(n > 0)) return
|
|
2108
|
+
persist({ maxKept: Math.min(Math.round(n), 200) })
|
|
2109
|
+
},
|
|
2110
|
+
style: {
|
|
2111
|
+
font: 'inherit', fontSize: '12px', padding: '1px 6px', width: '64px',
|
|
2112
|
+
borderRadius: '6px', color: 'var(--dsw-alias-label-primary,inherit)',
|
|
2113
|
+
background: 'transparent',
|
|
2114
|
+
border: '1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.3))'
|
|
2115
|
+
}
|
|
2116
|
+
}),
|
|
2117
|
+
h('span', { style: dim }, t('options.maxKeptHint'))
|
|
2118
|
+
),
|
|
2119
|
+
h('div', { style: dim },
|
|
2120
|
+
t('options.rev') + ' ' + (policy.configRevision === null ? '-' : policy.configRevision) +
|
|
2121
|
+
(policy.configFile ? ' · ' + policy.configFile : ''))
|
|
2122
|
+
)
|
|
2123
|
+
),
|
|
1878
2124
|
h('div', { 'data-dsbs-temp-policy': ENTRY, style: {
|
|
1879
2125
|
display: 'flex', flexDirection: 'column', gap: '8px',
|
|
1880
2126
|
paddingTop: '12px', marginTop: '2px',
|
|
@@ -1922,7 +2168,20 @@ window.__ModuleLoader__.load({
|
|
|
1922
2168
|
: 'var(--dsw-alias-text-error,#e5484d)'
|
|
1923
2169
|
}
|
|
1924
2170
|
}, saveStatus === 'saving' ? t('policy.saving') : saveStatus === 'saved' ? t('policy.saved') : saveStatus)
|
|
1925
|
-
: null
|
|
2171
|
+
: null,
|
|
2172
|
+
h('div', {
|
|
2173
|
+
style: { display: 'flex', alignItems: 'center', gap: '8px', flexWrap: 'wrap', paddingTop: '2px' }
|
|
2174
|
+
},
|
|
2175
|
+
h('button', {
|
|
2176
|
+
type: 'button',
|
|
2177
|
+
disabled: !policyReady || purgeStatus === 'busy',
|
|
2178
|
+
onClick: purgeNow,
|
|
2179
|
+
style: assign({}, pillBase, { minWidth: '0' })
|
|
2180
|
+
}, purgeStatus === 'busy' ? t('options.purging') : t('options.purge')),
|
|
2181
|
+
purgeStatus && purgeStatus !== 'busy'
|
|
2182
|
+
? h('span', { style: dim }, purgeStatus)
|
|
2183
|
+
: null
|
|
2184
|
+
)
|
|
1926
2185
|
)
|
|
1927
2186
|
),
|
|
1928
2187
|
h('div', { 'data-dsbs-feedback': ENTRY, style: {
|
|
@@ -2154,6 +2413,30 @@ window.__ModuleLoader__.load({
|
|
|
2154
2413
|
}
|
|
2155
2414
|
}, 'sidebar-suite: sync')
|
|
2156
2415
|
|
|
2416
|
+
effect(function () {
|
|
2417
|
+
// 0.2.8 hot reload. The Host stamps the config file and bumps a
|
|
2418
|
+
// monotone revision; one cheap status poll is enough to notice a save
|
|
2419
|
+
// in Notepad and re-apply every sidebar setting in place. Old Host
|
|
2420
|
+
// halves have no revision field — those are simply left on boot values.
|
|
2421
|
+
var timer = setInterval(function () {
|
|
2422
|
+
if (runtime.disposed || configPollPending) return
|
|
2423
|
+
if (typeof document.hidden === 'boolean' && document.hidden) return
|
|
2424
|
+
configPollPending = true
|
|
2425
|
+
tempCall('api/dsh-temp-session.status')
|
|
2426
|
+
.then(function (data) {
|
|
2427
|
+
configPollPending = false
|
|
2428
|
+
if (runtime.disposed || !data || data.ok !== true) return
|
|
2429
|
+
if (typeof data.configRevision !== 'number') return
|
|
2430
|
+
if (data.configRevision === tempConfigRevision) return
|
|
2431
|
+
rememberConfigRevision(data)
|
|
2432
|
+
applyTempSettings(data)
|
|
2433
|
+
})
|
|
2434
|
+
.catch(function () { configPollPending = false })
|
|
2435
|
+
}, 3000)
|
|
2436
|
+
if (timer && typeof timer.unref === 'function') timer.unref()
|
|
2437
|
+
return function () { clearInterval(timer) }
|
|
2438
|
+
}, 'sidebar-suite: config hot reload')
|
|
2439
|
+
|
|
2157
2440
|
var teardown = null
|
|
2158
2441
|
effect(function () {
|
|
2159
2442
|
teardown = function () {
|