dsh-quick-toc 0.7.1 → 0.7.3
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.en.md +131 -121
- package/CHANGELOG.md +122 -112
- package/README.en.md +5 -4
- package/README.md +5 -4
- package/lib/client.js +209 -30
- package/lib/index.js +65 -26
- package/package.json +8 -5
package/lib/client.js
CHANGED
|
@@ -159,6 +159,8 @@ window.__ModuleLoader__.load({
|
|
|
159
159
|
"settings.levels": "显示的标题层级",
|
|
160
160
|
"settings.zoom": "面板缩放",
|
|
161
161
|
"settings.zoom.tip": "文字、图标与按钮按比例缩放,面板本身的尺寸不变",
|
|
162
|
+
"settings.sheetZoom": "幕布缩放",
|
|
163
|
+
"settings.sheetZoom.tip": "幕布里的文字、图标与按钮按比例缩放,幕布本身的尺寸不变",
|
|
162
164
|
"settings.handle": "把手位置",
|
|
163
165
|
"settings.handle.tip": "收起面板后,边缘把手在对话区里的上下位置(0% 最下、100% 最上)",
|
|
164
166
|
"settings.on": "开启",
|
|
@@ -255,6 +257,8 @@ window.__ModuleLoader__.load({
|
|
|
255
257
|
"settings.levels": "Heading levels shown",
|
|
256
258
|
"settings.zoom": "Panel scale",
|
|
257
259
|
"settings.zoom.tip": "Scales the text, icons and buttons; the panel's own size is unchanged",
|
|
260
|
+
"settings.sheetZoom": "Curtain scale",
|
|
261
|
+
"settings.sheetZoom.tip": "Scales the curtain's text, icons and buttons; the curtain's own size is unchanged",
|
|
258
262
|
"settings.handle": "Handle position",
|
|
259
263
|
"settings.handle.tip": "Where the collapsed-panel handle sits vertically (0% bottom, 100% top of the conversation area)",
|
|
260
264
|
"settings.on": "On",
|
|
@@ -335,13 +339,13 @@ window.__ModuleLoader__.load({
|
|
|
335
339
|
// purpose — they describe THIS screen, not the account.
|
|
336
340
|
var NAMESPACE = "dsh-quick-toc";
|
|
337
341
|
// Fields backed by the Host settings document; everything else is localStorage.
|
|
338
|
-
var HOST_FIELDS = { dock: 1, lang: 1, levels: 1, zoom: 1, handle: 1, fuzzy: 1, hover: 1, debug: 1, remember: 1, autoLoad: 1 };
|
|
342
|
+
var HOST_FIELDS = { dock: 1, lang: 1, levels: 1, zoom: 1, sheetZoom: 1, handle: 1, fuzzy: 1, hover: 1, debug: 1, remember: 1, autoLoad: 1 };
|
|
339
343
|
// The schema defaults (mirrored here for the card's per-field reset and for the
|
|
340
344
|
// one-time import of a 0.5.x install's stored values).
|
|
341
345
|
var DEFAULTS = {
|
|
342
346
|
dock: "left", y: 0, w: 0, h: 0,
|
|
343
347
|
levels: [1, 2, 3, 4, 5, 6],
|
|
344
|
-
zoom: 1, handle: 0.5,
|
|
348
|
+
zoom: 1, sheetZoom: 1, handle: 0.5,
|
|
345
349
|
fuzzy: false, hover: true, lang: "auto", debug: false,
|
|
346
350
|
remember: true, autoLoad: true
|
|
347
351
|
};
|
|
@@ -352,6 +356,7 @@ window.__ModuleLoader__.load({
|
|
|
352
356
|
h: "dsh-quick-toc.panelH.v1",
|
|
353
357
|
levels: "dsh-quick-toc.levels.v1",
|
|
354
358
|
zoom: "dsh-quick-toc.zoom.v1",
|
|
359
|
+
sheetZoom: "dsh-quick-toc.sheetZoom.v1",
|
|
355
360
|
handle: "dsh-quick-toc.handle.v1",
|
|
356
361
|
fuzzy: "dsh-quick-toc.fuzzy.v1",
|
|
357
362
|
hover: "dsh-quick-toc.hover.v1",
|
|
@@ -471,6 +476,7 @@ window.__ModuleLoader__.load({
|
|
|
471
476
|
var hRaw = read(PREF_KEYS.h);
|
|
472
477
|
var langRaw = read(PREF_KEYS.lang);
|
|
473
478
|
var zoomRaw = read(PREF_KEYS.zoom); // NB: absent (null) means 1 — Number(null) is 0!
|
|
479
|
+
var sheetZoomRaw = read(PREF_KEYS.sheetZoom); // same trap
|
|
474
480
|
var handleRaw = read(PREF_KEYS.handle); // absent (null) means 0.5 — same trap
|
|
475
481
|
var clampRatio = function (raw, fallback) {
|
|
476
482
|
return raw !== null && isFinite(Number(raw)) ? Math.min(1, Math.max(0, Math.round(Number(raw) * 100) / 100)) : fallback;
|
|
@@ -483,6 +489,7 @@ window.__ModuleLoader__.load({
|
|
|
483
489
|
h: hRaw !== null && isFinite(Number(hRaw)) && Number(hRaw) >= 60 ? Number(hRaw) : 0,
|
|
484
490
|
levels: parseLevels(rawLevels) || LEVELS_ALL.slice(),
|
|
485
491
|
zoom: clampRatio(zoomRaw, 1),
|
|
492
|
+
sheetZoom: clampRatio(sheetZoomRaw, 1),
|
|
486
493
|
handle: clampRatio(handleRaw, 0.5),
|
|
487
494
|
fuzzy: read(PREF_KEYS.fuzzy) === "1",
|
|
488
495
|
// absent means "on": the hover card predates this switch
|
|
@@ -503,6 +510,7 @@ window.__ModuleLoader__.load({
|
|
|
503
510
|
h: function (v) { return String(v); },
|
|
504
511
|
levels: function (v) { return JSON.stringify(v); },
|
|
505
512
|
zoom: function (v) { return String(v); },
|
|
513
|
+
sheetZoom: function (v) { return String(v); },
|
|
506
514
|
handle: function (v) { return String(v); },
|
|
507
515
|
fuzzy: function (v) { return v ? "1" : "0"; },
|
|
508
516
|
hover: function (v) { return v ? "1" : "0"; },
|
|
@@ -529,12 +537,14 @@ window.__ModuleLoader__.load({
|
|
|
529
537
|
var v = snap.value;
|
|
530
538
|
var levels = parseLevelList(v.levels);
|
|
531
539
|
var zoomNum = Number(v.zoom);
|
|
540
|
+
var sheetZoomNum = Number(v.sheetZoom);
|
|
532
541
|
var handleNum = Number(v.handle);
|
|
533
542
|
return {
|
|
534
543
|
dock: v.dock === "right" ? "right" : "left",
|
|
535
544
|
lang: v.lang === "zh" || v.lang === "en" ? v.lang : "auto",
|
|
536
545
|
levels: levels || LEVELS_ALL.slice(),
|
|
537
546
|
zoom: isFinite(zoomNum) ? Math.min(2, Math.max(0.5, Math.round(zoomNum * 100) / 100)) : 1,
|
|
547
|
+
sheetZoom: isFinite(sheetZoomNum) ? Math.min(2, Math.max(0.5, Math.round(sheetZoomNum * 100) / 100)) : 1,
|
|
538
548
|
handle: isFinite(handleNum) ? Math.min(1, Math.max(0, Math.round(handleNum * 100) / 100)) : 0.5,
|
|
539
549
|
fuzzy: v.fuzzy === true,
|
|
540
550
|
hover: v.hover !== false,
|
|
@@ -549,7 +559,7 @@ window.__ModuleLoader__.load({
|
|
|
549
559
|
if (!over) return local;
|
|
550
560
|
return {
|
|
551
561
|
dock: over.dock, y: local.y, w: local.w, h: local.h,
|
|
552
|
-
levels: over.levels, zoom: over.zoom, handle: over.handle, fuzzy: over.fuzzy, hover: over.hover,
|
|
562
|
+
levels: over.levels, zoom: over.zoom, sheetZoom: over.sheetZoom, handle: over.handle, fuzzy: over.fuzzy, hover: over.hover,
|
|
553
563
|
lang: over.lang, remember: over.remember, debug: over.debug, autoLoad: over.autoLoad
|
|
554
564
|
};
|
|
555
565
|
};
|
|
@@ -1441,6 +1451,17 @@ window.__ModuleLoader__.load({
|
|
|
1441
1451
|
return null;
|
|
1442
1452
|
}
|
|
1443
1453
|
};
|
|
1454
|
+
// { uiWorkspace() } bridge: the workspace-navigation service that switches sessions on
|
|
1455
|
+
// the alpha (`uiWorkspace.openSession(target)`); rc.2 has no such service and switches
|
|
1456
|
+
// through `sessions.open`. Same lookup discipline as the session bridge.
|
|
1457
|
+
var hostUiWorkspace = function () {
|
|
1458
|
+
if (!tocHost || typeof tocHost.uiWorkspace !== "function") return null;
|
|
1459
|
+
try {
|
|
1460
|
+
return tocHost.uiWorkspace();
|
|
1461
|
+
} catch (e) {
|
|
1462
|
+
return null;
|
|
1463
|
+
}
|
|
1464
|
+
};
|
|
1444
1465
|
// Which stage of the bridge is actually ready — reported once at mount so a
|
|
1445
1466
|
// missing jump loader can be diagnosed from the console instead of guessed.
|
|
1446
1467
|
// Past "the loader exists" the PAGE STATE is what decides whether a jump can do
|
|
@@ -1827,7 +1848,7 @@ window.__ModuleLoader__.load({
|
|
|
1827
1848
|
if (node) {
|
|
1828
1849
|
// put that row back where it SAT (not necessarily at the top): a row the reader left
|
|
1829
1850
|
// clipped by 120px goes home clipped by 120px, so the view lands exactly where it was
|
|
1830
|
-
var d = (node.getBoundingClientRect().top - el.getBoundingClientRect().top) - tgt.off;
|
|
1851
|
+
var d = ((node.getBoundingClientRect().top - el.getBoundingClientRect().top) - tgt.off) / liveZoom();
|
|
1831
1852
|
if (Math.abs(d) > 0.5) el.scrollTop = el.scrollTop + d;
|
|
1832
1853
|
}
|
|
1833
1854
|
syncAtBottom(el);
|
|
@@ -2510,14 +2531,17 @@ window.__ModuleLoader__.load({
|
|
|
2510
2531
|
if (!node) { sampleAnchor(el); return false; }
|
|
2511
2532
|
var top = node.getBoundingClientRect().top;
|
|
2512
2533
|
var moved = el.scrollTop - a.scrollTop; // what the reader (or a jump) scrolled
|
|
2534
|
+
var zf = liveZoom();
|
|
2513
2535
|
// Growth above the anchor moves it DOWN by exactly as much as the content grew; the
|
|
2514
2536
|
// reader scrolling up moves it down by exactly as much as they scrolled. Both happen
|
|
2515
2537
|
// at once when a page lands mid-gesture, so subtract the reader's part rather than
|
|
2516
2538
|
// refusing to compensate — the old "scrollTop changed, so the reader moved, skip"
|
|
2517
2539
|
// guard is exactly why a load landing during a scroll was still seen pushing the
|
|
2518
2540
|
// list. (The list carries `overflow-anchor: none`, so the browser is not adjusting
|
|
2519
|
-
// `scrollTop` behind our back and this sum stays honest.)
|
|
2520
|
-
|
|
2541
|
+
// `scrollTop` behind our back and this sum stays honest.) The measured growth is in
|
|
2542
|
+
// visual pixels while `moved` is already in the scroller's own units, so the growth
|
|
2543
|
+
// term is divided by the live factor.
|
|
2544
|
+
var grew = (top - a.top) / zf + moved;
|
|
2521
2545
|
if (Math.abs(grew) < 0.5 || Math.abs(grew) > (el.clientHeight || 0) * 3) {
|
|
2522
2546
|
// nothing grew — or something absurd happened (a repaged list, a session swap):
|
|
2523
2547
|
// re-baseline instead of chasing it
|
|
@@ -2532,7 +2556,7 @@ window.__ModuleLoader__.load({
|
|
|
2532
2556
|
return false;
|
|
2533
2557
|
}
|
|
2534
2558
|
a.scrollTop = el.scrollTop;
|
|
2535
|
-
a.top = top - grew; // the offset the anchor keeps from now on
|
|
2559
|
+
a.top = top - grew * zf; // the visual offset the anchor keeps from now on
|
|
2536
2560
|
return true;
|
|
2537
2561
|
};
|
|
2538
2562
|
var holdPlace = function (el) { sampleAnchor(el); };
|
|
@@ -2880,6 +2904,28 @@ window.__ModuleLoader__.load({
|
|
|
2880
2904
|
if (zoom === 1) return typeof v === "number" ? v + "px" : v;
|
|
2881
2905
|
return typeof v === "number" ? (v / zoom) + "px" : "calc(" + v + " / " + zoom + ")";
|
|
2882
2906
|
};
|
|
2907
|
+
// The curtain carries its OWN scale: the docked panel's factor does not apply to it,
|
|
2908
|
+
// and a reader who wants a dense outline in the dock and a larger one across the
|
|
2909
|
+
// curtain (or the other way round) gets two independent sliders.
|
|
2910
|
+
//
|
|
2911
|
+
// The slider's 100% is 0.9 of the size the curtain had before this setting existed: the
|
|
2912
|
+
// reader asked for that base once they could compare it with a scaled one, and 100% is
|
|
2913
|
+
// still what the card reads at the default — the mapping is re-based here instead of
|
|
2914
|
+
// shipping a slider whose default reads 90%, so a stored factor keeps meaning the same
|
|
2915
|
+
// fraction of this base.
|
|
2916
|
+
var SHEET_ZOOM_BASE = 0.9;
|
|
2917
|
+
var sheetZoom = (prefs.sheetZoom > 0 ? prefs.sheetZoom : 1) * SHEET_ZOOM_BASE;
|
|
2918
|
+
// Whichever surface is on screen owns the coordinate space the list lives in: the
|
|
2919
|
+
// scroller's scrollTop is in the zoomed space while getBoundingClientRect reports
|
|
2920
|
+
// visual pixels, so a measured delta has to be divided by THAT factor before it is
|
|
2921
|
+
// written back as a scroll offset (and a CSS length written inside the surface is
|
|
2922
|
+
// divided so it keeps its visual size).
|
|
2923
|
+
var liveZoom = function () { return sheetOpen ? sheetZoom : zoom; };
|
|
2924
|
+
var liveLen = function (v) {
|
|
2925
|
+
var f = liveZoom();
|
|
2926
|
+
if (f === 1) return typeof v === "number" ? v + "px" : v;
|
|
2927
|
+
return typeof v === "number" ? (v / f) + "px" : "calc(" + v + " / " + f + ")";
|
|
2928
|
+
};
|
|
2883
2929
|
|
|
2884
2930
|
// ---- resize drags (right edge = width, bottom edge = height) ----
|
|
2885
2931
|
// No upper bound and only a keep-it-usable floor: the reader decides how small
|
|
@@ -3397,7 +3443,8 @@ window.__ModuleLoader__.load({
|
|
|
3397
3443
|
// why the follow stopped "a little short of the bottom": for the newest turn the
|
|
3398
3444
|
// centre is above the end of the list, so the scroll landed half a viewport early.
|
|
3399
3445
|
// Aligning to the top needs no clamp at all — the list's own end does the work.
|
|
3400
|
-
|
|
3446
|
+
// (Rects are visual; the factor turns the delta back into the scroller's units.)
|
|
3447
|
+
var delta = (node.getBoundingClientRect().top - el.getBoundingClientRect().top) / liveZoom();
|
|
3401
3448
|
if (Math.abs(delta) > 1) el.scrollTop = el.scrollTop + delta;
|
|
3402
3449
|
};
|
|
3403
3450
|
followFrame = requestAnimationFrame(function () { followFrame = requestAnimationFrame(place); });
|
|
@@ -3557,9 +3604,33 @@ window.__ModuleLoader__.load({
|
|
|
3557
3604
|
// clicking a cross-session hit: switch the session, then leave the keyword for
|
|
3558
3605
|
// the panel that comes up (see pendingLocate) — the host hit has no seq, so the
|
|
3559
3606
|
// jump happens on the other side, after the same search runs there.
|
|
3560
|
-
|
|
3607
|
+
//
|
|
3608
|
+
// Two dialects again: rc.2 switches sessions with `sessions.open(id)`, while the
|
|
3609
|
+
// alpha moved that decision to the workspace navigation service
|
|
3610
|
+
// (`uiWorkspace.openSession(target)`) and dropped `open` from the session service
|
|
3611
|
+
// altogether. Both are optional lookups, so whatever the host offers is used and a
|
|
3612
|
+
// host offering neither says so instead of doing nothing.
|
|
3613
|
+
var openSessionById = function (id) {
|
|
3614
|
+
var uiWorkspace = hostUiWorkspace();
|
|
3615
|
+
if (uiWorkspace && typeof uiWorkspace.openSession === "function") {
|
|
3616
|
+
uiWorkspace.openSession(id);
|
|
3617
|
+
return true;
|
|
3618
|
+
}
|
|
3561
3619
|
var sessions = hostSessions();
|
|
3562
|
-
if (
|
|
3620
|
+
if (sessions && typeof sessions.open === "function") {
|
|
3621
|
+
sessions.open(id);
|
|
3622
|
+
return true;
|
|
3623
|
+
}
|
|
3624
|
+
return false;
|
|
3625
|
+
};
|
|
3626
|
+
var canOpenSession = function () {
|
|
3627
|
+
var uiWorkspace = hostUiWorkspace();
|
|
3628
|
+
if (uiWorkspace && typeof uiWorkspace.openSession === "function") return true;
|
|
3629
|
+
var sessions = hostSessions();
|
|
3630
|
+
return !!(sessions && typeof sessions.open === "function");
|
|
3631
|
+
};
|
|
3632
|
+
var openCrossHit = function (hitSessionId) {
|
|
3633
|
+
if (!canOpenSession()) {
|
|
3563
3634
|
showBanner(T("search.cross.unavailable"));
|
|
3564
3635
|
return;
|
|
3565
3636
|
}
|
|
@@ -3570,7 +3641,7 @@ window.__ModuleLoader__.load({
|
|
|
3570
3641
|
}
|
|
3571
3642
|
pendingLocate = { sessionId: hitSessionId, query: query.trim(), at: Date.now() };
|
|
3572
3643
|
try {
|
|
3573
|
-
|
|
3644
|
+
openSessionById(hitSessionId);
|
|
3574
3645
|
} catch (e) {
|
|
3575
3646
|
pendingLocate = null;
|
|
3576
3647
|
showBanner(T("search.cross.error") + String(e && e.message ? e.message : e));
|
|
@@ -4368,7 +4439,8 @@ window.__ModuleLoader__.load({
|
|
|
4368
4439
|
if (!el) return false;
|
|
4369
4440
|
var node = el.querySelector('[data-group-idx="' + gi + '"]');
|
|
4370
4441
|
if (!node) return false;
|
|
4371
|
-
|
|
4442
|
+
// the rects are visual: divide by the live factor before writing a scroll offset
|
|
4443
|
+
var delta = (node.getBoundingClientRect().top - el.getBoundingClientRect().top) / liveZoom();
|
|
4372
4444
|
if (Math.abs(delta) > 0.5) el.scrollTop = el.scrollTop + delta;
|
|
4373
4445
|
return true;
|
|
4374
4446
|
};
|
|
@@ -5429,9 +5501,9 @@ window.__ModuleLoader__.load({
|
|
|
5429
5501
|
if (root && typeof root.focus === "function") root.focus();
|
|
5430
5502
|
},
|
|
5431
5503
|
style: sheetOpen ? {
|
|
5432
|
-
// inside the sheet the panel IS the content: the wrapper owns position,
|
|
5433
|
-
//
|
|
5434
|
-
//
|
|
5504
|
+
// inside the sheet the panel IS the content: the wrapper owns position, size and
|
|
5505
|
+
// the drop-in animation, while the curtain's own scale (a separate preference)
|
|
5506
|
+
// zooms what is inside it — the box the wrapper gives it stays as it is
|
|
5435
5507
|
position: "relative",
|
|
5436
5508
|
width: "100%",
|
|
5437
5509
|
flex: "1 1 auto",
|
|
@@ -5445,6 +5517,8 @@ window.__ModuleLoader__.load({
|
|
|
5445
5517
|
overflow: "hidden",
|
|
5446
5518
|
color: C.text,
|
|
5447
5519
|
opacity: 1,
|
|
5520
|
+
// the curtain's own scale (the docked panel's does not apply here)
|
|
5521
|
+
zoom: sheetZoom === 1 ? undefined : sheetZoom,
|
|
5448
5522
|
pointerEvents: chatViewActive ? "auto" : "none"
|
|
5449
5523
|
} : {
|
|
5450
5524
|
position: "fixed",
|
|
@@ -5611,8 +5685,9 @@ window.__ModuleLoader__.load({
|
|
|
5611
5685
|
style: {
|
|
5612
5686
|
position: "absolute",
|
|
5613
5687
|
right: (sheetOpen && sheetColW > 0 ? SHEET_SEARCH_W : 0) + 18,
|
|
5614
|
-
// below whatever chrome this surface has
|
|
5615
|
-
|
|
5688
|
+
// below whatever chrome this surface has: `listTop` is measured in visual
|
|
5689
|
+
// pixels, so it is divided by the live factor to land where it was measured
|
|
5690
|
+
top: liveLen(listTop + 12),
|
|
5616
5691
|
width: sheetOpen ? 34 : 26,
|
|
5617
5692
|
height: sheetOpen ? 34 : 26,
|
|
5618
5693
|
display: "flex",
|
|
@@ -6859,15 +6934,18 @@ window.__ModuleLoader__.load({
|
|
|
6859
6934
|
var sliderAwaiting = react.useRef({});
|
|
6860
6935
|
var commitSlider = function (field, pct) {
|
|
6861
6936
|
sliderAwaiting.current[field] = pct;
|
|
6862
|
-
commit(field === "zoom" ? { zoom: pct / 100 }
|
|
6937
|
+
commit(field === "zoom" ? { zoom: pct / 100 }
|
|
6938
|
+
: field === "sheetZoom" ? { sheetZoom: pct / 100 }
|
|
6939
|
+
: { handle: pct / 100 });
|
|
6863
6940
|
};
|
|
6864
6941
|
react.useEffect(function () {
|
|
6865
6942
|
var a = sliderAwaiting.current;
|
|
6866
6943
|
var settled = false;
|
|
6867
6944
|
if (a.zoom !== undefined && Math.round(prefs.zoom * 100) === a.zoom) { delete a.zoom; settled = true; }
|
|
6945
|
+
if (a.sheetZoom !== undefined && Math.round(prefs.sheetZoom * 100) === a.sheetZoom) { delete a.sheetZoom; settled = true; }
|
|
6868
6946
|
if (a.handle !== undefined && Math.round(prefs.handle * 100) === a.handle) { delete a.handle; settled = true; }
|
|
6869
6947
|
if (settled) setSliderDrag(null);
|
|
6870
|
-
}, [prefs.zoom, prefs.handle]);
|
|
6948
|
+
}, [prefs.zoom, prefs.sheetZoom, prefs.handle]);
|
|
6871
6949
|
react.useEffect(function () {
|
|
6872
6950
|
if (sliderDrag === null) return undefined;
|
|
6873
6951
|
var release = function () {
|
|
@@ -6917,6 +6995,7 @@ window.__ModuleLoader__.load({
|
|
|
6917
6995
|
});
|
|
6918
6996
|
};
|
|
6919
6997
|
var zoomControl = sliderCtl("zoom", "settings.zoom", 50, 200, 5, Math.round(prefs.zoom * 100));
|
|
6998
|
+
var sheetZoomControl = sliderCtl("sheetZoom", "settings.sheetZoom", 50, 200, 5, Math.round(prefs.sheetZoom * 100));
|
|
6920
6999
|
var handleControl = sliderCtl("handle", "settings.handle", 0, 100, 1, Math.round(prefs.handle * 100));
|
|
6921
7000
|
var field = function (key, label, hint, control, inline, last) {
|
|
6922
7001
|
var overridden = Object.prototype.hasOwnProperty.call(user, key);
|
|
@@ -6978,6 +7057,7 @@ window.__ModuleLoader__.load({
|
|
|
6978
7057
|
function (v) { commit({ dock: v }); }), true),
|
|
6979
7058
|
field("levels", T("settings.levels"), "", levelsControl(true), true),
|
|
6980
7059
|
field("zoom", T("settings.zoom"), T("settings.zoom.tip"), zoomControl, false),
|
|
7060
|
+
field("sheetZoom", T("settings.sheetZoom"), T("settings.sheetZoom.tip"), sheetZoomControl, false),
|
|
6981
7061
|
field("handle", T("settings.handle"), T("settings.handle.tip"), handleControl, false),
|
|
6982
7062
|
field("fuzzy", T("settings.fuzzy"), "", onoff(prefs.fuzzy, function (v) { commit({ fuzzy: v }); }), true),
|
|
6983
7063
|
field("hover", T("settings.hover"), "", onoff(prefs.hover, function (v) { commit({ hover: v }); }), true),
|
|
@@ -7029,7 +7109,10 @@ window.__ModuleLoader__.load({
|
|
|
7029
7109
|
});
|
|
7030
7110
|
}
|
|
7031
7111
|
|
|
7032
|
-
|
|
7112
|
+
// Declared client services only. `slots` and `locale` exist on every supported line;
|
|
7113
|
+
// the settings service is looked up with `ctx.get` instead (see `pickSettingsScope`),
|
|
7114
|
+
// because declaring a name a host does not provide parks the whole package.
|
|
7115
|
+
var inject = ["slots", "locale"];
|
|
7033
7116
|
|
|
7034
7117
|
function apply(ctx) {
|
|
7035
7118
|
ctx.effect(function () {
|
|
@@ -7094,6 +7177,16 @@ window.__ModuleLoader__.load({
|
|
|
7094
7177
|
} catch (e) {
|
|
7095
7178
|
return null;
|
|
7096
7179
|
}
|
|
7180
|
+
},
|
|
7181
|
+
// The alpha's session navigation (uiWorkspace.openSession); the older line has no
|
|
7182
|
+
// such service and switches sessions through `sessions.open` instead. Optional by
|
|
7183
|
+
// the same rule: whichever exists is used, and neither existing is reported.
|
|
7184
|
+
uiWorkspace: function () {
|
|
7185
|
+
try {
|
|
7186
|
+
return typeof ctx.get === "function" ? (ctx.get("uiWorkspace") || null) : null;
|
|
7187
|
+
} catch (e) {
|
|
7188
|
+
return null;
|
|
7189
|
+
}
|
|
7097
7190
|
}
|
|
7098
7191
|
};
|
|
7099
7192
|
|
|
@@ -7114,15 +7207,86 @@ window.__ModuleLoader__.load({
|
|
|
7114
7207
|
});
|
|
7115
7208
|
});
|
|
7116
7209
|
|
|
7117
|
-
// The client settings scope: the browser face of the namespace the host
|
|
7118
|
-
//
|
|
7119
|
-
// host-backed fields and routes writes to it; on a host without
|
|
7210
|
+
// The client settings scope: the browser face of the settings namespace the host
|
|
7211
|
+
// half serves. The store adopts its snapshot as the authoritative layer for the
|
|
7212
|
+
// host-backed fields and routes writes to it; on a host without either dialect (or
|
|
7120
7213
|
// on a non-loopback page, where DSH keeps settings read-only) the localStorage
|
|
7121
7214
|
// mirror keeps every preference working, exactly as 0.5.x did.
|
|
7122
|
-
|
|
7123
|
-
|
|
7215
|
+
//
|
|
7216
|
+
// TWO DIALECTS, ONE SHAPE. Up to DSH 0.1.5-rc.2 the host serves a
|
|
7217
|
+
// `settingsScope` service whose `bind({namespace})` returns a namespace scope; from
|
|
7218
|
+
// 0.1.7-alpha.1 on, that service is gone and the same object is handed out by the
|
|
7219
|
+
// `configForms` service per PROFILE ENTRY ID (`get(id)`). Both expose
|
|
7220
|
+
// `getSnapshot()` / `mutate(ops)` / `unset(field)` over the same snapshot fields, so
|
|
7221
|
+
// one adapter covers them.
|
|
7222
|
+
//
|
|
7223
|
+
// Neither is declared in `inject`: a name the running host does not provide parks
|
|
7224
|
+
// the whole package until it appears, and on 0.1.7-alpha.1 that turned into a web
|
|
7225
|
+
// boot that never finished (`1 entry did not activate: dsh-quick-toc: pending
|
|
7226
|
+
// (waiting for service: settingsScope)`), taking the entire GUI with it. Both
|
|
7227
|
+
// declaration-free routes are tried — `ctx.get(name)` first, then the service
|
|
7228
|
+
// property the older line has always offered — and a missing service simply leaves
|
|
7229
|
+
// the localStorage layer in charge.
|
|
7230
|
+
var getService = function (name) {
|
|
7231
|
+
try {
|
|
7232
|
+
if (typeof ctx.get === "function") {
|
|
7233
|
+
var found = ctx.get(name);
|
|
7234
|
+
if (found) return found;
|
|
7235
|
+
}
|
|
7236
|
+
} catch (e) { /* fall through to the property read */ }
|
|
7237
|
+
try {
|
|
7238
|
+
return ctx[name] || null;
|
|
7239
|
+
} catch (e) {
|
|
7240
|
+
return null;
|
|
7241
|
+
}
|
|
7242
|
+
};
|
|
7243
|
+
// Which namespace the `configForms` dialect should be asked for. Its key is the
|
|
7244
|
+
// profile entry id, which the installing profile's patch decides (this package's own
|
|
7245
|
+
// cordis.patch.yml inserts the row as `quick-toc`), so the served schema is what
|
|
7246
|
+
// identifies us: a namespace carrying this plugin's own fields is ours whatever it
|
|
7247
|
+
// is called.
|
|
7248
|
+
var formsNamespace = function (forms) {
|
|
7249
|
+
var candidates = ["quick-toc", "dsh-quick-toc"];
|
|
7250
|
+
var served = [];
|
|
7251
|
+
try {
|
|
7252
|
+
var view = forms.describe ? forms.describe().getSnapshot().view : null;
|
|
7253
|
+
served = (view && view.namespaces) || [];
|
|
7254
|
+
} catch (e) {
|
|
7255
|
+
served = [];
|
|
7256
|
+
}
|
|
7257
|
+
for (var i = 0; i < served.length; i++) {
|
|
7258
|
+
var ns = served[i] || {};
|
|
7259
|
+
var json = "";
|
|
7260
|
+
try { json = JSON.stringify(ns.schema || {}); } catch (e) { json = ""; }
|
|
7261
|
+
if (json.indexOf("sheetZoom") >= 0 && json.indexOf("autoLoad") >= 0) return ns.ns;
|
|
7262
|
+
}
|
|
7263
|
+
for (var j = 0; j < candidates.length; j++) {
|
|
7264
|
+
try {
|
|
7265
|
+
var form = forms.get(candidates[j]);
|
|
7266
|
+
if (form && form.getSnapshot && form.getSnapshot().status === "ready") return candidates[j];
|
|
7267
|
+
} catch (e) { /* try the next candidate */ }
|
|
7268
|
+
}
|
|
7269
|
+
return candidates[0];
|
|
7270
|
+
};
|
|
7271
|
+
var pickSettingsScope = function () {
|
|
7272
|
+
var scope = getService("settingsScope");
|
|
7273
|
+
if (scope && typeof scope.bind === "function") {
|
|
7274
|
+
try {
|
|
7275
|
+
var bound = scope.bind({ namespace: NAMESPACE });
|
|
7276
|
+
if (bound) return bound;
|
|
7277
|
+
} catch (e) { /* fall through to the newer dialect */ }
|
|
7278
|
+
}
|
|
7279
|
+
var forms = getService("configForms");
|
|
7280
|
+
if (forms && typeof forms.get === "function") {
|
|
7281
|
+
try {
|
|
7282
|
+
return forms.get(formsNamespace(forms));
|
|
7283
|
+
} catch (e) { /* leave the localStorage layer in charge */ }
|
|
7284
|
+
}
|
|
7285
|
+
return null;
|
|
7286
|
+
};
|
|
7287
|
+
var boundScope = pickSettingsScope();
|
|
7288
|
+
if (boundScope) {
|
|
7124
7289
|
try {
|
|
7125
|
-
boundScope = ctx.settingsScope.bind({ namespace: NAMESPACE });
|
|
7126
7290
|
prefsStore.attachHost(boundScope);
|
|
7127
7291
|
} catch (e) {
|
|
7128
7292
|
boundScope = null;
|
|
@@ -7130,11 +7294,26 @@ window.__ModuleLoader__.load({
|
|
|
7130
7294
|
}
|
|
7131
7295
|
}
|
|
7132
7296
|
|
|
7133
|
-
// The plugin-configuration card
|
|
7134
|
-
//
|
|
7135
|
-
//
|
|
7136
|
-
//
|
|
7297
|
+
// The plugin-configuration card, in whichever seat the running host offers:
|
|
7298
|
+
// * up to rc.2 it is a keyed `settings.plugin.item` cell under
|
|
7299
|
+
// Settings → Plugins → Plugin configuration, keyed by the settings namespace;
|
|
7300
|
+
// * on the alpha the plugin's own page in the sidebar's Plugins list carries it,
|
|
7301
|
+
// as the bundle's configuration keyed by the npm package name
|
|
7302
|
+
// (`plugins.bundle.config`). A row-level cell (`plugins.row.config`, keyed
|
|
7303
|
+
// `<package>#<row id>`) exists too, but a registration there did not reach that
|
|
7304
|
+
// page in 0.1.7-alpha.1 while the bundle cell did — and these preferences are the
|
|
7305
|
+
// plugin's, not one row's, so the bundle page is the right seat anyway.
|
|
7306
|
+
// Registering into a slot a given host never declares is inert (the registration
|
|
7307
|
+
// waits), so both are offered unconditionally, and only once a scope bound keeps a
|
|
7308
|
+
// half-configured host from rendering a card that could not read or write anything.
|
|
7137
7309
|
if (boundScope) {
|
|
7310
|
+
ctx.slots.inject("plugins.bundle.config", function () {
|
|
7311
|
+
return ctx.slots.register({
|
|
7312
|
+
name: "plugins.bundle.config",
|
|
7313
|
+
key: "dsh-quick-toc",
|
|
7314
|
+
locale: "dsh-quick-toc"
|
|
7315
|
+
}, TocSettingsCard);
|
|
7316
|
+
});
|
|
7138
7317
|
ctx.slots.inject("settings.plugin.item", function () {
|
|
7139
7318
|
return ctx.slots.register({
|
|
7140
7319
|
name: "settings.plugin.item",
|
package/lib/index.js
CHANGED
|
@@ -16,70 +16,109 @@ import z from "@deepseek-ai/schemastery";
|
|
|
16
16
|
/** Namespace the browser half binds and the card is keyed on. */
|
|
17
17
|
export const NAMESPACE = "dsh-quick-toc";
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Mark one preference field as a live value where the running schemastery knows
|
|
21
|
+
* the modifier, and hand the field back untouched where it does not; see
|
|
22
|
+
* `apply` for why the difference exists.
|
|
23
|
+
* @param {object} schema - the field's schema.
|
|
24
|
+
* @returns {object} the same schema, volatile on hosts that support it.
|
|
25
|
+
*/
|
|
26
|
+
const volatileSchema = (schema) =>
|
|
27
|
+
typeof schema.volatile === "function" ? schema.volatile() : schema;
|
|
28
|
+
|
|
19
29
|
/**
|
|
20
30
|
* The durable settings section. Values are written by the browser half's card
|
|
21
31
|
* (and by the panel itself), so the schema is the validation layer, not a form.
|
|
22
32
|
*/
|
|
23
33
|
export const Config = z.object({
|
|
24
|
-
lang: z
|
|
34
|
+
lang: volatileSchema(z
|
|
25
35
|
.string()
|
|
26
36
|
.pattern(/^(auto|zh|en)$/)
|
|
27
37
|
.default("auto")
|
|
28
|
-
.description("Panel language: auto follows DSH, zh or en forces one"),
|
|
29
|
-
dock: z
|
|
38
|
+
.description("Panel language: auto follows DSH, zh or en forces one")),
|
|
39
|
+
dock: volatileSchema(z
|
|
30
40
|
.string()
|
|
31
41
|
.pattern(/^(left|right)$/)
|
|
32
42
|
.default("left")
|
|
33
|
-
.description("Which edge the panel docks to by default"),
|
|
34
|
-
levels: z
|
|
43
|
+
.description("Which edge the panel docks to by default")),
|
|
44
|
+
levels: volatileSchema(z
|
|
35
45
|
.array(z.number().min(1).max(6))
|
|
36
46
|
.default([1, 2, 3, 4, 5, 6])
|
|
37
|
-
.description("Heading levels the outline shows"),
|
|
38
|
-
zoom: z
|
|
47
|
+
.description("Heading levels the outline shows")),
|
|
48
|
+
zoom: volatileSchema(z
|
|
49
|
+
.number()
|
|
50
|
+
.min(0.5)
|
|
51
|
+
.max(2)
|
|
52
|
+
.default(1)
|
|
53
|
+
.description("Scale factor for the panel's content (text, icons, buttons) from 50% to 200%; the panel's own size is unchanged")),
|
|
54
|
+
sheetZoom: volatileSchema(z
|
|
39
55
|
.number()
|
|
40
56
|
.min(0.5)
|
|
41
57
|
.max(2)
|
|
42
58
|
.default(1)
|
|
43
|
-
.description("Scale factor for the
|
|
44
|
-
handle: z
|
|
59
|
+
.description("Scale factor for the curtain's content (text, icons, buttons) from 50% to 200%; the curtain's own size is unchanged")),
|
|
60
|
+
handle: volatileSchema(z
|
|
45
61
|
.number()
|
|
46
62
|
.min(0)
|
|
47
63
|
.max(1)
|
|
48
64
|
.default(0.5)
|
|
49
|
-
.description("Vertical position of the collapsed-panel handle: 0 = bottom, 1 = top of the conversation area"),
|
|
50
|
-
fuzzy: z
|
|
65
|
+
.description("Vertical position of the collapsed-panel handle: 0 = bottom, 1 = top of the conversation area")),
|
|
66
|
+
fuzzy: volatileSchema(z
|
|
51
67
|
.boolean()
|
|
52
68
|
.default(false)
|
|
53
|
-
.description("Start with fuzzy search on"),
|
|
54
|
-
hover: z
|
|
69
|
+
.description("Start with fuzzy search on")),
|
|
70
|
+
hover: volatileSchema(z
|
|
55
71
|
.boolean()
|
|
56
72
|
.default(true)
|
|
57
|
-
.description("Show the hover preview card"),
|
|
58
|
-
remember: z
|
|
73
|
+
.description("Show the hover preview card")),
|
|
74
|
+
remember: volatileSchema(z
|
|
59
75
|
.boolean()
|
|
60
76
|
.default(true)
|
|
61
|
-
.description("Remember where you were reading in each session and return there when it is reopened"),
|
|
62
|
-
autoLoad: z
|
|
77
|
+
.description("Remember where you were reading in each session and return there when it is reopened")),
|
|
78
|
+
autoLoad: volatileSchema(z
|
|
63
79
|
.boolean()
|
|
64
80
|
.default(true)
|
|
65
|
-
.description("Load history automatically when the outline is scrolled into turns that are not loaded yet; off means nothing loads until a row is clicked"),
|
|
66
|
-
debug: z
|
|
81
|
+
.description("Load history automatically when the outline is scrolled into turns that are not loaded yet; off means nothing loads until a row is clicked")),
|
|
82
|
+
debug: volatileSchema(z
|
|
67
83
|
.boolean()
|
|
68
84
|
.default(false)
|
|
69
|
-
.description("Print the mount diagnostic to the browser console"),
|
|
85
|
+
.description("Print the mount diagnostic to the browser console")),
|
|
70
86
|
});
|
|
71
87
|
|
|
72
88
|
/**
|
|
73
|
-
* Register the settings
|
|
74
|
-
*
|
|
89
|
+
* Register the settings namespace, in whichever dialect the running host speaks.
|
|
90
|
+
*
|
|
91
|
+
* Up to DSH 0.1.5-rc.2 the settings service registers a named section with a
|
|
92
|
+
* schema and reads and writes it by that name (`installSection`). From
|
|
93
|
+
* 0.1.7-alpha.1 on, the service addresses settings by PROFILE ENTRY ID, derives
|
|
94
|
+
* the entry's form from the schema this module exports, and only projects the
|
|
95
|
+
* fields marked `.volatile()` — an instance announces its page policy with
|
|
96
|
+
* `settings.configure` instead. Both are pure registrations that the browser
|
|
97
|
+
* half then reads through its own service, so the plugin does not care which
|
|
98
|
+
* one ran.
|
|
99
|
+
*
|
|
100
|
+
* `volatileSchema` marks a field only where the running schemastery knows the
|
|
101
|
+
* modifier: it arrived in schemastery 3.18.3 (the alpha), and calling it on
|
|
102
|
+
* 3.18.2 (the rc line) would throw while the module is being imported.
|
|
103
|
+
*
|
|
75
104
|
* @param {object} ctx - host cordis context.
|
|
76
105
|
* @param {object} [config] - the plugin entry's composition config (base layer).
|
|
77
106
|
*/
|
|
78
107
|
export function apply(ctx, config) {
|
|
79
108
|
ctx.inject(["settings"], (settingsCtx) => {
|
|
80
|
-
settingsCtx.settings
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
109
|
+
const settings = settingsCtx.settings;
|
|
110
|
+
if (settings && typeof settings.installSection === "function") {
|
|
111
|
+
settings.installSection(ctx, NAMESPACE, Config, config ?? {}, {
|
|
112
|
+
setSource: () => {},
|
|
113
|
+
onChange: () => {},
|
|
114
|
+
});
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if (settings && typeof settings.configure === "function") {
|
|
118
|
+
// `auto: true` keeps the host's generated page as the fallback wherever no
|
|
119
|
+
// custom page of ours is reachable (the browser half renders one where the
|
|
120
|
+
// host offers a configuration cell for this entry).
|
|
121
|
+
settingsCtx.effect(() => settings.configure({ auto: true }, ctx.fiber));
|
|
122
|
+
}
|
|
84
123
|
});
|
|
85
124
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-quick-toc",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.3",
|
|
4
4
|
"description": "Conversation TOC for DeepSeek Harness (DSH): a whole-session, turn-grouped Markdown outline with hover previews, a level filter, a full-width curtain, jump-to-end buttons, title/full-text/cross-session search and a Chinese/English interface.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"LICENSE"
|
|
15
15
|
],
|
|
16
16
|
"engines": {
|
|
17
|
-
"dsh": ">=0.1.5-rc.1"
|
|
17
|
+
"dsh": ">=0.1.5-rc.1 <0.1.7-0 || >=0.1.7-alpha.1 <0.2.0-0"
|
|
18
18
|
},
|
|
19
19
|
"exports": {
|
|
20
20
|
".": "./lib/index.js",
|
|
@@ -36,15 +36,18 @@
|
|
|
36
36
|
"compatibility": {
|
|
37
37
|
"dshReleases": {
|
|
38
38
|
"0.1.5-rc.1": "compatible",
|
|
39
|
-
"0.1.5-rc.2": "compatible"
|
|
39
|
+
"0.1.5-rc.2": "compatible",
|
|
40
|
+
"0.1.5-rc.3": "compatible",
|
|
41
|
+
"0.1.7-alpha.1": "compatible",
|
|
42
|
+
"0.1.7-alpha.2": "compatible"
|
|
40
43
|
}
|
|
41
44
|
}
|
|
42
45
|
},
|
|
43
46
|
"peerDependencies": {
|
|
44
47
|
"react": "^18.2.0",
|
|
45
48
|
"@deepseek-ai/schemastery": "^3.18.0",
|
|
46
|
-
"@deepseek-ai/dsh-client-ui-chat": ">=0.1.5-rc.1",
|
|
47
|
-
"@deepseek-ai/dsh-client-ui-conversation": ">=0.1.5-rc.1"
|
|
49
|
+
"@deepseek-ai/dsh-client-ui-chat": ">=0.1.5-rc.1 <0.1.7-0 || >=0.1.7-alpha.1 <0.2.0-0",
|
|
50
|
+
"@deepseek-ai/dsh-client-ui-conversation": ">=0.1.5-rc.1 <0.1.7-0 || >=0.1.7-alpha.1 <0.2.0-0"
|
|
48
51
|
},
|
|
49
52
|
"peerDependenciesMeta": {
|
|
50
53
|
"@deepseek-ai/schemastery": {
|