dsh-conversation-navigator 0.2.9 → 0.2.10
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.en.md +9 -5
- package/README.md +9 -5
- package/lib/client.js +219 -5
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -27,6 +27,7 @@ Browser-only (no host behavior), plain JavaScript, zero build step, zero npm dep
|
|
|
27
27
|
|
|
28
28
|
- **Turn-folded outline**: shows "Turn N + your question" rows by default, long conversations at a glance
|
|
29
29
|
- **Keyword filter**: click the top-left search icon to reveal the inline input; matches only your questions + the assistant's actual reply text (context, tool calls, commands, compaction and reasoning never match); hits are highlighted and the list text is windowed to the keyword
|
|
30
|
+
- **Jump straight to the hit**: clicking a search result scrolls to where the keyword actually appears in the conversation (not just the top of the message) and marks every occurrence inside that message — a brief flash that eases into a soft persistent tint, so long replies no longer need hunting
|
|
30
31
|
- **Expand / collapse steps**: the arrow button on the right of each turn row (`▸ N`, rotates to `▾` when expanded) smoothly expands or collapses the turn's step details (assistant replies, tool calls, commands, compaction points, etc.)
|
|
31
32
|
- **Hover full text**: rest the mouse on a turn row and a bubble shows the user's complete question, no longer truncated to one line
|
|
32
33
|
- **Full / Hidden / Minimal-left modes**: the header toggle cycles three states —
|
|
@@ -101,7 +102,7 @@ Official v0.1.2-rc.1 made a breaking change to the conversation data layer (conv
|
|
|
101
102
|
|
|
102
103
|
| Host `dsh-client-ui-conversation` | Matching plugin version | npm tag |
|
|
103
104
|
|---|---|---|
|
|
104
|
-
| **≥ 0.1.2-rc.1** (has the `uiConversation` service) | **0.2.6 and later** (currently 0.2.
|
|
105
|
+
| **≥ 0.1.2-rc.1** (has the `uiConversation` service) | **0.2.6 and later** (currently 0.2.10) | `latest` (default) |
|
|
105
106
|
| **≤ 0.1.1-rc.2** (no `uiConversation`, legacy `session.chat` snapshot) | **0.2.5 and earlier** | `legacy` |
|
|
106
107
|
|
|
107
108
|
- New hosts can install the latest normally (`dsh plugin --profile web add dsh-conversation-navigator`).
|
|
@@ -122,11 +123,14 @@ example.patch.yml # patch example for manual installs
|
|
|
122
123
|
|
|
123
124
|
## Traffic
|
|
124
125
|
|
|
125
|
-
|
|
126
|
+
Window **2026-08-17 → 2026-09-20** (refreshed daily by GitHub Actions):
|
|
126
127
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
128
|
+
| npm downloads | GitHub unique visitors | GitHub unique cloners | Stars | Forks |
|
|
129
|
+
| ---: | ---: | ---: | ---: | ---: |
|
|
130
|
+
| 6,206 | 540 | 335 | 23 | 1 |
|
|
131
|
+
|
|
132
|
+
- Details: [traffic-report.md](./docs/traffic/traffic-report.md) | raw archive: [traffic-archive.json](./docs/traffic/traffic-archive.json) | chart & docs: [docs/traffic](./docs/traffic/README.md)
|
|
133
|
+
- Caveats: npm downloads include mirrors, CI and reinstalls, with a 1–2 day settlement lag; GitHub unique visitors / cloners are closer to real user counts.
|
|
130
134
|
|
|
131
135
|
## License
|
|
132
136
|
|
package/README.md
CHANGED
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
|
|
28
28
|
- **按轮折叠大纲**:默认只显示"第 N 轮 + 你的问题"列表,长对话一目了然
|
|
29
29
|
- **关键词过滤**:点击左上角搜索图标呼出输入框,只按「你的提问 + 助手实际回复文本」过滤(上下文、工具、命令、压缩、推理等不参与匹配),命中词高亮,列表文本自动定位到关键词处
|
|
30
|
+
- **搜索命中直达**:点击命中的搜索结果,直接滚动到关键词在会话正文中出现的位置(不再只对齐消息顶部),并把该条消息内所有命中处标出来——先亮一下,再平滑淡出为常驻淡色,长回复里不用再自己找
|
|
30
31
|
- **展开/折叠步次**:点击轮次行右侧的箭头按钮(`▸ N`,展开时旋转为 `▾`)平滑展开/折叠该轮的步次明细(助手回复、工具调用、命令、压缩点等)
|
|
31
32
|
- **悬停查看全文**:鼠标在轮次行停留片刻,气泡显示该轮用户提问的完整原文,不再被单行截断
|
|
32
33
|
- **显示 / 隐藏 / 极简·左三种形态**:头部切换按钮三态循环——「显示轮次」为经典分组视图(压缩等系统事件与轮次同级加粗展示)→「隐藏轮次」每行统一为轨迹徽标 + 文本(用户 = 业务蓝、助手 = 紫罗兰、压缩 = 中性灰)→「极简·左」锚定对话区左缘(为 dsh-better-sidebar 让出右侧,并与官方右侧轨道零重叠),再回到「显示轮次」;「极简·右」形态自 0.2.6 起暂缓(与官方 TurnNavigator 轨道视觉重叠),代码保留;
|
|
@@ -98,7 +99,7 @@ dsh plugin --profile web add ./dsh-conversation-navigator-<version>.tgz
|
|
|
98
99
|
|
|
99
100
|
| 宿主 `dsh-client-ui-conversation` | 对应插件版本 | npm 标签 |
|
|
100
101
|
|---|---|---|
|
|
101
|
-
| **≥ 0.1.2-rc.1**(有 `uiConversation` 服务) | **0.2.6 及以后**(当前 0.2.
|
|
102
|
+
| **≥ 0.1.2-rc.1**(有 `uiConversation` 服务) | **0.2.6 及以后**(当前 0.2.10) | `latest`(默认) |
|
|
102
103
|
| **≤ 0.1.1-rc.2**(无 `uiConversation`,旧 `session.chat` 快照) | **0.2.5 及以前** | `legacy` |
|
|
103
104
|
|
|
104
105
|
- 新宿主直接安装最新版即可(`dsh plugin --profile web add dsh-conversation-navigator`)。
|
|
@@ -119,11 +120,14 @@ example.patch.yml # 手动安装时的补丁示例
|
|
|
119
120
|
|
|
120
121
|
## 流量
|
|
121
122
|
|
|
122
|
-
|
|
123
|
+
统计区间 **2026-08-17 → 2026-09-20**(GitHub Actions 每日自动刷新):
|
|
123
124
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
125
|
+
| npm 下载 | GitHub 独立访客 | GitHub 独立克隆者 | Stars | Forks |
|
|
126
|
+
| ---: | ---: | ---: | ---: | ---: |
|
|
127
|
+
| 6,206 | 540 | 335 | 23 | 1 |
|
|
128
|
+
|
|
129
|
+
- 明细报告:[traffic-report.md](./docs/traffic/traffic-report.md)|原始归档:[traffic-archive.json](./docs/traffic/traffic-archive.json)|趋势图与说明:[docs/traffic](./docs/traffic/README.md)
|
|
130
|
+
- 口径:npm 下载含镜像站、CI 与重复安装,且有 1–2 天结算延迟;GitHub 独立访客 / 独立克隆者更接近真实人数。
|
|
127
131
|
|
|
128
132
|
## License
|
|
129
133
|
|
package/lib/client.js
CHANGED
|
@@ -232,6 +232,7 @@ window.__ModuleLoader__.load({
|
|
|
232
232
|
background: color-mix(in srgb, var(--dsw-alias-state-warn-primary, #f59e0b) 30%, transparent);
|
|
233
233
|
color: inherit; border-radius: 2px; padding: 0 1px; font-weight: 600;
|
|
234
234
|
}
|
|
235
|
+
|
|
235
236
|
.cnvnav-empty { padding: 18px 10px; text-align: center; color: var(--dsw-alias-label-secondary, #888); }
|
|
236
237
|
/* ---------- 极简模式 ---------- */
|
|
237
238
|
.cnvnav-mini-wrap {
|
|
@@ -911,13 +912,223 @@ window.__ModuleLoader__.load({
|
|
|
911
912
|
return null;
|
|
912
913
|
}
|
|
913
914
|
let lastActive = null;
|
|
914
|
-
|
|
915
|
+
/* ---------- 会话正文内的命中定位(点击搜索命中后把关键词标在正文里) ---------- */
|
|
916
|
+
const FIND_STEPS = 28; /* 淡出用的预置层数(层名 cnvnav-find-s0 … s27) */
|
|
917
|
+
const FIND_HOT_PCT = 55; /* 命中瞬间的背景浓度(%) */
|
|
918
|
+
const FIND_DIM_PCT = 22; /* 最终常驻的淡色浓度(%) */
|
|
919
|
+
const FIND_HOLD_MS = 300; /* 最亮保持时长 */
|
|
920
|
+
const FIND_FADE_MS = 900; /* 变淡时长(easeOutCubic 逐帧换层) */
|
|
921
|
+
const FIND_MAX_RANGES = 200;
|
|
922
|
+
const FIND_BLOCK_TAGS = { P: 1, DIV: 1, LI: 1, UL: 1, OL: 1, PRE: 1, BLOCKQUOTE: 1, TD: 1, TH: 1, TR: 1, TABLE: 1, SECTION: 1, ARTICLE: 1, H1: 1, H2: 1, H3: 1, H4: 1, H5: 1, H6: 1 };
|
|
923
|
+
let findRaf = null; /* 淡出动画句柄 */
|
|
924
|
+
let findStep = null; /* 当前注册的层号 */
|
|
925
|
+
let findMark = null; /* 当前高亮的 Highlight 对象(各层共用同一份 Range) */
|
|
926
|
+
function findLayerName(step) {
|
|
927
|
+
return "cnvnav-find-s" + step;
|
|
928
|
+
}
|
|
929
|
+
function findAlphaPct(step) {
|
|
930
|
+
const k = FIND_STEPS <= 1 ? 1 : step / (FIND_STEPS - 1);
|
|
931
|
+
return FIND_HOT_PCT + (FIND_DIM_PCT - FIND_HOT_PCT) * k;
|
|
932
|
+
}
|
|
933
|
+
/* ::highlight 伪元素不支持 transition/animation, 只能预置多层不同浓度逐帧切换 */
|
|
934
|
+
function findFadeCss() {
|
|
935
|
+
let out = "\n/* 会话正文内的命中高亮: 逐帧换层实现平滑淡出(::highlight 不支持 transition) */";
|
|
936
|
+
for (let i = 0; i < FIND_STEPS; i++) {
|
|
937
|
+
out += "\n::highlight(" + findLayerName(i) + ") {\n background: color-mix(in srgb, var(--dsw-alias-state-warn-primary, #f59e0b) " + findAlphaPct(i).toFixed(1) + "%, transparent);\n color: inherit;\n}";
|
|
938
|
+
}
|
|
939
|
+
return out;
|
|
940
|
+
}
|
|
941
|
+
(function injectFindFadeCss() {
|
|
942
|
+
try {
|
|
943
|
+
const el = document.createElement("style");
|
|
944
|
+
el.textContent = findFadeCss();
|
|
945
|
+
document.head.appendChild(el);
|
|
946
|
+
} catch (err) { console.error(err); }
|
|
947
|
+
})();
|
|
948
|
+
/* 模块顶部的 const CSS 是插件自己的样式字符串, 会遮蔽浏览器全局 CSS, 故走 globalThis */
|
|
949
|
+
function highlightApi() {
|
|
950
|
+
try {
|
|
951
|
+
const api = typeof globalThis === "undefined" ? undefined : globalThis.CSS;
|
|
952
|
+
if (api === undefined || api === null || api.highlights === undefined) return null;
|
|
953
|
+
return api.highlights;
|
|
954
|
+
} catch (err) { return null; }
|
|
955
|
+
}
|
|
956
|
+
function highlightCtor() {
|
|
957
|
+
try {
|
|
958
|
+
return typeof globalThis === "undefined" ? undefined : globalThis.Highlight;
|
|
959
|
+
} catch (err) { return undefined; }
|
|
960
|
+
}
|
|
961
|
+
function dropFindLayer() {
|
|
962
|
+
const store = highlightApi();
|
|
963
|
+
if (store !== null && findStep !== null) store.delete(findLayerName(findStep));
|
|
964
|
+
findStep = null;
|
|
965
|
+
}
|
|
966
|
+
function showFindStep(step) {
|
|
967
|
+
const store = highlightApi();
|
|
968
|
+
if (store === null || findMark === null) return;
|
|
969
|
+
const next = Math.max(0, Math.min(FIND_STEPS - 1, step));
|
|
970
|
+
if (next === findStep) return;
|
|
971
|
+
if (findStep !== null) store.delete(findLayerName(findStep));
|
|
972
|
+
store.set(findLayerName(next), findMark);
|
|
973
|
+
findStep = next;
|
|
974
|
+
}
|
|
975
|
+
function stopFindFade() {
|
|
976
|
+
if (findRaf !== null) {
|
|
977
|
+
if (typeof cancelAnimationFrame === "function") cancelAnimationFrame(findRaf);
|
|
978
|
+
findRaf = null;
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
function startFindFade() {
|
|
982
|
+
stopFindFade();
|
|
983
|
+
if (typeof requestAnimationFrame !== "function") { showFindStep(FIND_STEPS - 1); return; }
|
|
984
|
+
const now = () => (typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now());
|
|
985
|
+
const started = now();
|
|
986
|
+
const tick = () => {
|
|
987
|
+
const t = now() - started;
|
|
988
|
+
if (t <= FIND_HOLD_MS) showFindStep(0);
|
|
989
|
+
else if (t >= FIND_HOLD_MS + FIND_FADE_MS) { showFindStep(FIND_STEPS - 1); findRaf = null; return; }
|
|
990
|
+
else {
|
|
991
|
+
const u = (t - FIND_HOLD_MS) / FIND_FADE_MS;
|
|
992
|
+
const eased = 1 - Math.pow(1 - u, 3); /* easeOutCubic */
|
|
993
|
+
showFindStep(Math.round(eased * (FIND_STEPS - 1)));
|
|
994
|
+
}
|
|
995
|
+
findRaf = requestAnimationFrame(tick);
|
|
996
|
+
};
|
|
997
|
+
findRaf = requestAnimationFrame(tick);
|
|
998
|
+
}
|
|
999
|
+
function clearFindHighlight() {
|
|
1000
|
+
try {
|
|
1001
|
+
stopFindFade();
|
|
1002
|
+
dropFindLayer();
|
|
1003
|
+
findMark = null;
|
|
1004
|
+
} catch (err) { console.error(err); }
|
|
1005
|
+
}
|
|
1006
|
+
/* 最近的块级祖先: 跨块边界补一个分隔符, 避免两段文字被误判成一个词 */
|
|
1007
|
+
function findBlockOf(node) {
|
|
1008
|
+
let el = node.parentElement;
|
|
1009
|
+
while (el !== null && el !== undefined) {
|
|
1010
|
+
if (FIND_BLOCK_TAGS[el.tagName] === 1) return el;
|
|
1011
|
+
el = el.parentElement;
|
|
1012
|
+
}
|
|
1013
|
+
return null;
|
|
1014
|
+
}
|
|
1015
|
+
/* 收集锚点子树内可见的文本节点(跳过脚本/样式) */
|
|
1016
|
+
function collectFindTextNodes(root) {
|
|
1017
|
+
const out = [];
|
|
1018
|
+
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
|
|
1019
|
+
acceptNode(node) {
|
|
1020
|
+
const text = node.nodeValue;
|
|
1021
|
+
if (text === null || text === "" || text.trim() === "") return NodeFilter.FILTER_REJECT;
|
|
1022
|
+
const parent = node.parentElement;
|
|
1023
|
+
if (parent === null || parent === undefined) return NodeFilter.FILTER_REJECT;
|
|
1024
|
+
const tag = parent.tagName;
|
|
1025
|
+
if (tag === "SCRIPT" || tag === "STYLE" || tag === "NOSCRIPT" || tag === "TEXTAREA") return NodeFilter.FILTER_REJECT;
|
|
1026
|
+
return NodeFilter.FILTER_ACCEPT;
|
|
1027
|
+
},
|
|
1028
|
+
});
|
|
1029
|
+
for (let node = walker.nextNode(); node !== null; node = walker.nextNode()) out.push(node);
|
|
1030
|
+
return out;
|
|
1031
|
+
}
|
|
1032
|
+
/* 归一化索引: 连续空白折叠成单个空格; map[i] = [文本节点, 节点内偏移] */
|
|
1033
|
+
function buildFindIndex(root) {
|
|
1034
|
+
const nodes = collectFindTextNodes(root);
|
|
1035
|
+
let text = "";
|
|
1036
|
+
const map = [];
|
|
1037
|
+
let prevBlock = null;
|
|
1038
|
+
let pendingSpace = false;
|
|
1039
|
+
for (const node of nodes) {
|
|
1040
|
+
const value = node.nodeValue;
|
|
1041
|
+
const block = findBlockOf(node);
|
|
1042
|
+
if (text !== "" && (block === null || block !== prevBlock)) pendingSpace = true;
|
|
1043
|
+
prevBlock = block;
|
|
1044
|
+
for (let i = 0; i < value.length; i++) {
|
|
1045
|
+
const ch = value.charAt(i);
|
|
1046
|
+
if (ch === " " || ch === "\t" || ch === "\n" || ch === "\r" || ch === "\f" || ch === "\v") { pendingSpace = text !== ""; continue; }
|
|
1047
|
+
if (pendingSpace) { text += " "; map.push([node, i]); pendingSpace = false; }
|
|
1048
|
+
text += ch;
|
|
1049
|
+
map.push([node, i]);
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
return { text, map };
|
|
1053
|
+
}
|
|
1054
|
+
function findNeedle(kw) {
|
|
1055
|
+
return String(kw).replace(/\s+/g, " ").trim().toLowerCase();
|
|
1056
|
+
}
|
|
1057
|
+
/* 关键词在 DOM 里的每一处出现 → Range(最多 FIND_MAX_RANGES 处) */
|
|
1058
|
+
function findKeywordRanges(root, needle) {
|
|
1059
|
+
const ranges = [];
|
|
1060
|
+
if (needle === "") return ranges;
|
|
1061
|
+
const index = buildFindIndex(root);
|
|
1062
|
+
if (index.text === "") return ranges;
|
|
1063
|
+
let hay = index.text.toLowerCase();
|
|
1064
|
+
if (hay.length !== index.text.length) hay = index.text; /* 极端 Unicode 大小写长度变化时退回原串 */
|
|
1065
|
+
let from = 0;
|
|
1066
|
+
for (;;) {
|
|
1067
|
+
const at = hay.indexOf(needle, from);
|
|
1068
|
+
if (at === -1) break;
|
|
1069
|
+
const start = index.map[at];
|
|
1070
|
+
const end = index.map[at + needle.length - 1];
|
|
1071
|
+
if (start !== undefined && end !== undefined) {
|
|
1072
|
+
try {
|
|
1073
|
+
const range = document.createRange();
|
|
1074
|
+
range.setStart(start[0], start[1]);
|
|
1075
|
+
range.setEnd(end[0], end[1] + 1);
|
|
1076
|
+
ranges.push(range);
|
|
1077
|
+
} catch (err) { /* 节点已被宿主重渲染替换: 跳过这一处 */ }
|
|
1078
|
+
}
|
|
1079
|
+
from = at + needle.length;
|
|
1080
|
+
if (ranges.length >= FIND_MAX_RANGES) break;
|
|
1081
|
+
}
|
|
1082
|
+
return ranges;
|
|
1083
|
+
}
|
|
1084
|
+
/* 把首个命中滚到视口偏上处(而不是消息顶部) */
|
|
1085
|
+
function scrollRangeIntoView(range) {
|
|
1086
|
+
try {
|
|
1087
|
+
const rect = range.getBoundingClientRect();
|
|
1088
|
+
const scrollport = document.querySelector("[data-conversation-scroll]");
|
|
1089
|
+
if (scrollport !== null && scrollport !== undefined) {
|
|
1090
|
+
const box = scrollport.getBoundingClientRect();
|
|
1091
|
+
const delta = rect.top - box.top - Math.max(72, box.height * 0.32);
|
|
1092
|
+
if (typeof scrollport.scrollBy === "function") scrollport.scrollBy({ top: delta, behavior: "smooth" });
|
|
1093
|
+
else scrollport.scrollTop += delta;
|
|
1094
|
+
return;
|
|
1095
|
+
}
|
|
1096
|
+
const host = range.startContainer;
|
|
1097
|
+
const el = host.nodeType === 1 ? host : host.parentElement;
|
|
1098
|
+
if (el !== null && el !== undefined && typeof el.scrollIntoView === "function") el.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
1099
|
+
} catch (err) { console.error(err); }
|
|
1100
|
+
}
|
|
1101
|
+
/* 正文内定位并高亮: 先最亮, 再 easeOut 逐帧变淡到常驻淡色; 返回是否成功 */
|
|
1102
|
+
function locateInConversation(row, kw) {
|
|
1103
|
+
clearFindHighlight();
|
|
1104
|
+
try {
|
|
1105
|
+
const store = highlightApi();
|
|
1106
|
+
const Ctor = highlightCtor();
|
|
1107
|
+
if (store === null || typeof Ctor !== "function") return false;
|
|
1108
|
+
const needle = findNeedle(kw);
|
|
1109
|
+
const ranges = findKeywordRanges(row, needle);
|
|
1110
|
+
if (ranges.length === 0) return false;
|
|
1111
|
+
findMark = new Ctor(...ranges);
|
|
1112
|
+
showFindStep(0);
|
|
1113
|
+
startFindFade();
|
|
1114
|
+
scrollRangeIntoView(ranges[0]);
|
|
1115
|
+
return true;
|
|
1116
|
+
} catch (err) {
|
|
1117
|
+
console.error(err);
|
|
1118
|
+
clearFindHighlight();
|
|
1119
|
+
return false;
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
function jumpTo(key, kw) {
|
|
915
1123
|
try {
|
|
916
1124
|
const el = findAnchor(key);
|
|
917
1125
|
if (el === null) return false;
|
|
918
|
-
el.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
919
1126
|
lastActive = key;
|
|
920
1127
|
setState({ activeKey: key });
|
|
1128
|
+
/* 搜索命中: 直接定位到关键词所在处并在正文里高亮; 其余情况退回消息顶部 */
|
|
1129
|
+
if (typeof kw === "string" && kw !== "" && locateInConversation(el, kw)) return true;
|
|
1130
|
+
clearFindHighlight();
|
|
1131
|
+
el.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
921
1132
|
return true;
|
|
922
1133
|
} catch (err) {
|
|
923
1134
|
console.error(err);
|
|
@@ -1095,6 +1306,7 @@ window.__ModuleLoader__.load({
|
|
|
1095
1306
|
}
|
|
1096
1307
|
function toggleSearch() {
|
|
1097
1308
|
if (searchOpen) setFilter("");
|
|
1309
|
+
clearFindHighlight();
|
|
1098
1310
|
setSearchOpen(!searchOpen);
|
|
1099
1311
|
}
|
|
1100
1312
|
/* ---------- 拖动定位(仅面板形式, 极简条不参与) ---------- */
|
|
@@ -1504,7 +1716,7 @@ window.__ModuleLoader__.load({
|
|
|
1504
1716
|
ref: (el) => { itemEls[item.key] = el; },
|
|
1505
1717
|
className: "cnvnav-item" + (item.key === snap.activeKey ? " cnvnav-item-active" : "") + (staggerMs !== undefined ? " cnvnav-fade-item" : ""),
|
|
1506
1718
|
style: staggerMs !== undefined ? { animationDelay: staggerMs + "ms" } : undefined,
|
|
1507
|
-
onClick: () => jumpTo(item.key),
|
|
1719
|
+
onClick: () => jumpTo(item.key, filtering && hit(item.search) ? kw : null),
|
|
1508
1720
|
},
|
|
1509
1721
|
React.createElement("span", { className: "cnvnav-badge cnvnav-badge-" + item.cls }, item.label),
|
|
1510
1722
|
hasPreview
|
|
@@ -1521,6 +1733,8 @@ window.__ModuleLoader__.load({
|
|
|
1521
1733
|
const useSnippet = filtering && hit(headSearch);
|
|
1522
1734
|
const sub = useSnippet ? snippetNodes(headSearch, kw, 5, 55) : g.subtitle;
|
|
1523
1735
|
const hasSub = sub !== "" && (!Array.isArray(sub) || sub.length > 0);
|
|
1736
|
+
/* 组头自身命中时, 点击同样在正文里定位关键词 */
|
|
1737
|
+
const headKw = filtering && g.headIsUser && g.items.length > 0 && hit(g.items[0].search) ? kw : null;
|
|
1524
1738
|
/* 标题策略:
|
|
1525
1739
|
显示轮次: 轮次行 = 加粗「第 N 轮」; 压缩等非轮次行 = 加粗类型标签, 视作轮次同级
|
|
1526
1740
|
隐藏轮次: 每行统一轨迹徽标 — 轮次头 = 「用户」业务蓝(助手开头则助手紫), 非轮次行 = 各自徽标(压缩=中性灰) */
|
|
@@ -1557,7 +1771,7 @@ window.__ModuleLoader__.load({
|
|
|
1557
1771
|
React.createElement("button", {
|
|
1558
1772
|
type: "button",
|
|
1559
1773
|
className: "cnvnav-group-head",
|
|
1560
|
-
onClick: () => jumpTo(g.headKey),
|
|
1774
|
+
onClick: () => jumpTo(g.headKey, headKw),
|
|
1561
1775
|
},
|
|
1562
1776
|
titleNode,
|
|
1563
1777
|
hasSub
|
|
@@ -1702,7 +1916,7 @@ window.__ModuleLoader__.load({
|
|
|
1702
1916
|
placeholder: "请输入关键词",
|
|
1703
1917
|
value: filter,
|
|
1704
1918
|
autoFocus: true,
|
|
1705
|
-
onChange: (e) => setFilter(e.target.value),
|
|
1919
|
+
onChange: (e) => { clearFindHighlight(); setFilter(e.target.value); },
|
|
1706
1920
|
})
|
|
1707
1921
|
: null,
|
|
1708
1922
|
searchOpen
|