@luziyang2026/dsh-question-nav 0.7.3 → 0.7.4
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 +126 -25
- 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 +80 -18
- package/src/client/index.ts +3 -1
- package/src/client/locales.ts +8 -4
- package/src/client/question-nav.module.css +61 -17
- 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_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_cardTitle{color:var(--dsw-alias-label-tertiary);margin-bottom:4px;font-size:11px;font-weight:600}.TWf_pa_cardBody{scrollbar-width:thin;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");
|
|
@@ -207,12 +207,13 @@ window.__ModuleLoader__.load({
|
|
|
207
207
|
"cascade": "TWf_pa_cascade",
|
|
208
208
|
"count": "TWf_pa_count",
|
|
209
209
|
"dot": "TWf_pa_dot",
|
|
210
|
+
"dotEnterFromBottom": "TWf_pa_dotEnterFromBottom",
|
|
211
|
+
"dotEnterFromTop": "TWf_pa_dotEnterFromTop",
|
|
210
212
|
"dots": "TWf_pa_dots",
|
|
211
213
|
"empty": "TWf_pa_empty",
|
|
212
214
|
"focused": "TWf_pa_focused",
|
|
213
215
|
"hint": "TWf_pa_hint",
|
|
214
216
|
"list": "TWf_pa_list",
|
|
215
|
-
"listScrollable": "TWf_pa_listScrollable",
|
|
216
217
|
"navBtn": "TWf_pa_navBtn",
|
|
217
218
|
"navDown": "TWf_pa_navDown",
|
|
218
219
|
"navUp": "TWf_pa_navUp",
|
|
@@ -246,9 +247,12 @@ window.__ModuleLoader__.load({
|
|
|
246
247
|
* the band — the dot column stays put while you browse.
|
|
247
248
|
*
|
|
248
249
|
* 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
|
-
*
|
|
250
|
+
* style sit above and below the dot queue (▲ / ▼), each click revealing one
|
|
251
|
+
* page of hidden dots (the page size is configurable in the plugin settings,
|
|
252
|
+
* default 5) with a staggered pop-in animation as click feedback. The native
|
|
253
|
+
* scrollbar stays hidden; the triangles themselves are the overflow cue (each
|
|
254
|
+
* appears only while its direction has more to reveal) — no hover auto-scroll
|
|
255
|
+
* of any kind.
|
|
252
256
|
*
|
|
253
257
|
* Data source: the host-folded `questionIndex` session projection (whole
|
|
254
258
|
* history, persisted host-side, pushed live through session/projection
|
|
@@ -298,6 +302,7 @@ window.__ModuleLoader__.load({
|
|
|
298
302
|
const [hint, setHint] = (0, react.useState)(null);
|
|
299
303
|
const [focus, setFocus] = (0, react.useState)(null);
|
|
300
304
|
const [align, setAlign] = (0, react.useState)(() => props.align());
|
|
305
|
+
const [pageSize, setPageSize] = (0, react.useState)(() => props.pageSize());
|
|
301
306
|
const panelRef = (0, react.useRef)(null);
|
|
302
307
|
const listRef = (0, react.useRef)(null);
|
|
303
308
|
const hintTimerRef = (0, react.useRef)(null);
|
|
@@ -308,6 +313,8 @@ window.__ModuleLoader__.load({
|
|
|
308
313
|
top: 0,
|
|
309
314
|
max: 0
|
|
310
315
|
});
|
|
316
|
+
const [revealed, setRevealed] = (0, react.useState)(null);
|
|
317
|
+
const revealTimerRef = (0, react.useRef)(null);
|
|
311
318
|
const syncScroll = () => {
|
|
312
319
|
const list = listRef.current;
|
|
313
320
|
if (list === null) return;
|
|
@@ -321,8 +328,33 @@ window.__ModuleLoader__.load({
|
|
|
321
328
|
const list = listRef.current;
|
|
322
329
|
if (list === null) return;
|
|
323
330
|
const max = Math.max(0, list.scrollHeight - list.clientHeight);
|
|
324
|
-
|
|
331
|
+
const target = clampScrollTop(list.scrollTop + dir * pageStep(8, 6, pageSize), max);
|
|
332
|
+
if (target === list.scrollTop) return;
|
|
333
|
+
const band = list.getBoundingClientRect();
|
|
334
|
+
const els = Array.from(list.querySelectorAll("[data-question-nav-key]"));
|
|
335
|
+
const wasVisible = els.map((el) => {
|
|
336
|
+
const r = el.getBoundingClientRect();
|
|
337
|
+
return r.bottom > band.top && r.top < band.bottom;
|
|
338
|
+
});
|
|
339
|
+
list.scrollTop = target;
|
|
325
340
|
syncScroll();
|
|
341
|
+
const next = /* @__PURE__ */ new Map();
|
|
342
|
+
const fresh = [];
|
|
343
|
+
els.forEach((el, i) => {
|
|
344
|
+
if (wasVisible[i]) return;
|
|
345
|
+
const r = el.getBoundingClientRect();
|
|
346
|
+
const key = el.dataset.questionNavKey;
|
|
347
|
+
if (key !== void 0 && r.bottom > band.top && r.top < band.bottom) fresh.push(key);
|
|
348
|
+
});
|
|
349
|
+
if (dir === 1) fresh.reverse();
|
|
350
|
+
fresh.forEach((key, order) => next.set(key, {
|
|
351
|
+
dir,
|
|
352
|
+
order
|
|
353
|
+
}));
|
|
354
|
+
if (next.size === 0) return;
|
|
355
|
+
setRevealed(next);
|
|
356
|
+
if (revealTimerRef.current !== null) window.clearTimeout(revealTimerRef.current);
|
|
357
|
+
revealTimerRef.current = window.setTimeout(() => setRevealed(null), 320 + next.size * 35 + 120);
|
|
326
358
|
};
|
|
327
359
|
const canPageUp = scrollable && scrollPos.top > 0;
|
|
328
360
|
const canPageDown = scrollable && scrollPos.top < scrollPos.max;
|
|
@@ -336,7 +368,10 @@ window.__ModuleLoader__.load({
|
|
|
336
368
|
cancelClearFocus();
|
|
337
369
|
clearFocusTimerRef.current = window.setTimeout(() => setFocus(null), 240);
|
|
338
370
|
};
|
|
339
|
-
(0, react.useEffect)(() => props.
|
|
371
|
+
(0, react.useEffect)(() => props.subscribeSettings(() => {
|
|
372
|
+
setAlign(props.align());
|
|
373
|
+
setPageSize(props.pageSize());
|
|
374
|
+
}), [props]);
|
|
340
375
|
const showHint = (message) => {
|
|
341
376
|
setHint(message);
|
|
342
377
|
if (hintTimerRef.current !== null) window.clearTimeout(hintTimerRef.current);
|
|
@@ -476,6 +511,7 @@ window.__ModuleLoader__.load({
|
|
|
476
511
|
(0, react.useEffect)(() => () => {
|
|
477
512
|
if (hintTimerRef.current !== null) window.clearTimeout(hintTimerRef.current);
|
|
478
513
|
if (clearFocusTimerRef.current !== null) window.clearTimeout(clearFocusTimerRef.current);
|
|
514
|
+
if (revealTimerRef.current !== null) window.clearTimeout(revealTimerRef.current);
|
|
479
515
|
}, []);
|
|
480
516
|
if (!visible) return null;
|
|
481
517
|
const onJump = (dot) => {
|
|
@@ -532,7 +568,7 @@ window.__ModuleLoader__.load({
|
|
|
532
568
|
}) : null,
|
|
533
569
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
534
570
|
ref: listRef,
|
|
535
|
-
className:
|
|
571
|
+
className: question_nav_module_css_default.list,
|
|
536
572
|
onScroll: syncScroll,
|
|
537
573
|
onMouseLeave: scheduleClearFocus,
|
|
538
574
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
@@ -541,14 +577,20 @@ window.__ModuleLoader__.load({
|
|
|
541
577
|
const isFocused = focus !== null && focus.key === dot.key;
|
|
542
578
|
const tier = selectedIndex < 0 ? null : focusTier(index - selectedIndex);
|
|
543
579
|
const scale = jumpingKey === dot.key ? 1.6 : tier !== null ? focusScale(tier) : 1;
|
|
580
|
+
const reveal = revealed?.get(dot.key);
|
|
544
581
|
const cls = [question_nav_module_css_default.dot];
|
|
545
582
|
if (isFocused) cls.push(question_nav_module_css_default.focused);
|
|
546
583
|
if (jumpingKey === dot.key) cls.push(question_nav_module_css_default.active);
|
|
584
|
+
if (reveal !== void 0) cls.push(reveal.dir === 1 ? question_nav_module_css_default.dotEnterFromBottom : question_nav_module_css_default.dotEnterFromTop);
|
|
547
585
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
548
586
|
className: cls.join(" "),
|
|
549
|
-
style: {
|
|
587
|
+
style: {
|
|
588
|
+
transform: `scale(${scale})`,
|
|
589
|
+
...reveal !== void 0 ? { animationDelay: `${reveal.order * 35}ms` } : {}
|
|
590
|
+
},
|
|
550
591
|
"data-question-nav-focused": isFocused ? "true" : void 0,
|
|
551
592
|
"data-question-nav-index": index,
|
|
593
|
+
"data-question-nav-key": dot.key,
|
|
552
594
|
"aria-label": dot.texts[0] ?? "",
|
|
553
595
|
onMouseEnter: (e) => openFocus(dot, e.currentTarget),
|
|
554
596
|
onClick: () => onJump(dot)
|
|
@@ -646,24 +688,45 @@ window.__ModuleLoader__.load({
|
|
|
646
688
|
/** Field carrying the selected anchor edge. */
|
|
647
689
|
const ALIGN_FIELD = "align";
|
|
648
690
|
//#endregion
|
|
691
|
+
//#region src/core/page-size.ts
|
|
692
|
+
/**
|
|
693
|
+
* Page-size constants for the ▲/▼ paging buttons, shared by the host schema
|
|
694
|
+
* and the browser settings scope. Pure data: no DSH imports, so the client
|
|
695
|
+
* bundle may inline this module (a Host import here would leak into the
|
|
696
|
+
* browser half).
|
|
697
|
+
*
|
|
698
|
+
* @module dsh-question-nav/page-size
|
|
699
|
+
*/
|
|
700
|
+
/** Selectable page sizes: how many hidden dots one ▲/▼ click reveals. */
|
|
701
|
+
const PAGE_SIZE_OPTIONS = [
|
|
702
|
+
3,
|
|
703
|
+
5,
|
|
704
|
+
8,
|
|
705
|
+
10
|
|
706
|
+
];
|
|
707
|
+
/** Field carrying the selected page size. */
|
|
708
|
+
const PAGE_SIZE_FIELD = "pageSize";
|
|
709
|
+
//#endregion
|
|
649
710
|
//#region src/client/QuestionNavSettingsTab.tsx
|
|
650
711
|
/**
|
|
651
712
|
* The plugin's settings page inside the shell's Plugins section
|
|
652
|
-
* (`settings.plugins.tab`):
|
|
653
|
-
* conversation column the dot rail anchors to
|
|
713
|
+
* (`settings.plugins.tab`): two segmented controls — which edge of the
|
|
714
|
+
* conversation column the dot rail anchors to, and how many hidden dots one
|
|
715
|
+
* ▲/▼ paging-button click reveals. The choices are written to the
|
|
654
716
|
* `question-nav` settings namespace (registered by the host half); the strip
|
|
655
|
-
* re-anchors live when the snapshot changes.
|
|
717
|
+
* re-anchors and re-steps live when the snapshot changes.
|
|
656
718
|
*
|
|
657
719
|
* @module dsh-question-nav/client/settings-tab
|
|
658
720
|
*/
|
|
659
721
|
/** Re-render on settings snapshot changes (the register inject face is static). */
|
|
660
|
-
function
|
|
722
|
+
function useSettingsTick(subscribe) {
|
|
661
723
|
const [, bump] = (0, react.useState)(0);
|
|
662
724
|
(0, react.useEffect)(() => subscribe(() => bump((n) => n + 1)), [subscribe]);
|
|
663
725
|
}
|
|
664
726
|
function QuestionNavSettingsTab(props) {
|
|
665
|
-
|
|
727
|
+
useSettingsTick(props.subscribeSettings);
|
|
666
728
|
const align = props.align();
|
|
729
|
+
const pageSize = props.pageSize();
|
|
667
730
|
const t = props.t;
|
|
668
731
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
669
732
|
className: question_nav_module_css_default.settings,
|
|
@@ -688,16 +751,41 @@ window.__ModuleLoader__.load({
|
|
|
688
751
|
onClick: () => props.setAlign(option),
|
|
689
752
|
children: t(option === "left" ? "settings.align.left" : "settings.align.right")
|
|
690
753
|
}, option))
|
|
754
|
+
}),
|
|
755
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
756
|
+
className: question_nav_module_css_default.settingsTitle,
|
|
757
|
+
children: t("settings.pagesize.title")
|
|
758
|
+
}),
|
|
759
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
760
|
+
className: question_nav_module_css_default.settingsDesc,
|
|
761
|
+
children: t("settings.pagesize.desc")
|
|
762
|
+
}),
|
|
763
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
764
|
+
className: question_nav_module_css_default.segmented,
|
|
765
|
+
role: "radiogroup",
|
|
766
|
+
"aria-label": t("settings.pagesize.title"),
|
|
767
|
+
children: PAGE_SIZE_OPTIONS.map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
768
|
+
type: "button",
|
|
769
|
+
role: "radio",
|
|
770
|
+
"aria-checked": pageSize === option,
|
|
771
|
+
className: pageSize === option ? `${question_nav_module_css_default.segment} ${question_nav_module_css_default.segmentActive}` : question_nav_module_css_default.segment,
|
|
772
|
+
onClick: () => props.setPageSize(option),
|
|
773
|
+
children: option
|
|
774
|
+
}, option))
|
|
691
775
|
})
|
|
692
776
|
]
|
|
693
777
|
});
|
|
694
778
|
}
|
|
695
779
|
//#endregion
|
|
696
780
|
//#region src/client/settings.ts
|
|
697
|
-
/** Narrow a raw section to the anchor field. */
|
|
781
|
+
/** Narrow a raw section value to the anchor field. */
|
|
698
782
|
function isAlignPreference(value) {
|
|
699
783
|
return ALIGN_OPTIONS.some((option) => option === value);
|
|
700
784
|
}
|
|
785
|
+
/** Narrow a raw section value to the page-size field. */
|
|
786
|
+
function isPageSize(value) {
|
|
787
|
+
return PAGE_SIZE_OPTIONS.includes(value);
|
|
788
|
+
}
|
|
701
789
|
/** Reactive handle over the plugin's durable settings section. */
|
|
702
790
|
var QuestionNavSettingsController = class {
|
|
703
791
|
scope;
|
|
@@ -705,6 +793,7 @@ window.__ModuleLoader__.load({
|
|
|
705
793
|
unsubscribe = () => {};
|
|
706
794
|
state = {
|
|
707
795
|
align: DEFAULT_ALIGN,
|
|
796
|
+
pageSize: 5,
|
|
708
797
|
overridden: false
|
|
709
798
|
};
|
|
710
799
|
/**
|
|
@@ -721,7 +810,7 @@ window.__ModuleLoader__.load({
|
|
|
721
810
|
this.unsubscribe = this.scope.subscribe(() => {
|
|
722
811
|
if (this.scope === void 0) return;
|
|
723
812
|
const next = this.derive(this.scope.getSnapshot());
|
|
724
|
-
if (next.align === this.state.align && next.overridden === this.state.overridden) return;
|
|
813
|
+
if (next.align === this.state.align && next.pageSize === this.state.pageSize && next.overridden === this.state.overridden) return;
|
|
725
814
|
this.state = next;
|
|
726
815
|
for (const listener of this.listeners) listener();
|
|
727
816
|
});
|
|
@@ -730,7 +819,8 @@ window.__ModuleLoader__.load({
|
|
|
730
819
|
const user = snapshot.user;
|
|
731
820
|
return {
|
|
732
821
|
align: snapshot.status === "ready" && isAlignPreference(snapshot.value?.align) ? snapshot.value.align : DEFAULT_ALIGN,
|
|
733
|
-
|
|
822
|
+
pageSize: snapshot.status === "ready" && isPageSize(snapshot.value?.pageSize) ? snapshot.value.pageSize : 5,
|
|
823
|
+
overridden: user !== void 0 && (user.align !== void 0 || user.pageSize !== void 0)
|
|
734
824
|
};
|
|
735
825
|
}
|
|
736
826
|
/** Release the scope subscription (bound on the settings fiber's lifecycle). */
|
|
@@ -754,6 +844,11 @@ window.__ModuleLoader__.load({
|
|
|
754
844
|
if (this.scope === void 0) return;
|
|
755
845
|
this.scope.set(ALIGN_FIELD, align);
|
|
756
846
|
}
|
|
847
|
+
/** Route the user's page-size choice to the Host document. */
|
|
848
|
+
setPageSize(pageSize) {
|
|
849
|
+
if (this.scope === void 0) return;
|
|
850
|
+
this.scope.set(PAGE_SIZE_FIELD, pageSize);
|
|
851
|
+
}
|
|
757
852
|
};
|
|
758
853
|
//#endregion
|
|
759
854
|
//#region src/client/locales.ts
|
|
@@ -763,8 +858,8 @@ window.__ModuleLoader__.load({
|
|
|
763
858
|
*/
|
|
764
859
|
const zh = {
|
|
765
860
|
"strip.empty": "本会话还没有提问",
|
|
766
|
-
"strip.up": "
|
|
767
|
-
"strip.down": "
|
|
861
|
+
"strip.up": "向上翻出更多提问圆点",
|
|
862
|
+
"strip.down": "向下翻出更多提问圆点",
|
|
768
863
|
"jump.inactive": "聊天视图未激活",
|
|
769
864
|
"jump.hidden": "目标无独立气泡,已定位到邻近内容",
|
|
770
865
|
"jump.notfound": "目标未加载或不存在(可能已压缩)",
|
|
@@ -773,12 +868,14 @@ window.__ModuleLoader__.load({
|
|
|
773
868
|
"settings.align.title": "导航条对齐",
|
|
774
869
|
"settings.align.desc": "选择圆点导航条锚定在对话栏的哪一侧。",
|
|
775
870
|
"settings.align.left": "左侧",
|
|
776
|
-
"settings.align.right": "右侧"
|
|
871
|
+
"settings.align.right": "右侧",
|
|
872
|
+
"settings.pagesize.title": "每次翻出数量",
|
|
873
|
+
"settings.pagesize.desc": "点击 ▲/▼ 三角按钮时,每次翻出的隐藏圆点数量。"
|
|
777
874
|
};
|
|
778
875
|
const en = {
|
|
779
876
|
"strip.empty": "No questions in this session yet",
|
|
780
|
-
"strip.up": "Reveal
|
|
781
|
-
"strip.down": "Reveal
|
|
877
|
+
"strip.up": "Reveal more question dots above",
|
|
878
|
+
"strip.down": "Reveal more question dots below",
|
|
782
879
|
"jump.inactive": "Chat view is not active",
|
|
783
880
|
"jump.hidden": "No dedicated bubble; landed on nearby content",
|
|
784
881
|
"jump.notfound": "Target not loaded or missing (maybe compacted)",
|
|
@@ -787,7 +884,9 @@ window.__ModuleLoader__.load({
|
|
|
787
884
|
"settings.align.title": "Rail alignment",
|
|
788
885
|
"settings.align.desc": "Choose which edge of the conversation column the dot rail anchors to.",
|
|
789
886
|
"settings.align.left": "Left",
|
|
790
|
-
"settings.align.right": "Right"
|
|
887
|
+
"settings.align.right": "Right",
|
|
888
|
+
"settings.pagesize.title": "Dots per page",
|
|
889
|
+
"settings.pagesize.desc": "How many hidden dots one click of the ▲/▼ paging buttons reveals."
|
|
791
890
|
};
|
|
792
891
|
//#endregion
|
|
793
892
|
//#region src/core/nodes.ts
|
|
@@ -1017,8 +1116,10 @@ window.__ModuleLoader__.load({
|
|
|
1017
1116
|
jumpToQuestion(ports, key);
|
|
1018
1117
|
},
|
|
1019
1118
|
align: () => settings.getSnapshot().align,
|
|
1020
|
-
|
|
1021
|
-
setAlign: (align) => settings.setAlign(align)
|
|
1119
|
+
subscribeSettings: (cb) => settings.subscribe(cb),
|
|
1120
|
+
setAlign: (align) => settings.setAlign(align),
|
|
1121
|
+
pageSize: () => settings.getSnapshot().pageSize,
|
|
1122
|
+
setPageSize: (pageSize) => settings.setPageSize(pageSize)
|
|
1022
1123
|
};
|
|
1023
1124
|
}
|
|
1024
1125
|
/**
|