@luziyang2026/dsh-question-nav 0.7.3 → 0.7.5
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/README.md +12 -5
- package/README.zh.md +10 -3
- package/lib/client.js +130 -27
- package/lib/client.js.map +1 -1
- package/lib/index.js +30 -5
- package/lib/types/client/QuestionNavSettingsTab.d.ts +4 -3
- package/lib/types/client/QuestionNavStrip.d.ts +7 -2
- package/lib/types/client/locales.d.ts +8 -4
- package/lib/types/client/settings.d.ts +13 -7
- package/lib/types/core/page-size.d.ts +16 -0
- package/lib/types/settings.d.ts +8 -4
- package/package.json +1 -1
- package/src/client/QuestionNavSettingsTab.tsx +24 -5
- package/src/client/QuestionNavStrip.tsx +84 -20
- package/src/client/index.ts +3 -1
- package/src/client/locales.ts +8 -4
- package/src/client/question-nav.module.css +90 -19
- package/src/client/settings.ts +36 -13
- package/src/core/page-size.ts +20 -0
- package/src/settings.ts +12 -4
package/README.md
CHANGED
|
@@ -21,7 +21,11 @@ Package: **`@luziyang2026/dsh-question-nav`** ([npm][npm] · [GitHub][github]).
|
|
|
21
21
|
|
|
22
22
|
## Preview
|
|
23
23
|
|
|
24
|
-

|
|
25
|
+
|
|
26
|
+
Plugin settings (rail alignment + dots per page):
|
|
27
|
+
|
|
28
|
+

|
|
25
29
|
|
|
26
30
|
## What it does
|
|
27
31
|
|
|
@@ -58,10 +62,13 @@ Package: **`@luziyang2026/dsh-question-nav`** ([npm][npm] · [GitHub][github]).
|
|
|
58
62
|
whole history up front.
|
|
59
63
|
- **Paged overflow**: when many dots don't fit the 60% band, the native
|
|
60
64
|
scrollbar is hidden and two small triangle buttons in the dot style appear —
|
|
61
|
-
**▲ above the dot queue and ▼ below it** — each click revealing
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
+
**▲ above the dot queue and ▼ below it** — each click revealing the next
|
|
66
|
+
page of hidden dots, which pop in with a short staggered animation as click
|
|
67
|
+
feedback. The page size is configurable — **Settings → Plugins → Question
|
|
68
|
+
Nav → Dots per page** (3 / 5 / 8 / 10, default 5). The triangles themselves
|
|
69
|
+
are the overflow cue — each appears only while its direction has more dots
|
|
70
|
+
to reveal (wheel and trackpad still scroll too); **hovering never scrolls
|
|
71
|
+
the band**, so the dots stay put while you browse.
|
|
65
72
|
- Empty/left areas of the rail pass pointer events through to the conversation
|
|
66
73
|
(it never blocks the chat).
|
|
67
74
|
|
package/README.zh.md
CHANGED
|
@@ -18,7 +18,11 @@
|
|
|
18
18
|
|
|
19
19
|
## 效果展示
|
|
20
20
|
|
|
21
|
-

|
|
22
|
+
|
|
23
|
+
插件设置页(导航条对齐 + 每次翻出数量):
|
|
24
|
+
|
|
25
|
+

|
|
22
26
|
|
|
23
27
|
## 功能
|
|
24
28
|
|
|
@@ -47,8 +51,11 @@
|
|
|
47
51
|
带进视图——绝不会一次性展开整个历史。
|
|
48
52
|
- **分页式溢出**:圆点太多超出 60% 限高时,原生滚动条被隐藏,点列上下会出现
|
|
49
53
|
两个与圆点同风格的三角按钮——**队列上方 ▲、队列下方 ▼**——每次点击翻出
|
|
50
|
-
|
|
51
|
-
|
|
54
|
+
下一页隐藏圆点,新翻出的圆点会带一个简短的逐个点弹入动画作为点击反馈。
|
|
55
|
+
每页数量可在 **设置 → 插件 → 提问导航 → 每次翻出数量** 中配置
|
|
56
|
+
(3 / 5 / 8 / 10,默认 5)。三角按钮本身就是溢出提示——只有对应方向还有
|
|
57
|
+
更多圆点时它才会出现(滚轮/触控板同样可滚);**悬停绝不滚动点列**,
|
|
58
|
+
浏览时点列保持不动。
|
|
52
59
|
- 圆点列空白区域**点穿**到对话内容,不会挡住聊天。
|
|
53
60
|
|
|
54
61
|
## 环境要求
|
package/lib/client.js
CHANGED
|
@@ -186,7 +186,7 @@ window.__ModuleLoader__.load({
|
|
|
186
186
|
}
|
|
187
187
|
//#endregion
|
|
188
188
|
//#region \0dsh-css:dsh-question-nav/src/client/question-nav.module.css.mjs
|
|
189
|
-
const css = ".TWf_pa_rail{z-index:1;box-sizing:border-box;pointer-events:none;background:0 0;flex-direction:column;align-items:center;width:44px;display:flex;position:absolute;top:0;bottom:0;left:0}.TWf_pa_railRight{left:auto}.TWf_pa_queue{box-sizing:border-box;pointer-events:none;flex-direction:column;flex:0 auto;align-items:center;gap:6px;width:100%;min-height:0;max-height:60%;margin:auto 0;display:flex}.TWf_pa_list{scrollbar-width:none;pointer-events:auto;flex-direction:column;flex:auto;align-items:center;gap:6px;width:100%;min-height:0;padding:8px 0;display:flex;overflow:hidden auto}.TWf_pa_list::-webkit-scrollbar{width:0;height:0;display:none}.
|
|
189
|
+
const css = ".TWf_pa_rail{z-index:1;box-sizing:border-box;pointer-events:none;background:0 0;flex-direction:column;align-items:center;width:44px;display:flex;position:absolute;top:0;bottom:0;left:0}.TWf_pa_railRight{left:auto}.TWf_pa_queue{box-sizing:border-box;pointer-events:none;flex-direction:column;flex:0 auto;align-items:center;gap:6px;width:100%;min-height:0;max-height:60%;margin:auto 0;display:flex}.TWf_pa_list{scrollbar-width:none;pointer-events:auto;flex-direction:column;flex:auto;align-items:center;gap:6px;width:100%;min-height:0;padding:8px 0;display:flex;overflow:hidden auto}.TWf_pa_list::-webkit-scrollbar{width:0;height:0;display:none}.TWf_pa_dot{pointer-events:auto;background:var(--dsw-alias-border-l3);cursor:pointer;border:none;border-radius:50%;flex:none;width:8px;height:8px;padding:0;transition:transform .12s,background .12s}.TWf_pa_dot:hover,.TWf_pa_dot.TWf_pa_focused,.TWf_pa_dot.TWf_pa_active{background:var(--dsw-alias-brand-primary)}@keyframes TWf_pa_dotEnterFromBottom{0%{opacity:0;transform:translateY(7px)scale(.4)}60%{opacity:1}to{opacity:1;transform:translateY(0)scale(1)}}@keyframes TWf_pa_dotEnterFromTop{0%{opacity:0;transform:translateY(-7px)scale(.4)}60%{opacity:1}to{opacity:1;transform:translateY(0)scale(1)}}.TWf_pa_dotEnterFromBottom{animation:.32s cubic-bezier(.2,.9,.3,1.2) both TWf_pa_dotEnterFromBottom}.TWf_pa_dotEnterFromTop{animation:.32s cubic-bezier(.2,.9,.3,1.2) both TWf_pa_dotEnterFromTop}@media (prefers-reduced-motion:reduce){.TWf_pa_dotEnterFromBottom,.TWf_pa_dotEnterFromTop{animation:none}}.TWf_pa_count{color:var(--dsw-alias-label-tertiary);user-select:none;flex:none;font-size:10px;font-weight:600;line-height:1}.TWf_pa_dots{flex-direction:column;align-items:center;gap:6px;display:flex}.TWf_pa_navBtn{pointer-events:auto;cursor:pointer;background:0 0;border:none;flex:none;justify-content:center;align-items:center;width:14px;height:12px;padding:0;display:flex}.TWf_pa_navBtn:before{content:\"\";border-left:5px solid #0000;border-right:5px solid #0000;width:0;height:0;transition:border-color .12s;display:block}.TWf_pa_navUp:before{border-bottom:7px solid var(--dsw-alias-border-l3)}.TWf_pa_navDown:before{border-top:7px solid var(--dsw-alias-border-l3)}.TWf_pa_navUp:hover:before{border-bottom-color:var(--dsw-alias-brand-primary)}.TWf_pa_navDown:hover:before{border-top-color:var(--dsw-alias-brand-primary)}.TWf_pa_empty{color:var(--dsw-alias-label-tertiary);text-align:center;word-break:break-word;padding:10px 4px;font-size:11px}.TWf_pa_hint{z-index:30;background:var(--dsw-alias-bg-layer-3);border:1px solid var(--dsw-alias-border-l1);max-width:260px;color:var(--dsw-alias-label-secondary);pointer-events:none;border-radius:6px;padding:6px 10px;font-size:12px;line-height:16px;position:fixed;box-shadow:0 2px 10px #0000002e}.TWf_pa_cascade{z-index:20;pointer-events:none;flex-direction:column;align-items:flex-start;gap:6px;display:flex;position:fixed}.TWf_pa_cascadeRight{align-items:flex-end}.TWf_pa_card{background:var(--dsw-alias-bg-layer-3);border:1px solid var(--dsw-alias-border-l2);width:380px;color:var(--dsw-alias-label-secondary);white-space:normal;word-break:break-word;pointer-events:auto;cursor:pointer;border-radius:10px;padding:8px 12px 10px;font-size:13px;line-height:18px;transition:border-color .12s,box-shadow .12s;box-shadow:0 1px 6px #0000001a}.TWf_pa_card:hover{border-color:var(--dsw-alias-brand-primary);color:var(--dsw-alias-label-primary);box-shadow:0 3px 12px #0000002e}.TWf_pa_cardSelected{border-color:var(--dsw-alias-border-l1);color:var(--dsw-alias-label-primary);box-shadow:inset 3px 0 0 0 var(--dsw-alias-brand-primary), 0 6px 20px #00000038}.TWf_pa_cardSelected:hover{border-color:var(--dsw-alias-brand-primary);box-shadow:inset 3px 0 0 0 var(--dsw-alias-brand-primary), 0 6px 22px #00000042}.TWf_pa_cardSelectedRight{box-shadow:inset -3px 0 0 0 var(--dsw-alias-brand-primary), 0 6px 20px #00000038}.TWf_pa_cardSelectedRight:hover{box-shadow:inset -3px 0 0 0 var(--dsw-alias-brand-primary), 0 6px 22px #00000042}.TWf_pa_cardTitle{color:var(--dsw-alias-label-tertiary);margin-bottom:4px;font-size:11px;font-weight:600}.TWf_pa_cardBody{--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2);--dsh-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2);max-height:96px;overflow-y:auto}.TWf_pa_cardClamp{word-break:break-word;-webkit-box-orient:vertical;font-size:13px;line-height:18px;display:-webkit-box;overflow:hidden}.TWf_pa_cardLine+.TWf_pa_cardLine{border-top:1px solid var(--dsw-alias-border-l1);margin-top:6px;padding-top:6px}.TWf_pa_cardTime{color:var(--dsw-alias-label-tertiary);margin-top:6px;font-size:11px}.TWf_pa_settings{flex-direction:column;gap:8px;padding:4px 0;display:flex}.TWf_pa_settingsTitle{color:var(--dsw-alias-label-primary);margin:0;font-size:13px;font-weight:600}.TWf_pa_segmented+.TWf_pa_settingsTitle{margin-top:12px}.TWf_pa_settingsDesc{color:var(--dsw-alias-label-secondary);margin:0;font-size:12px;line-height:18px}.TWf_pa_segmented{border:1px solid var(--dsw-alias-border-l1);border-radius:8px;align-self:flex-start;display:inline-flex;overflow:hidden}.TWf_pa_segment{color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;padding:8px 16px;font-size:13px;line-height:1;transition:background .12s,color .12s}.TWf_pa_segment+.TWf_pa_segment{border-left:1px solid var(--dsw-alias-border-l1)}.TWf_pa_segment:hover{background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary)}.TWf_pa_segmentActive,.TWf_pa_segmentActive:hover{background:var(--dsw-alias-brand-primary);color:var(--dsw-alias-label-inverse)}";
|
|
190
190
|
const tagId = "@luziyang2026/dsh-question-nav/question-nav.module.css";
|
|
191
191
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
192
192
|
const tag = document.createElement("style");
|
|
@@ -202,17 +202,20 @@ window.__ModuleLoader__.load({
|
|
|
202
202
|
"cardClamp": "TWf_pa_cardClamp",
|
|
203
203
|
"cardLine": "TWf_pa_cardLine",
|
|
204
204
|
"cardSelected": "TWf_pa_cardSelected",
|
|
205
|
+
"cardSelectedRight": "TWf_pa_cardSelectedRight",
|
|
205
206
|
"cardTime": "TWf_pa_cardTime",
|
|
206
207
|
"cardTitle": "TWf_pa_cardTitle",
|
|
207
208
|
"cascade": "TWf_pa_cascade",
|
|
209
|
+
"cascadeRight": "TWf_pa_cascadeRight",
|
|
208
210
|
"count": "TWf_pa_count",
|
|
209
211
|
"dot": "TWf_pa_dot",
|
|
212
|
+
"dotEnterFromBottom": "TWf_pa_dotEnterFromBottom",
|
|
213
|
+
"dotEnterFromTop": "TWf_pa_dotEnterFromTop",
|
|
210
214
|
"dots": "TWf_pa_dots",
|
|
211
215
|
"empty": "TWf_pa_empty",
|
|
212
216
|
"focused": "TWf_pa_focused",
|
|
213
217
|
"hint": "TWf_pa_hint",
|
|
214
218
|
"list": "TWf_pa_list",
|
|
215
|
-
"listScrollable": "TWf_pa_listScrollable",
|
|
216
219
|
"navBtn": "TWf_pa_navBtn",
|
|
217
220
|
"navDown": "TWf_pa_navDown",
|
|
218
221
|
"navUp": "TWf_pa_navUp",
|
|
@@ -246,9 +249,12 @@ window.__ModuleLoader__.load({
|
|
|
246
249
|
* the band — the dot column stays put while you browse.
|
|
247
250
|
*
|
|
248
251
|
* Overflow is paged, not auto-scrolled: two small triangle buttons in the dot
|
|
249
|
-
* style sit above and below the dot queue (▲ / ▼), each click revealing
|
|
250
|
-
* hidden dots
|
|
251
|
-
*
|
|
252
|
+
* style sit above and below the dot queue (▲ / ▼), each click revealing one
|
|
253
|
+
* page of hidden dots (the page size is configurable in the plugin settings,
|
|
254
|
+
* default 5) with a staggered pop-in animation as click feedback. The native
|
|
255
|
+
* scrollbar stays hidden; the triangles themselves are the overflow cue (each
|
|
256
|
+
* appears only while its direction has more to reveal) — no hover auto-scroll
|
|
257
|
+
* of any kind.
|
|
252
258
|
*
|
|
253
259
|
* Data source: the host-folded `questionIndex` session projection (whole
|
|
254
260
|
* history, persisted host-side, pushed live through session/projection
|
|
@@ -298,6 +304,7 @@ window.__ModuleLoader__.load({
|
|
|
298
304
|
const [hint, setHint] = (0, react.useState)(null);
|
|
299
305
|
const [focus, setFocus] = (0, react.useState)(null);
|
|
300
306
|
const [align, setAlign] = (0, react.useState)(() => props.align());
|
|
307
|
+
const [pageSize, setPageSize] = (0, react.useState)(() => props.pageSize());
|
|
301
308
|
const panelRef = (0, react.useRef)(null);
|
|
302
309
|
const listRef = (0, react.useRef)(null);
|
|
303
310
|
const hintTimerRef = (0, react.useRef)(null);
|
|
@@ -308,6 +315,8 @@ window.__ModuleLoader__.load({
|
|
|
308
315
|
top: 0,
|
|
309
316
|
max: 0
|
|
310
317
|
});
|
|
318
|
+
const [revealed, setRevealed] = (0, react.useState)(null);
|
|
319
|
+
const revealTimerRef = (0, react.useRef)(null);
|
|
311
320
|
const syncScroll = () => {
|
|
312
321
|
const list = listRef.current;
|
|
313
322
|
if (list === null) return;
|
|
@@ -321,8 +330,33 @@ window.__ModuleLoader__.load({
|
|
|
321
330
|
const list = listRef.current;
|
|
322
331
|
if (list === null) return;
|
|
323
332
|
const max = Math.max(0, list.scrollHeight - list.clientHeight);
|
|
324
|
-
|
|
333
|
+
const target = clampScrollTop(list.scrollTop + dir * pageStep(8, 6, pageSize), max);
|
|
334
|
+
if (target === list.scrollTop) return;
|
|
335
|
+
const band = list.getBoundingClientRect();
|
|
336
|
+
const els = Array.from(list.querySelectorAll("[data-question-nav-key]"));
|
|
337
|
+
const wasVisible = els.map((el) => {
|
|
338
|
+
const r = el.getBoundingClientRect();
|
|
339
|
+
return r.bottom > band.top && r.top < band.bottom;
|
|
340
|
+
});
|
|
341
|
+
list.scrollTop = target;
|
|
325
342
|
syncScroll();
|
|
343
|
+
const next = /* @__PURE__ */ new Map();
|
|
344
|
+
const fresh = [];
|
|
345
|
+
els.forEach((el, i) => {
|
|
346
|
+
if (wasVisible[i]) return;
|
|
347
|
+
const r = el.getBoundingClientRect();
|
|
348
|
+
const key = el.dataset.questionNavKey;
|
|
349
|
+
if (key !== void 0 && r.bottom > band.top && r.top < band.bottom) fresh.push(key);
|
|
350
|
+
});
|
|
351
|
+
if (dir === 1) fresh.reverse();
|
|
352
|
+
fresh.forEach((key, order) => next.set(key, {
|
|
353
|
+
dir,
|
|
354
|
+
order
|
|
355
|
+
}));
|
|
356
|
+
if (next.size === 0) return;
|
|
357
|
+
setRevealed(next);
|
|
358
|
+
if (revealTimerRef.current !== null) window.clearTimeout(revealTimerRef.current);
|
|
359
|
+
revealTimerRef.current = window.setTimeout(() => setRevealed(null), 320 + next.size * 35 + 120);
|
|
326
360
|
};
|
|
327
361
|
const canPageUp = scrollable && scrollPos.top > 0;
|
|
328
362
|
const canPageDown = scrollable && scrollPos.top < scrollPos.max;
|
|
@@ -336,7 +370,10 @@ window.__ModuleLoader__.load({
|
|
|
336
370
|
cancelClearFocus();
|
|
337
371
|
clearFocusTimerRef.current = window.setTimeout(() => setFocus(null), 240);
|
|
338
372
|
};
|
|
339
|
-
(0, react.useEffect)(() => props.
|
|
373
|
+
(0, react.useEffect)(() => props.subscribeSettings(() => {
|
|
374
|
+
setAlign(props.align());
|
|
375
|
+
setPageSize(props.pageSize());
|
|
376
|
+
}), [props]);
|
|
340
377
|
const showHint = (message) => {
|
|
341
378
|
setHint(message);
|
|
342
379
|
if (hintTimerRef.current !== null) window.clearTimeout(hintTimerRef.current);
|
|
@@ -476,6 +513,7 @@ window.__ModuleLoader__.load({
|
|
|
476
513
|
(0, react.useEffect)(() => () => {
|
|
477
514
|
if (hintTimerRef.current !== null) window.clearTimeout(hintTimerRef.current);
|
|
478
515
|
if (clearFocusTimerRef.current !== null) window.clearTimeout(clearFocusTimerRef.current);
|
|
516
|
+
if (revealTimerRef.current !== null) window.clearTimeout(revealTimerRef.current);
|
|
479
517
|
}, []);
|
|
480
518
|
if (!visible) return null;
|
|
481
519
|
const onJump = (dot) => {
|
|
@@ -532,7 +570,7 @@ window.__ModuleLoader__.load({
|
|
|
532
570
|
}) : null,
|
|
533
571
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
534
572
|
ref: listRef,
|
|
535
|
-
className:
|
|
573
|
+
className: question_nav_module_css_default.list,
|
|
536
574
|
onScroll: syncScroll,
|
|
537
575
|
onMouseLeave: scheduleClearFocus,
|
|
538
576
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
@@ -541,14 +579,20 @@ window.__ModuleLoader__.load({
|
|
|
541
579
|
const isFocused = focus !== null && focus.key === dot.key;
|
|
542
580
|
const tier = selectedIndex < 0 ? null : focusTier(index - selectedIndex);
|
|
543
581
|
const scale = jumpingKey === dot.key ? 1.6 : tier !== null ? focusScale(tier) : 1;
|
|
582
|
+
const reveal = revealed?.get(dot.key);
|
|
544
583
|
const cls = [question_nav_module_css_default.dot];
|
|
545
584
|
if (isFocused) cls.push(question_nav_module_css_default.focused);
|
|
546
585
|
if (jumpingKey === dot.key) cls.push(question_nav_module_css_default.active);
|
|
586
|
+
if (reveal !== void 0) cls.push(reveal.dir === 1 ? question_nav_module_css_default.dotEnterFromBottom : question_nav_module_css_default.dotEnterFromTop);
|
|
547
587
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
548
588
|
className: cls.join(" "),
|
|
549
|
-
style: {
|
|
589
|
+
style: {
|
|
590
|
+
transform: `scale(${scale})`,
|
|
591
|
+
...reveal !== void 0 ? { animationDelay: `${reveal.order * 35}ms` } : {}
|
|
592
|
+
},
|
|
550
593
|
"data-question-nav-focused": isFocused ? "true" : void 0,
|
|
551
594
|
"data-question-nav-index": index,
|
|
595
|
+
"data-question-nav-key": dot.key,
|
|
552
596
|
"aria-label": dot.texts[0] ?? "",
|
|
553
597
|
onMouseEnter: (e) => openFocus(dot, e.currentTarget),
|
|
554
598
|
onClick: () => onJump(dot)
|
|
@@ -579,7 +623,7 @@ window.__ModuleLoader__.load({
|
|
|
579
623
|
children: hint
|
|
580
624
|
}), document.body) : null,
|
|
581
625
|
focus !== null ? (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
582
|
-
className: question_nav_module_css_default.cascade,
|
|
626
|
+
className: align === "right" ? `${question_nav_module_css_default.cascade} ${question_nav_module_css_default.cascadeRight}` : question_nav_module_css_default.cascade,
|
|
583
627
|
style: {
|
|
584
628
|
...focus.left !== void 0 ? { left: focus.left } : {},
|
|
585
629
|
...focus.right !== void 0 ? { right: focus.right } : {},
|
|
@@ -593,7 +637,7 @@ window.__ModuleLoader__.load({
|
|
|
593
637
|
if (metrics === null) return null;
|
|
594
638
|
const isSelected = item.distance === 0;
|
|
595
639
|
const text = item.dot.texts.join(" · ");
|
|
596
|
-
const cls = isSelected ? `${question_nav_module_css_default.card} ${question_nav_module_css_default.cardSelected}` : question_nav_module_css_default.card;
|
|
640
|
+
const cls = isSelected ? `${question_nav_module_css_default.card} ${question_nav_module_css_default.cardSelected}${align === "right" ? ` ${question_nav_module_css_default.cardSelectedRight}` : ""}` : question_nav_module_css_default.card;
|
|
597
641
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
598
642
|
className: cls,
|
|
599
643
|
role: "button",
|
|
@@ -646,24 +690,45 @@ window.__ModuleLoader__.load({
|
|
|
646
690
|
/** Field carrying the selected anchor edge. */
|
|
647
691
|
const ALIGN_FIELD = "align";
|
|
648
692
|
//#endregion
|
|
693
|
+
//#region src/core/page-size.ts
|
|
694
|
+
/**
|
|
695
|
+
* Page-size constants for the ▲/▼ paging buttons, shared by the host schema
|
|
696
|
+
* and the browser settings scope. Pure data: no DSH imports, so the client
|
|
697
|
+
* bundle may inline this module (a Host import here would leak into the
|
|
698
|
+
* browser half).
|
|
699
|
+
*
|
|
700
|
+
* @module dsh-question-nav/page-size
|
|
701
|
+
*/
|
|
702
|
+
/** Selectable page sizes: how many hidden dots one ▲/▼ click reveals. */
|
|
703
|
+
const PAGE_SIZE_OPTIONS = [
|
|
704
|
+
3,
|
|
705
|
+
5,
|
|
706
|
+
8,
|
|
707
|
+
10
|
|
708
|
+
];
|
|
709
|
+
/** Field carrying the selected page size. */
|
|
710
|
+
const PAGE_SIZE_FIELD = "pageSize";
|
|
711
|
+
//#endregion
|
|
649
712
|
//#region src/client/QuestionNavSettingsTab.tsx
|
|
650
713
|
/**
|
|
651
714
|
* The plugin's settings page inside the shell's Plugins section
|
|
652
|
-
* (`settings.plugins.tab`):
|
|
653
|
-
* conversation column the dot rail anchors to
|
|
715
|
+
* (`settings.plugins.tab`): two segmented controls — which edge of the
|
|
716
|
+
* conversation column the dot rail anchors to, and how many hidden dots one
|
|
717
|
+
* ▲/▼ paging-button click reveals. The choices are written to the
|
|
654
718
|
* `question-nav` settings namespace (registered by the host half); the strip
|
|
655
|
-
* re-anchors live when the snapshot changes.
|
|
719
|
+
* re-anchors and re-steps live when the snapshot changes.
|
|
656
720
|
*
|
|
657
721
|
* @module dsh-question-nav/client/settings-tab
|
|
658
722
|
*/
|
|
659
723
|
/** Re-render on settings snapshot changes (the register inject face is static). */
|
|
660
|
-
function
|
|
724
|
+
function useSettingsTick(subscribe) {
|
|
661
725
|
const [, bump] = (0, react.useState)(0);
|
|
662
726
|
(0, react.useEffect)(() => subscribe(() => bump((n) => n + 1)), [subscribe]);
|
|
663
727
|
}
|
|
664
728
|
function QuestionNavSettingsTab(props) {
|
|
665
|
-
|
|
729
|
+
useSettingsTick(props.subscribeSettings);
|
|
666
730
|
const align = props.align();
|
|
731
|
+
const pageSize = props.pageSize();
|
|
667
732
|
const t = props.t;
|
|
668
733
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
669
734
|
className: question_nav_module_css_default.settings,
|
|
@@ -688,16 +753,41 @@ window.__ModuleLoader__.load({
|
|
|
688
753
|
onClick: () => props.setAlign(option),
|
|
689
754
|
children: t(option === "left" ? "settings.align.left" : "settings.align.right")
|
|
690
755
|
}, option))
|
|
756
|
+
}),
|
|
757
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
758
|
+
className: question_nav_module_css_default.settingsTitle,
|
|
759
|
+
children: t("settings.pagesize.title")
|
|
760
|
+
}),
|
|
761
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
762
|
+
className: question_nav_module_css_default.settingsDesc,
|
|
763
|
+
children: t("settings.pagesize.desc")
|
|
764
|
+
}),
|
|
765
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
766
|
+
className: question_nav_module_css_default.segmented,
|
|
767
|
+
role: "radiogroup",
|
|
768
|
+
"aria-label": t("settings.pagesize.title"),
|
|
769
|
+
children: PAGE_SIZE_OPTIONS.map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
770
|
+
type: "button",
|
|
771
|
+
role: "radio",
|
|
772
|
+
"aria-checked": pageSize === option,
|
|
773
|
+
className: pageSize === option ? `${question_nav_module_css_default.segment} ${question_nav_module_css_default.segmentActive}` : question_nav_module_css_default.segment,
|
|
774
|
+
onClick: () => props.setPageSize(option),
|
|
775
|
+
children: option
|
|
776
|
+
}, option))
|
|
691
777
|
})
|
|
692
778
|
]
|
|
693
779
|
});
|
|
694
780
|
}
|
|
695
781
|
//#endregion
|
|
696
782
|
//#region src/client/settings.ts
|
|
697
|
-
/** Narrow a raw section to the anchor field. */
|
|
783
|
+
/** Narrow a raw section value to the anchor field. */
|
|
698
784
|
function isAlignPreference(value) {
|
|
699
785
|
return ALIGN_OPTIONS.some((option) => option === value);
|
|
700
786
|
}
|
|
787
|
+
/** Narrow a raw section value to the page-size field. */
|
|
788
|
+
function isPageSize(value) {
|
|
789
|
+
return PAGE_SIZE_OPTIONS.includes(value);
|
|
790
|
+
}
|
|
701
791
|
/** Reactive handle over the plugin's durable settings section. */
|
|
702
792
|
var QuestionNavSettingsController = class {
|
|
703
793
|
scope;
|
|
@@ -705,6 +795,7 @@ window.__ModuleLoader__.load({
|
|
|
705
795
|
unsubscribe = () => {};
|
|
706
796
|
state = {
|
|
707
797
|
align: DEFAULT_ALIGN,
|
|
798
|
+
pageSize: 5,
|
|
708
799
|
overridden: false
|
|
709
800
|
};
|
|
710
801
|
/**
|
|
@@ -721,7 +812,7 @@ window.__ModuleLoader__.load({
|
|
|
721
812
|
this.unsubscribe = this.scope.subscribe(() => {
|
|
722
813
|
if (this.scope === void 0) return;
|
|
723
814
|
const next = this.derive(this.scope.getSnapshot());
|
|
724
|
-
if (next.align === this.state.align && next.overridden === this.state.overridden) return;
|
|
815
|
+
if (next.align === this.state.align && next.pageSize === this.state.pageSize && next.overridden === this.state.overridden) return;
|
|
725
816
|
this.state = next;
|
|
726
817
|
for (const listener of this.listeners) listener();
|
|
727
818
|
});
|
|
@@ -730,7 +821,8 @@ window.__ModuleLoader__.load({
|
|
|
730
821
|
const user = snapshot.user;
|
|
731
822
|
return {
|
|
732
823
|
align: snapshot.status === "ready" && isAlignPreference(snapshot.value?.align) ? snapshot.value.align : DEFAULT_ALIGN,
|
|
733
|
-
|
|
824
|
+
pageSize: snapshot.status === "ready" && isPageSize(snapshot.value?.pageSize) ? snapshot.value.pageSize : 5,
|
|
825
|
+
overridden: user !== void 0 && (user.align !== void 0 || user.pageSize !== void 0)
|
|
734
826
|
};
|
|
735
827
|
}
|
|
736
828
|
/** Release the scope subscription (bound on the settings fiber's lifecycle). */
|
|
@@ -754,6 +846,11 @@ window.__ModuleLoader__.load({
|
|
|
754
846
|
if (this.scope === void 0) return;
|
|
755
847
|
this.scope.set(ALIGN_FIELD, align);
|
|
756
848
|
}
|
|
849
|
+
/** Route the user's page-size choice to the Host document. */
|
|
850
|
+
setPageSize(pageSize) {
|
|
851
|
+
if (this.scope === void 0) return;
|
|
852
|
+
this.scope.set(PAGE_SIZE_FIELD, pageSize);
|
|
853
|
+
}
|
|
757
854
|
};
|
|
758
855
|
//#endregion
|
|
759
856
|
//#region src/client/locales.ts
|
|
@@ -763,8 +860,8 @@ window.__ModuleLoader__.load({
|
|
|
763
860
|
*/
|
|
764
861
|
const zh = {
|
|
765
862
|
"strip.empty": "本会话还没有提问",
|
|
766
|
-
"strip.up": "
|
|
767
|
-
"strip.down": "
|
|
863
|
+
"strip.up": "向上翻出更多提问圆点",
|
|
864
|
+
"strip.down": "向下翻出更多提问圆点",
|
|
768
865
|
"jump.inactive": "聊天视图未激活",
|
|
769
866
|
"jump.hidden": "目标无独立气泡,已定位到邻近内容",
|
|
770
867
|
"jump.notfound": "目标未加载或不存在(可能已压缩)",
|
|
@@ -773,12 +870,14 @@ window.__ModuleLoader__.load({
|
|
|
773
870
|
"settings.align.title": "导航条对齐",
|
|
774
871
|
"settings.align.desc": "选择圆点导航条锚定在对话栏的哪一侧。",
|
|
775
872
|
"settings.align.left": "左侧",
|
|
776
|
-
"settings.align.right": "右侧"
|
|
873
|
+
"settings.align.right": "右侧",
|
|
874
|
+
"settings.pagesize.title": "每次翻出数量",
|
|
875
|
+
"settings.pagesize.desc": "点击 ▲/▼ 三角按钮时,每次翻出的隐藏圆点数量。"
|
|
777
876
|
};
|
|
778
877
|
const en = {
|
|
779
878
|
"strip.empty": "No questions in this session yet",
|
|
780
|
-
"strip.up": "Reveal
|
|
781
|
-
"strip.down": "Reveal
|
|
879
|
+
"strip.up": "Reveal more question dots above",
|
|
880
|
+
"strip.down": "Reveal more question dots below",
|
|
782
881
|
"jump.inactive": "Chat view is not active",
|
|
783
882
|
"jump.hidden": "No dedicated bubble; landed on nearby content",
|
|
784
883
|
"jump.notfound": "Target not loaded or missing (maybe compacted)",
|
|
@@ -787,7 +886,9 @@ window.__ModuleLoader__.load({
|
|
|
787
886
|
"settings.align.title": "Rail alignment",
|
|
788
887
|
"settings.align.desc": "Choose which edge of the conversation column the dot rail anchors to.",
|
|
789
888
|
"settings.align.left": "Left",
|
|
790
|
-
"settings.align.right": "Right"
|
|
889
|
+
"settings.align.right": "Right",
|
|
890
|
+
"settings.pagesize.title": "Dots per page",
|
|
891
|
+
"settings.pagesize.desc": "How many hidden dots one click of the ▲/▼ paging buttons reveals."
|
|
791
892
|
};
|
|
792
893
|
//#endregion
|
|
793
894
|
//#region src/core/nodes.ts
|
|
@@ -1017,8 +1118,10 @@ window.__ModuleLoader__.load({
|
|
|
1017
1118
|
jumpToQuestion(ports, key);
|
|
1018
1119
|
},
|
|
1019
1120
|
align: () => settings.getSnapshot().align,
|
|
1020
|
-
|
|
1021
|
-
setAlign: (align) => settings.setAlign(align)
|
|
1121
|
+
subscribeSettings: (cb) => settings.subscribe(cb),
|
|
1122
|
+
setAlign: (align) => settings.setAlign(align),
|
|
1123
|
+
pageSize: () => settings.getSnapshot().pageSize,
|
|
1124
|
+
setPageSize: (pageSize) => settings.setPageSize(pageSize)
|
|
1022
1125
|
};
|
|
1023
1126
|
}
|
|
1024
1127
|
/**
|