dsh-auto-collapse 0.1.6 → 0.1.8
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 +111 -100
- package/README.md +111 -101
- package/lib/client.js +365 -48
- package/lib/index.js +41 -10
- package/lib/types/client.d.ts +4 -1
- package/lib/types/fold.d.ts +63 -4
- package/lib/types/index.d.ts +7 -1
- package/package.json +68 -69
package/lib/client.js
CHANGED
|
@@ -36,6 +36,7 @@ var __dshcfBundle = (() => {
|
|
|
36
36
|
// src/fold.ts
|
|
37
37
|
var STYLE_ID = "dshcf-style";
|
|
38
38
|
var DEFAULT_STATUS_TEXT = "Deep sleeping...";
|
|
39
|
+
var TURN_STATUS_COPY_RE = /Deep diving[.…]*|深度求索中\s*[.…]*/;
|
|
39
40
|
var ANIM_DURATION_MS = 180;
|
|
40
41
|
var ANIM_EASING = "ease-out";
|
|
41
42
|
var DISPLAY_OWNED_PROP = "--dshcf-display-owned";
|
|
@@ -58,6 +59,8 @@ var __dshcfBundle = (() => {
|
|
|
58
59
|
cordis_stop: "Stop",
|
|
59
60
|
cordis_undefine: "Remove"
|
|
60
61
|
};
|
|
62
|
+
var TURN_PROCESS_KIND = "turn-process";
|
|
63
|
+
var NATIVE_OPEN_ATTR = "data-open";
|
|
61
64
|
var CHIP_CSS = `
|
|
62
65
|
.dshcf-chip {
|
|
63
66
|
box-sizing: border-box;
|
|
@@ -395,6 +398,7 @@ var __dshcfBundle = (() => {
|
|
|
395
398
|
this.bodyTextCache = /* @__PURE__ */ new WeakMap();
|
|
396
399
|
/** 自上次 pass 以来子树发生变化的 flow 顶层消息;pass 开头统一失效。 */
|
|
397
400
|
this.dirtyMessages = /* @__PURE__ */ new Set();
|
|
401
|
+
/** 在途显示动画(元素 → 记录):冲突仲裁、记账对齐与生命周期清理的依据。
|
|
398
402
|
/** 在途显示动画(元素 → 记录):冲突仲裁、记账对齐与生命周期清理的依据。
|
|
399
403
|
* 用 Map 不用 WeakMap——switchFlow/stop 需要遍历全量 cancel。 */
|
|
400
404
|
this.pendingAnims = /* @__PURE__ */ new Map();
|
|
@@ -413,6 +417,20 @@ var __dshcfBundle = (() => {
|
|
|
413
417
|
* attributes 批次(流式文本、data-state 翻转)不改变块结构,跳过全量
|
|
414
418
|
* querySelectorAll 重扫(issue #14:长会话下每轮重扫造成主线程卡顿)。 */
|
|
415
419
|
this.structureDirty = true;
|
|
420
|
+
/** 本轮 pass 的原生 turn 摘要快照(turn id → 开合态):每轮重建,不跨轮复用;
|
|
421
|
+
* data-open 翻转不改变块结构也能驱动重算,见 buildNativeTurnMap。 */
|
|
422
|
+
this.nativeTurns = /* @__PURE__ */ new Map();
|
|
423
|
+
/** 被折叠掏空后藏起的中间包装层(真机 44px vs 28px 真因:think 行全隐后
|
|
424
|
+
* 其父容器变零高度空壳,仍作为 flex item 参与父级 gap,凭空多出 16px)。
|
|
425
|
+
* 内容恢复(展开/流式追加/块转世)时同函数恢复显示;switchFlow 清空。 */
|
|
426
|
+
this.emptiedWrappers = /* @__PURE__ */ new Set();
|
|
427
|
+
/** 本轮 pass 内有过 display 实写(hide/restore 瞬时路径)。空洞发现的触发
|
|
428
|
+
* 条件之一:纯 display 收放不产生 childList,不触发 structureDirty。
|
|
429
|
+
* settle 触发的后续 pass 则由 settleFired 覆盖。读后即清(见发现调用点)。 */
|
|
430
|
+
this.displayTouched = false;
|
|
431
|
+
/** 有 fade 自然结算过(chipSettle 跑过)。结算本身不写 display,但它意味着
|
|
432
|
+
* 某行刚变隐藏——空洞可能刚形成。 */
|
|
433
|
+
this.settleFired = false;
|
|
416
434
|
/** 滚动稳定化(issue #14):flow 最近的滚动容器缓存(按 flow 身份失效)。 */
|
|
417
435
|
this.scrollContainer = null;
|
|
418
436
|
this.scrollContainerFlow = null;
|
|
@@ -448,7 +466,10 @@ var __dshcfBundle = (() => {
|
|
|
448
466
|
childList: true,
|
|
449
467
|
subtree: true,
|
|
450
468
|
attributes: true,
|
|
451
|
-
|
|
469
|
+
// 原生 TurnProcess 开合只改 attribute(成员 data-turn-process-hidden、
|
|
470
|
+
// 摘要按钮 data-open),必须进过滤器才能即时调度;插件自身从不写
|
|
471
|
+
// 这两个属性,不会自激。
|
|
472
|
+
attributeFilter: ["data-selected", "data-state", "data-turn-process-hidden", "data-open"],
|
|
452
473
|
// 流式文本更新(React 改 text node 的 data)属于 characterData
|
|
453
474
|
// mutation:不观察则二级摘要/滚动跟随只能靠偶发结构变化驱动,
|
|
454
475
|
// 变成“隔几秒跳一次”。所有文本写入都有守卫(值不变不写),
|
|
@@ -518,6 +539,17 @@ var __dshcfBundle = (() => {
|
|
|
518
539
|
return;
|
|
519
540
|
}
|
|
520
541
|
}
|
|
542
|
+
for (const el of [...this.emptiedWrappers]) {
|
|
543
|
+
if (!el.isConnected) {
|
|
544
|
+
this.emptiedWrappers.delete(el);
|
|
545
|
+
continue;
|
|
546
|
+
}
|
|
547
|
+
if (!this.isHollow(el)) {
|
|
548
|
+
this.restoreElement(el);
|
|
549
|
+
if (el.style.display === "none") el.style.display = "";
|
|
550
|
+
this.emptiedWrappers.delete(el);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
521
553
|
}
|
|
522
554
|
stop() {
|
|
523
555
|
this.disposed = true;
|
|
@@ -652,6 +684,7 @@ var __dshcfBundle = (() => {
|
|
|
652
684
|
this.currentBlocks = new Map(blocks.map((block) => [block.key, block]));
|
|
653
685
|
const segments = buildSegments(flow, blocks, (el) => this.hasBodyCached(el));
|
|
654
686
|
const liveSegmentKeys = new Set(segments.map((segment) => segment.key));
|
|
687
|
+
this.nativeTurns = buildNativeTurnMap(flow);
|
|
655
688
|
const scrollAnchor = this.captureScrollAnchor(flow);
|
|
656
689
|
for (const segment of segments) {
|
|
657
690
|
if (!segment.running) continue;
|
|
@@ -668,6 +701,12 @@ var __dshcfBundle = (() => {
|
|
|
668
701
|
if (!snapshot.closed || snapshot.running || !snapshot.hasWork) continue;
|
|
669
702
|
completedKeys.add(snapshot.key);
|
|
670
703
|
this.completedOnce.add(snapshot.key);
|
|
704
|
+
if (segmentHasNativeTurn(snapshot, this.nativeTurns)) {
|
|
705
|
+
const prev = this.segmentStates.get(snapshot.key);
|
|
706
|
+
prev?.row?.remove();
|
|
707
|
+
if (prev !== void 0) this.segmentStates.delete(snapshot.key);
|
|
708
|
+
continue;
|
|
709
|
+
}
|
|
671
710
|
let state = this.segmentStates.get(snapshot.key);
|
|
672
711
|
if (state === void 0) {
|
|
673
712
|
state = { key: snapshot.key, row: null, expanded: false, snapshot };
|
|
@@ -697,6 +736,21 @@ var __dshcfBundle = (() => {
|
|
|
697
736
|
seenBlocks.add(block.key);
|
|
698
737
|
this.reconcileBlock(block, segmentByBlock.get(block.key) ?? null, desiredHidden);
|
|
699
738
|
}
|
|
739
|
+
if (rebuildBlocks || this.displayTouched || this.settleFired) {
|
|
740
|
+
const hosts = /* @__PURE__ */ new Set();
|
|
741
|
+
for (const block of this.currentBlocks.values()) {
|
|
742
|
+
if (block.host.isConnected) hosts.add(block.host);
|
|
743
|
+
}
|
|
744
|
+
for (const segment of segments) {
|
|
745
|
+
if (segment.finalStep?.isConnected) hosts.add(segment.finalStep);
|
|
746
|
+
for (const middle of segment.middleSteps) {
|
|
747
|
+
if (middle.isConnected) hosts.add(middle);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
this.discoverHollowWrappers(hosts, desiredHidden);
|
|
751
|
+
}
|
|
752
|
+
this.displayTouched = false;
|
|
753
|
+
this.settleFired = false;
|
|
700
754
|
for (const segment of segments) {
|
|
701
755
|
const state = this.segmentStates.get(segment.key);
|
|
702
756
|
const collapse = state !== void 0 && !state.expanded;
|
|
@@ -719,6 +773,11 @@ var __dshcfBundle = (() => {
|
|
|
719
773
|
if (segment.finalStep !== null) this.retainDisplayControl(segment.finalStep, desiredHidden);
|
|
720
774
|
}
|
|
721
775
|
this.cleanupStaleChips(seenBlocks);
|
|
776
|
+
for (const [el, record] of [...this.pendingAnims]) {
|
|
777
|
+
if (record.target === "hidden" && record.kind === "fade" && this.pendingAnims.get(el) === record && this.isAnimOverdue(record.anim)) {
|
|
778
|
+
this.finishFadeCollapse(el, record);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
722
781
|
this.restoreUnusedDisplays(desiredHidden);
|
|
723
782
|
for (const state of this.segmentStates.values()) this.placeProcessedRow(flow, state);
|
|
724
783
|
this.stabilizeScrollAfterFold(scrollAnchor);
|
|
@@ -808,6 +867,8 @@ var __dshcfBundle = (() => {
|
|
|
808
867
|
this.bodyTextCache = /* @__PURE__ */ new WeakMap();
|
|
809
868
|
this.dirtyMessages.clear();
|
|
810
869
|
this.auditDisplays.clear();
|
|
870
|
+
this.nativeTurns.clear();
|
|
871
|
+
this.emptiedWrappers.clear();
|
|
811
872
|
this.structureDirty = true;
|
|
812
873
|
this.restoreAllDisplays();
|
|
813
874
|
restoreTurnStatus(this.turnStatusTexts);
|
|
@@ -859,7 +920,29 @@ var __dshcfBundle = (() => {
|
|
|
859
920
|
}
|
|
860
921
|
if (row.parentElement !== flow || row.nextElementSibling !== target) target.before(row);
|
|
861
922
|
}
|
|
923
|
+
/** 原生收起时隐藏本块插件自有 overlay(chip/合并行+内容块):只做
|
|
924
|
+
* display 直写,不清钉住之外的账本、不删展开态,供原生再展开后复用。
|
|
925
|
+
* 在途 WAAPI 动画不追踪取消——收尾回调只做幂等终态对齐,无残留。 */
|
|
926
|
+
hideOverlayForNativeCollapse(block) {
|
|
927
|
+
const existing = this.chips.get(block.key)?.chip;
|
|
928
|
+
if (existing !== void 0 && existing.style.display !== "none") {
|
|
929
|
+
existing.style.marginBottom = "";
|
|
930
|
+
existing.style.display = "none";
|
|
931
|
+
}
|
|
932
|
+
const row = this.mergedThinks.get(block.host);
|
|
933
|
+
if (row !== void 0 && row.style.display !== "none") {
|
|
934
|
+
row.style.display = "none";
|
|
935
|
+
const body = row.nextElementSibling;
|
|
936
|
+
if (body instanceof HTMLElement && body.classList.contains("dshcf-merged-body") && body.style.display !== "none") {
|
|
937
|
+
body.style.display = "none";
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
}
|
|
862
941
|
reconcileBlock(block, segment, desiredHidden) {
|
|
942
|
+
if (blockNativelyCollapsed(block, this.nativeTurns)) {
|
|
943
|
+
this.hideOverlayForNativeCollapse(block);
|
|
944
|
+
return;
|
|
945
|
+
}
|
|
863
946
|
const state = segment === null ? void 0 : this.segmentStates.get(segment.key);
|
|
864
947
|
const segmentAnimatableBlocks = segment === null ? void 0 : this.animatableSegmentBlocks.get(segment.key);
|
|
865
948
|
const animate = this.animatableKeys.has(block.key) || segment !== null && this.animatableKeys.has(segment.key) && (segmentAnimatableBlocks === void 0 || segmentAnimatableBlocks.has(block.key));
|
|
@@ -881,7 +964,7 @@ var __dshcfBundle = (() => {
|
|
|
881
964
|
existing.style.display = "none";
|
|
882
965
|
}
|
|
883
966
|
}
|
|
884
|
-
this.
|
|
967
|
+
this.syncEmptiedWrappers(block, desiredHidden);
|
|
885
968
|
return;
|
|
886
969
|
}
|
|
887
970
|
let expanded = this.blockExpanded.get(block.key) ?? false;
|
|
@@ -901,6 +984,7 @@ var __dshcfBundle = (() => {
|
|
|
901
984
|
if (chipWasHidden && animate && !(hostAnimate && block.mount === "inside")) this.revealVisual(chip);
|
|
902
985
|
if (expanded || !this.hasPendingCollapse(block)) this.unpinChipMargin(chip);
|
|
903
986
|
const chipSettle = () => {
|
|
987
|
+
this.settleFired = true;
|
|
904
988
|
if (!this.hasPendingCollapse(block)) this.unpinChipMargin(chip);
|
|
905
989
|
};
|
|
906
990
|
for (const container of block.containers) {
|
|
@@ -922,9 +1006,123 @@ var __dshcfBundle = (() => {
|
|
|
922
1006
|
} else {
|
|
923
1007
|
if (this.releaseMergedThink(block.host, animate, chipSettle)) this.pinChipMargin(chip);
|
|
924
1008
|
}
|
|
1009
|
+
this.syncEmptiedWrappers(block, desiredHidden);
|
|
925
1010
|
chip.classList.toggle("dshcf-has-body", block.mount === "inside" && this.hasBodyCached(block.host));
|
|
926
1011
|
updateChip(chip, block.rows, expanded);
|
|
927
1012
|
}
|
|
1013
|
+
/**
|
|
1014
|
+
* 掏空包装层对账(真机 44px vs 28px 真因修复)。
|
|
1015
|
+
*
|
|
1016
|
+
* 折叠把某容器的子行全部 display:none 后,该容器变零高度空壳,但仍是
|
|
1017
|
+
* 父级 flex 的 item 并参与 gap(如正文体 flex-column gap:16px),凭空多出
|
|
1018
|
+
* 一份间距。隐藏这类空壳可让 gap 塌缩;子内容恢复可见(展开/流式追加/
|
|
1019
|
+
* 块转世)时恢复显示。
|
|
1020
|
+
*
|
|
1021
|
+
* 安全边界:
|
|
1022
|
+
* - 只看 block.host 内部、不含 host 本人(宿主显隐归 segment 逻辑);
|
|
1023
|
+
* - 跳过结构 seat(data-chat-flow-kind / data-chat-anchor-key)与插件
|
|
1024
|
+
* 自身 overlay(dshcf- 前缀类);
|
|
1025
|
+
* - 只隐藏内容空洞(无可见子内容):零高度无文本的空壳 display:none 与
|
|
1026
|
+
* 保持显示像素一致,只塌缩父级 gap;任何一方恢复内容即恢复显示。
|
|
1027
|
+
*/
|
|
1028
|
+
/**
|
|
1029
|
+
* 空洞包装层兜底发现:按宿主直扫 div(仅结构变化轮次调用)。
|
|
1030
|
+
* 只做隐藏侧:空洞即藏并记入 tracked;恢复侧由 syncEmptiedWrappers 的
|
|
1031
|
+
* tracked 复核与 audit 承担。与按行 walk 共用同一安全边界(结构 seat /
|
|
1032
|
+
* 插件 overlay / 宿主本人不碰)。
|
|
1033
|
+
*/
|
|
1034
|
+
discoverHollowWrappers(hosts, desiredHidden) {
|
|
1035
|
+
for (const host of hosts) {
|
|
1036
|
+
if (!host.isConnected) continue;
|
|
1037
|
+
let divs;
|
|
1038
|
+
try {
|
|
1039
|
+
divs = [...host.querySelectorAll("div")];
|
|
1040
|
+
} catch {
|
|
1041
|
+
continue;
|
|
1042
|
+
}
|
|
1043
|
+
for (const el of divs) {
|
|
1044
|
+
if (el === host || !(el instanceof HTMLElement)) continue;
|
|
1045
|
+
const cls = el.className;
|
|
1046
|
+
if (typeof cls === "string" && cls.split(" ").some((c) => c.startsWith("dshcf-"))) continue;
|
|
1047
|
+
if (el.hasAttribute("data-chat-flow-kind") || el.hasAttribute("data-chat-anchor-key")) continue;
|
|
1048
|
+
if (this.isHollow(el)) {
|
|
1049
|
+
this.hideElement(el, desiredHidden, false);
|
|
1050
|
+
this.emptiedWrappers.add(el);
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
syncEmptiedWrappers(block, desiredHidden) {
|
|
1056
|
+
const flow = this.flow;
|
|
1057
|
+
if (flow === null) return;
|
|
1058
|
+
const scopeOf = (el) => {
|
|
1059
|
+
let cur = el?.parentElement ?? null;
|
|
1060
|
+
while (cur instanceof HTMLElement && cur.parentElement !== flow) {
|
|
1061
|
+
cur = cur.parentElement;
|
|
1062
|
+
}
|
|
1063
|
+
return cur instanceof HTMLElement && cur.parentElement === flow ? cur : null;
|
|
1064
|
+
};
|
|
1065
|
+
const candidates = [];
|
|
1066
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1067
|
+
const scopes = /* @__PURE__ */ new Set();
|
|
1068
|
+
const collect = (el) => {
|
|
1069
|
+
const scope = scopeOf(el);
|
|
1070
|
+
if (scope === null) return;
|
|
1071
|
+
scopes.add(scope);
|
|
1072
|
+
let node = el?.parentElement ?? null;
|
|
1073
|
+
while (node instanceof HTMLElement && node !== scope) {
|
|
1074
|
+
if (!seen.has(node)) {
|
|
1075
|
+
seen.add(node);
|
|
1076
|
+
candidates.push(node);
|
|
1077
|
+
}
|
|
1078
|
+
node = node.parentElement;
|
|
1079
|
+
}
|
|
1080
|
+
};
|
|
1081
|
+
for (const row of block.rows) collect(row);
|
|
1082
|
+
for (const container of block.containers) collect(container);
|
|
1083
|
+
for (const el of [...this.emptiedWrappers]) {
|
|
1084
|
+
if (!el.isConnected) {
|
|
1085
|
+
this.emptiedWrappers.delete(el);
|
|
1086
|
+
continue;
|
|
1087
|
+
}
|
|
1088
|
+
const scope = scopeOf(el);
|
|
1089
|
+
if (scope === null || !scopes.has(scope)) continue;
|
|
1090
|
+
if (!seen.has(el)) {
|
|
1091
|
+
seen.add(el);
|
|
1092
|
+
candidates.push(el);
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
for (const el of candidates) {
|
|
1096
|
+
if (!el.isConnected) {
|
|
1097
|
+
this.emptiedWrappers.delete(el);
|
|
1098
|
+
continue;
|
|
1099
|
+
}
|
|
1100
|
+
const cls = el.className;
|
|
1101
|
+
if (typeof cls === "string" && cls.split(" ").some((c) => c.startsWith("dshcf-"))) continue;
|
|
1102
|
+
if (el.hasAttribute("data-chat-flow-kind") || el.hasAttribute("data-chat-anchor-key")) continue;
|
|
1103
|
+
if (this.isHollow(el)) {
|
|
1104
|
+
this.hideElement(el, desiredHidden, false);
|
|
1105
|
+
this.emptiedWrappers.add(el);
|
|
1106
|
+
} else if (this.emptiedWrappers.delete(el)) {
|
|
1107
|
+
this.restoreElement(el);
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
/** 内容空洞判定:无可见子内容(文本/元素任一可见即非空洞)。隐藏者是谁
|
|
1112
|
+
* 不重要——零高度无文本节点 display:none 与保持显示像素一致(只塌缩父级
|
|
1113
|
+
* gap,正是要修的幻影);任何一方恢复内容,下轮 pass/audit 即恢复显示。
|
|
1114
|
+
* isDisplayed 走 getComputedStyle(桩内退化为内联,语义一致)。 */
|
|
1115
|
+
isHollow(el) {
|
|
1116
|
+
for (const child of [...el.childNodes]) {
|
|
1117
|
+
if (child.nodeType === 3) {
|
|
1118
|
+
if ((child.textContent ?? "").trim() !== "") return false;
|
|
1119
|
+
continue;
|
|
1120
|
+
}
|
|
1121
|
+
if (!(child instanceof HTMLElement)) continue;
|
|
1122
|
+
if (isDisplayed(child)) return false;
|
|
1123
|
+
}
|
|
1124
|
+
return true;
|
|
1125
|
+
}
|
|
928
1126
|
ensureChip(block) {
|
|
929
1127
|
let record = this.chips.get(block.key);
|
|
930
1128
|
const validParent = record !== void 0 && (block.mount === "inside" ? record.chip.parentElement === block.host : record.chip.parentElement === block.host.parentElement);
|
|
@@ -1287,6 +1485,7 @@ var __dshcfBundle = (() => {
|
|
|
1287
1485
|
}
|
|
1288
1486
|
el.style.display = "none";
|
|
1289
1487
|
this.writtenDisplay.set(el, "none");
|
|
1488
|
+
this.displayTouched = true;
|
|
1290
1489
|
return false;
|
|
1291
1490
|
}
|
|
1292
1491
|
restoreElement(el, animate = false) {
|
|
@@ -1302,11 +1501,17 @@ var __dshcfBundle = (() => {
|
|
|
1302
1501
|
}
|
|
1303
1502
|
const original = this.originalDisplay.get(el);
|
|
1304
1503
|
if (!animate || !this.canAnimate(el) || this.hasAnimatingAncestor(el)) {
|
|
1305
|
-
if (el.style.display !== original)
|
|
1504
|
+
if (el.style.display !== original) {
|
|
1505
|
+
el.style.display = original;
|
|
1506
|
+
this.displayTouched = true;
|
|
1507
|
+
}
|
|
1306
1508
|
this.releaseDisplayLedger(el);
|
|
1307
1509
|
return;
|
|
1308
1510
|
}
|
|
1309
|
-
if (el.style.display !== original)
|
|
1511
|
+
if (el.style.display !== original) {
|
|
1512
|
+
el.style.display = original;
|
|
1513
|
+
this.displayTouched = true;
|
|
1514
|
+
}
|
|
1310
1515
|
this.writtenDisplay.set(el, original);
|
|
1311
1516
|
this.startReveal(el);
|
|
1312
1517
|
}
|
|
@@ -1378,28 +1583,48 @@ var __dshcfBundle = (() => {
|
|
|
1378
1583
|
],
|
|
1379
1584
|
{ duration: ANIM_DURATION_MS, easing: ANIM_EASING, fill: "forwards" }
|
|
1380
1585
|
);
|
|
1381
|
-
const record = { anim, target: "hidden", kind: "fade" };
|
|
1586
|
+
const record = { anim, target: "hidden", kind: "fade", settle };
|
|
1382
1587
|
this.pendingAnims.set(el, record);
|
|
1383
|
-
anim.onfinish = () =>
|
|
1384
|
-
if (this.pendingAnims.get(el) !== record) return;
|
|
1385
|
-
this.pendingAnims.delete(el);
|
|
1386
|
-
if (this.displayForeign(el)) {
|
|
1387
|
-
this.releaseDisplayLedger(el);
|
|
1388
|
-
this.schedule();
|
|
1389
|
-
anim.cancel();
|
|
1390
|
-
return;
|
|
1391
|
-
}
|
|
1392
|
-
if (el.style.display !== "none") el.style.display = "none";
|
|
1393
|
-
this.writtenDisplay.set(el, "none");
|
|
1394
|
-
settle?.();
|
|
1395
|
-
anim.cancel();
|
|
1396
|
-
this.schedule();
|
|
1397
|
-
};
|
|
1588
|
+
anim.onfinish = () => this.finishFadeCollapse(el, record);
|
|
1398
1589
|
anim.oncancel = () => {
|
|
1399
1590
|
if (this.pendingAnims.get(el) !== record) return;
|
|
1400
1591
|
this.pendingAnims.delete(el);
|
|
1401
1592
|
};
|
|
1402
1593
|
}
|
|
1594
|
+
/**
|
|
1595
|
+
* 收起 fade 终态结算(onfinish 与僵尸收割共用)。仅被同元素新动画
|
|
1596
|
+
* supersede 时早退;被 sweep 删账(元素已断连)或收割时仍执行 settle——
|
|
1597
|
+
* 回调经 controller 可达,动画必然触发 onfinish,早退会把 chip 内联 16px
|
|
1598
|
+
* 钉住永久残留。重挂载同节点不可能(React 只建新节点),重启动的新动画
|
|
1599
|
+
* 由第一条守卫覆盖,无误伤。
|
|
1600
|
+
*/
|
|
1601
|
+
finishFadeCollapse(el, record) {
|
|
1602
|
+
const cur = this.pendingAnims.get(el);
|
|
1603
|
+
if (cur !== void 0 && cur !== record) return;
|
|
1604
|
+
this.pendingAnims.delete(el);
|
|
1605
|
+
if (this.displayForeign(el)) {
|
|
1606
|
+
this.releaseDisplayLedger(el);
|
|
1607
|
+
this.schedule();
|
|
1608
|
+
record.anim.cancel();
|
|
1609
|
+
return;
|
|
1610
|
+
}
|
|
1611
|
+
if (el.style.display !== "none") el.style.display = "none";
|
|
1612
|
+
this.writtenDisplay.set(el, "none");
|
|
1613
|
+
record.settle?.();
|
|
1614
|
+
record.anim.cancel();
|
|
1615
|
+
this.schedule();
|
|
1616
|
+
}
|
|
1617
|
+
/** 动画是否已播完(终态未结算):finish 事件丢失时的兜底判定。WAAPI 桩
|
|
1618
|
+
* 无 playState/effect 时一律 false,走正常事件路径,测试行为不变。 */
|
|
1619
|
+
isAnimOverdue(anim) {
|
|
1620
|
+
const a = anim;
|
|
1621
|
+
if (a.playState === "finished") return true;
|
|
1622
|
+
if (typeof a.currentTime === "number") {
|
|
1623
|
+
const end = a.effect?.getComputedTiming?.().endTime;
|
|
1624
|
+
if (typeof end === "number" && a.currentTime >= end) return true;
|
|
1625
|
+
}
|
|
1626
|
+
return false;
|
|
1627
|
+
}
|
|
1403
1628
|
/** 轻量视觉 reveal(opacity + 4px 微位移):用于插件全资元素的即时显示
|
|
1404
1629
|
* 路径——chip(一级展开时出现)与 merged-think 行(二级展开时出现)。
|
|
1405
1630
|
* 这些元素的 display 完全由插件直写、无 React 协调竞争,因此不入
|
|
@@ -1417,7 +1642,12 @@ var __dshcfBundle = (() => {
|
|
|
1417
1642
|
}
|
|
1418
1643
|
restoreUnusedDisplays(desired) {
|
|
1419
1644
|
for (const el of [...this.controlledDisplay]) {
|
|
1420
|
-
if (
|
|
1645
|
+
if (desired.has(el)) continue;
|
|
1646
|
+
if (!el.isConnected && this.pendingAnims.get(el)?.target === "hidden") {
|
|
1647
|
+
this.releaseDisplayLedger(el);
|
|
1648
|
+
continue;
|
|
1649
|
+
}
|
|
1650
|
+
this.restoreElement(el);
|
|
1421
1651
|
}
|
|
1422
1652
|
}
|
|
1423
1653
|
/** 收集 audit 需要观察的 display 集合;只读取内联样式,不触发布局计算。 */
|
|
@@ -1663,6 +1893,39 @@ var __dshcfBundle = (() => {
|
|
|
1663
1893
|
if (typeof getComputedStyle === "function") return getComputedStyle(el).display !== "none";
|
|
1664
1894
|
return el.style.display !== "none";
|
|
1665
1895
|
}
|
|
1896
|
+
function buildNativeTurnMap(flow) {
|
|
1897
|
+
const states = /* @__PURE__ */ new Map();
|
|
1898
|
+
for (const el of flow.children) {
|
|
1899
|
+
if (!(el instanceof HTMLElement)) continue;
|
|
1900
|
+
if (el.getAttribute("data-chat-flow-kind") !== TURN_PROCESS_KIND) continue;
|
|
1901
|
+
const turn = el.getAttribute("data-chat-turn");
|
|
1902
|
+
if (turn === null || turn === "") continue;
|
|
1903
|
+
const button = el.querySelector("button[data-turn-process]");
|
|
1904
|
+
if (button === null) continue;
|
|
1905
|
+
const openAttr = button.getAttribute(NATIVE_OPEN_ATTR) !== null;
|
|
1906
|
+
const expandedAttr = button.getAttribute("aria-expanded");
|
|
1907
|
+
const open = expandedAttr === null ? openAttr : expandedAttr === "true";
|
|
1908
|
+
states.set(turn, { collapsed: !open });
|
|
1909
|
+
}
|
|
1910
|
+
return states;
|
|
1911
|
+
}
|
|
1912
|
+
function elementTurn(el) {
|
|
1913
|
+
const turn = el.getAttribute("data-chat-turn");
|
|
1914
|
+
return turn === null || turn === "" ? null : turn;
|
|
1915
|
+
}
|
|
1916
|
+
function segmentHasNativeTurn(segment, nativeTurns) {
|
|
1917
|
+
if (nativeTurns.size === 0) return false;
|
|
1918
|
+
for (const block of segment.blocks) {
|
|
1919
|
+
const turn = elementTurn(block.host);
|
|
1920
|
+
if (turn !== null && nativeTurns.has(turn)) return true;
|
|
1921
|
+
}
|
|
1922
|
+
return false;
|
|
1923
|
+
}
|
|
1924
|
+
function blockNativelyCollapsed(block, nativeTurns) {
|
|
1925
|
+
if (nativeTurns.size === 0) return false;
|
|
1926
|
+
const turn = elementTurn(block.host);
|
|
1927
|
+
return turn !== null && nativeTurns.get(turn)?.collapsed === true;
|
|
1928
|
+
}
|
|
1666
1929
|
function stableElementKey(el, fallbackIndex) {
|
|
1667
1930
|
const kind = el.getAttribute("data-chat-flow-kind") ?? "node";
|
|
1668
1931
|
const key = el.getAttribute("data-chat-flow-key") ?? el.getAttribute("data-chat-anchor-key") ?? `${kind}:${fallbackIndex}`;
|
|
@@ -1776,6 +2039,7 @@ var __dshcfBundle = (() => {
|
|
|
1776
2039
|
};
|
|
1777
2040
|
for (const el of children) {
|
|
1778
2041
|
const kind = el.getAttribute("data-chat-flow-kind");
|
|
2042
|
+
if (kind === TURN_PROCESS_KIND) continue;
|
|
1779
2043
|
if (kind === "user" || kind === "steering" || kind === "turn-tail") {
|
|
1780
2044
|
flushCarry();
|
|
1781
2045
|
run = null;
|
|
@@ -2127,14 +2391,23 @@ var __dshcfBundle = (() => {
|
|
|
2127
2391
|
const statuses = flow.matches('[role="status"]') ? [flow, ...flow.querySelectorAll('[role="status"]')] : [...flow.querySelectorAll('[role="status"]')];
|
|
2128
2392
|
for (const status of statuses) {
|
|
2129
2393
|
for (const node of status.childNodes) {
|
|
2130
|
-
if (node instanceof Text
|
|
2131
|
-
|
|
2394
|
+
if (!(node instanceof Text)) continue;
|
|
2395
|
+
let record = originals.get(node);
|
|
2396
|
+
if (record !== void 0 && node.data === record.written) {
|
|
2397
|
+
const next = record.original.replace(TURN_STATUS_COPY_RE, statusText);
|
|
2398
|
+
if (node.data !== next) {
|
|
2399
|
+
node.data = next;
|
|
2400
|
+
record.written = next;
|
|
2401
|
+
}
|
|
2402
|
+
continue;
|
|
2403
|
+
}
|
|
2404
|
+
if (TURN_STATUS_COPY_RE.test(node.data)) {
|
|
2132
2405
|
if (record === void 0) {
|
|
2133
2406
|
record = { original: node.data, written: "" };
|
|
2134
2407
|
originals.set(node, record);
|
|
2135
2408
|
}
|
|
2136
2409
|
if (node.data !== record.written) record.original = node.data;
|
|
2137
|
-
const next = node.data.replace(
|
|
2410
|
+
const next = node.data.replace(TURN_STATUS_COPY_RE, statusText);
|
|
2138
2411
|
if (node.data !== next) {
|
|
2139
2412
|
node.data = next;
|
|
2140
2413
|
record.written = next;
|
|
@@ -2257,6 +2530,7 @@ var __dshcfBundle = (() => {
|
|
|
2257
2530
|
.dshcf-settings-save:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 1px; }
|
|
2258
2531
|
`;
|
|
2259
2532
|
var STYLE_ID2 = "dshcf-settings-style";
|
|
2533
|
+
var styleOwners = 0;
|
|
2260
2534
|
function injectCardStyle() {
|
|
2261
2535
|
if (typeof document === "undefined" || document.getElementById(STYLE_ID2) !== null) return;
|
|
2262
2536
|
const style = document.createElement("style");
|
|
@@ -2264,6 +2538,17 @@ var __dshcfBundle = (() => {
|
|
|
2264
2538
|
style.textContent = CARD_CSS;
|
|
2265
2539
|
document.head.appendChild(style);
|
|
2266
2540
|
}
|
|
2541
|
+
function acquireCardStyle() {
|
|
2542
|
+
styleOwners += 1;
|
|
2543
|
+
injectCardStyle();
|
|
2544
|
+
let released = false;
|
|
2545
|
+
return () => {
|
|
2546
|
+
if (released) return;
|
|
2547
|
+
released = true;
|
|
2548
|
+
styleOwners = Math.max(0, styleOwners - 1);
|
|
2549
|
+
if (styleOwners === 0 && typeof document !== "undefined") document.getElementById(STYLE_ID2)?.remove();
|
|
2550
|
+
};
|
|
2551
|
+
}
|
|
2267
2552
|
function ChevronIcon(open) {
|
|
2268
2553
|
const React = __require("react");
|
|
2269
2554
|
const className = open ? "dshcf-settings-chevron dshcf-settings-chevronOpen" : "dshcf-settings-chevron";
|
|
@@ -2337,7 +2622,7 @@ var __dshcfBundle = (() => {
|
|
|
2337
2622
|
[
|
|
2338
2623
|
React.createElement("span", { className: "dshcf-settings-headText" }, [
|
|
2339
2624
|
React.createElement("span", { className: "dshcf-settings-name" }, "\u72B6\u6001\u63D0\u793A\u8BCD"),
|
|
2340
|
-
React.createElement("span", { className: "dshcf-settings-description" }, "\u81EA\u5B9A\u4E49\u72B6\u6001\u63D0\u793A\u8BCD\uFF0C\u53EF\u4EE5\u66FF\u6362\u539F\u6709\
|
|
2625
|
+
React.createElement("span", { className: "dshcf-settings-description" }, "\u81EA\u5B9A\u4E49\u72B6\u6001\u63D0\u793A\u8BCD\uFF0C\u53EF\u4EE5\u66FF\u6362\u539F\u6709\u7684\u8FD0\u884C\u72B6\u6001\u6587\u6848\uFF08\u6DF1\u5EA6\u6C42\u7D22\u4E2D...\uFF09\uFF0C\u7531\u63D2\u4EF6dsh-auto-collapse\u63D0\u4F9B")
|
|
2341
2626
|
]),
|
|
2342
2627
|
dirty ? React.createElement("span", { className: "dshcf-settings-pending" }, "\u672A\u4FDD\u5B58") : null,
|
|
2343
2628
|
ChevronIcon(open)
|
|
@@ -2358,11 +2643,11 @@ var __dshcfBundle = (() => {
|
|
|
2358
2643
|
className: "dshcf-settings-input",
|
|
2359
2644
|
type: "text",
|
|
2360
2645
|
value: text,
|
|
2361
|
-
placeholder: "
|
|
2646
|
+
placeholder: "\u6DF1\u5EA6\u6C42\u7D22\u4E2D...",
|
|
2362
2647
|
disabled: !writable,
|
|
2363
2648
|
onChange: (event) => edit(event.target.value)
|
|
2364
2649
|
}),
|
|
2365
|
-
React.createElement("p", { className: "dshcf-settings-hint" }, "\u4E3A\u7A7A\u65F6\u6062\u590D\u9ED8\
|
|
2650
|
+
React.createElement("p", { className: "dshcf-settings-hint" }, "\u4E3A\u7A7A\u65F6\u6062\u590D\u5B98\u65B9\u9ED8\u8BA4\u6587\u6848\uFF08\u6DF1\u5EA6\u6C42\u7D22\u4E2D...\uFF09")
|
|
2366
2651
|
]),
|
|
2367
2652
|
React.createElement("div", { className: "dshcf-settings-footer" }, [
|
|
2368
2653
|
failed ? React.createElement("p", { className: "dshcf-settings-failed", role: "status" }, "\u672C\u90E8\u7F72\u6CA1\u6709\u63A5\u53D7\u8FD9\u4E9B\u503C\uFF0C\u5DF2\u4FDD\u7559\u4F9B\u4F60\u4FEE\u6539\u3002") : null,
|
|
@@ -2373,35 +2658,67 @@ var __dshcfBundle = (() => {
|
|
|
2373
2658
|
]);
|
|
2374
2659
|
}
|
|
2375
2660
|
function setupSettingsCard(ctx, scope) {
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2661
|
+
const releaseStyle = acquireCardStyle();
|
|
2662
|
+
try {
|
|
2663
|
+
const offSlot = ctx.slots.inject("settings.plugin.item", () => ctx.slots.register(
|
|
2664
|
+
{
|
|
2665
|
+
name: "settings.plugin.item",
|
|
2666
|
+
key: AUTO_COLLAPSE_NS,
|
|
2667
|
+
inject: () => ({ scope })
|
|
2668
|
+
},
|
|
2669
|
+
StatusTextCard
|
|
2670
|
+
));
|
|
2671
|
+
return () => {
|
|
2672
|
+
try {
|
|
2673
|
+
offSlot();
|
|
2674
|
+
} finally {
|
|
2675
|
+
releaseStyle();
|
|
2676
|
+
}
|
|
2677
|
+
};
|
|
2678
|
+
} catch (error) {
|
|
2679
|
+
releaseStyle();
|
|
2680
|
+
throw error;
|
|
2681
|
+
}
|
|
2385
2682
|
}
|
|
2386
2683
|
|
|
2387
2684
|
// src/client.ts
|
|
2388
2685
|
var name = "dsh-auto-collapse";
|
|
2389
2686
|
var inject = [];
|
|
2390
2687
|
function apply(ctx) {
|
|
2688
|
+
const fallbackStatusText = () => DEFAULT_STATUS_TEXT2;
|
|
2689
|
+
let readStatusText = fallbackStatusText;
|
|
2690
|
+
const controller = new FoldController(() => readStatusText());
|
|
2391
2691
|
ctx.effect(() => {
|
|
2392
|
-
const settingsScope = ctx.get?.("settingsScope");
|
|
2393
|
-
const slots = ctx.get?.("slots");
|
|
2394
|
-
const scope = settingsScope?.bind({ namespace: AUTO_COLLAPSE_NS });
|
|
2395
|
-
const controller = new FoldController(statusTextProvider(scope));
|
|
2396
2692
|
controller.start();
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2693
|
+
return () => controller.stop();
|
|
2694
|
+
}, "dsh-auto-collapse: fold observer");
|
|
2695
|
+
ctx.inject?.(["settingsScope", "slots"], (serviceCtx) => {
|
|
2696
|
+
serviceCtx.effect(() => {
|
|
2697
|
+
const settingsScope = serviceCtx.get?.("settingsScope");
|
|
2698
|
+
const slots = serviceCtx.get?.("slots");
|
|
2699
|
+
if (settingsScope === void 0 || slots === void 0) return;
|
|
2700
|
+
const scope = settingsScope.bind({ namespace: AUTO_COLLAPSE_NS });
|
|
2701
|
+
const scopedStatusText = statusTextProvider(scope);
|
|
2702
|
+
const offScope = scope.subscribe(() => controller.refresh());
|
|
2703
|
+
let offSettings;
|
|
2704
|
+
try {
|
|
2705
|
+
offSettings = setupSettingsCard({ slots }, scope);
|
|
2706
|
+
} catch (error) {
|
|
2707
|
+
offScope();
|
|
2708
|
+
throw error;
|
|
2709
|
+
}
|
|
2710
|
+
readStatusText = scopedStatusText;
|
|
2711
|
+
controller.refresh();
|
|
2712
|
+
return () => {
|
|
2713
|
+
offScope();
|
|
2714
|
+
offSettings();
|
|
2715
|
+
if (readStatusText === scopedStatusText) {
|
|
2716
|
+
readStatusText = fallbackStatusText;
|
|
2717
|
+
controller.refresh();
|
|
2718
|
+
}
|
|
2719
|
+
};
|
|
2720
|
+
}, "dsh-auto-collapse: settings scope + plugin card");
|
|
2721
|
+
});
|
|
2405
2722
|
}
|
|
2406
2723
|
return __toCommonJS(client_exports);
|
|
2407
2724
|
})();
|